diff --git a/README.md b/README.md index 598d345..dd4be48 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Helper plugin for analyzing UEFI firmware. This plugin contains the following fe * Locate known protocol GUIDs and assign the GUID type and a symbol * Locate global assigments in entry and initialization functions and assign types * `EFI_SYSTEM_TABLE`, `EFI_RUNTIME_SERVICES`, `EFI_BOOT_SERVICES`, etc... +* Loader for Terse Executables ![demo bn-uefi-helper](screen.gif) diff --git a/__init__.py b/__init__.py index 6861999..cc3a6f9 100644 --- a/__init__.py +++ b/__init__.py @@ -1,231 +1,7 @@ -""" -Binary Ninja plugin that aids in analysis of UEFI PEI and DXE modules -""" - -import os -import csv -import glob -import uuid -from binaryninja import (PluginCommand, BackgroundTaskThread, SegmentFlag, SectionSemantics, BinaryReader, Symbol, - SymbolType, HighLevelILOperation, BinaryView) -from binaryninja.highlevelil import HighLevelILInstruction -from binaryninja.types import (Type, FunctionParameter) - -class UEFIHelper(BackgroundTaskThread): - """Class for analyzing UEFI firmware to automate GUID annotation, segment fixup, type imports, and more - """ - - def __init__(self, bv: BinaryView): - BackgroundTaskThread.__init__(self, '', False) - self.bv = bv - self.br = BinaryReader(self.bv) - self.dirname = os.path.dirname(os.path.abspath(__file__)) - self.guids = self._load_guids() - - def _fix_segments(self): - """UEFI modules run during boot, without page protections. Everything is RWX despite that the PE is built with - the segments not being writable. It needs to be RWX so calls through global function pointers are displayed - properly. - """ - - for seg in self.bv.segments: - # Make segment RWX - self.bv.add_user_segment(seg.start, seg.data_length, seg.data_offset, seg.data_length, - SegmentFlag.SegmentWritable|SegmentFlag.SegmentReadable|SegmentFlag.SegmentExecutable) - - # Make section semantics ReadWriteDataSectionSemantics - for section in self.bv.get_sections_at(seg.start): - self.bv.add_user_section(section.name, section.end-section.start, SectionSemantics.ReadWriteDataSectionSemantics) - - def _import_types_from_headers(self): - """Parse EDKII types from header files - """ - - hdrs_path = os.path.join(self.dirname, 'headers') - headers = glob.glob(os.path.join(hdrs_path, '*.h')) - for hdr in headers: - _types = self.bv.platform.parse_types_from_source_file(hdr) - for name, _type in _types.types.items(): - self.bv.define_user_type(name, _type) - - def _set_entry_point_prototype(self): - """Apply correct prototype to the module entry point - """ - - _start = self.bv.get_function_at(self.bv.entry_point) - _start.function_type = "EFI_STATUS ModuleEntryPoint(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)" - - def _load_guids(self): - """Read known GUIDs from CSV and convert string GUIDs to bytes - - :return: Dictionary containing GUID bytes and associated names - """ - - guids_path = os.path.join(self.dirname, 'guids.csv') - with open(guids_path) as f: - reader = csv.reader(f, skipinitialspace=True) - guids = dict(reader) - - # Convert to bytes for faster lookup - guid_bytes = dict() - for guid, name in guids.items(): - guid_bytes[name] = uuid.UUID(guid).bytes_le - - return guid_bytes - - def _apply_guid_name_if_data(self, name: str, address: int): - """Check if there is a function at the address. If not, then apply the EFI_GUID type and name it - - :param name: Name/symbol to apply to the GUID - :param address: Address of the GUID - """ - - print(f'Found {name} at 0x{hex(address)} ({uuid.UUID(bytes_le=self.guids[name])})') - - # Just to avoid a unlikely false positive and screwing up disassembly - if self.bv.get_functions_at(address) != []: - print(f'There is code at {address}, not applying GUID type and name') - return - - self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, address, 'g'+name)) - t = self.bv.parse_type_string("EFI_GUID") - self.bv.define_user_data_var(address, t[0]) - - def _find_known_guids(self): - """Search for known GUIDs and apply names to matches not within a function - """ - - names_list = list(self.guids.keys()) - guids_list = list(self.guids.values()) - def _check_guid_and_get_name(guid): - try: - return names_list[guids_list.index(guid)] - except ValueError: - return None - - for seg in self.bv.segments: - for i in range(seg.start, seg.end): - self.br.seek(i) - data = self.br.read(16) - if not data or len(data) != 16: - continue - - found_name = _check_guid_and_get_name(data) - if found_name: - self._apply_guid_name_if_data(found_name, i) - - def _set_if_uefi_core_type(self, instr: HighLevelILInstruction): - """Using HLIL, scrutinize the instruction to determine if it's a move of a local variable to a global variable. - If it is, check if the source operand type is a UEFI core type and apply the type to the destination global - variable. - - :param instr: High level IL instruction object - """ - - if instr.operation != HighLevelILOperation.HLIL_ASSIGN: - return - - if instr.dest.operation != HighLevelILOperation.HLIL_DEREF: - return - - if instr.dest.src.operation != HighLevelILOperation.HLIL_CONST_PTR: - return - - if instr.src.operation != HighLevelILOperation.HLIL_VAR: - return - - _type = instr.src.var.type - if len(_type.tokens) == 1 and str(_type.tokens[0]) == 'EFI_HANDLE': - self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gImageHandle')) - elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_BOOT_SERVICES': - self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gBS')) - elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_RUNTIME_SERVICES': - self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gRS')) - elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_SYSTEM_TABLE': - self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gST')) - else: - return - - self.bv.define_user_data_var(instr.dest.src.constant, instr.src.var.type) - print(f'Found global assignment - offset:0x{hex(instr.dest.src.constant)} type:{instr.src.var.type}') - - def _check_and_prop_types_on_call(self, instr: HighLevelILInstruction): - """Most UEFI modules don't assign globals in the entry function and instead call a initialization routine and - pass the system table to it where global assignments are made. This function ensures that the types are applied - to the initialization function params so that we can catch global assignments outside of the module entry - - :param instr: High level IL instruction object - """ - - if instr.operation not in [HighLevelILOperation.HLIL_TAILCALL, HighLevelILOperation.HLIL_CALL]: - return - - if instr.dest.operation != HighLevelILOperation.HLIL_CONST_PTR: - return - - argv_is_passed = False - for arg in instr.params: - if 'ImageHandle' in str(arg) or 'SystemTable' in str(arg): - argv_is_passed = True - break - - if not argv_is_passed: - return - - func = self.bv.get_function_at(instr.dest.constant) - old = func.function_type - call_args = instr.params - new_params = [] - for arg, param in zip(call_args, old.parameters): - if hasattr(arg, 'var'): - new_type = arg.var.type - else: - new_type = param.type - new_type.confidence = 256 - new_params.append(FunctionParameter(new_type, param.name)) - - # TODO: this is a hack to account for odd behavior. func.function_type should be able to set directly to - # Type.Function(...). However, during testing this isn't the case. I am only able to get it to work if I - # set function_type to a string and update analysis. - gross_hack = str( - Type.function(old.return_value, new_params, old.calling_convention, old.has_variable_arguments, old.stack_adjustment) - ).replace('(', '{}('.format(func.name)) - func.function_type = gross_hack - self.bv.update_analysis_and_wait() - - def _set_global_variables(self): - """On entry, UEFI modules usually set global variables for EFI_BOOT_SERVICES, EFI_RUNTIME_SERIVCES, and - EFI_SYSTEM_TABLE. This function attempts to identify these assignments and apply types. - """ - - func = self.bv.get_function_at(self.bv.entry_point) - for block in func.high_level_il: - for instr in block: - self._check_and_prop_types_on_call(instr) - - for func in self.bv.functions: - for block in func.high_level_il: - for instr in block: - self._set_if_uefi_core_type(instr) - - def run(self): - """Run the task in the background - """ - - self.progress = "UEFI Helper: Fixing up segments, applying types, and searching for known GUIDs ..." - self._fix_segments() - self._import_types_from_headers() - self._set_entry_point_prototype() - self._find_known_guids() - self.progress = "UEFI Helper: searching for global assignments for UEFI core services ..." - self._set_global_variables() - print('UEFI Helper completed successfully!') - -def run_uefi_helper(bv: BinaryView): - """Run UEFI helper utilities in the background - """ - - task = UEFIHelper(bv) - task.start() +from binaryninja import PluginCommand +from .helper import run_uefi_helper +from .teloader import TerseExecutableView PluginCommand.register('UEFI Helper', 'Run UEFI Helper analysis', run_uefi_helper) +TerseExecutableView.register() + diff --git a/headers/efi_pei_services_64.h b/headers/efi_pei_services_64.h new file mode 100644 index 0000000..5702ce8 --- /dev/null +++ b/headers/efi_pei_services_64.h @@ -0,0 +1,322 @@ +typedef unsigned char undefined; + +typedef unsigned char byte; +typedef unsigned int dword; +typedef unsigned long long qword; +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long long ulonglong; +typedef unsigned char undefined1; +typedef unsigned int undefined4; +typedef unsigned short ushort; +typedef unsigned short word; + +struct EFI_PEI_PPI_DESCRIPTOR; +struct EFI_STATUS_CODE_DATA; +struct EFI_FV_FILE_INFO; +struct EFI_FV_FILE_INFO2; +struct EFI_PEI_PCI_CFG2_PPI; +struct EFI_PEI_CPU_IO_PPI; +struct EFI_FV_INFO; + +struct _EFI_PEI_SERVICES; +typedef struct _EFI_PEI_SERVICES EFI_PEI_SERVICES; + +typedef UINT64 UINTN; + +typedef UINTN RETURN_STATUS; + +typedef RETURN_STATUS EFI_STATUS; + +typedef EFI_STATUS (* EFI_PEI_INSTALL_PPI)(EFI_PEI_SERVICES * *, struct EFI_PEI_PPI_DESCRIPTOR *); + +typedef EFI_STATUS (* EFI_PEI_REINSTALL_PPI)(EFI_PEI_SERVICES * *, struct EFI_PEI_PPI_DESCRIPTOR *, struct EFI_PEI_PPI_DESCRIPTOR *); + +typedef GUID EFI_GUID; + +typedef EFI_STATUS (* EFI_PEI_LOCATE_PPI)(EFI_PEI_SERVICES * *, EFI_GUID *, UINTN, struct EFI_PEI_PPI_DESCRIPTOR * *, void * *); + +struct _EFI_PEI_NOTIFY_DESCRIPTOR; +typedef struct _EFI_PEI_NOTIFY_DESCRIPTOR EFI_PEI_NOTIFY_DESCRIPTOR; + +typedef EFI_STATUS (* EFI_PEI_NOTIFY_PPI)(EFI_PEI_SERVICES * *, EFI_PEI_NOTIFY_DESCRIPTOR *); + +typedef UINT32 EFI_BOOT_MODE; + +typedef EFI_STATUS (* EFI_PEI_GET_BOOT_MODE)(EFI_PEI_SERVICES * *, EFI_BOOT_MODE *); + +typedef EFI_STATUS (* EFI_PEI_SET_BOOT_MODE)(EFI_PEI_SERVICES * *, EFI_BOOT_MODE); + +typedef EFI_STATUS (* EFI_PEI_GET_HOB_LIST)(EFI_PEI_SERVICES * *, void * *); + +typedef EFI_STATUS (* EFI_PEI_CREATE_HOB)(EFI_PEI_SERVICES * *, UINT16, UINT16, void * *); + +typedef void * EFI_PEI_FV_HANDLE; + +typedef EFI_STATUS (* EFI_PEI_FFS_FIND_NEXT_VOLUME2)(EFI_PEI_SERVICES * *, UINTN, EFI_PEI_FV_HANDLE *); + +typedef UINT8 EFI_FV_FILETYPE; + +typedef void * EFI_PEI_FILE_HANDLE; + +typedef EFI_STATUS (* EFI_PEI_FFS_FIND_NEXT_FILE2)(EFI_PEI_SERVICES * *, EFI_FV_FILETYPE, EFI_PEI_FV_HANDLE, EFI_PEI_FILE_HANDLE *); + +typedef UINT8 EFI_SECTION_TYPE; + +typedef EFI_STATUS (* EFI_PEI_FFS_FIND_SECTION_DATA2)(EFI_PEI_SERVICES * *, EFI_SECTION_TYPE, EFI_PEI_FILE_HANDLE, void * *); + +typedef UINT64 EFI_PHYSICAL_ADDRESS; + +typedef EFI_STATUS (* EFI_PEI_INSTALL_PEI_MEMORY)(EFI_PEI_SERVICES * *, EFI_PHYSICAL_ADDRESS, UINT64); + +enum enum_16 { + EfiBootServicesCode=3, + EfiRuntimeServicesData=6, + EfiMemoryMappedIOPortSpace=12, + EfiLoaderData=2, + EfiBootServicesData=4, + EfiLoaderCode=1, + EfiReservedMemoryType=0, + EfiRuntimeServicesCode=5, + EfiACPIReclaimMemory=9, + EfiMaxMemoryType=15, + EfiConventionalMemory=7, + EfiMemoryMappedIO=11, + EfiPalCode=13, + EfiPersistentMemory=14, + EfiACPIMemoryNVS=10, + EfiUnusableMemory=8 +}; + +typedef enum enum_16 EFI_MEMORY_TYPE; + +typedef EFI_STATUS (* EFI_PEI_ALLOCATE_PAGES)(EFI_PEI_SERVICES * *, EFI_MEMORY_TYPE, UINTN, EFI_PHYSICAL_ADDRESS *); + +typedef EFI_STATUS (* EFI_PEI_ALLOCATE_POOL)(EFI_PEI_SERVICES * *, UINTN, void * *); + +typedef void (* EFI_PEI_COPY_MEM)(void *, void *, UINTN); + +typedef void (* EFI_PEI_SET_MEM)(void *, UINTN, UINT8); + +typedef UINT32 EFI_STATUS_CODE_TYPE; + +typedef UINT32 EFI_STATUS_CODE_VALUE; + +typedef EFI_STATUS (* EFI_PEI_REPORT_STATUS_CODE)(EFI_PEI_SERVICES * *, EFI_STATUS_CODE_TYPE, EFI_STATUS_CODE_VALUE, UINT32, EFI_GUID *, struct EFI_STATUS_CODE_DATA *); + +typedef EFI_STATUS (* EFI_PEI_RESET_SYSTEM)(EFI_PEI_SERVICES * *); + + +typedef EFI_STATUS (* EFI_PEI_FFS_FIND_BY_NAME)(EFI_GUID *, EFI_PEI_FV_HANDLE, EFI_PEI_FILE_HANDLE *); + +typedef EFI_STATUS (* EFI_PEI_FFS_GET_FILE_INFO)(EFI_PEI_FILE_HANDLE, struct EFI_FV_FILE_INFO *); + +typedef EFI_STATUS (* EFI_PEI_FFS_GET_VOLUME_INFO)(EFI_PEI_FV_HANDLE, struct EFI_FV_INFO *); + +typedef EFI_STATUS (* EFI_PEI_REGISTER_FOR_SHADOW)(EFI_PEI_FILE_HANDLE); + +typedef EFI_STATUS (* EFI_PEI_FFS_FIND_SECTION_DATA3)(EFI_PEI_SERVICES * *, EFI_SECTION_TYPE, UINTN, EFI_PEI_FILE_HANDLE, void * *, UINT32 *); + +typedef EFI_STATUS (* EFI_PEI_FFS_GET_FILE_INFO2)(EFI_PEI_FILE_HANDLE, struct EFI_FV_FILE_INFO2 *); + +enum enum_17 { + EfiResetCold=0, + EfiResetShutdown=2, + EfiResetWarm=1, + EfiResetPlatformSpecific=3 +}; + +typedef enum enum_17 EFI_RESET_TYPE; + +typedef void (* EFI_PEI_RESET2_SYSTEM)(EFI_RESET_TYPE, EFI_STATUS, UINTN, void *); + +typedef EFI_STATUS (* EFI_PEI_FREE_PAGES)(EFI_PEI_SERVICES * *, EFI_PHYSICAL_ADDRESS, UINTN); + +typedef EFI_STATUS (* EFI_PEIM_NOTIFY_ENTRY_POINT)(EFI_PEI_SERVICES * *, EFI_PEI_NOTIFY_DESCRIPTOR *, void *); + +typedef UINT8 (* EFI_PEI_CPU_IO_PPI_IO_READ8)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64); + +typedef UINT16 (* EFI_PEI_CPU_IO_PPI_IO_READ16)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64); + +typedef UINT32 (* EFI_PEI_CPU_IO_PPI_IO_READ32)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64); + +typedef UINT64 (* EFI_PEI_CPU_IO_PPI_IO_READ64)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64); + +typedef void (* EFI_PEI_CPU_IO_PPI_IO_WRITE8)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64, UINT8); + +typedef void (* EFI_PEI_CPU_IO_PPI_IO_WRITE16)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64, UINT16); + +typedef void (* EFI_PEI_CPU_IO_PPI_IO_WRITE32)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64, UINT32); + +typedef void (* EFI_PEI_CPU_IO_PPI_IO_WRITE64)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64, UINT64); + +typedef UINT8 (* EFI_PEI_CPU_IO_PPI_MEM_READ8)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64); + +typedef UINT16 (* EFI_PEI_CPU_IO_PPI_MEM_READ16)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64); + +typedef UINT32 (* EFI_PEI_CPU_IO_PPI_MEM_READ32)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64); + +typedef UINT64 (* EFI_PEI_CPU_IO_PPI_MEM_READ64)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64); + +typedef void (* EFI_PEI_CPU_IO_PPI_MEM_WRITE8)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64, UINT8); + +typedef void (* EFI_PEI_CPU_IO_PPI_MEM_WRITE16)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64, UINT16); + +typedef void (* EFI_PEI_CPU_IO_PPI_MEM_WRITE32)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64, UINT32); + +typedef void (* EFI_PEI_CPU_IO_PPI_MEM_WRITE64)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, UINT64, UINT64); + +enum enum_897 { + EfiPeiPciCfgWidthUint64=3, + EfiPeiPciCfgWidthUint32=2, + EfiPeiPciCfgWidthUint8=0, + EfiPeiPciCfgWidthUint16=1, + EfiPeiPciCfgWidthMaximum=4 +}; + +typedef enum enum_897 EFI_PEI_PCI_CFG_PPI_WIDTH; + +typedef EFI_STATUS (* EFI_PEI_PCI_CFG2_PPI_IO)(EFI_PEI_SERVICES * *, EFI_PEI_PCI_CFG2_PPI *, EFI_PEI_PCI_CFG_PPI_WIDTH, UINT64, void *); + +typedef EFI_STATUS (* EFI_PEI_PCI_CFG2_PPI_RW)(EFI_PEI_SERVICES * *, EFI_PEI_PCI_CFG2_PPI *, EFI_PEI_PCI_CFG_PPI_WIDTH, UINT64, void *, void *); + +typedef UINT32 EFI_FV_FILE_ATTRIBUTES; + +typedef UINT32 EFI_FVB_ATTRIBUTES_2; + +enum enum_868 { + EfiPeiCpuIoWidthFifoUint16=5, + EfiPeiCpuIoWidthFillUint32=10, + EfiPeiCpuIoWidthFillUint16=9, + EfiPeiCpuIoWidthFifoUint64=7, + EfiPeiCpuIoWidthFifoUint32=6, + EfiPeiCpuIoWidthFillUint8=8, + EfiPeiCpuIoWidthUint8=0, + EfiPeiCpuIoWidthUint16=1, + EfiPeiCpuIoWidthMaximum=12, + EfiPeiCpuIoWidthUint64=3, + EfiPeiCpuIoWidthUint32=2, + EfiPeiCpuIoWidthFifoUint8=4, + EfiPeiCpuIoWidthFillUint64=11 +}; + +typedef enum enum_868 EFI_PEI_CPU_IO_PPI_WIDTH; + +typedef EFI_STATUS (* EFI_PEI_CPU_IO_PPI_IO_MEM)(EFI_PEI_SERVICES * *, EFI_PEI_CPU_IO_PPI *, EFI_PEI_CPU_IO_PPI_WIDTH, UINT64, UINTN, void *); + +struct _EFI_PEI_NOTIFY_DESCRIPTOR { + UINTN Flags; + EFI_GUID * Guid; + EFI_PEIM_NOTIFY_ENTRY_POINT Notify; +}; + +struct EFI_FV_FILE_INFO2 { + EFI_GUID FileName; + EFI_FV_FILETYPE FileType; + EFI_FV_FILE_ATTRIBUTES FileAttributes; + void * Buffer; + UINT32 BufferSize; + UINT32 AuthenticationStatus; +}; + +struct EFI_TABLE_HEADER { + UINT64 Signature; + UINT32 Revision; + UINT32 HeaderSize; + UINT32 CRC32; + UINT32 Reserved; +}; + +struct EFI_PEI_CPU_IO_PPI_ACCESS { + EFI_PEI_CPU_IO_PPI_IO_MEM Read; + EFI_PEI_CPU_IO_PPI_IO_MEM Write; +}; + +struct EFI_PEI_CPU_IO_PPI { + struct EFI_PEI_CPU_IO_PPI_ACCESS Mem; + struct EFI_PEI_CPU_IO_PPI_ACCESS Io; + EFI_PEI_CPU_IO_PPI_IO_READ8 IoRead8; + EFI_PEI_CPU_IO_PPI_IO_READ16 IoRead16; + EFI_PEI_CPU_IO_PPI_IO_READ32 IoRead32; + EFI_PEI_CPU_IO_PPI_IO_READ64 IoRead64; + EFI_PEI_CPU_IO_PPI_IO_WRITE8 IoWrite8; + EFI_PEI_CPU_IO_PPI_IO_WRITE16 IoWrite16; + EFI_PEI_CPU_IO_PPI_IO_WRITE32 IoWrite32; + EFI_PEI_CPU_IO_PPI_IO_WRITE64 IoWrite64; + EFI_PEI_CPU_IO_PPI_MEM_READ8 MemRead8; + EFI_PEI_CPU_IO_PPI_MEM_READ16 MemRead16; + EFI_PEI_CPU_IO_PPI_MEM_READ32 MemRead32; + EFI_PEI_CPU_IO_PPI_MEM_READ64 MemRead64; + EFI_PEI_CPU_IO_PPI_MEM_WRITE8 MemWrite8; + EFI_PEI_CPU_IO_PPI_MEM_WRITE16 MemWrite16; + EFI_PEI_CPU_IO_PPI_MEM_WRITE32 MemWrite32; + EFI_PEI_CPU_IO_PPI_MEM_WRITE64 MemWrite64; +}; + +struct EFI_FV_INFO { + EFI_FVB_ATTRIBUTES_2 FvAttributes; + EFI_GUID FvFormat; + EFI_GUID FvName; + void * FvStart; + UINT64 FvSize; +}; + +struct EFI_PEI_PCI_CFG2_PPI { + EFI_PEI_PCI_CFG2_PPI_IO Read; + EFI_PEI_PCI_CFG2_PPI_IO Write; + EFI_PEI_PCI_CFG2_PPI_RW Modify; + UINT16 Segment; +}; + +struct EFI_PEI_PPI_DESCRIPTOR { + UINTN Flags; + EFI_GUID * Guid; + void * Ppi; +}; + +struct EFI_FV_FILE_INFO { + EFI_GUID FileName; + EFI_FV_FILETYPE FileType; + EFI_FV_FILE_ATTRIBUTES FileAttributes; + void * Buffer; + UINT32 BufferSize; +}; + +struct _EFI_PEI_SERVICES { + struct EFI_TABLE_HEADER Hdr; + EFI_PEI_INSTALL_PPI InstallPpi; + EFI_PEI_REINSTALL_PPI ReInstallPpi; + EFI_PEI_LOCATE_PPI LocatePpi; + EFI_PEI_NOTIFY_PPI NotifyPpi; + EFI_PEI_GET_BOOT_MODE GetBootMode; + EFI_PEI_SET_BOOT_MODE SetBootMode; + EFI_PEI_GET_HOB_LIST GetHobList; + EFI_PEI_CREATE_HOB CreateHob; + EFI_PEI_FFS_FIND_NEXT_VOLUME2 FfsFindNextVolume; + EFI_PEI_FFS_FIND_NEXT_FILE2 FfsFindNextFile; + EFI_PEI_FFS_FIND_SECTION_DATA2 FfsFindSectionData; + EFI_PEI_INSTALL_PEI_MEMORY InstallPeiMemory; + EFI_PEI_ALLOCATE_PAGES AllocatePages; + EFI_PEI_ALLOCATE_POOL AllocatePool; + EFI_PEI_COPY_MEM CopyMem; + EFI_PEI_SET_MEM SetMem; + EFI_PEI_REPORT_STATUS_CODE ReportStatusCode; + EFI_PEI_RESET_SYSTEM ResetSystem; + EFI_PEI_CPU_IO_PPI * CpuIo; + EFI_PEI_PCI_CFG2_PPI * PciCfg; + EFI_PEI_FFS_FIND_BY_NAME FfsFindFileByName; + EFI_PEI_FFS_GET_FILE_INFO FfsGetFileInfo; + EFI_PEI_FFS_GET_VOLUME_INFO FfsGetVolumeInfo; + EFI_PEI_REGISTER_FOR_SHADOW RegisterForShadow; + EFI_PEI_FFS_FIND_SECTION_DATA3 FindSectionData3; + EFI_PEI_FFS_GET_FILE_INFO2 FfsGetFileInfo2; + EFI_PEI_RESET2_SYSTEM ResetSystem2; + EFI_PEI_FREE_PAGES FreePages; +}; + +struct EFI_STATUS_CODE_DATA { + UINT16 HeaderSize; + UINT16 Size; + EFI_GUID Type; +}; + diff --git a/headers/efi_system_table_x64.h b/headers/efi_system_table_64.h similarity index 100% rename from headers/efi_system_table_x64.h rename to headers/efi_system_table_64.h diff --git a/helper.py b/helper.py new file mode 100644 index 0000000..8985c89 --- /dev/null +++ b/helper.py @@ -0,0 +1,240 @@ +""" +Binary Ninja plugin that aids in analysis of UEFI PEI and DXE modules +""" + +import os +import csv +import glob +import uuid +from binaryninja import (BackgroundTaskThread, SegmentFlag, SectionSemantics, BinaryReader, Symbol, + SymbolType, HighLevelILOperation, BinaryView) +from binaryninja.highlevelil import HighLevelILInstruction +from binaryninja.types import (Type, FunctionParameter) + +class UEFIHelper(BackgroundTaskThread): + """Class for analyzing UEFI firmware to automate GUID annotation, segment fixup, type imports, and more + """ + + def __init__(self, bv: BinaryView): + BackgroundTaskThread.__init__(self, '', False) + self.bv = bv + self.br = BinaryReader(self.bv) + self.dirname = os.path.dirname(os.path.abspath(__file__)) + self.guids = self._load_guids() + + def _fix_segments(self): + """UEFI modules run during boot, without page protections. Everything is RWX despite that the PE is built with + the segments not being writable. It needs to be RWX so calls through global function pointers are displayed + properly. + """ + + for seg in self.bv.segments: + # Make segment RWX + self.bv.add_user_segment(seg.start, seg.data_length, seg.data_offset, seg.data_length, + SegmentFlag.SegmentWritable|SegmentFlag.SegmentReadable|SegmentFlag.SegmentExecutable) + + # Make section semantics ReadWriteDataSectionSemantics + for section in self.bv.get_sections_at(seg.start): + self.bv.add_user_section(section.name, section.end-section.start, SectionSemantics.ReadWriteDataSectionSemantics) + + def _import_types_from_headers(self): + """Parse EDKII types from header files + """ + + hdrs_path = os.path.join(self.dirname, 'headers') + headers = glob.glob(os.path.join(hdrs_path, '*.h')) + for hdr in headers: + _types = self.bv.platform.parse_types_from_source_file(hdr) + for name, _type in _types.types.items(): + self.bv.define_user_type(name, _type) + + def _set_entry_point_prototype(self): + """Apply correct prototype to the module entry point + """ + + _start = self.bv.get_function_at(self.bv.entry_point) + if self.bv.view_type != 'TE': + _start.function_type = "EFI_STATUS ModuleEntryPoint(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)" + + def _load_guids(self): + """Read known GUIDs from CSV and convert string GUIDs to bytes + + :return: Dictionary containing GUID bytes and associated names + """ + + guids_path = os.path.join(self.dirname, 'guids.csv') + with open(guids_path) as f: + reader = csv.reader(f, skipinitialspace=True) + guids = dict(reader) + + # Convert to bytes for faster lookup + guid_bytes = dict() + for guid, name in guids.items(): + guid_bytes[name] = uuid.UUID(guid).bytes_le + + return guid_bytes + + def _apply_guid_name_if_data(self, name: str, address: int): + """Check if there is a function at the address. If not, then apply the EFI_GUID type and name it + + :param name: Name/symbol to apply to the GUID + :param address: Address of the GUID + """ + + print(f'Found {name} at 0x{hex(address)} ({uuid.UUID(bytes_le=self.guids[name])})') + + # Just to avoid a unlikely false positive and screwing up disassembly + if self.bv.get_functions_at(address) != []: + print(f'There is code at {address}, not applying GUID type and name') + return + + self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, address, 'g'+name)) + t = self.bv.parse_type_string("EFI_GUID") + self.bv.define_user_data_var(address, t[0]) + + def _find_known_guids(self): + """Search for known GUIDs and apply names to matches not within a function + """ + + names_list = list(self.guids.keys()) + guids_list = list(self.guids.values()) + def _check_guid_and_get_name(guid): + try: + return names_list[guids_list.index(guid)] + except ValueError: + return None + + for seg in self.bv.segments: + for i in range(seg.start, seg.end): + self.br.seek(i) + data = self.br.read(16) + if not data or len(data) != 16: + continue + + found_name = _check_guid_and_get_name(data) + if found_name: + self._apply_guid_name_if_data(found_name, i) + + def _set_if_uefi_core_type(self, instr: HighLevelILInstruction): + """Using HLIL, scrutinize the instruction to determine if it's a move of a local variable to a global variable. + If it is, check if the source operand type is a UEFI core type and apply the type to the destination global + variable. + + :param instr: High level IL instruction object + """ + + if instr.operation != HighLevelILOperation.HLIL_ASSIGN: + return + + if instr.dest.operation != HighLevelILOperation.HLIL_DEREF: + return + + if instr.dest.src.operation != HighLevelILOperation.HLIL_CONST_PTR: + return + + if instr.src.operation != HighLevelILOperation.HLIL_VAR: + return + + _type = instr.src.var.type + if len(_type.tokens) == 1 and str(_type.tokens[0]) == 'EFI_HANDLE': + self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gHandle')) + elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_BOOT_SERVICES': + self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gBS')) + elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_RUNTIME_SERVICES': + self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gRS')) + elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_SYSTEM_TABLE': + self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gST')) + elif len(_type.tokens) == 1 and str(_type.tokens[0]) == 'EFI_PEI_FILE_HANDLE': + self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gHandle')) + elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_PEI_SERVICES': + self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gPeiServices')) + else: + return + + self.bv.define_user_data_var(instr.dest.src.constant, instr.src.var.type) + print(f'Found global assignment - offset:0x{hex(instr.dest.src.constant)} type:{instr.src.var.type}') + + def _check_and_prop_types_on_call(self, instr: HighLevelILInstruction): + """Most UEFI modules don't assign globals in the entry function and instead call a initialization routine and + pass the system table to it where global assignments are made. This function ensures that the types are applied + to the initialization function params so that we can catch global assignments outside of the module entry + + :param instr: High level IL instruction object + """ + + if instr.operation not in [HighLevelILOperation.HLIL_TAILCALL, HighLevelILOperation.HLIL_CALL]: + return + + if instr.dest.operation != HighLevelILOperation.HLIL_CONST_PTR: + return + + argv_is_passed = False + for arg in instr.params: + if 'ImageHandle' in str(arg) or 'SystemTable' or 'FileHandle' or 'PeiServices' in str(arg): + argv_is_passed = True + break + + if not argv_is_passed: + return + + func = self.bv.get_function_at(instr.dest.constant) + old = func.function_type + call_args = instr.params + new_params = [] + for arg, param in zip(call_args, old.parameters): + if hasattr(arg, 'var'): + new_type = arg.var.type + else: + new_type = param.type + new_type.confidence = 256 + new_params.append(FunctionParameter(new_type, param.name)) + + # TODO: this is a hack to account for odd behavior. func.function_type should be able to set directly to + # Type.Function(...). However, during testing this isn't the case. I am only able to get it to work if I + # set function_type to a string and update analysis. + gross_hack = str( + Type.function(old.return_value, new_params, old.calling_convention, old.has_variable_arguments, old.stack_adjustment) + ).replace('(', '{}('.format(func.name)) + try: + func.function_type = gross_hack + self.bv.update_analysis_and_wait() + except SyntaxError: + pass # BN can't parse int48_t and other types despite that it uses it. Ran into this from a sidt instruction + + def _set_global_variables(self): + """On entry, UEFI modules usually set global variables for EFI_BOOT_SERVICES, EFI_RUNTIME_SERIVCES, and + EFI_SYSTEM_TABLE. This function attempts to identify these assignments and apply types. + """ + + func = self.bv.get_function_at(self.bv.entry_point) + for block in func.high_level_il: + for instr in block: + self._check_and_prop_types_on_call(instr) + + for func in self.bv.functions: + for block in func.high_level_il: + for instr in block: + self._set_if_uefi_core_type(instr) + + def run(self): + """Run the task in the background + """ + + self.progress = "UEFI Helper: Fixing up segments, applying types, and searching for known GUIDs ..." + self._fix_segments() + self._import_types_from_headers() + self._set_entry_point_prototype() + self._find_known_guids() + self.progress = "UEFI Helper: searching for global assignments for UEFI core services ..." + self._set_global_variables() + print('UEFI Helper completed successfully!') + +def run_uefi_helper(bv: BinaryView): + """Run UEFI helper utilities in the background + + :param bv: BinaryView + """ + + task = UEFIHelper(bv) + task.start() + diff --git a/plugin.json b/plugin.json index 9935cd3..763195f 100644 --- a/plugin.json +++ b/plugin.json @@ -24,6 +24,6 @@ "Windows": "", "Linux": "" }, - "version": "0.1", + "version": "0.2", "minimumbinaryninjaversion": 2660 -} \ No newline at end of file +} diff --git a/teloader.py b/teloader.py new file mode 100644 index 0000000..5193505 --- /dev/null +++ b/teloader.py @@ -0,0 +1,195 @@ +"""BinaryView for UEFI Terse Executables +""" + +import glob +import os +import struct +from binaryninja import BinaryView, platform, SegmentFlag, SectionSemantics, Symbol, SymbolType + +TERSE_IMAGE_HEADER_SIZE = 40 +SECTION_HEADER_SIZE = 40 + +class TerseExecutableView(BinaryView): + """This class implements the BinaryView for Terse Executables + """ + + name = 'TE' + long_name = 'Terse Executable' + + def __init__(self, data: bytes): + BinaryView.__init__(self, parent_view=data, file_metadata=data.file) + self.raw = data + + @classmethod + def is_valid_for_data(self, data: bytes) -> bool: + """Determine if the loaded binary is a Terse Executable + + :param data: Raw binary data + :return: True if the binary is a TE, otherwise False + """ + + if len(data) < TERSE_IMAGE_HEADER_SIZE: + return False + + if data[0:2].decode('utf-8', 'replace') != 'VZ': + return False + + return True + + def _set_platform(self, machine_type: int): + """Set platform/architecture from machine type + + :param machine_type: Machine type from TE header + """ + + if machine_type == 332: + self.platform = platform.Platform['windows-x86'] + elif machine_type == -31132: + self.platform = platform.Platform['windows-x86_64'] + elif machine_type == -21916: + self.platform = platform.Platform['windows-aarch64'] + + def _create_segments(self, image_base: int, num_of_sections: int): + """There's really only one segment in a TE and it's RWX. However, we set the header to read only jsut to make + sure it isn't disassembled as code. + + :param image_base: Virtual base address + :param num_of_sections: Number of sections (for header region size calculation) + """ + + headers_size = TERSE_IMAGE_HEADER_SIZE + num_of_sections * SECTION_HEADER_SIZE + self.add_auto_segment(image_base, headers_size, 0, headers_size, SegmentFlag.SegmentReadable) + code_region_size = len(self.raw)-headers_size + self.add_auto_segment(image_base+headers_size, code_region_size, headers_size, code_region_size, + SegmentFlag.SegmentReadable|SegmentFlag.SegmentWritable|SegmentFlag.SegmentExecutable) + + def _create_sections(self, image_base: int, num_of_sections: int): + """Create sections + + :param image_base: Virtual base address + :param num_of_sections: Number of sections + """ + + base = TERSE_IMAGE_HEADER_SIZE + for _ in range(0, num_of_sections): + name = self.raw[base:base+8].decode('utf-8') + virtual_size = struct.unpack(' bool: + """Terse Executables are executable, return true + + :return: True + """ + + return True + + def perform_get_entry_point(self) -> int: + """Determine the address of the entry point function + + :return: Address of the entry point + """ + image_base = struct.unpack('