diff --git a/examples/simple_efi_x8664.py b/examples/simple_efi_x8664.py index 1c36664dd..32c21a28d 100644 --- a/examples/simple_efi_x8664.py +++ b/examples/simple_efi_x8664.py @@ -11,7 +11,7 @@ from qiling import Qiling from qiling.const import QL_INTERCEPT, QL_VERBOSE from qiling.os.uefi.const import EFI_SUCCESS, EFI_INVALID_PARAMETER -from qiling.os.uefi.utils import check_and_notify_protocols, signal_event +from qiling.os.uefi.utils import execute_protocol_notifications, signal_event def force_notify_RegisterProtocolNotify(ql: Qiling, address: int, params): event_id = params['Event'] @@ -23,7 +23,7 @@ def force_notify_RegisterProtocolNotify(ql: Qiling, address: int, params): event["Set"] = False signal_event(ql, event_id) - check_and_notify_protocols(ql, True) + execute_protocol_notifications(ql, True) return EFI_SUCCESS diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 79058d596..7bcaec500 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -3,17 +3,17 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Optional, Sequence from pefile import PE +from typing import Any, Mapping, Optional, Sequence from qiling import Qiling from qiling.const import QL_ARCH from qiling.exception import QlErrorArch, QlMemoryMappedError from qiling.loader.loader import QlLoader, Image +from qiling.os.const import PARAM_INTN, POINTER -from qiling.os.uefi import context, st, smst -from qiling.os.uefi.ProcessorBind import CPU_STACK_ALIGNMENT -from qiling.os.uefi.shutdown import hook_EndOfExecution +from qiling.os.uefi import st, smst, utils +from qiling.os.uefi.context import DxeContext, SmmContext, UefiContext from qiling.os.uefi.protocols import EfiLoadedImageProtocol from qiling.os.uefi.protocols import EfiSmmAccess2Protocol from qiling.os.uefi.protocols import EfiSmmBase2Protocol @@ -28,23 +28,20 @@ def __init__(self, ql: Qiling): self.modules = [] self.events = {} self.notify_list = [] - self.next_image_base = 0 + + self.dxe_context: DxeContext + self.smm_context: SmmContext + self.context: UefiContext # list of members names to save and restore __save_members = ( 'modules', 'events', 'notify_list', - 'next_image_base', - 'loaded_image_protocol_modules', - 'tpl', - 'efi_conf_table_array', - 'efi_conf_table_array_ptr', - 'efi_conf_table_data_ptr', - 'efi_conf_table_data_next_ptr' + 'tpl' ) - def save(self) -> dict: + def save(self) -> Mapping[str, Any]: saved_state = super(QlLoaderPE_UEFI, self).save() for member in QlLoaderPE_UEFI.__save_members: @@ -55,7 +52,7 @@ def save(self) -> dict: return saved_state - def restore(self, saved_state: dict): + def restore(self, saved_state: Mapping[str, Any]): super(QlLoaderPE_UEFI, self).restore(saved_state) for member in QlLoaderPE_UEFI.__save_members: @@ -63,7 +60,7 @@ def restore(self, saved_state: dict): self.ql.os.heap.restore(saved_state['heap']) - def install_loaded_image_protocol(self, image_base, image_size): + def install_loaded_image_protocol(self, image_base: int, image_size: int): fields = { 'gST' : self.gST, 'image_base' : image_base, @@ -71,11 +68,11 @@ def install_loaded_image_protocol(self, image_base, image_size): } descriptor = EfiLoadedImageProtocol.make_descriptor(fields) - self.dxe_context.install_protocol(descriptor, image_base) + self.context.install_protocol(descriptor, image_base) - self.loaded_image_protocol_modules.append(image_base) + self.context.loaded_image_protocol_modules.append(image_base) - def map_and_load(self, path: str, exec_now: bool=False): + def map_and_load(self, path: str, context: UefiContext, exec_now: bool=False): """Map and load a module into memory. The specified module would be mapped and loaded into the address set @@ -86,6 +83,7 @@ def map_and_load(self, path: str, exec_now: bool=False): Args: path : path of the module binary to load + context : uefi context the module belongs to exec_now : execute module right away; will be enququed if not Raises: @@ -96,7 +94,7 @@ def map_and_load(self, path: str, exec_now: bool=False): pe = PE(path, fast_load=True) # use image base only if it does not point to NULL - image_base = pe.OPTIONAL_HEADER.ImageBase or self.next_image_base + image_base = pe.OPTIONAL_HEADER.ImageBase or context.next_image_base image_size = ql.mem.align_up(pe.OPTIONAL_HEADER.SizeOfImage) assert (image_base % ql.mem.pagesize) == 0, 'image base is expected to be page-aligned' @@ -126,9 +124,9 @@ def map_and_load(self, path: str, exec_now: bool=False): # update next memory slot to allow sequencial loading. its availability # is unknown though - self.next_image_base = image_base + image_size + context.next_image_base = image_base + image_size - module_info = (path, image_base, entry_point) + module_info = (path, image_base, entry_point, context) # execute the module right away or enqueue it if exec_now: @@ -146,43 +144,43 @@ def call_function(self, addr: int, args: Sequence[int], ret: Optional[int]): ret : return address; may be None """ - # arguments gpr (ms x64 cc) - regs = ('rcx', 'rdx', 'r8', 'r9') - assert len(args) <= len(regs), f'currently supporting up to {len(regs)} arguments' + types = (PARAM_INTN, ) * len(args) + targs = tuple(zip(types, args)) + + self.ql.os.fcall.call_native(addr, targs, ret) - # set up the arguments - for reg, arg in zip(regs, args): - self.ql.reg.write(reg, arg) + def unload_modules(self, context: UefiContext) -> bool: + """Invoke images unload callbacks, if set. - # if provided, set return address - if ret is not None: - self.ql.stack_push(ret) + Args: + context: uefi context instance - self.ql.reg.rip = addr + Returns: `True` to stop the teardown process, `False` to proceed + """ - def unload_modules(self): - for handle in self.loaded_image_protocol_modules: - struct_addr = self.dxe_context.protocols[handle][self.loaded_image_protocol_guid] + for handle in context.loaded_image_protocol_modules: + struct_addr = context.protocols[handle][self.loaded_image_protocol_guid] loaded_image_protocol = EfiLoadedImageProtocol.EFI_LOADED_IMAGE_PROTOCOL.loadFrom(self.ql, struct_addr) - unload_ptr = self.ql.unpack64(loaded_image_protocol.Unload) + unload_ptr = loaded_image_protocol.Unload.value if unload_ptr != 0: self.ql.log.info(f'Unloading module {handle:#x}, calling {unload_ptr:#x}') - self.call_function(unload_ptr, [handle], self.end_of_execution_ptr) - self.loaded_image_protocol_modules.remove(handle) + self.ql.os.fcall.call_native(unload_ptr, ((POINTER, handle),), context.end_of_execution_ptr) + context.loaded_image_protocol_modules.remove(handle) return True return False - def execute_module(self, path: str, image_base: int, entry_point: int, eoe_trap: Optional[int]): + def execute_module(self, path: str, image_base: int, entry_point: int, context: UefiContext, eoe_trap: Optional[int]): """Start the execution of a UEFI module. Args: image_base : module base address entry_point : module entry point address + context : module execution context (either dxe or smm) eoe_trap : end-of-execution trap address; may be None """ @@ -190,120 +188,211 @@ def execute_module(self, path: str, image_base: int, entry_point: int, eoe_trap: ImageHandle = image_base SystemTable = self.gST - self.call_function(entry_point, [ImageHandle, SystemTable], eoe_trap) - self.ql.os.entry_point = entry_point + # set effectively active heap + self.ql.os.heap = context.heap + + # set stack and frame pointers + self.ql.reg.rsp = context.top_of_stack + self.ql.reg.rbp = context.top_of_stack + self.ql.os.fcall.call_native(entry_point, ( + (POINTER, ImageHandle), + (POINTER, SystemTable) + ), eoe_trap) + + self.ql.os.running_module = path + self.ql.os.entry_point = entry_point self.ql.log.info(f'Running from {entry_point:#010x} of {path}') def execute_next_module(self): - if not self.modules or self.ql.os.notify_before_module_execution(self.ql, self.modules[0][0]): + if not self.modules: return - path, image_base, entry_point = self.modules.pop(0) - self.execute_module(path, image_base, entry_point, self.end_of_execution_ptr) - - def run(self): - # intel architecture uefi implementation only - if self.ql.archtype not in (QL_ARCH.X86, QL_ARCH.X8664): - raise QlErrorArch("Unsupported architecture") - - # x86-64 arch only - if self.ql.archtype != QL_ARCH.X8664: - raise QlErrorArch("Only 64 bit arch is supported at the moment") + path, image_base, entry_point, context = self.modules.pop(0) - self.loaded_image_protocol_guid = self.ql.os.profile["LOADED_IMAGE_PROTOCOL"]["Guid"] - self.loaded_image_protocol_modules = [] - self.tpl = 4 # TPL_APPLICATION + if self.ql.os.notify_before_module_execution(path): + return - arch_key = { - QL_ARCH.X86 : "OS32", - QL_ARCH.X8664 : "OS64" - }[self.ql.archtype] + self.execute_module(path, image_base, entry_point, context, context.end_of_execution_ptr) - # -------- init BS / RT / DXE data structures and protocols -------- + def __init_dxe_environment(self, ql: Qiling) -> DxeContext: + """Initialize DXE data structures (BS, RT and DS) and install essential protocols. + """ - os_profile = self.ql.os.profile[arch_key] - self.dxe_context = context.DxeContext(self.ql) + profile = ql.os.profile['DXE'] + context = DxeContext(ql) # initialize and locate heap - heap_base = int(os_profile["heap_address"], 0) - heap_size = int(os_profile["heap_size"], 0) - self.dxe_context.init_heap(heap_base, heap_size) - self.heap_base_address = heap_base - self.ql.log.info(f"Located heap at {heap_base:#010x}") + heap_base = int(profile['heap_address'], 0) + heap_size = int(profile['heap_size'], 0) + context.init_heap(heap_base, heap_size) + ql.log.info(f'DXE heap at {heap_base:#010x}') # initialize and locate stack - stack_base = int(os_profile["stack_address"], 0) - stack_size = int(os_profile["stack_size"], 0) - self.dxe_context.init_stack(stack_base, stack_size) - sp = stack_base + stack_size - CPU_STACK_ALIGNMENT - self.ql.log.info(f"Located stack at {sp:#010x}") + stack_base = int(profile['stack_address'], 0) + stack_size = int(profile['stack_size'], 0) + context.init_stack(stack_base, stack_size) + ql.log.info(f'DXE stack at {context.top_of_stack:#010x}') + + # base address for next image + context.next_image_base = int(profile['image_address'], 0) + + # statically allocating 4 KiB for ST, RT, BS, DS and about 100 configuration table entries. + # the actual size needed was rounded up to the nearest page boundary. + gST = context.heap.alloc(4 * 1024) - # TODO: statically allocating 256 KiB for ST, RT, BS, DS and Configuration Tables. - # however, this amount of memory is rather arbitrary - gST = self.dxe_context.heap.alloc(256 * 1024) - st.initialize(self.ql, gST) + # TODO: statically allocating 64 KiB for data pointed by configuration table. + # note that this amount of memory was picked arbitrarily + conf_data = context.heap.alloc(64 * 1024) + + context.conf_table_data_ptr = conf_data + context.conf_table_data_next_ptr = conf_data + + # the end of execution hook should be set on an address that is not expected to be + # executed, like the system table location + context.end_of_execution_ptr = gST + + st.initialize(ql, context, gST) protocols = ( EfiSmmAccess2Protocol, EfiSmmBase2Protocol, ) - for proto in protocols: - self.dxe_context.install_protocol(proto.descriptor, 1) + for p in protocols: + context.install_protocol(p.descriptor, 1) + + return context + + def __init_smm_environment(self, ql: Qiling) -> SmmContext: + """Initialize SMM data structures (SMST and SmmRT) and install essential protocols. + """ + + profile = ql.os.profile['SMM'] + context = SmmContext(ql) + + # set smram boundaries + context.smram_base = int(profile["smram_base"], 0) + context.smram_size = int(profile["smram_size"], 0) - # workaround - self.ql.os.heap = self.dxe_context.heap + # initialize and locate heap + heap_base = int(profile["heap_address"], 0) + heap_size = int(profile["heap_size"], 0) + context.init_heap(heap_base, heap_size) + ql.log.info(f"SMM heap at {heap_base:#010x}") - # -------- init SMM data structures and protocols -------- + # initialize and locate stack + stack_base = int(profile['stack_address'], 0) + stack_size = int(profile['stack_size'], 0) + context.init_stack(stack_base, stack_size) + ql.log.info(f'SMM stack at {context.top_of_stack:#010x}') - smm_profile = self.ql.os.profile['SMRAM'] - self.smm_context = context.SmmContext(self.ql) + # base address for next image + context.next_image_base = int(profile['image_address'], 0) - # initialize and locate SMM heap - heap_base = int(smm_profile["heap_address"], 0) - heap_size = int(smm_profile["heap_size"], 0) - self.smm_context.init_heap(heap_base, heap_size) - self.ql.log.info(f"Located SMM heap at {heap_base:#010x}") + # statically allocating 4 KiB for SMM ST and about 100 configuration table entries + # the actual size needed was rounded up to the nearest page boundary. + gSmst = context.heap.alloc(4 * 1024) - # TODO: statically allocating 256 KiB for SMM ST. - # however, this amount of memory is rather arbitrary - gSmst = self.smm_context.heap.alloc(256 * 1024) - smst.initialize(self.ql, gSmst) + # TODO: statically allocating 64 KiB for data pointed by configuration table. + # note that this amount of memory was picked arbitrarily + conf_data = context.heap.alloc(64 * 1024) - self.in_smm = False + context.conf_table_data_ptr = conf_data + context.conf_table_data_next_ptr = conf_data + + # the end of execution hook should be set on an address that is not expected to be + # executed, like the system table location + context.end_of_execution_ptr = gSmst + + smst.initialize(ql, context, gSmst) protocols = ( EfiSmmCpuProtocol, EfiSmmSwDispatch2Protocol ) - for proto in protocols: - self.smm_context.install_protocol(proto.descriptor, 1) + for p in protocols: + context.install_protocol(p.descriptor, 1) - # set stack and frame pointers - self.ql.reg.rsp = sp - self.ql.reg.rbp = sp + return context + + def run(self): + ql = self.ql + + # intel architecture uefi implementation only + if ql.archtype not in (QL_ARCH.X86, QL_ARCH.X8664): + raise QlErrorArch("Unsupported architecture") + + # x86-64 arch only + if ql.archtype != QL_ARCH.X8664: + raise QlErrorArch("Only 64-bit modules are supported at the moment") + + self.loaded_image_protocol_guid = ql.os.profile["LOADED_IMAGE_PROTOCOL"]["Guid"] + self.tpl = 4 # TPL_APPLICATION + + # TODO: assign context to os rather than loader + self.dxe_context = self.__init_dxe_environment(ql) + self.smm_context = self.__init_smm_environment(ql) self.entry_point = 0 self.load_address = 0 - self.next_image_base = int(os_profile["image_address"], 0) try: - for dependency in self.ql.argv: - self.map_and_load(dependency) - except QlMemoryMappedError: - self.ql.log.critical("Couldn't map dependency") + for dependency in ql.argv: + + # TODO: determine whether this is an smm or dxe module + is_smm_module = 'Smm' in dependency - self.ql.log.info(f"Done with loading {self.ql.path}") + if is_smm_module: + self.context = self.smm_context + else: + self.context = self.dxe_context - # set up an end-of-execution hook to regain control when module is done - # executing (i.e. when the entry point function returns). that should be - # set on a non-executable address, so SystemTable's address was picked - self.end_of_execution_ptr = gST - self.ql.hook_address(hook_EndOfExecution, self.end_of_execution_ptr) + self.map_and_load(dependency, self.context) + + ql.log.info(f"Done loading modules") + + except QlMemoryMappedError: + ql.log.critical("Could not map dependency") + + self.set_exit_hook(self.dxe_context.end_of_execution_ptr) + self.set_exit_hook(self.smm_context.end_of_execution_ptr) self.execute_next_module() - def restore_runtime_services(self): - pass # not sure why do we need to restore RT + def set_exit_hook(self, address: int): + """Set up an end-of-execution hook to regain control when module is done + executing; i.e. when the module entry point function returns. + """ + + def __module_exit_trap(ql: Qiling): + # this trap will be called when the current module entry point function + # returns. this is done do regain control, run necessary tear down code + # and proceed to the execution of the next module. if no more modules + # left, terminate gracefully. + # + # the tear down code may include queued protocol notifications and module + # unload callbacks. in such case the trap returns without calling 'os.stop' + # and the execution resumes with the current cpu state. + # + # note that the trap may be called multiple times for a single module, + # every time a tear down code needs to be executed, or for any other + # reason defined by the user. + + if ql.os.notify_after_module_execution(len(self.modules)): + return + + if utils.execute_protocol_notifications(ql): + return + + if self.modules: + self.execute_next_module() + else: + if self.unload_modules(self.smm_context) or self.unload_modules(self.dxe_context): + return + + ql.log.info(f'No more modules to run') + ql.os.stop() + + self.ql.hook_address(__module_exit_trap, address) diff --git a/qiling/os/os.py b/qiling/os/os.py index e5833a302..b5824b921 100644 --- a/qiling/os/os.py +++ b/qiling/os/os.py @@ -11,7 +11,7 @@ from qiling import Qiling from qiling.const import QL_OS, QL_INTERCEPT, QL_OS_POSIX from qiling.os.const import STRING, WSTRING, GUID -from qiling.os.fcall import QlFunctionCall +from qiling.os.fcall import QlFunctionCall, TypedArg from .filestruct import ql_file from .mapper import QlFsMapper @@ -81,10 +81,10 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}): self.ql.arch.utils.setup_output() - def save(self): + def save(self) -> Mapping[str, Any]: return {} - def restore(self, saved_state): + def restore(self, saved_state: Mapping[str, Any]): pass @property @@ -163,7 +163,7 @@ def resolve_fcall_params(self, params: Mapping[str, Any]) -> Mapping[str, Any]: return resolved - def process_fcall_params(self, targs: Iterable[Tuple[Any, str, Any]]) -> Sequence[Tuple[str, str]]: + def process_fcall_params(self, targs: Iterable[TypedArg]) -> Sequence[Tuple[str, str]]: ahandlers: Mapping[type, Callable[[Any], str]] = { int : lambda v: f'{v:#x}' if v else f'0', str : lambda v: QlOsUtils.stringify(v), @@ -212,7 +212,7 @@ def set_api(self, api_name: str, intercept_function: Callable, intercept: QL_INT else: self.add_function_hook(api_name, intercept_function, intercept) - def find_containing_image(self, pc): + def find_containing_image(self, pc: int): for image in self.ql.loader.images: if image.base <= pc < image.end: return image diff --git a/qiling/os/uefi/ProcessorBind.py b/qiling/os/uefi/ProcessorBind.py index 49af7574e..817be539f 100644 --- a/qiling/os/uefi/ProcessorBind.py +++ b/qiling/os/uefi/ProcessorBind.py @@ -4,6 +4,7 @@ # import ctypes +from contextlib import contextmanager from typing import Mapping, MutableMapping, Sequence, Optional from qiling import Qiling @@ -27,13 +28,13 @@ def PTR(ptype: Optional[type]) -> type: return _pointer_type_cache[pname] VOID = None -INT8 = ctypes.c_byte +INT8 = ctypes.c_int8 INT16 = ctypes.c_int16 INT32 = ctypes.c_int32 INT64 = ctypes.c_int64 INTN = INT64 -UINT8 = ctypes.c_ubyte +UINT8 = ctypes.c_uint8 UINT16 = ctypes.c_uint16 UINT32 = ctypes.c_uint32 UINT64 = ctypes.c_uint64 @@ -45,8 +46,6 @@ def PTR(ptype: Optional[type]) -> type: FUNCPTR = lambda *args: PTR(ctypes.CFUNCTYPE(*args)) UNION = ctypes.Union -# SIZEOF = lambda t: ctypes.sizeof(t) -# OFFSETOF = lambda cls, fname: getattr(cls, fname).offset CPU_STACK_ALIGNMENT = 16 PAGE_SIZE = 0x1000 @@ -79,6 +78,16 @@ def loadFrom(cls, ql: Qiling, address: int) -> 'STRUCT': return cls.from_buffer_copy(data) + @classmethod + @contextmanager + def bindTo(cls, ql: Qiling, address: int): + instance = cls.loadFrom(ql, address) + + try: + yield instance + finally: + instance.saveTo(ql, address) + @classmethod def sizeof(cls) -> int: """Get the C structure size in bytes. @@ -95,9 +104,10 @@ def offsetof(cls, fname: str) -> int: @classmethod def memberat(cls, offset: int) -> Optional[str]: - for fname, _ in cls._fields_: - if cls.offsetof(fname) == offset: - return fname + """Get the member name at a given offset. + """ + + return next((fname for fname, *_ in cls._fields_ if cls.offsetof(fname) == offset), None) class EnumMeta(type(ctypes.c_int)): def __getattr__(self, key): diff --git a/qiling/os/uefi/UefiSpec.py b/qiling/os/uefi/UefiSpec.py index 14816b88f..6e9e85e66 100644 --- a/qiling/os/uefi/UefiSpec.py +++ b/qiling/os/uefi/UefiSpec.py @@ -10,6 +10,13 @@ from .UefiBaseType import * from .UefiMultiPhase import * +# definitions for EFI_TIME.Daylight +EFI_TIME_ADJUST_DAYLIGHT = (1 << 1) +EFI_TIME_IN_DAYLIGHT = (1 << 2) + +# definition for EFI_TIME.TimeZone +EFI_UNSPECIFIED_TIMEZONE = 0x07ff + class EFI_ALLOCATE_TYPE(ENUM): _members_ = [ 'AllocateAnyPages', @@ -38,17 +45,19 @@ class EFI_LOCATE_SEARCH_TYPE(ENUM): ] class EFI_TIME_CAPABILITIES(STRUCT): + _pack_ = 8 + _fields_ = [ ('Resolution', UINT32), ('Accuracy', UINT32), ('SetsToZero', BOOLEAN), - ('PADDING_0', UINT8 * 3) ] class EFI_MEMORY_DESCRIPTOR(STRUCT): + _pack_ = 8 + _fields_ = [ ('Type', UINT32), - ('PADDING_0', UINT8 * 4), ('PhysicalStart', EFI_PHYSICAL_ADDRESS), ('VirtualStart', EFI_VIRTUAL_ADDRESS), ('NumberOfPages', UINT64), @@ -223,11 +232,12 @@ class EFI_CONFIGURATION_TABLE(STRUCT): EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL = STRUCT class EFI_SYSTEM_TABLE(STRUCT): + _pack_ = 8 + _fields_ = [ ('Hdr', EFI_TABLE_HEADER), ('FirmwareVendor', PTR(CHAR16)), ('FirmwareRevision', UINT32), - ('PADDING_0', UINT8 * 4), ('ConsoleInHandle', EFI_HANDLE), ('ConIn', PTR(EFI_SIMPLE_TEXT_INPUT_PROTOCOL)), ('ConsoleOutHandle', EFI_HANDLE), @@ -241,6 +251,9 @@ class EFI_SYSTEM_TABLE(STRUCT): ] __all__ = [ + 'EFI_TIME_ADJUST_DAYLIGHT', + 'EFI_TIME_IN_DAYLIGHT', + 'EFI_UNSPECIFIED_TIMEZONE', 'EFI_RUNTIME_SERVICES', 'EFI_BOOT_SERVICES', 'EFI_CONFIGURATION_TABLE', diff --git a/qiling/os/uefi/__init__.py b/qiling/os/uefi/__init__.py index 46646dd3e..5b8faeab6 100644 --- a/qiling/os/uefi/__init__.py +++ b/qiling/os/uefi/__init__.py @@ -2,12 +2,16 @@ from typing import Mapping from os import path -# -------- init GUIDs dictionary -------- -csv_path = path.dirname(path.abspath(__file__)) -csv_path = path.join(csv_path,'guids.csv') +def __init_guids_db() -> Mapping[str, str]: + """Initialize GUIDs dictionary from a local database. + """ -guids_db: Mapping[str, str] = {} -with open(csv_path) as guids_file: - guids_reader = csv.reader(guids_file) + csv_path = path.dirname(path.abspath(__file__)) + csv_path = path.join(csv_path, 'guids.csv') - guids_db = dict(tuple(entry) for entry in guids_reader) + with open(csv_path) as guids_file: + guids_reader = csv.reader(guids_file) + + return dict(tuple(entry) for entry in guids_reader) + +guids_db = __init_guids_db() diff --git a/qiling/os/uefi/bs.py b/qiling/os/uefi/bs.py index dc7ad65de..d80ac385a 100644 --- a/qiling/os/uefi/bs.py +++ b/qiling/os/uefi/bs.py @@ -7,6 +7,7 @@ from qiling.const import QL_ENDIAN from qiling.os.const import * +from qiling.os.uefi import guids_db from qiling.os.uefi.const import * from qiling.os.uefi.fncc import dxeapi from qiling.os.uefi.utils import * @@ -14,9 +15,6 @@ from qiling.os.uefi.UefiSpec import * from qiling.os.uefi.protocols import common -# TODO: find a better solution than hardcoding this -pointer_size = 8 - @dxeapi(params = { "NewTpl" : ULONGLONG # EFI_TPL }) @@ -135,17 +133,22 @@ def hook_WaitForEvent(ql: Qiling, address: int, params): def hook_SignalEvent(ql: Qiling, address: int, params): event_id = params["Event"] - if event_id in ql.loader.events: - signal_event(ql, event_id) - return EFI_SUCCESS - else: + if event_id not in ql.loader.events: return EFI_INVALID_PARAMETER + signal_event(ql, event_id) + + return EFI_SUCCESS + @dxeapi(params = { "Event": POINTER # EFI_EVENT }) def hook_CloseEvent(ql: Qiling, address: int, params): event_id = params["Event"] + + if event_id not in ql.loader.events: + return EFI_INVALID_PARAMETER + del ql.loader.events[event_id] return EFI_SUCCESS @@ -289,6 +292,9 @@ def hook_UnloadImage(ql: Qiling, address: int, params): def hook_ExitBootServices(ql: Qiling, address: int, params): ql.emu_stop() + # TODO: cleanup BS tableas and data, and notify signal list gEfiEventExitBootServicesGuid + # @see: MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c, CoreExitBootServices + return EFI_SUCCESS @dxeapi(params = { @@ -393,7 +399,7 @@ def hook_LocateHandleBuffer(ql: Qiling, address: int, params): for handle in handles: write_int64(ql, address, handle) - address += pointer_size + address += ql.pointersize return EFI_SUCCESS @@ -413,7 +419,7 @@ def hook_InstallMultipleProtocolInterfaces(ql: Qiling, address: int, params): handle = read_int64(ql, params["Handle"]) if handle == 0: - handle = ql.loader.dxe_context.heap.alloc(pointer_size) + handle = ql.loader.dxe_context.heap.alloc(ql.pointersize) dic = ql.loader.dxe_context.protocols.get(handle, {}) @@ -426,11 +432,11 @@ def hook_InstallMultipleProtocolInterfaces(ql: Qiling, address: int, params): GUID = str(ql.os.utils.read_guid(GUID_ptr)) dic[GUID] = protocol_ptr - ql.log.info(f' | {GUID} {protocol_ptr:#x}') + ql.log.info(f'Installing protocol interface {guids_db.get(GUID.upper(), GUID)} to {protocol_ptr:#x}') index += 2 ql.loader.dxe_context.protocols[handle] = dic - check_and_notify_protocols(ql, True) + execute_protocol_notifications(ql, True) write_int64(ql, params["Handle"], handle) return EFI_SUCCESS @@ -460,7 +466,7 @@ def hook_UninstallMultipleProtocolInterfaces(ql: Qiling, address: int, params): del dic[GUID] - ql.log.info(f' | {GUID}, {protocol_ptr:#x}') + ql.log.info(f'Uninstalling protocol interface {guids_db.get(GUID.upper(), GUID)} from {protocol_ptr:#x}') index += 2 return EFI_SUCCESS diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index 3f2257b30..4975d1370 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -1,31 +1,45 @@ -from abc import ABC -from typing import Mapping, Tuple +from abc import ABC, abstractmethod +from typing import Any, Mapping, MutableSequence, Optional, Tuple from qiling import Qiling from qiling.os.memory import QlMemoryHeap -from qiling.os.uefi.utils import init_struct, update_struct, str_to_guid, execute_protocol_notifications, signal_event +from qiling.os.uefi.ProcessorBind import STRUCT, CPU_STACK_ALIGNMENT from qiling.os.uefi.UefiSpec import EFI_CONFIGURATION_TABLE, EFI_SYSTEM_TABLE from qiling.os.uefi.smst import EFI_SMM_SYSTEM_TABLE2 +from qiling.os.uefi import utils class UefiContext(ABC): def __init__(self, ql: Qiling): self.ql = ql - self.heap = None + self.heap: QlMemoryHeap + self.top_of_stack: int self.protocols = {} + self.loaded_image_protocol_modules: MutableSequence[int] = [] + self.next_image_base: int # These members must be initialized before attempting to install a configuration table. - self.conf_table_array = [] - self.conf_table_array_ptr = 0 self.conf_table_data_ptr = 0 self.conf_table_data_next_ptr = 0 + self.conftable: UefiConfTable + self.end_of_execution_ptr: int + + # TODO: implement save state + def save(self) -> Mapping[str, Any]: + return {} + + # TODO: implement restore state + def restore(self, saved_state: Mapping[str, Any]): + pass + def init_heap(self, base: int, size: int): self.heap = QlMemoryHeap(self.ql, base, base + size) def init_stack(self, base: int, size: int): - self.ql.mem.map(base, size) + self.ql.mem.map(base, size, info='[stack]') + self.top_of_stack = (base + size - 1) & ~(CPU_STACK_ALIGNMENT - 1) - def install_protocol(self, proto_desc: Mapping, handle, address: int = None, from_hook: bool = False): + def install_protocol(self, proto_desc: Mapping, handle: int, address: int = None, from_hook: bool = False): guid = proto_desc['guid'] if handle not in self.protocols: @@ -38,54 +52,43 @@ def install_protocol(self, proto_desc: Mapping, handle, address: int = None, fro struct_class = proto_desc['struct'] address = self.heap.alloc(struct_class.sizeof()) - instance = init_struct(self.ql, address, proto_desc) + instance = utils.init_struct(self.ql, address, proto_desc) instance.saveTo(self.ql, address) self.protocols[handle][guid] = address return self.notify_protocol(handle, guid, address, from_hook) - def notify_protocol(self, handle, protocol, interface, from_hook): + def notify_protocol(self, handle: int, protocol: str, interface: int, from_hook: bool): for (event_id, event_dic) in self.ql.loader.events.items(): if event_dic['Guid'] == protocol: if event_dic['CallbackArgs'] == None: # To support smm notification, we use None for CallbackArgs on SmmRegisterProtocolNotify # and updare it here. - guid = str_to_guid(protocol) + guid = utils.str_to_guid(protocol) guid_ptr = self.heap.alloc(guid.sizeof()) guid.saveTo(self.ql, guid_ptr) - event_dic['CallbackArgs'] = [guid_ptr, interface, handle] - # The event was previously registered by 'RegisterProtocolNotify'. - signal_event(self.ql, event_id) - return execute_protocol_notifications(self.ql, from_hook) - - def install_configuration_table(self, guid: str, table: int): - guid = guid.lower() - confs = self.conf_table_array - # find configuration table entry by guid. if found, idx would be set to the entry index - # in the array. if not, idx would be set to one past end of array - if guid not in confs: - confs.append(guid) + event_dic['CallbackArgs'] = [guid_ptr, interface, handle] - idx = confs.index(guid) - ptr = self.conf_table_array_ptr + (idx * EFI_CONFIGURATION_TABLE.sizeof()) + # The event was previously registered by 'RegisterProtocolNotify'. + utils.signal_event(self.ql, event_id) - instance = EFI_CONFIGURATION_TABLE() - instance.VendorGuid = str_to_guid(guid) - instance.VendorTable = table - instance.saveTo(self.ql, ptr) + return utils.execute_protocol_notifications(self.ql, from_hook) class DxeContext(UefiContext): - def install_configuration_table(self, guid: str, table: int): - super().install_configuration_table(guid, table) + def __init__(self, ql: Qiling): + super().__init__(ql) - # Update number of configuration table entries in the ST. - with update_struct(EFI_SYSTEM_TABLE, self.ql, self.ql.loader.gST) as gST: - gST.NumberOfTableEntries = len(self.conf_table_array) + self.conftable = DxeConfTable(ql) class SmmContext(UefiContext): - def __init__(self, ql): - super(SmmContext, self).__init__(ql) + def __init__(self, ql: Qiling): + super().__init__(ql) + + self.conftable = SmmConfTable(ql) + + self.smram_base: int + self.smram_size: int # assume tseg is inaccessible to non-smm self.tseg_open = False @@ -96,9 +99,96 @@ def __init__(self, ql): # registered sw smi handlers self.swsmi_handlers: Mapping[int, Tuple[int, Mapping]] = {} - def install_configuration_table(self, guid: str, table: int): - super().install_configuration_table(guid, table) +class UefiConfTable: + _struct_systbl: STRUCT + _fname_arrptr: str + _fname_nitems: str + + def __init__(self, ql: Qiling): + self.ql = ql + + self.__arrptr_off = self._struct_systbl.offsetof(self._fname_arrptr) + self.__nitems_off = self._struct_systbl.offsetof(self._fname_nitems) + + @property + @abstractmethod + def system_table(self) -> int: + pass + + @property + def baseptr(self) -> int: + addr = self.system_table + self.__arrptr_off + + return utils.read_int64(self.ql, addr) + + @property + def nitems(self) -> int: + addr = self.system_table + self.__nitems_off + + return utils.read_int64(self.ql, addr) # UINTN + + @nitems.setter + def nitems(self, value: int): + addr = self.system_table + self.__nitems_off + + utils.write_int64(self.ql, addr, value) + + def install(self, guid: str, table: int): + ptr = self.find(guid) + append = ptr is None + + if append: + ptr = self.baseptr + self.nitems * EFI_CONFIGURATION_TABLE.sizeof() + append = True + + instance = EFI_CONFIGURATION_TABLE() + instance.VendorGuid = utils.str_to_guid(guid) + instance.VendorTable = table + instance.saveTo(self.ql, ptr) + + if append: + self.nitems += 1 + + def find(self, guid: str) -> Optional[int]: + ptr = self.baseptr + nitems = self.nitems + efi_guid = utils.str_to_guid(guid) + + for _ in range(nitems): + entry = EFI_CONFIGURATION_TABLE.loadFrom(self.ql, ptr) + + if utils.CompareGuid(entry.VendorGuid, efi_guid): + return ptr + + ptr += EFI_CONFIGURATION_TABLE.sizeof() + + return None + + def get_vendor_table(self, guid: str) -> Optional[int]: + ptr = self.find(guid) + + if ptr is not None: + entry = EFI_CONFIGURATION_TABLE.loadFrom(self.ql, ptr) + + return entry.VendorTable.value + + # not found + return None + +class DxeConfTable(UefiConfTable): + _struct_systbl = EFI_SYSTEM_TABLE + _fname_arrptr = 'ConfigurationTable' + _fname_nitems = 'NumberOfTableEntries' + + @property + def system_table(self) -> int: + return self.ql.loader.gST + +class SmmConfTable(UefiConfTable): + _struct_systbl = EFI_SMM_SYSTEM_TABLE2 + _fname_arrptr = 'SmmConfigurationTable' + _fname_nitems = 'NumberOfTableEntries' - # Update number of configuration table entries in the SMST. - with update_struct(EFI_SMM_SYSTEM_TABLE2, self.ql, self.ql.loader.gSmst) as gSmst: - gSmst.NumberOfTableEntries = len(self.conf_table_array) + @property + def system_table(self) -> int: + return self.ql.loader.gSmst diff --git a/qiling/os/uefi/ds.py b/qiling/os/uefi/ds.py index d6004e1a0..df76615cc 100644 --- a/qiling/os/uefi/ds.py +++ b/qiling/os/uefi/ds.py @@ -25,13 +25,14 @@ class EFI_GCD_MEMORY_TYPE(ENUM): ] class EFI_GCD_MEMORY_SPACE_DESCRIPTOR(STRUCT): + _pack_ = 8 + _fields_ = [ ('BaseAddress', EFI_PHYSICAL_ADDRESS), ('Length', UINT64), ('Capabilities', UINT64), ('Attributes', UINT64), ('GcdMemoryType', EFI_GCD_MEMORY_TYPE), - ('PADDING_0', UINT8 * 4), ('ImageHandle', EFI_HANDLE), ('DeviceHandle', EFI_HANDLE) ] @@ -45,11 +46,12 @@ class EFI_GCD_IO_TYPE(ENUM): ] class EFI_GCD_IO_SPACE_DESCRIPTOR(STRUCT): + _pack_ = 8 + _fields_ = [ ('BaseAddress', EFI_PHYSICAL_ADDRESS), ('Length', UINT64), ('GcdIoType', EFI_GCD_IO_TYPE), - ('PADDING_0', UINT8 * 4), ('ImageHandle', EFI_HANDLE), ('DeviceHandle', EFI_HANDLE) ] @@ -246,7 +248,7 @@ def hook_ProcessFirmwareVolume(ctx, address, params): def hook_SetMemorySpaceCapabilities(ctx, address, params): return EFI_UNSUPPORTED -def initialize(ql, gDS): +def initialize(ql: Qiling, gDS: int): descriptor = { 'struct' : EFI_DXE_SERVICES, 'fields' : ( diff --git a/qiling/os/uefi/hob.py b/qiling/os/uefi/hob.py index af554081d..a698be34f 100644 --- a/qiling/os/uefi/hob.py +++ b/qiling/os/uefi/hob.py @@ -7,7 +7,6 @@ from qiling.os.uefi.context import UefiContext from qiling.os.uefi.utils import GetEfiConfigurationTable, CompareGuid, str_to_guid from qiling.os.uefi.UefiBaseType import STRUCT, EFI_GUID, UINT32, UINT16 -from qiling.os.uefi.UefiSpec import EFI_CONFIGURATION_TABLE EFI_HOB_TYPE_HANDOFF = 0x0001 EFI_HOB_TYPE_GUID_EXTENSION = 0x0004 @@ -30,11 +29,12 @@ def GetHobList(ql: Qiling, context: UefiContext) -> int: """Get HOB list location in memory (ostensibly set by PEI). """ - conftable_guid = ql.os.profile['HOB_LIST']['Guid'] - conftable_ptr = GetEfiConfigurationTable(context, conftable_guid) - conftable = EFI_CONFIGURATION_TABLE.loadFrom(ql, conftable_ptr) + hoblist_guid = ql.os.profile['HOB_LIST']['Guid'] + hoblist_vend = GetEfiConfigurationTable(context, hoblist_guid) - return ql.unpack64(conftable.VendorTable) + assert hoblist_vend is not None, 'hob list guid not found' + + return hoblist_vend def CreateHob(ql: Qiling, context: UefiContext, hob) -> int: """Add a HOB to the end of the HOB list. @@ -82,7 +82,7 @@ def GetNextHob(ql: Qiling, hobtype: int, hoblist: int) -> int: return hobaddr -def GetNextGuidHob(ql: Qiling, guid: str, hoblist: int): +def GetNextGuidHob(ql: Qiling, guid: str, hoblist: int) -> int: """Find next HOB with the specified GUID. """ diff --git a/qiling/os/uefi/protocols/EfiLoadedImageProtocol.py b/qiling/os/uefi/protocols/EfiLoadedImageProtocol.py index d7114dde9..fb450c35a 100644 --- a/qiling/os/uefi/protocols/EfiLoadedImageProtocol.py +++ b/qiling/os/uefi/protocols/EfiLoadedImageProtocol.py @@ -9,16 +9,16 @@ from ..UefiMultiPhase import EFI_MEMORY_TYPE class EFI_LOADED_IMAGE_PROTOCOL(STRUCT): + _pack_ = 8 + _fields_ = [ ('Revision', UINT32), - ('PADDING_0', UINT8 * 4), ('ParentHandle', EFI_HANDLE), ('SystemTable', PTR(EFI_SYSTEM_TABLE)), ('DeviceHandle', EFI_HANDLE), ('FilePath', PTR(EFI_DEVICE_PATH_PROTOCOL)), ('Reserved', PTR(VOID)), ('LoadOptionsSize', UINT32), - ('PADDING_1', UINT8 * 4), ('LoadOptions', PTR(VOID)), ('ImageBase', PTR(VOID)), ('ImageSize', UINT64), diff --git a/qiling/os/uefi/protocols/EfiSmmAccess2Protocol.py b/qiling/os/uefi/protocols/EfiSmmAccess2Protocol.py index 8e9c74ae1..405d26a05 100644 --- a/qiling/os/uefi/protocols/EfiSmmAccess2Protocol.py +++ b/qiling/os/uefi/protocols/EfiSmmAccess2Protocol.py @@ -24,6 +24,7 @@ class EFI_MMRAM_DESCRIPTOR(STRUCT): # @see: MdePkg\Include\Protocol\MmAccess.h class EFI_SMM_ACCESS2_PROTOCOL(STRUCT): EFI_SMM_ACCESS2_PROTOCOL = STRUCT + _pack_ = 8 _fields_ = [ ('Open', FUNCPTR(EFI_STATUS, PTR(EFI_SMM_ACCESS2_PROTOCOL))), @@ -31,8 +32,7 @@ class EFI_SMM_ACCESS2_PROTOCOL(STRUCT): ('Lock', FUNCPTR(EFI_STATUS, PTR(EFI_SMM_ACCESS2_PROTOCOL))), ('GetCapabilities', FUNCPTR(EFI_STATUS, PTR(EFI_SMM_ACCESS2_PROTOCOL), PTR(UINTN), PTR(EFI_MMRAM_DESCRIPTOR))), ('LockState', BOOLEAN), - ('OpenState', BOOLEAN), - ('PADDING_0', CHAR8 * 6) + ('OpenState', BOOLEAN) ] @dxeapi(params = { diff --git a/qiling/os/uefi/protocols/EfiSmmBase2Protocol.py b/qiling/os/uefi/protocols/EfiSmmBase2Protocol.py index b7536e50a..671e9a81c 100644 --- a/qiling/os/uefi/protocols/EfiSmmBase2Protocol.py +++ b/qiling/os/uefi/protocols/EfiSmmBase2Protocol.py @@ -27,9 +27,9 @@ class EFI_SMM_BASE2_PROTOCOL(STRUCT): "InSmram" : POINTER }) def hook_InSmm(ql: Qiling, address: int, params): - ql.log.info(f'InSmram = {ql.loader.in_smm}') + ql.log.debug(f'InSmram = {ql.os.smm.active}') - write_int8(ql, params["InSmram"], int(ql.loader.in_smm)) + write_int8(ql, params["InSmram"], int(ql.os.smm.active)) return EFI_SUCCESS diff --git a/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py b/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py index 34bfb1add..eaa6ec681 100644 --- a/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py +++ b/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py @@ -101,9 +101,21 @@ class EFI_SMM_SAVE_STATE_REGISTER(ENUM_UC): "Width" : ULONGLONG,# UINTN "Register" : INT, # EFI_SMM_SAVE_STATE_REGISTER "CpuIndex" : ULONGLONG,# UINTN - "Buffer" : POINTER # PTR(VOID)) + "Buffer" : POINTER # PTR(VOID) }) -def hook_SmmReadSaveState(ql, address, params): +def hook_SmmReadSaveState(ql: Qiling, address: int, params): + Width = params['Width'] + Register = params['Register'] + CpuIndex = params['CpuIndex'] + Buffer = params['Buffer'] + + # currently supporting only one cpu + if CpuIndex > 0: + return EFI_INVALID_PARAMETER + + data = ql.os.smm.ssa.read(Register, Width) + ql.mem.write(Buffer, bytes(data)) + return EFI_SUCCESS @dxeapi(params = { @@ -111,9 +123,21 @@ def hook_SmmReadSaveState(ql, address, params): "Width" : ULONGLONG,# UINTN "Register" : INT, # EFI_SMM_SAVE_STATE_REGISTER "CpuIndex" : ULONGLONG,# UINTN - "Buffer" : POINTER # PTR(VOID)) + "Buffer" : POINTER # PTR(VOID) }) -def hook_SmmWriteSaveState(ql, address, params): +def hook_SmmWriteSaveState(ql: Qiling, address: int, params): + Width = params['Width'] + Register = params['Register'] + CpuIndex = params['CpuIndex'] + Buffer = params['Buffer'] + + # currently supporting only one cpu + if CpuIndex > 0: + return EFI_INVALID_PARAMETER + + data = ql.mem.read(Buffer, Width) + ql.os.smm.ssa.write(Register, bytes(data)) + return EFI_SUCCESS class EFI_SMM_CPU_PROTOCOL(STRUCT): diff --git a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py index 772812a91..cda5e9461 100644 --- a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py +++ b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py @@ -14,6 +14,8 @@ MAXIMUM_SWI_VALUE = 0xff class EFI_SMM_SW_CONTEXT(STRUCT): + _pack_ = 8 + _fields_ = [ ('SwSmiCpuIndex', UINTN), # index of the cpu which generated the swsmi ('CommandPort', UINT8), # port number used to trigger the swsmi @@ -59,7 +61,7 @@ def hook_Register(ql: Qiling, address: int, params): # a value of -1 indicates that the swsmi index for this handler is flexible and # should be assigned by the protocol - if idx == 0xffffffff: + if idx == ((1 << ql.archbit) - 1): idx = next((i for i in range(1, MAXIMUM_SWI_VALUE) if i not in handlers), None) if idx is None: @@ -77,21 +79,13 @@ def hook_Register(ql: Qiling, address: int, params): if idx > This.MaximumSwiValue: return EFI_INVALID_PARAMETER - # prepare the context for the sw smi handler - SwContext = EFI_SMM_SW_CONTEXT() - SwContext.SwSmiCpuIndex = 0 - SwContext.CommandPort = idx - SwContext.DataPort = 0 - # allocate handle and return it through out parameter Handle = ql.loader.smm_context.heap.alloc(ql.pointersize) utils.write_int64(ql, DispatchHandle, Handle) args = { - 'DispatchHandle' : Handle, - 'SwRegisterContext' : SwRegisterContext, - 'SwContext' : SwContext, - 'CommBufferSize' : 0 + 'DispatchHandle' : Handle, + 'RegisterContext' : SwRegisterContext } handlers[idx] = (DispatchFunction, args) diff --git a/qiling/os/uefi/protocols/common.py b/qiling/os/uefi/protocols/common.py index 365fd0ff0..fe582b542 100644 --- a/qiling/os/uefi/protocols/common.py +++ b/qiling/os/uefi/protocols/common.py @@ -129,6 +129,6 @@ def InstallConfigurationTable(context, params): if not guid: return EFI_INVALID_PARAMETER - context.install_configuration_table(guid, table) + context.conftable.install(guid, table) return EFI_SUCCESS diff --git a/qiling/os/uefi/rt.py b/qiling/os/uefi/rt.py index 0e6d104cb..1e108ebe4 100644 --- a/qiling/os/uefi/rt.py +++ b/qiling/os/uefi/rt.py @@ -3,12 +3,15 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import time + from qiling import Qiling from qiling.os.const import * from .const import * from .utils import * from .fncc import * from .ProcessorBind import * +from .UefiBaseType import EFI_TIME from .UefiSpec import * @dxeapi(params={ @@ -16,6 +19,29 @@ "Capabilities" : POINTER # OUT PTR(EFI_TIME_CAPABILITIES) }) def hook_GetTime(ql: Qiling, address: int, params): + Time = params['Time'] + + if not Time: + return EFI_INVALID_PARAMETER + + localtime = time.localtime() + + efitime = EFI_TIME() + efitime.Year = localtime.tm_year + efitime.Month = localtime.tm_mon + efitime.Day = localtime.tm_mday + efitime.Hour = localtime.tm_hour + efitime.Minute = localtime.tm_min + efitime.Second = localtime.tm_sec + efitime.Nanosecond = 0 + + # tz and dst settings are stored in the "RtcTimeSettings" nvram variable. + # we just use the default settings instead + efitime.TimeZone = EFI_UNSPECIFIED_TIMEZONE + efitime.Daylight = 0 + + efitime.saveTo(ql, Time) + return EFI_SUCCESS @dxeapi(params={ @@ -99,7 +125,7 @@ def hook_GetNextVariableName(ql: Qiling, address: int, params): return EFI_INVALID_PARAMETER name_size = read_int64(ql, var_name_size) - last_name = ql.os.read_wstring(var_name) + last_name = ql.os.utils.read_wstring(var_name) vars = ql.env['Names'] # This is a list of variable names in correct order. @@ -184,7 +210,7 @@ def hook_QueryCapsuleCapabilities(ql: Qiling, address: int, params): def hook_QueryVariableInfo(ql: Qiling, address: int, params): return EFI_SUCCESS -def initialize(ql, gRT : int): +def initialize(ql: Qiling, gRT: int): descriptor = { 'struct' : EFI_RUNTIME_SERVICES, 'fields' : ( diff --git a/qiling/os/uefi/shutdown.py b/qiling/os/uefi/shutdown.py deleted file mode 100644 index 1261e4f8c..000000000 --- a/qiling/os/uefi/shutdown.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -# -# Cross Platform and Multi Architecture Advanced Binary Emulation Framework -# - - -from .utils import execute_protocol_notifications - -def hook_EndOfExecution(ql): - if ql.os.notify_after_module_execution(ql, len(ql.loader.modules)): - return - - ql.loader.restore_runtime_services() - - if execute_protocol_notifications(ql): - return - - if ql.loader.modules: - ql.loader.execute_next_module() - else: - if ql.loader.unload_modules(): - return - - ql.log.info(f'No more modules to run') - ql.emu_stop() - ql.os.PE_RUN = False diff --git a/qiling/os/uefi/smm.py b/qiling/os/uefi/smm.py new file mode 100644 index 000000000..c1cee75df --- /dev/null +++ b/qiling/os/uefi/smm.py @@ -0,0 +1,270 @@ +#!/usr/bin/python3 + +from typing import Any, Callable, Iterator, Mapping, Tuple + +from unicorn.unicorn_const import UC_PROT_ALL, UC_PROT_NONE +from unicorn.x86_const import * + +from qiling import Qiling +from qiling.os.const import POINTER +from qiling.os.memory import QlMemoryHeap +from qiling.os.uefi import utils +from qiling.os.uefi.context import SmmContext +from qiling.os.uefi.protocols.EfiSmmCpuProtocol import EFI_SMM_SAVE_STATE_REGISTER +from qiling.os.uefi.protocols.EfiSmmSwDispatch2Protocol import EFI_SMM_SW_REGISTER_CONTEXT, EFI_SMM_SW_CONTEXT + +class SaveStateArea: + # SSA map for x64; note that it does not include all register enumerated in + # EFI_SMM_SAVE_STATE_REGISTER, but only the most commonly used ones + # + # see: Intel SDM vol. 3 chapter 30.4.1.1 + offsets = { + EFI_SMM_SAVE_STATE_REGISTER.GDTBASE : 0x7E8C, + EFI_SMM_SAVE_STATE_REGISTER.IDTBASE : 0x7E94, + EFI_SMM_SAVE_STATE_REGISTER.LDTBASE : 0x7E9C, + EFI_SMM_SAVE_STATE_REGISTER.GDTLIMIT: 0x7DD0, + EFI_SMM_SAVE_STATE_REGISTER.IDTLIMIT: 0x7DD8, + EFI_SMM_SAVE_STATE_REGISTER.LDTLIMIT: 0x7DD4, + # EFI_SMM_SAVE_STATE_REGISTER.LDTINFO : ?, + + EFI_SMM_SAVE_STATE_REGISTER.ES : 0x7FA8, + EFI_SMM_SAVE_STATE_REGISTER.CS : 0x7FAC, + EFI_SMM_SAVE_STATE_REGISTER.SS : 0x7FB0, + EFI_SMM_SAVE_STATE_REGISTER.DS : 0x7FB4, + EFI_SMM_SAVE_STATE_REGISTER.FS : 0x7FB8, + EFI_SMM_SAVE_STATE_REGISTER.GS : 0x7FBC, + EFI_SMM_SAVE_STATE_REGISTER.LDTR_SEL: 0x7FC0, + EFI_SMM_SAVE_STATE_REGISTER.TR_SEL : 0x7FC4, + EFI_SMM_SAVE_STATE_REGISTER.DR7 : 0x7FC8, + EFI_SMM_SAVE_STATE_REGISTER.DR6 : 0x7FD0, + EFI_SMM_SAVE_STATE_REGISTER.R8 : 0x7F54, + EFI_SMM_SAVE_STATE_REGISTER.R9 : 0x7F4C, + EFI_SMM_SAVE_STATE_REGISTER.R10 : 0x7F44, + EFI_SMM_SAVE_STATE_REGISTER.R11 : 0x7F3C, + EFI_SMM_SAVE_STATE_REGISTER.R12 : 0x7F34, + EFI_SMM_SAVE_STATE_REGISTER.R13 : 0x7F2C, + EFI_SMM_SAVE_STATE_REGISTER.R14 : 0x7F24, + EFI_SMM_SAVE_STATE_REGISTER.R15 : 0x7F1C, + EFI_SMM_SAVE_STATE_REGISTER.RAX : 0x7F5C, + EFI_SMM_SAVE_STATE_REGISTER.RBX : 0x7F74, + EFI_SMM_SAVE_STATE_REGISTER.RCX : 0x7F64, + EFI_SMM_SAVE_STATE_REGISTER.RDX : 0x7F6C, + EFI_SMM_SAVE_STATE_REGISTER.RSP : 0x7F7C, + EFI_SMM_SAVE_STATE_REGISTER.RBP : 0x7F84, + EFI_SMM_SAVE_STATE_REGISTER.RSI : 0x7F8C, + EFI_SMM_SAVE_STATE_REGISTER.RDI : 0x7F94, + EFI_SMM_SAVE_STATE_REGISTER.RIP : 0x7FD8, + + EFI_SMM_SAVE_STATE_REGISTER.RFLAGS : 0x7FE8, + EFI_SMM_SAVE_STATE_REGISTER.CR0 : 0x7FF8, + EFI_SMM_SAVE_STATE_REGISTER.CR3 : 0x7FF0, + EFI_SMM_SAVE_STATE_REGISTER.CR4 : 0x7E40 + } + + def __init__(self, ql: Qiling): + self.ql = ql + + self.ssa_base = ql.loader.smm_context.smram_base + 0x8000 + self.ssa_size = 0x8000 + + # map smram save state area, but do not make it available just yet + if ql.mem.is_available(self.ssa_base, self.ssa_size): + ql.mem.map(self.ssa_base, self.ssa_size, UC_PROT_NONE, '[SMRAM SSA]') + + def read(self, regidx: EFI_SMM_SAVE_STATE_REGISTER, width: int) -> bytes: + """Retrieve a register value from SMM save state area. + """ + + reg = self.ssa_base + SaveStateArea.offsets[regidx] + + return self.ql.mem.read(reg, width) + + def write(self, regidx: EFI_SMM_SAVE_STATE_REGISTER, data: bytes) -> None: + """Replace a register value in SMM save state area. + """ + + reg = self.ssa_base + SaveStateArea.offsets[regidx] + + self.ql.mem.write(reg, data) + +class SmmEnv: + SSA_REG_MAP = { + UC_X86_REG_ES : (4, EFI_SMM_SAVE_STATE_REGISTER.ES), + UC_X86_REG_CS : (4, EFI_SMM_SAVE_STATE_REGISTER.CS), + UC_X86_REG_SS : (4, EFI_SMM_SAVE_STATE_REGISTER.SS), + UC_X86_REG_DS : (4, EFI_SMM_SAVE_STATE_REGISTER.DS), + UC_X86_REG_FS : (4, EFI_SMM_SAVE_STATE_REGISTER.FS), + UC_X86_REG_GS : (4, EFI_SMM_SAVE_STATE_REGISTER.GS), + UC_X86_REG_R8 : (8, EFI_SMM_SAVE_STATE_REGISTER.R8), + UC_X86_REG_R9 : (8, EFI_SMM_SAVE_STATE_REGISTER.R9), + UC_X86_REG_R10 : (8, EFI_SMM_SAVE_STATE_REGISTER.R10), + UC_X86_REG_R11 : (8, EFI_SMM_SAVE_STATE_REGISTER.R11), + UC_X86_REG_R12 : (8, EFI_SMM_SAVE_STATE_REGISTER.R12), + UC_X86_REG_R13 : (8, EFI_SMM_SAVE_STATE_REGISTER.R13), + UC_X86_REG_R14 : (8, EFI_SMM_SAVE_STATE_REGISTER.R14), + UC_X86_REG_R15 : (8, EFI_SMM_SAVE_STATE_REGISTER.R15), + UC_X86_REG_RAX : (8, EFI_SMM_SAVE_STATE_REGISTER.RAX), + UC_X86_REG_RBX : (8, EFI_SMM_SAVE_STATE_REGISTER.RBX), + UC_X86_REG_RCX : (8, EFI_SMM_SAVE_STATE_REGISTER.RCX), + UC_X86_REG_RDX : (8, EFI_SMM_SAVE_STATE_REGISTER.RDX), + UC_X86_REG_RSP : (8, EFI_SMM_SAVE_STATE_REGISTER.RSP), + UC_X86_REG_RBP : (8, EFI_SMM_SAVE_STATE_REGISTER.RBP), + UC_X86_REG_RSI : (8, EFI_SMM_SAVE_STATE_REGISTER.RSI), + UC_X86_REG_RDI : (8, EFI_SMM_SAVE_STATE_REGISTER.RDI), + UC_X86_REG_RIP : (8, EFI_SMM_SAVE_STATE_REGISTER.RIP), + UC_X86_REG_EFLAGS : (8, EFI_SMM_SAVE_STATE_REGISTER.RFLAGS), + UC_X86_REG_CR0 : (8, EFI_SMM_SAVE_STATE_REGISTER.CR0), + UC_X86_REG_CR3 : (8, EFI_SMM_SAVE_STATE_REGISTER.CR3), + UC_X86_REG_CR4 : (8, EFI_SMM_SAVE_STATE_REGISTER.CR4) + } + + def __init__(self, ql: Qiling): + self.ql = ql + self.ssa = SaveStateArea(ql) + + # by default the system is out of smm + self.active = False + + def __mapped_smram_ranges(self) -> Iterator[Tuple[int, int]]: + """Iterate through all mapped ranges enclosed within SMRAM. + """ + + context: SmmContext = self.ql.loader.smm_context + + smram_lbound = context.smram_base + smram_ubound = smram_lbound + context.smram_size + + for lbound, ubound, *_ in self.ql.mem.get_mapinfo(): + if (smram_lbound <= lbound) and (ubound <= smram_ubound): + yield lbound, ubound + + def enter(self) -> None: + """Enter SMM. + + Save CPU state and unlock SMM resources. + """ + + self.ql.log.info(f'Entering SMM') + + assert not self.active, 'SMM is not reentrant' + + # unlock smram ranges for access + for lbound, ubound in self.__mapped_smram_ranges(): + self.ql.mem.protect(lbound, ubound - lbound, UC_PROT_ALL) + + # write cpu state to ssa (partially) + # that can take place only after smram ranges have been unlocked + for ucreg, (width, regidx) in SmmEnv.SSA_REG_MAP.items(): + val = self.ql.reg.read(ucreg) + + pack = { + 8 : self.ql.pack64, + 4 : self.ql.pack32, + 2 : self.ql.pack16, + 1 : self.ql.pack8 + }[width] + + self.ssa.write(regidx, pack(val)) + + # let os know that the code is now executing in smm + self.active = True + + def leave(self) -> None: + """Leave SMM. + + Restore CPU state and lock SMM resources. + """ + + self.ql.log.info(f'Leaving SMM') + + # restore cpu state from ssa (partially) + # that can take place only before smram ranges have been locked + for ucreg, (width, regidx) in SmmEnv.SSA_REG_MAP.items(): + data = self.ssa.read(regidx, width) + + unpack = { + 8 : self.ql.unpack64, + 4 : self.ql.unpack32, + 2 : self.ql.unpack16, + 1 : self.ql.unpack8 + }[width] + + self.ql.reg.write(ucreg, unpack(data)) + + # lock smram ranges for access + for lbound, ubound in self.__mapped_smram_ranges(): + self.ql.mem.protect(lbound, ubound - lbound, UC_PROT_NONE) + + # let os know that the code is no longer executing in smm + self.active = False + + def invoke_swsmi(self, cpu: int, idx: int, entry: int, args: Mapping[str, Any], *, onexit: Callable[[Qiling], None] = None) -> None: + """Invoke a native SWSMI handler. + + Args: + cpu: initiating logical processor index + idx: swsmi index + entry: swsmi handler entry point + args: data arguments collected on handler registration + onexit: optionally specify a method to call on handler exit + """ + + ql = self.ql + heap: QlMemoryHeap = self.ql.loader.smm_context.heap + + self.enter() + + DispatchHandle = args['DispatchHandle'] + Context = heap.alloc(EFI_SMM_SW_REGISTER_CONTEXT.sizeof()) + CommBuffer = heap.alloc(EFI_SMM_SW_CONTEXT.sizeof()) + CommBufferSize = heap.alloc(ql.pointersize) + + # setup Context + args['RegisterContext'].saveTo(ql, Context) + + # setup CommBuffer + SmmSwContext = EFI_SMM_SW_CONTEXT() + SmmSwContext.SwSmiCpuIndex = cpu + SmmSwContext.CommandPort = idx + SmmSwContext.DataPort = 0 + SmmSwContext.saveTo(ql, CommBuffer) + + # setup CommBufferSize + utils.ptr_write64(ql, CommBufferSize, SmmSwContext.sizeof()) + + # clean up handler resources + def __cleanup(ql: Qiling): + ql.log.info(f'Leaving SWSMI handler {idx:#04x}') + + # unwind ms64 shadow space + ql.reg.arch_sp += (4 * ql.pointersize) + + # release handler resources + heap.free(DispatchHandle) + heap.free(Context) + heap.free(CommBuffer) + heap.free(CommBufferSize) + + # release hook + heap.free(cleanup_trap) + hret.remove() + + self.leave() + + # if specified, call on-exit callback + if onexit: + onexit(ql) + + # hook returning from swsmi handler + cleanup_trap = heap.alloc(ql.pointersize) + hret = ql.hook_address(__cleanup, cleanup_trap) + + ql.log.info(f'Entering SWSMI handler {idx:#04x}') + + # invoke the swsmi handler + ql.os.fcall.call_native(entry, ( + (POINTER, DispatchHandle), + (POINTER, Context), + (POINTER, CommBuffer), + (POINTER, CommBufferSize) + ), cleanup_trap) diff --git a/qiling/os/uefi/smst.py b/qiling/os/uefi/smst.py index f3e3199d2..bdd40c0c1 100644 --- a/qiling/os/uefi/smst.py +++ b/qiling/os/uefi/smst.py @@ -45,12 +45,12 @@ class EFI_SMM_CPU_IO2_PROTOCOL(STRUCT): class EFI_SMM_SYSTEM_TABLE2(STRUCT): EFI_SMM_SYSTEM_TABLE2 = STRUCT + _pack_ = 8 _fields_ = [ ('Hdr', EFI_TABLE_HEADER), ('SmmFirmwareVendor', PTR(CHAR16)), ('SmmFirmwareRevision', UINT32), - ('PADDING_0', UINT8 * 4), ('SmmInstallConfigurationTable', FUNCPTR(EFI_STATUS, PTR(EFI_SMM_SYSTEM_TABLE2), PTR(EFI_GUID), PTR(VOID), UINTN)), ('SmmIo', EFI_SMM_CPU_IO2_PROTOCOL), ('SmmAllocatePool', FUNCPTR(EFI_STATUS, EFI_MEMORY_TYPE, UINTN, PTR(PTR(VOID)))), @@ -79,7 +79,7 @@ class EFI_SMM_SYSTEM_TABLE2(STRUCT): "Guid" : GUID, # PTR(EFI_GUID) "Table" : POINTER # PTR(VOID) }) -def hook_SmmInstallConfigurationTable(ql, address, params): +def hook_SmmInstallConfigurationTable(ql: Qiling, address: int, params): return common.InstallConfigurationTable(ql.loader.smm_context, params) @dxeapi(params = { @@ -88,7 +88,7 @@ def hook_SmmInstallConfigurationTable(ql, address, params): "Pages" : ULONGLONG, # UINTN "Memory" : POINTER # PTR(EFI_PHYSICAL_ADDRESS) }) -def hook_SmmAllocatePages(ql, address, params): +def hook_SmmAllocatePages(ql: Qiling, address: int, params): alloc_size = params["Pages"] * PAGE_SIZE if params['type'] == EFI_ALLOCATE_TYPE.AllocateAddress: @@ -111,7 +111,7 @@ def hook_SmmAllocatePages(ql, address, params): "Memory" : ULONGLONG, # EFI_PHYSICAL_ADDRESS "Pages" : ULONGLONG # UINTN }) -def hook_SmmFreePages(ql, address, params): +def hook_SmmFreePages(ql: Qiling, address: int, params): address = params["Memory"] ret = ql.loader.smm_context.heap.free(address) @@ -123,7 +123,7 @@ def hook_SmmFreePages(ql, address, params): "Size" : INT, # UINTN "Buffer" : POINTER # PTR(PTR(VOID)) }) -def hook_SmmAllocatePool(ql, address, params): +def hook_SmmAllocatePool(ql: Qiling, address: int, params): # TODO: allocate memory acording to "PoolType" address = ql.loader.smm_context.heap.alloc(params["Size"]) write_int64(ql, params["Buffer"], address) @@ -133,7 +133,7 @@ def hook_SmmAllocatePool(ql, address, params): @dxeapi(params = { "Buffer": POINTER # PTR(VOID) }) -def hook_SmmFreePool(ql, address, params): +def hook_SmmFreePool(ql: Qiling, address: int, params): address = params["Buffer"] ret = ql.loader.smm_context.heap.free(address) @@ -144,7 +144,7 @@ def hook_SmmFreePool(ql, address, params): "CpuNumber" : INT, "ProcArguments" : POINTER }) -def hook_SmmStartupThisAp(ql, address, params): +def hook_SmmStartupThisAp(ql: Qiling, address: int, params): return EFI_INVALID_PARAMETER @dxeapi(params = { @@ -153,7 +153,7 @@ def hook_SmmStartupThisAp(ql, address, params): "InterfaceType" : ULONGLONG, # EFI_INTERFACE_TYPE "Interface" : POINTER, # PTR(VOID) }) -def hook_SmmInstallProtocolInterface(ql, address, params): +def hook_SmmInstallProtocolInterface(ql: Qiling, address: int, params): return common.InstallProtocolInterface(ql.loader.smm_context, params) @dxeapi(params = { @@ -161,7 +161,7 @@ def hook_SmmInstallProtocolInterface(ql, address, params): "Protocol" : GUID, # PTR(EFI_GUID) "Interface" : POINTER # PTR(VOID) }) -def hook_SmmUninstallProtocolInterface(ql, address, params): +def hook_SmmUninstallProtocolInterface(ql: Qiling, address: int, params): return common.UninstallProtocolInterface(ql.loader.smm_context, params) @dxeapi(params = { @@ -169,7 +169,7 @@ def hook_SmmUninstallProtocolInterface(ql, address, params): "Protocol" : GUID, # PTR(EFI_GUID) "Interface" : POINTER # PTR(PTR(VOID)) }) -def hook_SmmHandleProtocol(ql, address, params): +def hook_SmmHandleProtocol(ql: Qiling, address: int, params): return common.HandleProtocol(ql.loader.smm_context, params) @dxeapi(params = { @@ -177,7 +177,7 @@ def hook_SmmHandleProtocol(ql, address, params): "Function" : POINTER, # EFI_MM_NOTIFY_FN "Registration" : POINTER # PTR(PTR(VOID)) }) -def hook_SmmRegisterProtocolNotify(ql, address, params): +def hook_SmmRegisterProtocolNotify(ql: Qiling, address: int, params): event_id = len(ql.loader.events) event_dic = { "NotifyFunction": params["Function"], @@ -196,7 +196,7 @@ def hook_SmmRegisterProtocolNotify(ql, address, params): "BufferSize": POINTER, # PTR(UINTN) "Buffer" : POINTER # PTR(EFI_HANDLE) }) -def hook_SmmLocateHandle(ql, address, params): +def hook_SmmLocateHandle(ql: Qiling, address: int, params): return common.LocateHandle(ql.loader.smm_context, params) @dxeapi(params = { @@ -204,7 +204,7 @@ def hook_SmmLocateHandle(ql, address, params): "Registration" : POINTER, # PTR(VOID) "Interface" : POINTER # PTR(PTR(VOID)) }) -def hook_SmmLocateProtocol(ql, address, params): +def hook_SmmLocateProtocol(ql: Qiling, address: int, params): return common.LocateProtocol(ql.loader.smm_context, params) @dxeapi(params = { @@ -213,7 +213,7 @@ def hook_SmmLocateProtocol(ql, address, params): "CommBuffer" : POINTER, "CommBufferSize": POINTER }) -def hook_SmiManage(ql, address, params): +def hook_SmiManage(ql: Qiling, address: int, params): return EFI_NOT_FOUND @dxeapi(params = { @@ -221,16 +221,16 @@ def hook_SmiManage(ql, address, params): "HandlerType" : GUID, "DispatchHandle": POINTER }) -def hook_SmiHandlerRegister(ql, address, params): +def hook_SmiHandlerRegister(ql: Qiling, address: int, params): return EFI_SUCCESS @dxeapi(params = { "DispatchHandle": POINTER }) -def hook_SmiHandlerUnRegister(ql, address, params): +def hook_SmiHandlerUnRegister(ql: Qiling, address: int, params): return EFI_SUCCESS -def initialize(ql, gSmst : int): +def initialize(ql: Qiling, context, gSmst: int): ql.loader.gSmst = gSmst gSmmRT = gSmst + EFI_SMM_SYSTEM_TABLE2.sizeof() # smm runtime services @@ -244,7 +244,6 @@ def initialize(ql, gSmst : int): ('Hdr', None), ('SmmFirmwareVendor', None), ('SmmFirmwareRevision', None), - ('PADDING_0', None), ('SmmInstallConfigurationTable', hook_SmmInstallConfigurationTable), ('SmmIo', None), ('SmmAllocatePool', hook_SmmAllocatePool), @@ -273,22 +272,8 @@ def initialize(ql, gSmst : int): instance = init_struct(ql, gSmst, descriptor) instance.saveTo(ql, gSmst) - # configuration tables bookkeeping - confs = [] - - # these are needed for utils.SmmInstallConfigurationTable - ql.loader.smm_context.conf_table_array = confs - ql.loader.smm_context.conf_table_array_ptr = cfg - - # configuration table data space; its location is calculated by leaving - # enough space for 100 configuration table entries. only a few entries are - # expected, so 100 should definitely suffice - conf_data = cfg + EFI_CONFIGURATION_TABLE.sizeof() * 100 - ql.loader.smm_context.conf_table_data_ptr = conf_data - ql.loader.smm_context.conf_table_data_next_ptr = conf_data - - install_configuration_table(ql.loader.smm_context, "HOB_LIST", None) - install_configuration_table(ql.loader.smm_context, "SMM_RUNTIME_SERVICES_TABLE", gSmmRT) + install_configuration_table(context, "HOB_LIST", None) + install_configuration_table(context, "SMM_RUNTIME_SERVICES_TABLE", gSmmRT) __all__ = [ 'EFI_SMM_SYSTEM_TABLE2', diff --git a/qiling/os/uefi/st.py b/qiling/os/uefi/st.py index c343785c7..c88c22b64 100644 --- a/qiling/os/uefi/st.py +++ b/qiling/os/uefi/st.py @@ -5,8 +5,9 @@ from qiling import Qiling from qiling.os.uefi import bs, rt, ds +from qiling.os.uefi.context import UefiContext from qiling.os.uefi.utils import install_configuration_table -from qiling.os.uefi.UefiSpec import EFI_SYSTEM_TABLE, EFI_BOOT_SERVICES, EFI_RUNTIME_SERVICES, EFI_CONFIGURATION_TABLE +from qiling.os.uefi.UefiSpec import EFI_SYSTEM_TABLE, EFI_BOOT_SERVICES, EFI_RUNTIME_SERVICES # static mem layout: # @@ -39,15 +40,15 @@ # | VendorTable* -> (3) | # +-----------------------------+ # -# ... sizeof EFI_CONFIGURATION_TABLE x 98 +# ... the remainder of the chunk may be used for additional EFI_CONFIGURATION_TABLE entries + +# dynamically allocated (context.conf_table_data_ptr): # # (5) +-- VOID* --------------------+ # | ... | # +-----------------------------+ -# -# ... the remainder of the 256 KiB chunk may be used for more conf table data -def initialize(ql: Qiling, gST: int): +def initialize(ql: Qiling, context: UefiContext, gST: int): ql.loader.gST = gST gBS = gST + EFI_SYSTEM_TABLE.sizeof() # boot services @@ -74,22 +75,8 @@ def initialize(ql: Qiling, gST: int): instance.saveTo(ql, gST) - # configuration tables bookkeeping - confs = [] - - # these are needed for utils.CoreInstallConfigurationTable - ql.loader.dxe_context.conf_table_array = confs - ql.loader.dxe_context.conf_table_array_ptr = cfg - - # configuration table data space; its location is calculated by leaving - # enough space for 100 configuration table entries. only a few entries are - # expected, so 100 should definitely suffice - conf_data = cfg + EFI_CONFIGURATION_TABLE.sizeof() * 100 - ql.loader.dxe_context.conf_table_data_ptr = conf_data - ql.loader.dxe_context.conf_table_data_next_ptr = conf_data - - install_configuration_table(ql.loader.dxe_context, "HOB_LIST", None) - install_configuration_table(ql.loader.dxe_context, "DXE_SERVICE_TABLE", gDS) + install_configuration_table(context, "HOB_LIST", None) + install_configuration_table(context, "DXE_SERVICE_TABLE", gDS) __all__ = [ 'initialize' diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 35fa9bdab..1ee19fbb1 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -3,24 +3,32 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Any, Callable, Iterable, Mapping, Sequence, Tuple +import re +from typing import Any, Callable, Iterable, Mapping, MutableSequence, Sequence, Tuple from unicorn import UcError from qiling import Qiling from qiling.cc import QlCC, intel from qiling.os.const import * +from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs, QlOsUtils from qiling.os.fcall import QlFunctionCall, TypedArg -from . import guids_db + +from qiling.os.uefi import guids_db +from qiling.os.uefi.smm import SmmEnv class QlOsUefi(QlOs): def __init__(self, ql: Qiling): super().__init__(ql) self.entry_point = 0 - self.running_module = None - self.PE_RUN = True - self.heap = None # Will be initialized by the loader. + self.running_module: str + self.smm: SmmEnv + self.PE_RUN: bool + self.heap: QlMemoryHeap # Will be initialized by the loader. + + self.on_module_enter: MutableSequence[Callable[[str], bool]] = [] + self.on_module_exit: MutableSequence[Callable[[int], bool]] = [] cc: QlCC = { 32: intel.cdecl, @@ -61,61 +69,62 @@ def fallback(v): return super(QlOsUefi, self).process_fcall_params([(None, '', v)])[0][1] ahandlers: Mapping[Any, Callable[[Any], str]] = { - STRING : lambda v: QlOsUtils.stringify(v), - WSTRING : lambda v: f'L{QlOsUtils.stringify(v)}', - GUID : lambda v: guids_db.get(v.upper(), v) + POINTER : lambda v: f'{v:#010x}' if v else 'NULL', + STRING : lambda v: QlOsUtils.stringify(v), + WSTRING : lambda v: f'L{QlOsUtils.stringify(v)}', + GUID : lambda v: guids_db.get(v.upper(), v) if v else 'NULL' } return tuple((aname, ahandlers.get(atype, fallback)(avalue)) for atype, aname, avalue in targs) + def notify_after_module_execution(self, nmodules: int) -> bool: + """Callback fired after a module has finished executing successfully. + + Args: + nmodules: number of remaining modules to execute + + Returns: `True` if subsequent modules execution should be thwarted, `False` otherwise + """ - @staticmethod - def notify_after_module_execution(ql: Qiling, nmodules: int): - return False + return bool(sum(callback(nmodules) for callback in self.on_module_exit)) + def notify_before_module_execution(self, module: str) -> bool: + """Callback fired before a module is about to start executing. - @staticmethod - def notify_before_module_execution(ql: Qiling, module): - ql.os.running_module = module - return False + Args: + module: path of module to execute + + Returns: `True` if module execution should be thwarted, `False` otherwise + """ + + return bool(sum(callback(module) for callback in self.on_module_enter)) def emit_context(self): - # TODO: add xmm, ymm, zmm registers rgroups = ( - ('rax', 'eax', 'ax', 'ah', 'al'), - ('rbx', 'ebx', 'bx', 'bh', 'bl'), - ('rcx', 'ecx', 'cx', 'ch', 'cl'), - ('rdx', 'edx', 'dx', 'dh', 'dl'), - ('rsi', 'esi', 'si', ''), # BUG: sil is missing - ('rdi', 'edi', 'di', ''), # BUG: dil is missing - ('rsp', 'esp', 'sp', ''), # BUG: spl is missing - ('rbp', 'ebp', 'bp', ''), # BUG: bpl is missing - ('rip', 'eip', 'ip', ''), - (), - ('r8', 'r8d', 'r8w', 'r8b' ), - ('r9', 'r9d', 'r9w', 'r9b' ), - ('r10', 'r10d', 'r10w', 'r10b'), - ('r11', 'r11d', 'r11w', 'r11b'), - ('r12', 'r12d', 'r12w', 'r12b'), - ('r13', 'r13d', 'r13w', 'r13b'), - ('r14', 'r14d', 'r14w', 'r14b'), - ('r15', 'r15d', 'r15w', 'r15b'), - (), - ('', '', 'cs'), - ('', '', 'ds'), - ('', '', 'es'), - ('', '', 'fs'), - ('', '', 'gs'), - ('', '', 'ss') + ((8, 'rax'), (8, 'r8'), (4, 'cs')), + ((8, 'rbx'), (8, 'r9'), (4, 'ds')), + ((8, 'rcx'), (8, 'r10'), (4, 'es')), + ((8, 'rdx'), (8, 'r11'), (4, 'fs')), + ((8, 'rsi'), (8, 'r12'), (4, 'gs')), + ((8, 'rdi'), (8, 'r13'), (4, 'ss')), + ((8, 'rsp'), (8, 'r14')), + ((8, 'rbp'), (8, 'r15')), + ((8, 'rip'), ) ) - sizes = (64, 32, 16, 8, 8) + p = re.compile(r'^((?:00)+)') + + def __emit_reg(size: int, reg: str): + val = f'{self.ql.reg.read(reg):0{size * 2}x}' + padded = p.sub("\x1b[90m\\1\x1b[39m", val, 1) + + return f'{reg:3s} = {padded}' self.ql.log.error(f'CPU Context:') - for grp in rgroups: - self.ql.log.error(', '.join((f'{reg:4s} = {self.ql.reg.read(reg):0{bits // 4}x}') for reg, bits in zip(grp, sizes) if reg)) + for regs in rgroups: + self.ql.log.error(f'{" | ".join(__emit_reg(size, reg) for size, reg in regs)}') self.ql.log.error(f'') @@ -142,35 +151,56 @@ def emit_disasm(self, address: int, data: bytearray, num_insns: int = 8): self.ql.log.error('Disassembly:') for insn in tuple(md.disasm(data, address))[:num_insns]: - opcodes = ''.join(f'{ch:02x}' for ch in insn.bytes) - - self.ql.log.error(f'{insn.address:08x} : {opcodes:28s} {insn.mnemonic:10s} {insn.op_str:s}') + self.ql.log.error(f'{insn.address:08x} : {insn.bytes.hex():28s} {insn.mnemonic:10s} {insn.op_str:s}') self.ql.log.error(f'') + def emit_stack(self, nitems: int = 4): + self.ql.log.error('Stack:') + + for i in range(-nitems, nitems + 1): + offset = i * self.ql.pointersize + + try: + item = self.ql.arch.stack_read(offset) + except UcError: + data = '(unavailable)' + else: + data = f'{item:0{self.ql.pointersize * 2}x}' + + self.ql.log.error(f'{self.ql.reg.arch_sp + offset:08x} : {data}{" <=" if i == 0 else ""}') + + self.ql.log.error('') + def emu_error(self): pc = self.ql.reg.arch_pc try: data = self.ql.mem.read(pc, size=64) - + except UcError: + pc_info = ' (unreachable)' + else: self.emit_context() self.emit_hexdump(pc, data) self.emit_disasm(pc, data) containing_image = self.find_containing_image(pc) - img_info = f' ({containing_image.path} + {pc - containing_image.base:#x})' if containing_image else '' - self.ql.log.error(f'PC = {pc:#010x}{img_info}') + pc_info = f' ({containing_image.path} + {pc - containing_image.base:#x})' if containing_image else '' + finally: + self.ql.log.error(f'PC = {pc:#010x}{pc_info}') + self.ql.log.error(f'') - self.ql.log.error(f'Memory map:') - self.ql.mem.show_mapinfo() - except UcError: - self.ql.log.error(f'Error: PC({pc:#x}) is unreachable') + self.emit_stack() + self.ql.log.error(f'Memory map:') + self.ql.mem.show_mapinfo() def run(self): - self.notify_before_module_execution(self.ql, self.running_module) + # TODO: this is not the right place for this + self.smm = SmmEnv(self.ql) + + self.notify_before_module_execution(self.running_module) if self.ql.entry_point is not None: self.ql.loader.entry_point = self.ql.entry_point @@ -179,6 +209,8 @@ def run(self): self.exit_point = self.ql.exit_point try: + self.PE_RUN = True + self.ql.emu_start(self.ql.loader.entry_point, self.exit_point, self.ql.timeout, self.ql.count) except KeyboardInterrupt as ex: self.ql.log.critical(f'Execution interrupted by user') @@ -191,3 +223,7 @@ def run(self): if self.ql._internal_exception is not None: raise self.ql._internal_exception + + def stop(self) -> None: + self.ql.emu_stop() + self.PE_RUN = False diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 648eb52b0..8c66631cb 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -7,12 +7,9 @@ from uuid import UUID from typing import Optional, Mapping -from contextlib import contextmanager from qiling import Qiling from qiling.os.uefi.const import EFI_SUCCESS -from qiling.os.uefi.ProcessorBind import STRUCT -from qiling.os.uefi.UefiSpec import EFI_CONFIGURATION_TABLE from qiling.os.uefi.UefiBaseType import EFI_GUID def signal_event(ql: Qiling, event_id: int) -> None: @@ -29,9 +26,12 @@ def execute_protocol_notifications(ql: Qiling, from_hook: bool = False) -> bool: if not ql.loader.notify_list: return False - next_hook = ql.loader.smm_context.heap.alloc(ql.pointersize) + next_hook = ql.loader.context.heap.alloc(ql.pointersize) def __notify_next(ql: Qiling): + # discard previous callback's shadow space + ql.reg.arch_sp += (4 * ql.pointersize) + if ql.loader.notify_list: event_id, notify_func, callback_args = ql.loader.notify_list.pop(0) ql.log.info(f'Notify event: id = {event_id}, (*{notify_func:#x})({", ".join(f"{a:#x}" for a in callback_args)})') @@ -41,45 +41,28 @@ def __notify_next(ql: Qiling): ql.log.info(f'Notify event: done') # the last item on the list has been notified; tear down this hook - ql.loader.smm_context.heap.free(next_hook) + ql.loader.context.heap.free(next_hook) hret.remove() ql.reg.rax = EFI_SUCCESS - ql.reg.arch_sp += (4 * ql.pointersize) ql.reg.arch_pc = ql.stack_pop() hret = ql.hook_address(__notify_next, next_hook) - # functions with more than 4 parameters expect the extra parameters to appear on - # the stack. allocate room for another 4 parameters, in case one of the fucntions - # will need it + # __notify_next unwinds the previous callback shadow space allocated by call_function. however, on its first invocation + # there is no such shadow space. to maintain stack consistency we set here a bogus shadow space that may be discarded + # safely ql.reg.arch_sp -= (4 * ql.pointersize) # To avoid having two versions of the code the first notify function will also be called from the __notify_next hook. if from_hook: ql.stack_push(next_hook) else: - ql.stack_push(ql.loader.end_of_execution_ptr) + ql.stack_push(ql.loader.context.end_of_execution_ptr) ql.reg.arch_pc = next_hook return True -def check_and_notify_protocols(ql: Qiling, from_hook: bool = False) -> bool: - if ql.loader.notify_list: - event_id, notify_func, notify_context = ql.loader.notify_list.pop(0) - ql.log.info(f'Notify event: id = {event_id}, calling: {notify_func:#x} context: {notify_context}') - - if from_hook: - # When running from a hook the caller pops the return address from the stack. - # We need to push the address to the stack as opposed to setting it to the instruction pointer. - ql.loader.call_function(0, notify_context, notify_func) - else: - ql.loader.call_function(notify_func, notify_context, ql.loader.end_of_execution_ptr) - - return True - - return False - def ptr_read8(ql: Qiling, addr: int) -> int: """Read BYTE data from a pointer """ @@ -92,6 +75,18 @@ def ptr_write8(ql: Qiling, addr: int, val: int) -> None: ql.mem.write(addr, ql.pack8(val)) +def ptr_read16(ql: Qiling, addr: int) -> int: + """Read WORD data from a pointer + """ + + return ql.unpack16(ql.mem.read(addr, 2)) + +def ptr_write16(ql: Qiling, addr: int, val: int) -> None: + """Write WORD data to a pointer + """ + + ql.mem.write(addr, ql.pack16(val)) + def ptr_read32(ql: Qiling, addr: int) -> int: """Read DWORD data from a pointer """ @@ -119,6 +114,8 @@ def ptr_write64(ql: Qiling, addr: int, val: int) -> None: # backward comptability read_int8 = ptr_read8 write_int8 = ptr_write8 +read_int16 = ptr_read16 +write_int16 = ptr_write16 read_int32 = ptr_read32 write_int32 = ptr_write32 read_int64 = ptr_read64 @@ -150,15 +147,6 @@ def init_struct(ql: Qiling, base: int, descriptor: Mapping): return isntance -@contextmanager -def update_struct(cls: STRUCT, ql: Qiling, address: int): - struct = cls.loadFrom(ql, address) - - try: - yield struct - finally: - struct.saveTo(ql, address) - def str_to_guid(guid: str) -> EFI_GUID: """Construct an EFI_GUID structure out of a plain GUID string. """ @@ -170,7 +158,7 @@ def str_to_guid(guid: str) -> EFI_GUID: def CompareGuid(guid1: EFI_GUID, guid2: EFI_GUID) -> bool: return bytes(guid1) == bytes(guid2) -def install_configuration_table(context, key: str, table: int): +def install_configuration_table(context, key: str, table: Optional[int]): """Create a new Configuration Table entry and add it to the list. Args: @@ -192,20 +180,10 @@ def install_configuration_table(context, key: str, table: int): context.ql.mem.write(table, data) context.conf_table_data_next_ptr += len(data) - context.install_configuration_table(guid, table) + context.conftable.install(guid, table) def GetEfiConfigurationTable(context, guid: str) -> Optional[int]: """Find a configuration table by its GUID. """ - guid = guid.lower() - confs = context.conf_table_array - - if guid in confs: - idx = confs.index(guid) - ptr = context.conf_table_array_ptr + (idx * EFI_CONFIGURATION_TABLE.sizeof()) - - return ptr - - # not found - return None + return context.conftable.get_vendor_table(guid) \ No newline at end of file diff --git a/qiling/profiles/uefi.ql b/qiling/profiles/uefi.ql index 458370aae..1a9e11991 100644 --- a/qiling/profiles/uefi.ql +++ b/qiling/profiles/uefi.ql @@ -1,23 +1,20 @@ -[OS64] -heap_address = 0x78000000 -heap_size = 0x02000000 -stack_address = 0x77800000 -stack_size = 0x00800000 +[DXE] +heap_address = 0x04000000 +heap_size = 0x01000000 +stack_address = 0x05000000 +stack_size = 0x00080000 image_address = 0x00100000 -[OS32] -heap_address = 0x78000000 -heap_size = 0x02000000 -stack_address = 0x77800000 -stack_size = 0x00800000 -image_address = 0x00100000 - -[SMRAM] -heap_address = 0x7A000000 -heap_size = 0x02000000 -# stack_address = 0x77800000 -# stack_size = 0x00800000 -# image_address = 0x77000000 +[SMM] +smram_base = 0x70000000 +smram_size = 0x08000000 +heap_address = 0x77000000 +heap_size = 0x00800000 +# allocated somewhere in smram; address stored in gSmmInitStack +stack_address = 0x77ff0000 +# PcdCpuSmmStackSize +stack_size = 0x00010000 +image_address = 0x70100000 [HOB_LIST] # EFI_GLOBAL_VARIABLE @@ -34,16 +31,5 @@ Guid = 395c33fe-287f-413e-a055-8088c0e1d43e [LOADED_IMAGE_PROTOCOL] Guid = 5b1b31a1-9562-11d2-8e3f-00a0c969723b -[LOG] -# log directory output -# usage: dir = qlog -dir = -# split log file, use with multithread -split = False - [MISC] -# append string into different logs -# maily for multiple times Ql run with one file -# usage: append = test1 -append = -current_path = / \ No newline at end of file +current_path = / diff --git a/tests/test_uefi.py b/tests/test_uefi.py index 86d98b67a..b5180c360 100644 --- a/tests/test_uefi.py +++ b/tests/test_uefi.py @@ -3,130 +3,113 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os, pickle, sys,unittest +import pickle, sys, unittest sys.path.append("..") from qiling import Qiling from qiling.extensions.sanitizers.heap import QlSanitizedMemoryHeap from qiling.const import QL_INTERCEPT, QL_VERBOSE -from qiling.os.uefi.utils import execute_protocol_notifications +from qiling.os.uefi import utils from qiling.os.uefi.const import EFI_SUCCESS, EFI_INVALID_PARAMETER +ROOTFS_UEFI = r'../examples/rootfs/x8664_efi' + +class Checklist: + def __init__(self) -> None: + self.visited_oncall = False + self.visited_onenter = False + self.visited_onexit = False + class Test_UEFI(unittest.TestCase): def test_x8664_uefi_santizier(self): - def my_abort(msg): + def my_abort(msg: str): print(f"\n*** {msg} ***\n") - def enable_sanitized_heap(ql, fault_rate=0): - ql.os.heap = QlSanitizedMemoryHeap(ql, ql.os.heap, fault_rate) - ql.os.heap.oob_handler = lambda *args: my_abort("Out-of-bounds read detected") - ql.os.heap.bo_handler = lambda *args: my_abort("Buffer overflow/underflow detected") - ql.os.heap.bad_free_handler = lambda *args: my_abort("Double free or bad free detected") - ql.os.heap.uaf_handler = lambda *args: my_abort("Use-after-free detected") + def enable_sanitized_heap(ql: Qiling): + heap = QlSanitizedMemoryHeap(ql, ql.os.heap, fault_rate=0) + + heap.oob_handler = lambda *args: my_abort(f'Out-of-bounds read detected') + heap.bo_handler = lambda *args: my_abort(f'Buffer overflow/underflow detected') + heap.bad_free_handler = lambda *args: my_abort(f'Double free or bad free detected') + heap.uaf_handler = lambda *args: my_abort(f'Use-after-free detected') # make sure future allocated buffers are not too close to UEFI data - ql.os.heap.alloc(0x1000) + heap.alloc(0x1000) + + ql.os.heap = heap + + if __name__ == "__main__": + env = { + # the FaultType NVRAM variable is read by the executable to determine which + # memory corruption it should trigger. + # + # fault types are: + # 0 - POOL_OVERFLOW_MEMCPY + # 1 - POOL_UNDERFLOW_MEMCPY + # 2 - POOL_OVERFLOW_USER, + # 3 - POOL_UNDERFLOW_USER + # 4 - POOL_OOB_READ_AHEAD + # 5 - POOL_OOB_READ_BEHIND + # 6 - POOL_DOUBLE_FREE + # 7 - POOL_INVALID_FREE + 'FaultType': bytes([1]) + } + + ql = Qiling([f'{ROOTFS_UEFI}/bin/EfiPoolFault.efi'], ROOTFS_UEFI, env=env, verbose=QL_VERBOSE.DEBUG) - def sanitized_emulate(path, rootfs, fault_type, verbose=QL_VERBOSE.DEBUG, enable_trace=False): - ql = Qiling([path], rootfs, verbose=verbose) - ql.env['FaultType'] = fault_type enable_sanitized_heap(ql) + ql.run() - if not ql.os.heap.validate(): - my_abort("Canary corruption detected") + self.assertFalse(ql.os.heap.validate(), 'expected heap corruption') - # Valid fault types: - # 0 - POOL_OVERFLOW_MEMCPY - # 1 - POOL_UNDERFLOW_MEMCPY - # 2 - POOL_OVERFLOW_USER, - # 3 - POOL_UNDERFLOW_USER - # 4 - POOL_OOB_READ_AHEAD - # 5 - POOL_OOB_READ_BEHIND - # 6 - POOL_DOUBLE_FREE - # 7 - POOL_INVALID_FREE - fault_type = bytes([1]) + def test_x8664_uefi(self): + def force_notify_RegisterProtocolNotify(ql: Qiling, address: int, params): + ql.log.info(f'[force_notify] address = {address:#x}, params = {params}') - rootfs = "../examples/rootfs/x8664_efi" - path = "../examples/rootfs/x8664_efi/bin/EfiPoolFault.efi" - sanitized_emulate(path, rootfs, fault_type, verbose=QL_VERBOSE.DEBUG, enable_trace=True) + self.ck.visited_oncall = True - def test_x8664_uefi(self): - def force_notify_RegisterProtocolNotify(ql, address, params): - print("\n") - print("=" * 40) - print(" Enter into set_api mode") - print("=" * 40) - print("\n") event_id = params['Event'] - self.set_api = event_id + if event_id in ql.loader.events: - ql.loader.events[event_id]['Guid'] = params["Protocol"] - # let's force notify event = ql.loader.events[event_id] - event["Set"] = True - ql.loader.notify_list.append((event_id, event['NotifyFunction'], event['CallbackArgs'])) - execute_protocol_notifications(ql, True) - ###### - return EFI_SUCCESS - return EFI_INVALID_PARAMETER - def my_onenter(ql, address, params): - print("\n") - print("=" * 40) - print(" Enter into my_onenter mode") - print(params) - print("=" * 40) - print("\n") - self.set_api_onenter = params["Source"] - return address, params + # let's force notify + event["Set"] = False - def my_onexit(ql, address, params, retval): - print("\n") - print("=" * 40) - print(" Enter into my_exit mode") - print("params: %s" % params) - print("=" * 40) - print("\n") - self.set_api_onexit = params["Registration"] + utils.signal_event(ql, event_id) + utils.execute_protocol_notifications(ql, True) - def find_next_available(heap): - """Find next available address on heap. - """ + return EFI_SUCCESS - chunks = sorted(ql.os.heap.chunks, key=lambda c: c.address) + return EFI_INVALID_PARAMETER - for chunk in chunks: - na = chunk.address + def my_onenter(ql: Qiling, address: int, params): + ql.log.info(f'[my_onenter] address = {address:#x}, params = {params}') - if not chunk.inuse and chunk.size > 8: - break + self.ck.visited_onenter = True - na += chunk.size + def my_onexit(ql: Qiling, address: int, params, retval: int): + ql.log.info(f'[my_onexit] address = {address:#x}, params = {params}') - return na + self.ck.visited_onexit = True if __name__ == "__main__": - with open("../examples/rootfs/x8664_efi/rom2_nvar.pickel", 'rb') as f: + with open(f'{ROOTFS_UEFI}/rom2_nvar.pickel', 'rb') as f: env = pickle.load(f) - ql = Qiling(["../examples/rootfs/x8664_efi/bin/TcgPlatformSetupPolicy"], "../examples/rootfs/x8664_efi", env=env, verbose=QL_VERBOSE.DEBUG) + + ql = Qiling([f'{ROOTFS_UEFI}/bin/TcgPlatformSetupPolicy'], ROOTFS_UEFI, env=env, verbose=QL_VERBOSE.DEBUG) + self.ck = Checklist() + ql.set_api("RegisterProtocolNotify", force_notify_RegisterProtocolNotify) ql.set_api("CopyMem", my_onenter, QL_INTERCEPT.ENTER) ql.set_api("LocateProtocol", my_onexit, QL_INTERCEPT.EXIT) - # Source buffer for CopyMem - ptr = find_next_available(ql.os.heap) - ql.run() - self.assertEqual(0, self.set_api) - self.assertEqual(ptr + 1, self.set_api_onenter) - self.assertEqual(0, self.set_api_onexit) - - del ql - del self.set_api - del self.set_api_onenter - del self.set_api_onexit + self.assertTrue(self.ck.visited_oncall) + self.assertTrue(self.ck.visited_onenter) + self.assertTrue(self.ck.visited_onexit) if __name__ == "__main__": unittest.main()