From dc6b8d4f6a0fd872b0819a716f78923f6169b53b Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 4 May 2021 00:18:22 +0300 Subject: [PATCH 01/56] Move and rename update_struct --- qiling/os/uefi/ProcessorBind.py | 11 +++++++++++ qiling/os/uefi/utils.py | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/qiling/os/uefi/ProcessorBind.py b/qiling/os/uefi/ProcessorBind.py index 49af7574e..e05059c32 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 @@ -79,6 +80,16 @@ def loadFrom(cls, ql: Qiling, address: int) -> 'STRUCT': return cls.from_buffer_copy(data) + @contextmanager + @classmethod + 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. diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 648eb52b0..9c6c969bc 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -7,7 +7,6 @@ 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 @@ -150,15 +149,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. """ From 63378924ff2875fee52790c7f99a8e86b1bcc28e Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 4 May 2021 00:25:45 +0300 Subject: [PATCH 02/56] Move and rename update_struct (contd.) --- qiling/os/uefi/context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index 3f2257b30..e818e895e 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -80,7 +80,7 @@ def install_configuration_table(self, guid: str, table: int): super().install_configuration_table(guid, table) # Update number of configuration table entries in the ST. - with update_struct(EFI_SYSTEM_TABLE, self.ql, self.ql.loader.gST) as gST: + with EFI_SYSTEM_TABLE.bindTo(self.ql, self.ql.loader.gST) as gST: gST.NumberOfTableEntries = len(self.conf_table_array) class SmmContext(UefiContext): @@ -100,5 +100,5 @@ def install_configuration_table(self, guid: str, table: int): super().install_configuration_table(guid, table) # Update number of configuration table entries in the SMST. - with update_struct(EFI_SMM_SYSTEM_TABLE2, self.ql, self.ql.loader.gSmst) as gSmst: + with EFI_SMM_SYSTEM_TABLE2.bindTo(self.ql, self.ql.loader.gSmst) as gSmst: gSmst.NumberOfTableEntries = len(self.conf_table_array) From 743a43c5a1a2e00f45422fdcf143247d80acab5a Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 4 May 2021 00:28:37 +0300 Subject: [PATCH 03/56] Consolidate utils imports --- qiling/os/uefi/context.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index e818e895e..3316c4c2b 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -3,9 +3,9 @@ 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.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): @@ -38,7 +38,7 @@ 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 @@ -50,13 +50,16 @@ def notify_protocol(self, handle, protocol, interface, from_hook): 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) + utils.signal_event(self.ql, event_id) + + return utils.execute_protocol_notifications(self.ql, from_hook) def install_configuration_table(self, guid: str, table: int): guid = guid.lower() @@ -71,7 +74,7 @@ def install_configuration_table(self, guid: str, table: int): ptr = self.conf_table_array_ptr + (idx * EFI_CONFIGURATION_TABLE.sizeof()) instance = EFI_CONFIGURATION_TABLE() - instance.VendorGuid = str_to_guid(guid) + instance.VendorGuid = utils.str_to_guid(guid) instance.VendorTable = table instance.saveTo(self.ql, ptr) From a43985533beab36095346f335d0cd1893bb5c32b Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 4 May 2021 00:42:51 +0300 Subject: [PATCH 04/56] Handle conf table array directly from system table --- qiling/os/uefi/context.py | 62 +++++++++++++++++++++++++++------------ qiling/os/uefi/smst.py | 7 ----- qiling/os/uefi/st.py | 7 ----- qiling/os/uefi/utils.py | 17 +++++++---- 4 files changed, 54 insertions(+), 39 deletions(-) diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index 3316c4c2b..0435966b8 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -14,8 +14,6 @@ def __init__(self, ql: Qiling): self.protocols = {} # 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 @@ -62,29 +60,45 @@ def notify_protocol(self, handle, protocol, interface, from_hook): return utils.execute_protocol_notifications(self.ql, from_hook) def install_configuration_table(self, guid: str, table: int): - guid = guid.lower() - confs = self.conf_table_array + ptr = self.conf_table_array_ptr + nitems = self.conf_table_array_nitems + efi_guid = utils.str_to_guid(guid) - # 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) + idx = 0 - idx = confs.index(guid) - ptr = self.conf_table_array_ptr + (idx * EFI_CONFIGURATION_TABLE.sizeof()) + for _ in range(nitems): + entry = EFI_CONFIGURATION_TABLE.loadFrom(self.ql, ptr) + + if utils.CompareGuid(entry.VendorGuid, efi_guid): + break + + ptr += EFI_CONFIGURATION_TABLE.sizeof() + idx += 1 instance = EFI_CONFIGURATION_TABLE() - instance.VendorGuid = utils.str_to_guid(guid) + instance.VendorGuid = efi_guid instance.VendorTable = table instance.saveTo(self.ql, ptr) + self.conf_table_array_nitems = max(idx + 1, nitems) + class DxeContext(UefiContext): - def install_configuration_table(self, guid: str, table: int): - super().install_configuration_table(guid, table) + @property + def system_table(self): + return EFI_SYSTEM_TABLE.loadFrom(self.ql, self.ql.loader.gST) + + @property + def conf_table_array_ptr(self) -> int: + return self.system_table.ConfigurationTable.value - # Update number of configuration table entries in the ST. + @property + def conf_table_array_nitems(self) -> int: + return self.system_table.NumberOfTableEntries + + @conf_table_array_nitems.setter + def conf_table_array_nitems(self, value: int): with EFI_SYSTEM_TABLE.bindTo(self.ql, self.ql.loader.gST) as gST: - gST.NumberOfTableEntries = len(self.conf_table_array) + gST.NumberOfTableEntries = value class SmmContext(UefiContext): def __init__(self, ql): @@ -99,9 +113,19 @@ 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) + @property + def system_table(self): + return EFI_SMM_SYSTEM_TABLE2.loadFrom(self.ql, self.ql.loader.gSmst) + + @property + def conf_table_array_ptr(self) -> int: + return self.system_table.SmmConfigurationTable.value + + @property + def conf_table_array_nitems(self) -> int: + return self.system_table.NumberOfTableEntries - # Update number of configuration table entries in the SMST. + @conf_table_array_nitems.setter + def conf_table_array_nitems(self, value: int): with EFI_SMM_SYSTEM_TABLE2.bindTo(self.ql, self.ql.loader.gSmst) as gSmst: - gSmst.NumberOfTableEntries = len(self.conf_table_array) + gSmst.NumberOfTableEntries = value diff --git a/qiling/os/uefi/smst.py b/qiling/os/uefi/smst.py index f3e3199d2..312f16bad 100644 --- a/qiling/os/uefi/smst.py +++ b/qiling/os/uefi/smst.py @@ -273,13 +273,6 @@ 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 diff --git a/qiling/os/uefi/st.py b/qiling/os/uefi/st.py index c343785c7..9f1cf8d4c 100644 --- a/qiling/os/uefi/st.py +++ b/qiling/os/uefi/st.py @@ -74,13 +74,6 @@ 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 diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 9c6c969bc..73d202ab3 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -188,14 +188,19 @@ def GetEfiConfigurationTable(context, guid: str) -> Optional[int]: """Find a configuration table by its GUID. """ - guid = guid.lower() - confs = context.conf_table_array + ptr = context.conf_table_array_ptr + nitems = context.conf_table_array_nitems + efi_guid = str_to_guid(guid) - if guid in confs: - idx = confs.index(guid) - ptr = context.conf_table_array_ptr + (idx * EFI_CONFIGURATION_TABLE.sizeof()) + # find configuration table entry by guid. if found, ptr would be set to the matching entry + # in the array. if not, ptr would be set to one past end of array + for _ in range(nitems): + entry = EFI_CONFIGURATION_TABLE.loadFrom(context.ql, ptr) - return ptr + if CompareGuid(entry.VendorGuid, efi_guid): + return ptr + + ptr += EFI_CONFIGURATION_TABLE.sizeof() # not found return None From 9001047c2ca3ea88dbe61a61d806c79ecb466769 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 4 May 2021 00:44:30 +0300 Subject: [PATCH 05/56] GetEfiConfigurationTable should return VendorTable --- qiling/os/uefi/hob.py | 9 +++++---- qiling/os/uefi/utils.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qiling/os/uefi/hob.py b/qiling/os/uefi/hob.py index af554081d..300433f15 100644 --- a/qiling/os/uefi/hob.py +++ b/qiling/os/uefi/hob.py @@ -30,11 +30,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 ql.unpack64(hoblist_vend) def CreateHob(ql: Qiling, context: UefiContext, hob) -> int: """Add a HOB to the end of the HOB list. diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 73d202ab3..3dbfc14c6 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -198,7 +198,7 @@ def GetEfiConfigurationTable(context, guid: str) -> Optional[int]: entry = EFI_CONFIGURATION_TABLE.loadFrom(context.ql, ptr) if CompareGuid(entry.VendorGuid, efi_guid): - return ptr + return entry.VendorTable ptr += EFI_CONFIGURATION_TABLE.sizeof() From 3f326404791fdf4c8eb4a03dbdd9bf96846ab997 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 6 May 2021 16:32:22 +0300 Subject: [PATCH 06/56] Fix: swap decorators order --- qiling/os/uefi/ProcessorBind.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/uefi/ProcessorBind.py b/qiling/os/uefi/ProcessorBind.py index e05059c32..7c9da0496 100644 --- a/qiling/os/uefi/ProcessorBind.py +++ b/qiling/os/uefi/ProcessorBind.py @@ -80,8 +80,8 @@ def loadFrom(cls, ql: Qiling, address: int) -> 'STRUCT': return cls.from_buffer_copy(data) - @contextmanager @classmethod + @contextmanager def bindTo(cls, ql: Qiling, address: int): instance = cls.loadFrom(ql, address) From 2ea9b102f2901682aa3ee59a065b338b85065062 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 13 May 2021 21:21:00 +0300 Subject: [PATCH 07/56] Save state cleanup --- qiling/loader/pe_uefi.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 79058d596..88c934db2 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -37,11 +37,7 @@ def __init__(self, ql: Qiling): '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: @@ -229,7 +225,6 @@ def run(self): 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}") # initialize and locate stack From 87441115e36de2b6cc274494c3a6b83d85eac453 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 13 May 2021 21:24:49 +0300 Subject: [PATCH 08/56] More typing --- qiling/loader/pe_uefi.py | 6 ++-- qiling/os/os.py | 10 +++--- qiling/os/uefi/protocols/EfiSmmCpuProtocol.py | 4 +-- qiling/os/uefi/smst.py | 32 +++++++++---------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 88c934db2..0ddae0fb6 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -3,8 +3,8 @@ # 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 @@ -40,7 +40,7 @@ def __init__(self, ql: Qiling): '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: @@ -51,7 +51,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: 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/protocols/EfiSmmCpuProtocol.py b/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py index 34bfb1add..ebe8bf69f 100644 --- a/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py +++ b/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py @@ -103,7 +103,7 @@ class EFI_SMM_SAVE_STATE_REGISTER(ENUM_UC): "CpuIndex" : ULONGLONG,# UINTN "Buffer" : POINTER # PTR(VOID)) }) -def hook_SmmReadSaveState(ql, address, params): +def hook_SmmReadSaveState(ql: Qiling, address: int, params): return EFI_SUCCESS @dxeapi(params = { @@ -113,7 +113,7 @@ def hook_SmmReadSaveState(ql, address, params): "CpuIndex" : ULONGLONG,# UINTN "Buffer" : POINTER # PTR(VOID)) }) -def hook_SmmWriteSaveState(ql, address, params): +def hook_SmmWriteSaveState(ql: Qiling, address: int, params): return EFI_SUCCESS class EFI_SMM_CPU_PROTOCOL(STRUCT): diff --git a/qiling/os/uefi/smst.py b/qiling/os/uefi/smst.py index 312f16bad..8c9a8fc41 100644 --- a/qiling/os/uefi/smst.py +++ b/qiling/os/uefi/smst.py @@ -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, gSmst: int): ql.loader.gSmst = gSmst gSmmRT = gSmst + EFI_SMM_SYSTEM_TABLE2.sizeof() # smm runtime services From 6db39a13ac75b43b86ede925a608a357ee0b93af Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 8 Jun 2021 14:45:23 +0300 Subject: [PATCH 09/56] Bugfix: os utils migration leftover --- qiling/os/uefi/rt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/uefi/rt.py b/qiling/os/uefi/rt.py index 0e6d104cb..4cac33f79 100644 --- a/qiling/os/uefi/rt.py +++ b/qiling/os/uefi/rt.py @@ -99,7 +99,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. From 9a747ff83346ca6688c75623aef90c84aa7fab00 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 8 Jun 2021 14:51:51 +0300 Subject: [PATCH 10/56] Move "in_smm" from Loader to OS --- qiling/loader/pe_uefi.py | 2 -- qiling/os/uefi/protocols/EfiSmmBase2Protocol.py | 4 ++-- qiling/os/uefi/uefi.py | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 0ddae0fb6..f641f60bb 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -266,8 +266,6 @@ def run(self): gSmst = self.smm_context.heap.alloc(256 * 1024) smst.initialize(self.ql, gSmst) - self.in_smm = False - protocols = ( EfiSmmCpuProtocol, EfiSmmSwDispatch2Protocol diff --git a/qiling/os/uefi/protocols/EfiSmmBase2Protocol.py b/qiling/os/uefi/protocols/EfiSmmBase2Protocol.py index b7536e50a..1dd20071c 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.info(f'InSmram = {ql.os.in_smm}') - write_int8(ql, params["InSmram"], int(ql.loader.in_smm)) + write_int8(ql, params["InSmram"], int(ql.os.in_smm)) return EFI_SUCCESS diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 35fa9bdab..c247e9a59 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -18,7 +18,7 @@ def __init__(self, ql: Qiling): super().__init__(ql) self.entry_point = 0 - self.running_module = None + self.in_smm: bool self.PE_RUN = True self.heap = None # Will be initialized by the loader. From 69d0279918d6caa7cf4b0717f8e965f116b22117 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 8 Jun 2021 14:56:04 +0300 Subject: [PATCH 11/56] Make "notify_after_module_execution" a member func --- qiling/os/uefi/shutdown.py | 2 +- qiling/os/uefi/uefi.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/qiling/os/uefi/shutdown.py b/qiling/os/uefi/shutdown.py index 1261e4f8c..3fa6c3e6c 100644 --- a/qiling/os/uefi/shutdown.py +++ b/qiling/os/uefi/shutdown.py @@ -7,7 +7,7 @@ from .utils import execute_protocol_notifications def hook_EndOfExecution(ql): - if ql.os.notify_after_module_execution(ql, len(ql.loader.modules)): + if ql.os.notify_after_module_execution(len(ql.loader.modules)): return ql.loader.restore_runtime_services() diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index c247e9a59..416ceee42 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -68,9 +68,15 @@ def fallback(v): 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 From ddfc459dd323b3faf92b472573ff3d0d6de000f0 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 8 Jun 2021 14:57:45 +0300 Subject: [PATCH 12/56] Make "notify_before_module_execution" a member func --- qiling/loader/pe_uefi.py | 2 +- qiling/os/uefi/uefi.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index f641f60bb..7b3ed0223 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -192,7 +192,7 @@ def execute_module(self, path: str, image_base: int, entry_point: int, eoe_trap: 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 or self.ql.os.notify_before_module_execution(self.modules[0][0]): return path, image_base, entry_point = self.modules.pop(0) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 416ceee42..cdaf48b6d 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -79,10 +79,17 @@ def notify_after_module_execution(self, nmodules: int) -> bool: return False + def notify_before_module_execution(self, module: str) -> bool: + """Callback fired before a module is about to start executing. + + Args: + module: path of module to execute + + Returns: `True` if module execution should be thwarted, `False` otherwise + """ + + self.running_module = module - @staticmethod - def notify_before_module_execution(ql: Qiling, module): - ql.os.running_module = module return False @@ -176,7 +183,7 @@ def emu_error(self): def run(self): - self.notify_before_module_execution(self.ql, self.running_module) + 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 From fdfc3db8c40ab9b10e612e0f87b99dee3ed5e3a5 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 8 Jun 2021 14:59:09 +0300 Subject: [PATCH 13/56] Explicitly list running_module as an OS member --- qiling/os/uefi/uefi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index cdaf48b6d..d2ee87bc1 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -18,6 +18,7 @@ def __init__(self, ql: Qiling): super().__init__(ql) self.entry_point = 0 + self.running_module: str self.in_smm: bool self.PE_RUN = True self.heap = None # Will be initialized by the loader. From a85fc77f5a6c788f1a74cf2cf36b625646ed7c7d Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 8 Jun 2021 15:01:02 +0300 Subject: [PATCH 14/56] Show mapinfo also when PC is unreachable --- qiling/os/uefi/uefi.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index d2ee87bc1..7963e3dd0 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -175,13 +175,12 @@ def emu_error(self): 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}') - - self.ql.log.error(f'Memory map:') - self.ql.mem.show_mapinfo() + self.ql.log.error(f'PC = {pc:#x}{img_info}') except UcError: - self.ql.log.error(f'Error: PC({pc:#x}) is unreachable') + self.ql.log.error(f'PC = {pc:#x} (unreachable)') + self.ql.log.error(f'Memory map:') + self.ql.mem.show_mapinfo() def run(self): self.notify_before_module_execution(self.running_module) From 0273f1bd34b4de19349d9b9e7db2a71a96f7c4f6 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 8 Jun 2021 15:09:20 +0300 Subject: [PATCH 15/56] Typing, cleanup and other petty changes --- qiling/os/uefi/ProcessorBind.py | 13 ++++++------- qiling/os/uefi/bs.py | 3 +++ qiling/os/uefi/ds.py | 2 +- qiling/os/uefi/hob.py | 3 +-- qiling/os/uefi/protocols/EfiSmmCpuProtocol.py | 4 ++-- qiling/os/uefi/shutdown.py | 3 ++- qiling/os/uefi/utils.py | 3 +-- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/qiling/os/uefi/ProcessorBind.py b/qiling/os/uefi/ProcessorBind.py index 7c9da0496..817be539f 100644 --- a/qiling/os/uefi/ProcessorBind.py +++ b/qiling/os/uefi/ProcessorBind.py @@ -28,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 @@ -46,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 @@ -106,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/bs.py b/qiling/os/uefi/bs.py index dc7ad65de..141954785 100644 --- a/qiling/os/uefi/bs.py +++ b/qiling/os/uefi/bs.py @@ -289,6 +289,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 = { diff --git a/qiling/os/uefi/ds.py b/qiling/os/uefi/ds.py index d6004e1a0..b39dea9e3 100644 --- a/qiling/os/uefi/ds.py +++ b/qiling/os/uefi/ds.py @@ -246,7 +246,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 300433f15..75238b3c1 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 @@ -83,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/EfiSmmCpuProtocol.py b/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py index ebe8bf69f..5320332f3 100644 --- a/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py +++ b/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py @@ -101,7 +101,7 @@ 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: Qiling, address: int, params): return EFI_SUCCESS @@ -111,7 +111,7 @@ def hook_SmmReadSaveState(ql: Qiling, address: int, 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: Qiling, address: int, params): return EFI_SUCCESS diff --git a/qiling/os/uefi/shutdown.py b/qiling/os/uefi/shutdown.py index 3fa6c3e6c..2f4c9ec2a 100644 --- a/qiling/os/uefi/shutdown.py +++ b/qiling/os/uefi/shutdown.py @@ -3,10 +3,11 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from qiling import Qiling from .utils import execute_protocol_notifications -def hook_EndOfExecution(ql): +def hook_EndOfExecution(ql: Qiling): if ql.os.notify_after_module_execution(len(ql.loader.modules)): return diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 3dbfc14c6..17602ff07 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -10,7 +10,6 @@ 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 @@ -160,7 +159,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: From 8328008ead447502d3ea9af249643074c9da410c Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 26 Jul 2021 18:47:40 +0300 Subject: [PATCH 16/56] Nicer implementation of conf tables, now per context --- qiling/os/uefi/context.py | 147 +++++++++++++++++++++++++------------- 1 file changed, 97 insertions(+), 50 deletions(-) diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index 0435966b8..8fdf9c84c 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -1,8 +1,9 @@ -from abc import ABC -from typing import Mapping, Tuple +from abc import ABC, abstractmethod +from typing import Any, Mapping, Optional, Tuple from qiling import Qiling from qiling.os.memory import QlMemoryHeap +from qiling.os.uefi.ProcessorBind import STRUCT 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 @@ -17,6 +18,8 @@ def __init__(self, ql: Qiling): self.conf_table_data_ptr = 0 self.conf_table_data_next_ptr = 0 + self.conftable: UefiConfTable + def init_heap(self, base: int, size: int): self.heap = QlMemoryHeap(self.ql, base, base + size) @@ -59,73 +62,117 @@ def notify_protocol(self, handle, protocol, interface, from_hook): return utils.execute_protocol_notifications(self.ql, from_hook) - def install_configuration_table(self, guid: str, table: int): - ptr = self.conf_table_array_ptr - nitems = self.conf_table_array_nitems - efi_guid = utils.str_to_guid(guid) +class DxeContext(UefiContext): + def __init__(self, ql: Qiling): + super().__init__(ql) - idx = 0 + self.conftable = DxeConfTable(ql) - for _ in range(nitems): - entry = EFI_CONFIGURATION_TABLE.loadFrom(self.ql, ptr) +class SmmContext(UefiContext): + def __init__(self, ql: Qiling): + super().__init__(ql) - if utils.CompareGuid(entry.VendorGuid, efi_guid): - break + self.conftable = SmmConfTable(ql) - ptr += EFI_CONFIGURATION_TABLE.sizeof() - idx += 1 + # assume tseg is inaccessible to non-smm + self.tseg_open = False - instance = EFI_CONFIGURATION_TABLE() - instance.VendorGuid = efi_guid - instance.VendorTable = table - instance.saveTo(self.ql, ptr) + # assume tseg is locked + self.tseg_locked = True - self.conf_table_array_nitems = max(idx + 1, nitems) + # registered sw smi handlers + self.swsmi_handlers: Mapping[int, Tuple[int, Mapping]] = {} + +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) -class DxeContext(UefiContext): @property - def system_table(self): - return EFI_SYSTEM_TABLE.loadFrom(self.ql, self.ql.loader.gST) + @abstractmethod + def system_table(self) -> int: + pass @property - def conf_table_array_ptr(self) -> int: - return self.system_table.ConfigurationTable.value + def baseptr(self) -> int: + addr = self.system_table + self.__arrptr_off + + return utils.read_int64(self.ql, addr) @property - def conf_table_array_nitems(self) -> int: - return self.system_table.NumberOfTableEntries + def nitems(self) -> int: + addr = self.system_table + self.__nitems_off - @conf_table_array_nitems.setter - def conf_table_array_nitems(self, value: int): - with EFI_SYSTEM_TABLE.bindTo(self.ql, self.ql.loader.gST) as gST: - gST.NumberOfTableEntries = value + return utils.read_int64(self.ql, addr) # UINTN -class SmmContext(UefiContext): - def __init__(self, ql): - super(SmmContext, self).__init__(ql) + @nitems.setter + def nitems(self, value: int): + addr = self.system_table + self.__nitems_off - # assume tseg is inaccessible to non-smm - self.tseg_open = False + utils.write_int64(self.ql, addr, value) - # assume tseg is locked - self.tseg_locked = True + def install(self, guid: str, table: int): + ptr = self.find(guid) + append = ptr is None - # registered sw smi handlers - self.swsmi_handlers: Mapping[int, Tuple[int, Mapping]] = {} + if append: + ptr = self.baseptr + self.nitems * EFI_CONFIGURATION_TABLE.sizeof() + append = True - @property - def system_table(self): - return EFI_SMM_SYSTEM_TABLE2.loadFrom(self.ql, self.ql.loader.gSmst) + instance = EFI_CONFIGURATION_TABLE() + instance.VendorGuid = utils.str_to_guid(guid) + instance.VendorTable = table + instance.saveTo(self.ql, ptr) - @property - def conf_table_array_ptr(self) -> int: - return self.system_table.SmmConfigurationTable.value + 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 conf_table_array_nitems(self) -> int: - return self.system_table.NumberOfTableEntries + 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' - @conf_table_array_nitems.setter - def conf_table_array_nitems(self, value: int): - with EFI_SMM_SYSTEM_TABLE2.bindTo(self.ql, self.ql.loader.gSmst) as gSmst: - gSmst.NumberOfTableEntries = value + @property + def system_table(self) -> int: + return self.ql.loader.gSmst From 8897720e13a41a7eb27c0ec27f7a716a57178c53 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 26 Jul 2021 18:52:35 +0300 Subject: [PATCH 17/56] Nicer implementation of conf tables (contd.) --- qiling/os/uefi/hob.py | 2 +- qiling/os/uefi/protocols/common.py | 2 +- qiling/os/uefi/smst.py | 13 +++---------- qiling/os/uefi/st.py | 16 +++++----------- qiling/os/uefi/utils.py | 20 ++------------------ 5 files changed, 12 insertions(+), 41 deletions(-) diff --git a/qiling/os/uefi/hob.py b/qiling/os/uefi/hob.py index 75238b3c1..a698be34f 100644 --- a/qiling/os/uefi/hob.py +++ b/qiling/os/uefi/hob.py @@ -34,7 +34,7 @@ def GetHobList(ql: Qiling, context: UefiContext) -> int: assert hoblist_vend is not None, 'hob list guid not found' - return ql.unpack64(hoblist_vend) + return hoblist_vend def CreateHob(ql: Qiling, context: UefiContext, hob) -> int: """Add a HOB to the end of the HOB list. 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/smst.py b/qiling/os/uefi/smst.py index 8c9a8fc41..11108164b 100644 --- a/qiling/os/uefi/smst.py +++ b/qiling/os/uefi/smst.py @@ -230,7 +230,7 @@ def hook_SmiHandlerRegister(ql: Qiling, address: int, params): def hook_SmiHandlerUnRegister(ql: Qiling, address: int, params): return EFI_SUCCESS -def initialize(ql: Qiling, gSmst: int): +def initialize(ql: Qiling, context, gSmst: int): ql.loader.gSmst = gSmst gSmmRT = gSmst + EFI_SMM_SYSTEM_TABLE2.sizeof() # smm runtime services @@ -273,15 +273,8 @@ def initialize(ql: Qiling, gSmst: int): instance = init_struct(ql, gSmst, descriptor) instance.saveTo(ql, gSmst) - # 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 9f1cf8d4c..93b9faee3 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: # @@ -47,7 +48,7 @@ # # ... 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,15 +75,8 @@ def initialize(ql: Qiling, gST: int): instance.saveTo(ql, gST) - # 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/utils.py b/qiling/os/uefi/utils.py index 17602ff07..b1186028e 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -10,7 +10,6 @@ from qiling import Qiling from qiling.os.uefi.const import EFI_SUCCESS -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: @@ -181,25 +180,10 @@ def install_configuration_table(context, key: str, table: Optional[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. """ - ptr = context.conf_table_array_ptr - nitems = context.conf_table_array_nitems - efi_guid = str_to_guid(guid) - - # find configuration table entry by guid. if found, ptr would be set to the matching entry - # in the array. if not, ptr would be set to one past end of array - for _ in range(nitems): - entry = EFI_CONFIGURATION_TABLE.loadFrom(context.ql, ptr) - - if CompareGuid(entry.VendorGuid, efi_guid): - return entry.VendorTable - - ptr += EFI_CONFIGURATION_TABLE.sizeof() - - # not found - return None + return context.conftable.get_vendor_table(guid) \ No newline at end of file From a20ce896365ab6555df41ab81edcb6a563b1b207 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 26 Jul 2021 18:59:43 +0300 Subject: [PATCH 18/56] Further separation of DXE and SMM --- qiling/loader/pe_uefi.py | 211 +++++++++++++++++++++++-------------- qiling/os/uefi/context.py | 21 +++- qiling/os/uefi/shutdown.py | 2 +- qiling/profiles/uefi.ql | 33 +++--- 4 files changed, 165 insertions(+), 102 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 7b3ed0223..27b624174 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -11,8 +11,8 @@ from qiling.exception import QlErrorArch, QlMemoryMappedError from qiling.loader.loader import QlLoader, Image -from qiling.os.uefi import context, st, smst -from qiling.os.uefi.ProcessorBind import CPU_STACK_ALIGNMENT +from qiling.os.uefi import st, smst +from qiling.os.uefi.context import DxeContext, SmmContext, UefiContext from qiling.os.uefi.shutdown import hook_EndOfExecution from qiling.os.uefi.protocols import EfiLoadedImageProtocol from qiling.os.uefi.protocols import EfiSmmAccess2Protocol @@ -28,15 +28,12 @@ def __init__(self, ql: Qiling): self.modules = [] self.events = {} self.notify_list = [] - self.next_image_base = 0 # list of members names to save and restore __save_members = ( 'modules', 'events', 'notify_list', - 'next_image_base', - 'loaded_image_protocol_modules', 'tpl' ) @@ -67,11 +64,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 @@ -82,6 +79,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: @@ -122,9 +120,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: @@ -156,24 +154,24 @@ def call_function(self, addr: int, args: Sequence[int], ret: Optional[int]): self.ql.reg.rip = addr - def unload_modules(self): - for handle in self.loaded_image_protocol_modules: - struct_addr = self.dxe_context.protocols[handle][self.loaded_image_protocol_guid] + def unload_modules(self, context: UefiContext) -> bool: + 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) + 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: @@ -186,6 +184,16 @@ def execute_module(self, path: str, image_base: int, entry_point: int, eoe_trap: ImageHandle = image_base SystemTable = self.gST + # set InSmm indicator + self.ql.os.in_smm = isinstance(context, SmmContext) + + # 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.call_function(entry_point, [ImageHandle, SystemTable], eoe_trap) self.ql.os.entry_point = entry_point @@ -195,106 +203,149 @@ def execute_next_module(self): if not self.modules or self.ql.os.notify_before_module_execution(self.modules[0][0]): return - path, image_base, entry_point = self.modules.pop(0) - self.execute_module(path, image_base, entry_point, self.end_of_execution_ptr) + path, image_base, entry_point, context = self.modules.pop(0) + self.execute_module(path, image_base, entry_point, context, 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") + def __init_dxe_environment(self, ql: Qiling) -> DxeContext: + """Initialize DXE data structures (BS, RT and DS) and install essential protocols. + """ - # x86-64 arch only - if self.ql.archtype != QL_ARCH.X8664: - raise QlErrorArch("Only 64 bit arch is supported at the moment") + profile = ql.os.profile['DXE'] + context = DxeContext(ql) - self.loaded_image_protocol_guid = self.ql.os.profile["LOADED_IMAGE_PROTOCOL"]["Guid"] - self.loaded_image_protocol_modules = [] - self.tpl = 4 # TPL_APPLICATION + # 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'DXE heap at {heap_base:#010x}') - arch_key = { - QL_ARCH.X86 : "OS32", - QL_ARCH.X8664 : "OS64" - }[self.ql.archtype] + # 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'DXE stack at {context.top_of_stack:#010x}') - # -------- init BS / RT / DXE data structures and protocols -------- + # base address for next image + context.next_image_base = int(profile['image_address'], 0) - os_profile = self.ql.os.profile[arch_key] - self.dxe_context = context.DxeContext(self.ql) + # 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) - # 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.ql.log.info(f"Located heap at {heap_base:#010x}") + # 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) - # 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}") + context.conf_table_data_ptr = conf_data + context.conf_table_data_next_ptr = conf_data - # 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) + 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) - # workaround - self.ql.os.heap = self.dxe_context.heap + # set smram boundaries + context.smram_base = int(profile["smram_base"], 0) + context.smram_size = int(profile["smram_size"], 0) - # -------- init SMM data structures and protocols -------- + # 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}") + + # 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) + + context.conf_table_data_ptr = conf_data + context.conf_table_data_next_ptr = conf_data + + 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: - self.ql.log.info(f"Done with loading {self.ql.path}") + # TODO: determine whether this is an smm or dxe module + is_smm_module = 'Smm' in dependency + + if is_smm_module: + self.context = self.smm_context + self.system_table = self.gSmst + else: + self.context = self.dxe_context + self.system_table = self.gST + + self.map_and_load(dependency, self.context) + + ql.log.info(f"Done with loading {ql.path}") + + except QlMemoryMappedError: + ql.log.critical("Couldn't map dependency") # 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) + # set on an address that is not expected to be executed, so we picked SystemTable's address + self.end_of_execution_ptr = self.system_table + ql.hook_address(hook_EndOfExecution, self.end_of_execution_ptr) self.execute_next_module() diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index 8fdf9c84c..0da1244ac 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -3,7 +3,7 @@ from qiling import Qiling from qiling.os.memory import QlMemoryHeap -from qiling.os.uefi.ProcessorBind import STRUCT +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 @@ -11,8 +11,11 @@ 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 = [] + self.next_image_base: int # These members must be initialized before attempting to install a configuration table. self.conf_table_data_ptr = 0 @@ -20,11 +23,20 @@ def __init__(self, ql: Qiling): self.conftable: UefiConfTable + # 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): guid = proto_desc['guid'] @@ -74,6 +86,9 @@ def __init__(self, ql: Qiling): self.conftable = SmmConfTable(ql) + self.smram_base: int + self.smram_size: int + # assume tseg is inaccessible to non-smm self.tseg_open = False diff --git a/qiling/os/uefi/shutdown.py b/qiling/os/uefi/shutdown.py index 2f4c9ec2a..1b3421bd3 100644 --- a/qiling/os/uefi/shutdown.py +++ b/qiling/os/uefi/shutdown.py @@ -19,7 +19,7 @@ def hook_EndOfExecution(ql: Qiling): if ql.loader.modules: ql.loader.execute_next_module() else: - if ql.loader.unload_modules(): + if ql.loader.unload_modules(ql.loader.smm_context) or ql.loader.unload_modules(ql.loader.dxe_context): return ql.log.info(f'No more modules to run') diff --git a/qiling/profiles/uefi.ql b/qiling/profiles/uefi.ql index 458370aae..cdd0f89fb 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 From f4a56f615b1e4fe3ad1d080055c3c5b07116c321 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 26 Jul 2021 19:02:06 +0300 Subject: [PATCH 19/56] Comments update and minor changes --- qiling/os/uefi/rt.py | 2 +- qiling/os/uefi/st.py | 6 +++--- qiling/os/uefi/uefi.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qiling/os/uefi/rt.py b/qiling/os/uefi/rt.py index 4cac33f79..4dd731256 100644 --- a/qiling/os/uefi/rt.py +++ b/qiling/os/uefi/rt.py @@ -184,7 +184,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/st.py b/qiling/os/uefi/st.py index 93b9faee3..c88c22b64 100644 --- a/qiling/os/uefi/st.py +++ b/qiling/os/uefi/st.py @@ -40,13 +40,13 @@ # | 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, context: UefiContext, gST: int): ql.loader.gST = gST diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 7963e3dd0..d82b99f72 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -62,9 +62,9 @@ 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) + STRING : lambda v: QlOsUtils.stringify(v), + WSTRING : lambda v: f'L{QlOsUtils.stringify(v)}', + GUID : lambda v: guids_db.get(v.upper(), v) } return tuple((aname, ahandlers.get(atype, fallback)(avalue)) for atype, aname, avalue in targs) From cf11d27e9a54215ad1b1b09f023133e3074ef88f Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 26 Jul 2021 19:02:56 +0300 Subject: [PATCH 20/56] Adjust and cleanup UEFI test --- tests/test_uefi.py | 151 ++++++++++++++++++++------------------------- 1 file changed, 67 insertions(+), 84 deletions(-) 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() From d0f9a98e49fd56e3902fa3c1c8a5a17c6c1c914e Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 3 Aug 2021 19:02:52 +0300 Subject: [PATCH 21/56] Move end_of_execution_ptr to context --- qiling/loader/pe_uefi.py | 21 +++++++++++++-------- qiling/os/uefi/context.py | 1 + qiling/os/uefi/utils.py | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 27b624174..b3f4b587f 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -164,7 +164,7 @@ def unload_modules(self, context: UefiContext) -> bool: 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.call_function(unload_ptr, [handle], context.end_of_execution_ptr) context.loaded_image_protocol_modules.remove(handle) return True @@ -204,7 +204,7 @@ def execute_next_module(self): return path, image_base, entry_point, context = self.modules.pop(0) - self.execute_module(path, image_base, entry_point, context, self.end_of_execution_ptr) + self.execute_module(path, image_base, entry_point, context, context.end_of_execution_ptr) def __init_dxe_environment(self, ql: Qiling) -> DxeContext: """Initialize DXE data structures (BS, RT and DS) and install essential protocols. @@ -239,6 +239,10 @@ def __init_dxe_environment(self, ql: Qiling) -> DxeContext: 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 = ( @@ -288,6 +292,10 @@ def __init_smm_environment(self, ql: Qiling) -> SmmContext: 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 = ( @@ -329,10 +337,8 @@ def run(self): if is_smm_module: self.context = self.smm_context - self.system_table = self.gSmst else: self.context = self.dxe_context - self.system_table = self.gST self.map_and_load(dependency, self.context) @@ -342,10 +348,9 @@ def run(self): ql.log.critical("Couldn't map dependency") # 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 an address that is not expected to be executed, so we picked SystemTable's address - self.end_of_execution_ptr = self.system_table - ql.hook_address(hook_EndOfExecution, self.end_of_execution_ptr) + # executing (i.e. when the entry point function returns). + ql.hook_address(hook_EndOfExecution, self.dxe_context.end_of_execution_ptr) + ql.hook_address(hook_EndOfExecution, self.smm_context.end_of_execution_ptr) self.execute_next_module() diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index 0da1244ac..e85d1338a 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -22,6 +22,7 @@ def __init__(self, ql: Qiling): 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]: diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index b1186028e..35c9a5dac 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -56,7 +56,7 @@ def __notify_next(ql: Qiling): if from_hook: ql.stack_push(next_hook) else: - ql.stack_push(ql.loader.end_of_execution_ptr) + ql.stack_push(ql.loader.smm_context.end_of_execution_ptr) ql.reg.arch_pc = next_hook return True @@ -71,7 +71,7 @@ def check_and_notify_protocols(ql: Qiling, from_hook: bool = False) -> bool: # 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) + ql.loader.call_function(notify_func, notify_context, ql.loader.context.end_of_execution_ptr) return True From 956c94fc7b65cb087a0ebb164c7eae4e9023cb44 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 Aug 2021 07:52:18 +0300 Subject: [PATCH 22/56] Make zero pointers shown as 'NULL' --- qiling/os/uefi/uefi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index d82b99f72..aa6d2cf22 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -62,6 +62,7 @@ def fallback(v): return super(QlOsUefi, self).process_fcall_params([(None, '', v)])[0][1] ahandlers: Mapping[Any, Callable[[Any], str]] = { + 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) From afe22bf8bfefbf4de13790882d26ed305f041b52 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 Aug 2021 07:52:58 +0300 Subject: [PATCH 23/56] Remove obsolete entries from profile --- qiling/profiles/uefi.ql | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/qiling/profiles/uefi.ql b/qiling/profiles/uefi.ql index cdd0f89fb..1a9e11991 100644 --- a/qiling/profiles/uefi.ql +++ b/qiling/profiles/uefi.ql @@ -31,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 = / From 47a7781d88765e777bf850fb3513b61c56e96ace Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 6 Aug 2021 08:31:22 +0300 Subject: [PATCH 24/56] Use ql.pointersize --- qiling/os/uefi/bs.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/qiling/os/uefi/bs.py b/qiling/os/uefi/bs.py index 141954785..04f2f334b 100644 --- a/qiling/os/uefi/bs.py +++ b/qiling/os/uefi/bs.py @@ -14,9 +14,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 }) @@ -396,7 +393,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 @@ -416,7 +413,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, {}) From 0da4141596a8c047e5b2137dd3c0949f867aa7a0 Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 9 Aug 2021 07:53:39 +0300 Subject: [PATCH 25/56] Display guid names where possible --- qiling/os/uefi/bs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiling/os/uefi/bs.py b/qiling/os/uefi/bs.py index 04f2f334b..75cd288ed 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 * @@ -426,7 +427,7 @@ 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 @@ -460,7 +461,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 From e87b37cc32b96a82bdc9d94f6a48a27866494c20 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 Aug 2021 23:46:26 +0300 Subject: [PATCH 26/56] Add word ptr accessors --- qiling/os/uefi/utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 35c9a5dac..8080d1b21 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -89,6 +89,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 """ @@ -116,6 +128,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 From 0222852ec48d711dab5ad95c54de532b9c56a302 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 Aug 2021 14:37:50 +0300 Subject: [PATCH 27/56] Encapsulate init to reduce scope clutter --- qiling/os/uefi/__init__.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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() From c0866e5895e5449bcff687c8c81f194017956edb Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 18 Aug 2021 15:34:40 +0300 Subject: [PATCH 28/56] Module exec notifications now call registered callbacks --- qiling/os/uefi/uefi.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index aa6d2cf22..fe0d273ab 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Any, Callable, Iterable, Mapping, Sequence, Tuple +from typing import Any, Callable, Iterable, Mapping, MutableSequence, Sequence, Tuple from unicorn import UcError from qiling import Qiling @@ -23,6 +23,9 @@ def __init__(self, ql: Qiling): self.PE_RUN = True self.heap = None # 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, 64: intel.ms64 @@ -79,7 +82,7 @@ def notify_after_module_execution(self, nmodules: int) -> bool: Returns: `True` if subsequent modules execution should be thwarted, `False` otherwise """ - 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. @@ -92,7 +95,7 @@ def notify_before_module_execution(self, module: str) -> bool: self.running_module = module - return False + return bool(sum(callback(module) for callback in self.on_module_enter)) def emit_context(self): From 5abf056c6ba5f2d489b6bd111f86c392b1c7bf35 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 14 Sep 2021 08:47:56 +0300 Subject: [PATCH 29/56] Typing and comments fixes --- qiling/loader/pe_uefi.py | 19 ++++++++++++++++--- qiling/os/uefi/context.py | 4 ++-- qiling/os/uefi/uefi.py | 3 ++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index b3f4b587f..f389969b8 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -29,6 +29,10 @@ def __init__(self, ql: Qiling): self.events = {} self.notify_list = [] + self.dxe_context: DxeContext + self.smm_context: SmmContext + self.context: UefiContext + # list of members names to save and restore __save_members = ( 'modules', @@ -56,7 +60,7 @@ def restore(self, saved_state: Mapping[str, Any]): 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, @@ -155,6 +159,14 @@ def call_function(self, addr: int, args: Sequence[int], ret: Optional[int]): self.ql.reg.rip = addr def unload_modules(self, context: UefiContext) -> bool: + """Invoke images unload callbacks, if set. + + Args: + context: uefi context instance + + Returns: `True` to stop the teardown process, `False` to proceed + """ + 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) @@ -177,6 +189,7 @@ def execute_module(self, path: str, image_base: int, entry_point: int, context: 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 """ @@ -342,10 +355,10 @@ def run(self): self.map_and_load(dependency, self.context) - ql.log.info(f"Done with loading {ql.path}") + ql.log.info(f"Done loading modules") except QlMemoryMappedError: - ql.log.critical("Couldn't map dependency") + ql.log.critical("Could not map dependency") # set up an end-of-execution hook to regain control when module is done # executing (i.e. when the entry point function returns). diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index e85d1338a..2dcbf3499 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -39,7 +39,7 @@ def init_stack(self, base: int, size: int): 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: @@ -58,7 +58,7 @@ def install_protocol(self, proto_desc: Mapping, handle, address: int = None, fro 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: diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index fe0d273ab..9a31e5a9f 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -9,6 +9,7 @@ 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 @@ -21,7 +22,7 @@ def __init__(self, ql: Qiling): self.running_module: str self.in_smm: bool self.PE_RUN = True - self.heap = None # Will be initialized by the loader. + 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]] = [] From 265e3a375bd844eb07b5c06d2b9588e43f8f4042 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 14 Sep 2021 08:53:35 +0300 Subject: [PATCH 30/56] Slightly better exception handling on emu_error --- qiling/os/uefi/uefi.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 9a31e5a9f..f41e4d470 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -173,16 +173,18 @@ def emu_error(self): 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:#x}{img_info}') - except UcError: - self.ql.log.error(f'PC = {pc:#x} (unreachable)') + 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() From 4e652be94a5acc511d888222051288004dcea9ed Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 14 Sep 2021 08:54:44 +0300 Subject: [PATCH 31/56] Colorful CPU context dump on error --- qiling/os/uefi/uefi.py | 52 ++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index f41e4d470..5ba46ca0a 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -3,6 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import re from typing import Any, Callable, Iterable, Mapping, MutableSequence, Sequence, Tuple from unicorn import UcError @@ -100,41 +101,30 @@ def notify_before_module_execution(self, module: str) -> bool: 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'') @@ -161,9 +151,7 @@ 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'') From 7f02c5eed0fb4f96277681cb2df9ba74352b629a Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 10 Oct 2021 01:11:49 +0300 Subject: [PATCH 32/56] Show the stack on emu_error --- qiling/os/uefi/uefi.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 5ba46ca0a..358e38e8d 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -156,6 +156,23 @@ def emit_disasm(self, address: int, data: bytearray, num_insns: int = 8): 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 @@ -174,6 +191,8 @@ def emu_error(self): self.ql.log.error(f'PC = {pc:#010x}{pc_info}') self.ql.log.error(f'') + self.emit_stack() + self.ql.log.error(f'Memory map:') self.ql.mem.show_mapinfo() From 4d7b3adb8de98c4e1049ad127d8f5d0db8fe3b2f Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 10 Oct 2021 01:12:14 +0300 Subject: [PATCH 33/56] Add typing annotations --- qiling/os/uefi/protocols/common.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/qiling/os/uefi/protocols/common.py b/qiling/os/uefi/protocols/common.py index fe582b542..5c4b322fb 100644 --- a/qiling/os/uefi/protocols/common.py +++ b/qiling/os/uefi/protocols/common.py @@ -4,10 +4,11 @@ # from qiling.os.uefi.const import EFI_SUCCESS, EFI_NOT_FOUND, EFI_UNSUPPORTED, EFI_BUFFER_TOO_SMALL, EFI_INVALID_PARAMETER +from qiling.os.uefi.context import UefiContext from qiling.os.uefi.utils import read_int64, write_int64 from qiling.os.uefi.UefiSpec import EFI_LOCATE_SEARCH_TYPE -def LocateHandles(context, params): +def LocateHandles(context: UefiContext, params): SearchType = params["SearchType"] Protocol = params["Protocol"] @@ -24,7 +25,7 @@ def LocateHandles(context, params): return len(handles) * context.ql.pointersize, handles -def InstallProtocolInterface(context, params): +def InstallProtocolInterface(context: UefiContext, params): handle = read_int64(context.ql, params["Handle"]) if handle == 0: @@ -40,7 +41,7 @@ def InstallProtocolInterface(context, params): return EFI_SUCCESS -def ReinstallProtocolInterface(context, params): +def ReinstallProtocolInterface(context: UefiContext, params): handle = params["Handle"] if handle not in context.protocols: @@ -56,7 +57,7 @@ def ReinstallProtocolInterface(context, params): return EFI_SUCCESS -def UninstallProtocolInterface(context, params): +def UninstallProtocolInterface(context: UefiContext, params): handle = params["Handle"] if handle not in context.protocols: @@ -72,7 +73,7 @@ def UninstallProtocolInterface(context, params): return EFI_SUCCESS -def HandleProtocol(context, params): +def HandleProtocol(context: UefiContext, params): handle = params["Handle"] protocol = params["Protocol"] interface = params['Interface'] @@ -87,7 +88,7 @@ def HandleProtocol(context, params): return EFI_UNSUPPORTED -def LocateHandle(context, params): +def LocateHandle(context: UefiContext, params): buffer_size, handles = LocateHandles(context, params) if len(handles) == 0: @@ -108,7 +109,7 @@ def LocateHandle(context, params): return ret -def LocateProtocol(context, params): +def LocateProtocol(context: UefiContext, params): protocol = params['Protocol'] for handle, guid_dic in context.protocols.items(): @@ -122,7 +123,7 @@ def LocateProtocol(context, params): return EFI_NOT_FOUND -def InstallConfigurationTable(context, params): +def InstallConfigurationTable(context: UefiContext, params): guid = params["Guid"] table = params["Table"] From 2fb05a0834d4e107f485d4ce87def336086c08b3 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 10 Oct 2021 01:16:02 +0300 Subject: [PATCH 34/56] Embed exit hook inside the loader --- qiling/loader/pe_uefi.py | 37 ++++++++++++++++++++++++++++++------- qiling/os/uefi/shutdown.py | 27 --------------------------- 2 files changed, 30 insertions(+), 34 deletions(-) delete mode 100644 qiling/os/uefi/shutdown.py diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index f389969b8..35f106d96 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -11,9 +11,8 @@ from qiling.exception import QlErrorArch, QlMemoryMappedError from qiling.loader.loader import QlLoader, Image -from qiling.os.uefi import st, smst +from qiling.os.uefi import st, smst, utils from qiling.os.uefi.context import DxeContext, SmmContext, UefiContext -from qiling.os.uefi.shutdown import hook_EndOfExecution from qiling.os.uefi.protocols import EfiLoadedImageProtocol from qiling.os.uefi.protocols import EfiSmmAccess2Protocol from qiling.os.uefi.protocols import EfiSmmBase2Protocol @@ -360,12 +359,36 @@ def run(self): except QlMemoryMappedError: ql.log.critical("Could not map dependency") - # set up an end-of-execution hook to regain control when module is done - # executing (i.e. when the entry point function returns). - ql.hook_address(hook_EndOfExecution, self.dxe_context.end_of_execution_ptr) - ql.hook_address(hook_EndOfExecution, self.smm_context.end_of_execution_ptr) + 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 + pass + + 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): + if ql.os.notify_after_module_execution(len(self.modules)): + return + + self.restore_runtime_services() + + 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.emu_stop() + ql.os.PE_RUN = False + + self.ql.hook_address(__module_exit_trap, address) diff --git a/qiling/os/uefi/shutdown.py b/qiling/os/uefi/shutdown.py deleted file mode 100644 index 1b3421bd3..000000000 --- a/qiling/os/uefi/shutdown.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -# -# Cross Platform and Multi Architecture Advanced Binary Emulation Framework -# - -from qiling import Qiling - -from .utils import execute_protocol_notifications - -def hook_EndOfExecution(ql: Qiling): - if ql.os.notify_after_module_execution(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(ql.loader.smm_context) or ql.loader.unload_modules(ql.loader.dxe_context): - return - - ql.log.info(f'No more modules to run') - ql.emu_stop() - ql.os.PE_RUN = False From c5357c6a6aeb0244af9c7a6ae5be882132942051 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 10 Oct 2021 01:16:58 +0300 Subject: [PATCH 35/56] Dispose of restore_runtime_services --- qiling/loader/pe_uefi.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 35f106d96..34c95652d 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -364,9 +364,6 @@ def run(self): self.execute_next_module() - def restore_runtime_services(self): - pass - 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. @@ -376,8 +373,6 @@ def __module_exit_trap(ql: Qiling): if ql.os.notify_after_module_execution(len(self.modules)): return - self.restore_runtime_services() - if utils.execute_protocol_notifications(ql): return From 743e9ef981aff263abf801eac27a6c177623bb96 Mon Sep 17 00:00:00 2001 From: elicn Date: Fri, 19 Nov 2021 11:32:24 +0200 Subject: [PATCH 36/56] Use OS call_native --- qiling/loader/pe_uefi.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 34c95652d..c04cd834c 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -10,6 +10,7 @@ from qiling.const import QL_ARCH from qiling.exception import QlErrorArch, QlMemoryMappedError from qiling.loader.loader import QlLoader, Image +from qiling.os.const import POINTER from qiling.os.uefi import st, smst, utils from qiling.os.uefi.context import DxeContext, SmmContext, UefiContext @@ -175,7 +176,7 @@ def unload_modules(self, context: UefiContext) -> bool: if unload_ptr != 0: self.ql.log.info(f'Unloading module {handle:#x}, calling {unload_ptr:#x}') - self.call_function(unload_ptr, [handle], context.end_of_execution_ptr) + self.ql.os.fcall.call_native(unload_ptr, ((POINTER, handle),), context.end_of_execution_ptr) context.loaded_image_protocol_modules.remove(handle) return True @@ -206,9 +207,12 @@ def execute_module(self, path: str, image_base: int, entry_point: int, context: self.ql.reg.rsp = context.top_of_stack self.ql.reg.rbp = context.top_of_stack - self.call_function(entry_point, [ImageHandle, SystemTable], eoe_trap) - self.ql.os.entry_point = entry_point + self.ql.os.fcall.call_native(entry_point, ( + (POINTER, ImageHandle), + (POINTER, SystemTable) + ), eoe_trap) + self.ql.os.entry_point = entry_point self.ql.log.info(f'Running from {entry_point:#010x} of {path}') def execute_next_module(self): From e86a56c39f0c512087a7bfbe5c99a470a2513a65 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 23 Nov 2021 22:08:44 +0200 Subject: [PATCH 37/56] Implement os.stop method --- qiling/os/uefi/uefi.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 358e38e8d..a926c9dcd 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -218,3 +218,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 From 75172c576ad616f4126e38bbfb8a57abbd054e22 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 23 Nov 2021 22:12:19 +0200 Subject: [PATCH 38/56] Set PE_RUN only when running --- qiling/os/uefi/uefi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index a926c9dcd..a29adc317 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -22,7 +22,7 @@ def __init__(self, ql: Qiling): self.entry_point = 0 self.running_module: str self.in_smm: bool - self.PE_RUN = True + self.PE_RUN: bool self.heap: QlMemoryHeap # Will be initialized by the loader. self.on_module_enter: MutableSequence[Callable[[str], bool]] = [] @@ -206,6 +206,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') From 984ae1ccbaf20f9331050095ae193ccf84e712df Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 24 Nov 2021 16:55:19 +0200 Subject: [PATCH 39/56] Use OS stop method --- qiling/loader/pe_uefi.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index c04cd834c..e1a00de7b 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -387,7 +387,6 @@ def __module_exit_trap(ql: Qiling): return ql.log.info(f'No more modules to run') - ql.emu_stop() - ql.os.PE_RUN = False + ql.os.stop() self.ql.hook_address(__module_exit_trap, address) From c02405e19846ebe142829e7b6359b5f0634541a1 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 28 Nov 2021 12:26:42 +0200 Subject: [PATCH 40/56] Remove typing annotation due to circular imports --- qiling/os/uefi/protocols/common.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/qiling/os/uefi/protocols/common.py b/qiling/os/uefi/protocols/common.py index 5c4b322fb..fe582b542 100644 --- a/qiling/os/uefi/protocols/common.py +++ b/qiling/os/uefi/protocols/common.py @@ -4,11 +4,10 @@ # from qiling.os.uefi.const import EFI_SUCCESS, EFI_NOT_FOUND, EFI_UNSUPPORTED, EFI_BUFFER_TOO_SMALL, EFI_INVALID_PARAMETER -from qiling.os.uefi.context import UefiContext from qiling.os.uefi.utils import read_int64, write_int64 from qiling.os.uefi.UefiSpec import EFI_LOCATE_SEARCH_TYPE -def LocateHandles(context: UefiContext, params): +def LocateHandles(context, params): SearchType = params["SearchType"] Protocol = params["Protocol"] @@ -25,7 +24,7 @@ def LocateHandles(context: UefiContext, params): return len(handles) * context.ql.pointersize, handles -def InstallProtocolInterface(context: UefiContext, params): +def InstallProtocolInterface(context, params): handle = read_int64(context.ql, params["Handle"]) if handle == 0: @@ -41,7 +40,7 @@ def InstallProtocolInterface(context: UefiContext, params): return EFI_SUCCESS -def ReinstallProtocolInterface(context: UefiContext, params): +def ReinstallProtocolInterface(context, params): handle = params["Handle"] if handle not in context.protocols: @@ -57,7 +56,7 @@ def ReinstallProtocolInterface(context: UefiContext, params): return EFI_SUCCESS -def UninstallProtocolInterface(context: UefiContext, params): +def UninstallProtocolInterface(context, params): handle = params["Handle"] if handle not in context.protocols: @@ -73,7 +72,7 @@ def UninstallProtocolInterface(context: UefiContext, params): return EFI_SUCCESS -def HandleProtocol(context: UefiContext, params): +def HandleProtocol(context, params): handle = params["Handle"] protocol = params["Protocol"] interface = params['Interface'] @@ -88,7 +87,7 @@ def HandleProtocol(context: UefiContext, params): return EFI_UNSUPPORTED -def LocateHandle(context: UefiContext, params): +def LocateHandle(context, params): buffer_size, handles = LocateHandles(context, params) if len(handles) == 0: @@ -109,7 +108,7 @@ def LocateHandle(context: UefiContext, params): return ret -def LocateProtocol(context: UefiContext, params): +def LocateProtocol(context, params): protocol = params['Protocol'] for handle, guid_dic in context.protocols.items(): @@ -123,7 +122,7 @@ def LocateProtocol(context: UefiContext, params): return EFI_NOT_FOUND -def InstallConfigurationTable(context: UefiContext, params): +def InstallConfigurationTable(context, params): guid = params["Guid"] table = params["Table"] From 4cd37f5fcd462dd0959bcf362707d9129b6e9bfd Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 28 Nov 2021 20:06:16 +0200 Subject: [PATCH 41/56] Use structure packing instead of padding manualy --- qiling/os/uefi/UefiSpec.py | 9 ++++++--- qiling/os/uefi/ds.py | 6 ++++-- qiling/os/uefi/protocols/EfiLoadedImageProtocol.py | 4 ++-- qiling/os/uefi/protocols/EfiSmmAccess2Protocol.py | 4 ++-- qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py | 2 ++ qiling/os/uefi/smst.py | 3 +-- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/qiling/os/uefi/UefiSpec.py b/qiling/os/uefi/UefiSpec.py index 14816b88f..e656a8c11 100644 --- a/qiling/os/uefi/UefiSpec.py +++ b/qiling/os/uefi/UefiSpec.py @@ -38,17 +38,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 +225,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), diff --git a/qiling/os/uefi/ds.py b/qiling/os/uefi/ds.py index b39dea9e3..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) ] 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/EfiSmmSwDispatch2Protocol.py b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py index 772812a91..7f02d8fb8 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 diff --git a/qiling/os/uefi/smst.py b/qiling/os/uefi/smst.py index 11108164b..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)))), @@ -244,7 +244,6 @@ def initialize(ql: Qiling, context, gSmst: int): ('Hdr', None), ('SmmFirmwareVendor', None), ('SmmFirmwareRevision', None), - ('PADDING_0', None), ('SmmInstallConfigurationTable', hook_SmmInstallConfigurationTable), ('SmmIo', None), ('SmmAllocatePool', hook_SmmAllocatePool), From 5bf781dce125417219e2c4e4e1e593a2ef1718a6 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 16 Dec 2021 16:30:02 +0200 Subject: [PATCH 42/56] Handle NULL GUID pointer --- qiling/os/uefi/uefi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index a29adc317..832c5ed0c 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -70,7 +70,7 @@ def fallback(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) + 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) From b9fa8fa88bb5ba9ad37f49e452bc4330333d8dfb Mon Sep 17 00:00:00 2001 From: elicn Date: Mon, 20 Dec 2021 13:38:54 +0200 Subject: [PATCH 43/56] Handle closing a non-existing event --- qiling/os/uefi/bs.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/qiling/os/uefi/bs.py b/qiling/os/uefi/bs.py index 75cd288ed..611ea1abf 100644 --- a/qiling/os/uefi/bs.py +++ b/qiling/os/uefi/bs.py @@ -133,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 From b060607b26591683355a8b2f4fb4dd13a191732e Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 21 Dec 2021 22:11:18 +0200 Subject: [PATCH 44/56] Implemented GetTime --- qiling/os/uefi/UefiSpec.py | 10 ++++++++++ qiling/os/uefi/rt.py | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/qiling/os/uefi/UefiSpec.py b/qiling/os/uefi/UefiSpec.py index e656a8c11..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', @@ -244,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/rt.py b/qiling/os/uefi/rt.py index 4dd731256..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={ From 3e5b03868e19b6008f92cc36e3ab8d961283bdf9 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 1 Jan 2022 12:52:05 +0200 Subject: [PATCH 45/56] Let protocol notifications use current UEFI context --- qiling/os/uefi/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 8080d1b21..32f6b4171 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -26,7 +26,7 @@ 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): if ql.loader.notify_list: @@ -38,7 +38,7 @@ 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 @@ -56,7 +56,7 @@ def __notify_next(ql: Qiling): if from_hook: ql.stack_push(next_hook) else: - ql.stack_push(ql.loader.smm_context.end_of_execution_ptr) + ql.stack_push(ql.loader.context.end_of_execution_ptr) ql.reg.arch_pc = next_hook return True From 2302dfc91ca02f79fb6fabf1d394d77ed71d0149 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 1 Jan 2022 12:52:40 +0200 Subject: [PATCH 46/56] Fix stack consistency on protocol notifications --- qiling/os/uefi/utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index 32f6b4171..b473d1111 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -29,6 +29,9 @@ def execute_protocol_notifications(ql: Qiling, from_hook: bool = False) -> bool: 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)})') @@ -42,14 +45,13 @@ def __notify_next(ql: Qiling): 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. From f46b96d9bcf4b16e6d7ce5c027a6d841219dbfb3 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 1 Jan 2022 12:54:26 +0200 Subject: [PATCH 47/56] Modify call_function to use call_native --- qiling/loader/pe_uefi.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index e1a00de7b..d5ee4caee 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -10,7 +10,7 @@ from qiling.const import QL_ARCH from qiling.exception import QlErrorArch, QlMemoryMappedError from qiling.loader.loader import QlLoader, Image -from qiling.os.const import POINTER +from qiling.os.const import PARAM_INTN, POINTER from qiling.os.uefi import st, smst, utils from qiling.os.uefi.context import DxeContext, SmmContext, UefiContext @@ -144,19 +144,10 @@ 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)) - # set up the arguments - for reg, arg in zip(regs, args): - self.ql.reg.write(reg, arg) - - # if provided, set return address - if ret is not None: - self.ql.stack_push(ret) - - self.ql.reg.rip = addr + self.ql.os.fcall.call_native(addr, targs, ret) def unload_modules(self, context: UefiContext) -> bool: """Invoke images unload callbacks, if set. From 93fcba10d34836840b7b6455820a5a0451e9bd77 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 1 Jan 2022 12:54:59 +0200 Subject: [PATCH 48/56] Fix rebase bug --- qiling/loader/pe_uefi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index d5ee4caee..b6a801b1e 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -94,7 +94,7 @@ def map_and_load(self, path: str, context: UefiContext, 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' From 08bd5873b0011130340d9c0ab62c72da069b6df8 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 1 Jan 2022 12:57:17 +0200 Subject: [PATCH 49/56] Use -1 in native size --- qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py index 7f02d8fb8..c2c4cd43f 100644 --- a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py +++ b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py @@ -61,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: From cbd33e8a97f50a4854b4acca95402c3261c762b8 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 9 Jan 2022 20:27:38 +0200 Subject: [PATCH 50/56] Deduplicate protocol notifications handling --- examples/simple_efi_x8664.py | 4 ++-- qiling/os/uefi/bs.py | 2 +- qiling/os/uefi/utils.py | 16 ---------------- 3 files changed, 3 insertions(+), 19 deletions(-) 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/os/uefi/bs.py b/qiling/os/uefi/bs.py index 611ea1abf..d80ac385a 100644 --- a/qiling/os/uefi/bs.py +++ b/qiling/os/uefi/bs.py @@ -436,7 +436,7 @@ def hook_InstallMultipleProtocolInterfaces(ql: Qiling, address: int, params): 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 diff --git a/qiling/os/uefi/utils.py b/qiling/os/uefi/utils.py index b473d1111..8c66631cb 100644 --- a/qiling/os/uefi/utils.py +++ b/qiling/os/uefi/utils.py @@ -63,22 +63,6 @@ def __notify_next(ql: Qiling): 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.context.end_of_execution_ptr) - - return True - - return False - def ptr_read8(ql: Qiling, addr: int) -> int: """Read BYTE data from a pointer """ From d3feb60b977dda57ff61e7f89dd166b171cec339 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 9 Jan 2022 20:32:43 +0200 Subject: [PATCH 51/56] Set running_module only when module starts --- qiling/loader/pe_uefi.py | 1 + qiling/os/uefi/uefi.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index b6a801b1e..64b587400 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -203,6 +203,7 @@ def execute_module(self, path: str, image_base: int, entry_point: int, context: (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}') diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index 832c5ed0c..be02584d2 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -95,8 +95,6 @@ def notify_before_module_execution(self, module: str) -> bool: Returns: `True` if module execution should be thwarted, `False` otherwise """ - self.running_module = module - return bool(sum(callback(module) for callback in self.on_module_enter)) From 484c64138ccee0e6d01c5f4f4ac94692942fe860 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 9 Jan 2022 20:33:58 +0200 Subject: [PATCH 52/56] Notify module execution after consuming entry --- qiling/loader/pe_uefi.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 64b587400..4ee893af6 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -208,10 +208,14 @@ def execute_module(self, path: str, image_base: int, entry_point: int, context: 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.modules[0][0]): + if not self.modules: return path, image_base, entry_point, context = self.modules.pop(0) + + if self.ql.os.notify_before_module_execution(path): + return + self.execute_module(path, image_base, entry_point, context, context.end_of_execution_ptr) def __init_dxe_environment(self, ql: Qiling) -> DxeContext: From 0633b11a38cce909c0edc176e3636c368de47d5b Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 9 Jan 2022 20:34:30 +0200 Subject: [PATCH 53/56] Comment module exit trap --- qiling/loader/pe_uefi.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 4ee893af6..0a943602e 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -370,6 +370,19 @@ def set_exit_hook(self, address: int): """ 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 From 4d5be76c49bc6197c20c6cf5802684491e84f340 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 9 Jan 2022 20:35:06 +0200 Subject: [PATCH 54/56] Typing annotations --- qiling/os/uefi/context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiling/os/uefi/context.py b/qiling/os/uefi/context.py index 2dcbf3499..4975d1370 100644 --- a/qiling/os/uefi/context.py +++ b/qiling/os/uefi/context.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Any, Mapping, Optional, Tuple +from typing import Any, Mapping, MutableSequence, Optional, Tuple from qiling import Qiling from qiling.os.memory import QlMemoryHeap @@ -14,7 +14,7 @@ def __init__(self, ql: Qiling): self.heap: QlMemoryHeap self.top_of_stack: int self.protocols = {} - self.loaded_image_protocol_modules = [] + self.loaded_image_protocol_modules: MutableSequence[int] = [] self.next_image_base: int # These members must be initialized before attempting to install a configuration table. From 1f7168ac079bd2686d9cbcf5d49c9dffd86a0851 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 9 Jan 2022 20:39:46 +0200 Subject: [PATCH 55/56] Add SMM module with SSA and SMI functionalities --- qiling/loader/pe_uefi.py | 3 - .../os/uefi/protocols/EfiSmmBase2Protocol.py | 4 +- qiling/os/uefi/protocols/EfiSmmCpuProtocol.py | 24 ++ .../protocols/EfiSmmSwDispatch2Protocol.py | 12 +- qiling/os/uefi/smm.py | 270 ++++++++++++++++++ qiling/os/uefi/uefi.py | 6 +- 6 files changed, 303 insertions(+), 16 deletions(-) create mode 100644 qiling/os/uefi/smm.py diff --git a/qiling/loader/pe_uefi.py b/qiling/loader/pe_uefi.py index 0a943602e..7bcaec500 100644 --- a/qiling/loader/pe_uefi.py +++ b/qiling/loader/pe_uefi.py @@ -188,9 +188,6 @@ def execute_module(self, path: str, image_base: int, entry_point: int, context: ImageHandle = image_base SystemTable = self.gST - # set InSmm indicator - self.ql.os.in_smm = isinstance(context, SmmContext) - # set effectively active heap self.ql.os.heap = context.heap diff --git a/qiling/os/uefi/protocols/EfiSmmBase2Protocol.py b/qiling/os/uefi/protocols/EfiSmmBase2Protocol.py index 1dd20071c..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.os.in_smm}') + ql.log.debug(f'InSmram = {ql.os.smm.active}') - write_int8(ql, params["InSmram"], int(ql.os.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 5320332f3..eaa6ec681 100644 --- a/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py +++ b/qiling/os/uefi/protocols/EfiSmmCpuProtocol.py @@ -104,6 +104,18 @@ class EFI_SMM_SAVE_STATE_REGISTER(ENUM_UC): "Buffer" : POINTER # PTR(VOID) }) 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 = { @@ -114,6 +126,18 @@ def hook_SmmReadSaveState(ql: Qiling, address: int, params): "Buffer" : POINTER # PTR(VOID) }) 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 c2c4cd43f..cda5e9461 100644 --- a/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py +++ b/qiling/os/uefi/protocols/EfiSmmSwDispatch2Protocol.py @@ -79,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/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/uefi.py b/qiling/os/uefi/uefi.py index be02584d2..c4afeb842 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -14,6 +14,7 @@ from qiling.os.os import QlOs, QlOsUtils from qiling.os.fcall import QlFunctionCall, TypedArg from . import guids_db +from qiling.os.uefi.smm import SmmEnv class QlOsUefi(QlOs): def __init__(self, ql: Qiling): @@ -21,7 +22,7 @@ def __init__(self, ql: Qiling): self.entry_point = 0 self.running_module: str - self.in_smm: bool + self.smm: SmmEnv self.PE_RUN: bool self.heap: QlMemoryHeap # Will be initialized by the loader. @@ -195,6 +196,9 @@ def emu_error(self): self.ql.mem.show_mapinfo() def run(self): + # 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: From 84933ecf61c5f987c25ea5665caea66bd4132432 Mon Sep 17 00:00:00 2001 From: elicn Date: Sun, 9 Jan 2022 20:40:16 +0200 Subject: [PATCH 56/56] Import cosmetics --- qiling/os/uefi/uefi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiling/os/uefi/uefi.py b/qiling/os/uefi/uefi.py index c4afeb842..1ee19fbb1 100644 --- a/qiling/os/uefi/uefi.py +++ b/qiling/os/uefi/uefi.py @@ -13,7 +13,8 @@ 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):