From 60d9281e5ef95d5517a1c1225d9f75a49b3260e1 Mon Sep 17 00:00:00 2001 From: Frederic Vachon Date: Fri, 16 Feb 2018 18:49:28 -0500 Subject: [PATCH 01/13] Porting efiutils.py to IDA 7 --- efiutils.py | 257 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 153 insertions(+), 104 deletions(-) diff --git a/efiutils.py b/efiutils.py index caf12de..da75690 100644 --- a/efiutils.py +++ b/efiutils.py @@ -7,13 +7,25 @@ https://github.com/snarez/ida-efiutils """ -import re +import copy import struct -from idaapi import * -from idautils import * -from idc import * + +import idaapi +import idautils +import ida_bytes +import ida_entry +import ida_idp +import ida_kernwin +import ida_lines +import ida_name +import ida_pro +import ida_segment +import ida_struct +import ida_ua + import efiguids + MAX_STACK_DEPTH = 1 IMAGE_HANDLE_NAME = 'gImageHandle' SYSTEM_TABLE_NAME = 'gSystemTable' @@ -25,6 +37,7 @@ local_guids = { } + class GUID: Data1 = None Data2 = None @@ -64,6 +77,7 @@ def array(self): d = self.Data4 return [self.Data1, self.Data2, self.Data3, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]] + def go(): rename_tables() update_structs() @@ -86,63 +100,66 @@ def rename_tables(): regs['bs'] = [] regs['rs'] = [] - entry = GetEntryOrdinal(0) + entry = ida_entry.get_entry_ordinal(0) rename_tables_internal(entry, regs) -def rename_tables_internal(function, regs, stackdepth=0): +def rename_tables_internal(ea, regs, stackdepth=0): names = {'im': IMAGE_HANDLE_NAME, 'st': SYSTEM_TABLE_NAME, 'bs': BOOT_SERVICES_NAME, 'rs': RUNTIME_SERVICES_NAME} - print "Processing function at 0x%x" % function + print "Processing function at 0x{:08x}".format(ea) - for item in FuncItems(function): - #print "regs = " + str(regs) + for item in idautils.FuncItems(ea): + inst = idautils.DecodeInstruction(item) + # Bail out if we hit a call - if GetMnem(item) == "call": + if inst.get_canon_mnem() == "call": if stackdepth == MAX_STACK_DEPTH: print " - Hit stack depth limit, bailing!" return else: - if GetOpType(item, 0) in [o_imm, o_far, o_near]: - rename_tables_internal(LocByName(GetOpnd(item, 0)), regs, stackdepth+1) + if inst.Op1.type in [ida_ua.o_imm, ida_ua.o_far, ida_ua.o_near]: + # TODO : Currently assuming that the registry will be inaffected by calls + rename_tables_internal(inst.Op1.addr, copy.deepcopy(regs), stackdepth+1) else: print " - Can't follow call, bailing!" return - if GetMnem(item) in ["mov", "lea"]: + if inst.get_canon_mnem() in ["mov", "lea"]: # Rename data for key in names: - if GetOpnd(item, 1) in regs[key] and GetOpType(item, 0) == o_mem: - print " - Found a copy to a memory address for %s, updating: %s" % (names[key], GetDisasm(item)) - MakeName(LocByName(GetOpnd(item, 0).split(":")[-1]), names[key]) + if get_reg_str(inst.Op2) in regs[key] and inst.Op1.type == ida_ua.o_mem: + print " - Found a copy to a memory address for {}, updating: {}".format(names[key], disasm(inst.ea)) + ida_name.set_name(inst.Op1.addr, names[key]) break # Eliminate overwritten registers for key in names: - if GetOpnd(item, 0) in regs[key] and GetOpnd(item, 1) not in regs[key]: - print " - Untracking register %s for %s: %s" % (GetOpnd(item, 0), names[key], GetDisasm(item)) - regs[key].remove(GetOpnd(item, 0)) + if get_reg_str(inst.Op1) in regs[key] and get_reg_str(inst.Op2) not in regs[key]: + print " - Untracking register {} for {}: {}".format(get_reg_str(inst.Op1), names[key], disasm(inst.ea)) + regs[key].remove(get_reg_str(inst.Op1)) # Keep track of registers containing the EFI tables etc - if GetOpnd(item, 1) in regs['im'] and GetOpType(item, 0) == o_reg and GetOpnd(item, 0) not in regs['im']: + if get_reg_str(inst.Op2) in regs['im'] and inst.Op1.type == ida_ua.o_reg and get_reg_str(inst.Op1) not in regs['im']: # A tracked register was copied to a new register, track the new one - print " - Tracking register %s for image handle: %s" % (GetOpnd(item, 0), GetDisasm(item)) - regs['im'].append(GetOpnd(item, 0)) - if GetOpnd(item, 1) in regs['st'] and GetOpType(item, 0) == o_reg and GetOpnd(item, 0) not in regs['st']: + print " - Tracking register {} for image handle: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) + regs['im'].append(get_reg_str(inst.Op1)) + if get_reg_str(inst.Op2) in regs['st'] and inst.Op1.type == ida_ua.o_reg and get_reg_str(inst.Op1) not in regs['st']: # A tracked register was copied to a new register, track the new one - print " - Tracking register %s for system table: %s" % (GetOpnd(item, 0), GetDisasm(item)) - regs['st'].append(GetOpnd(item, 0)) - if GetOpType(item, 1) == o_displ and reg_from_displ(GetOpnd(item, 1)) in regs['st']: + print " - Tracking register {} for system table: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) + regs['st'].append(get_reg_str(inst.Op1)) + if inst.Op2.type == ida_ua.o_displ and ida_idp.get_reg_name(inst.Op2.reg, 8) in regs['st']: # A tracked register was used in a right operand with a displacement - offset = GetOperandValue(item, 1) + offset = inst.Op2.addr if offset == 0x60: - print " - Tracking register %s for boot services table: %s" % (GetOpnd(item, 0), GetDisasm(item)) - regs['bs'].append(GetOpnd(item, 0)) + print " - Tracking register {} for boot services table: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) + regs['bs'].append(get_reg_str(inst.Op1)) elif offset == 0x58: - print " - Tracking register %s for runtime services table: %s" % (GetOpnd(item, 0), GetDisasm(item)) - regs['rs'].append(GetOpnd(item, 0)) - OpStroffEx(item, 1, GetStrucIdByName(SYSTEM_TABLE_STRUCT), 0) + print " - Tracking register {} for runtime services table: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) + regs['rs'].append(get_reg_str(inst.Op1)) + + apply_struct_offset(inst, 1, SYSTEM_TABLE_STRUCT) def update_structs(): @@ -150,15 +167,15 @@ def update_structs(): structs = {SYSTEM_TABLE_NAME: SYSTEM_TABLE_STRUCT, BOOT_SERVICES_NAME: BOOT_SERVICES_STRUCT, RUNTIME_SERVICES_NAME: RUNTIME_SERVICES_STRUCT} for key in structs: - addr = LocByName(key); + addr = get_name_ea(0, key); if addr == BADADDR: print "Couldn't find address for " + key else: - print "Updating structure references for %s (%s)" % (key, structs[key]) - update_struct_offsets(addr, structs[key]) + print "Updating structure references for {} ({})".format(key, structs[key]) + update_struct_offsets(addr, key, structs[key]) -def update_struct_offsets(data_addr, struct_name): +def update_struct_offsets(data_addr, struct_label, struct_name): """ Find xrefs to a struct pointer and change all the offsets to be struct offsets. This is useful for updating references to function pointers in EFI tables. @@ -177,18 +194,19 @@ def update_struct_offsets(data_addr, struct_name): """ # Find all xrefs to this data in the code - xrefs = list(DataRefsTo(data_addr)) - print "Found %d xrefs" % len(xrefs) - + xrefs = list(idautils.DataRefsTo(data_addr)) + print "Found {} xrefs".format(len(xrefs)) + print "Struct name : {}".format(struct_name) # Process xrefs for xref in xrefs: + inst = idautils.DecodeInstruction(xref) # We're only interested in xrefs in code where the left operand is a register, and the right operand is the # memory address of our data structure. - if GetOpType(xref, 0) == o_reg and GetOpType(xref, 1) == o_mem and GetOperandValue(xref, 1) == struct_name: - print "Processing xref from 0x%x: %s" % (xref, GetDisasm(xref)) + if inst.Op1.type == o_reg and inst.Op2.type == o_mem and ida_name.get_name(inst.Op2.addr) == struct_label: + print "Processing xref from 0x{:08x}: {}".format(xref, disasm(xref)) update_struct_offsets_for_xref(xref, struct_name) else: - print "Too hard basket - xref from 0x%x: %s" % (xref, GetDisasm(xref)) + print "Too hard basket - xref from 0x{:08x}: {}".format(xref, disasm(xref)) def update_struct_offsets_for_xref(xref, struct_name): @@ -196,70 +214,76 @@ def update_struct_offsets_for_xref(xref, struct_name): regs['hndl'] = [] regs['ptr'] = [] + inst = idautils.DecodeInstruction(xref) # Are we looking at a handle or a pointer? - if GetMnem(xref) == "mov": - regs['ptr'].append(GetOpnd(xref, 0)) - print " - Tracking pointer register %s at 0x%x: %s" % (regs['ptr'][0], xref, GetDisasm(xref)) - elif GetMnem(xref) == "lea": - regs['hndl'].append(GetOpnd(xref, 0)) - print " - Tracking handle register %s at 0x%x: %s" % (regs['hndl'][0], xref, GetDisasm(xref)) + if inst.get_canon_mnem() == "mov": + regs['ptr'].append(get_reg_str(inst.Op1)) + print " - Tracking pointer register {} at 0x{:08x}: {}".format(regs['ptr'][0], xref, disasm(xref)) + elif inst.get_canon_mnem() == "lea": + regs['hndl'].append(get_reg_str(inst.Op1)) + print " - Tracking handle register {} at 0x{:08x}: {}".format(regs['hndl'][0], xref, disasm(xref)) # Get the rest of the instructions in this function - items = list(FuncItems(xref)) + items = list(idautils.FuncItems(xref)) if len(items): idx = items.index(xref)+1 items = items[idx:] else: - print " - Xref at 0x%x wasn't marked as a function" % xref - cur = next_head(xref, idaapi.cvar.inf.maxEA) + print " - Xref at 0x{:08x} wasn't marked as a function".format(xref) + cur = ida_bytes.next_head(xref, idaapi.cvar.inf.maxEA) while True: if cur not in items: items.append(cur) - print "adding 0x%x: %s" % (cur, GetDisasm(cur)) - if GetMnem(cur) in ['call', 'jmp', 'retn']: + print "adding 0x{:08x}: ".format(cur, disasm(cur)) + + inst = idautils.DecodeInstruction(cur) + if inst.get_canon_mnem() in ['call', 'jmp', 'retn']: break - cur = next_head(cur, idaapi.cvar.inf.maxEA) + cur = ida_bytes.next_head(cur, idaapi.cvar.inf.maxEA) # Iterate through the rest of the instructions in this function looking for tracked registers with a displacement for item in items: regs['nhndl'] = [] regs['nptr'] = [] + inst = idautils.DecodeInstruction(item) # Update any call instruction with a displacement from our register - for op in range(0, 2): - if GetOpType(item, op) == o_displ and reg_from_displ(GetOpnd(item, op)) in regs['ptr']: - print " - Updating operand %d in instruction at 0x%x: %s" % (op, item, GetDisasm(item)) - OpStroffEx(item, op, GetStrucIdByName(struct_name), 0) + for op_no, op in enumerate(inst.Operands): + if op_no == 2: + break + if op.type == o_displ and ida_idp.get_reg_name(op.reg, 8) in regs['ptr']: + print " - Updating operand {} in instruction at 0x{:08x}: {}".format(get_op_str(inst.ea, op_no), item, disasm(item)) + apply_struct_offset(inst, op_no, struct_name) # If we find a mov instruction that copies a tracked register, track the new register - if GetMnem(item) == 'mov': - if GetOpnd(item, 1) in regs['ptr'] and GetOpnd(item, 0) not in regs['ptr']: - print " - Copied tracked register at 0x%x, tracking register %s" % (item, GetOpnd(item, 0)) - regs['ptr'].append(GetOpnd(item, 0)) - regs['nptr'].append(GetOpnd(item, 0)) - if GetOpnd(item, 1) in regs['hndl'] and GetOpnd(item, 0) not in regs['hndl']: - print " - Copied tracked register at 0x%x, tracking register %s" % (item, GetOpnd(item, 0)) - regs['hndl'].append(GetOpnd(item, 0)) - regs['nhndl'].append(GetOpnd(item, 0)) + if inst.get_canon_mnem() == 'mov': + if get_reg_str(inst.Op2) in regs['ptr'] and get_reg_str(inst.Op1) not in regs['ptr']: + print " - Copied tracked register at 0x{:08x}, tracking register {}".format(item, get_reg_str(inst.Op1)) + regs['ptr'].append(get_reg_str(inst.Op1)) + regs['nptr'].append(get_reg_str(inst.Op1)) + if get_reg_str(inst.Op2) in regs['hndl'] and get_reg_str(inst.Op1) not in regs['hndl']: + print " - Copied tracked register at 0x{:08x}, tracking register {}".format(item, get_reg_str(inst.Op1)) + regs['hndl'].append(get_reg_str(inst.Op1)) + regs['nhndl'].append(get_reg_str(inst.Op1)) # If we find a mov instruction that dereferences a handle, track the destination register for reg in regs['hndl']: - if GetOpnd(item, 1) == "[%s]" % reg and GetMnem(item) == 'mov' and GetOpnd(item, 0) not in regs['ptr']: - print " - Found a dereference at 0x%x, tracking register %s" % (item, GetOpnd(item, 0)) - regs['ptr'].append(GetOpnd(item, 0)) - regs['nptr'].append(GetOpnd(item, 0)) + if get_reg_str(inst.Op2) == "[{}]".format(reg) and inst.get_canon_mnem() == 'mov' and get_reg_str(inst.Op1) not in regs['ptr']: + print " - Found a dereference at 0x{:08x}, tracking register {}".format(item, get_reg_str(inst.Op1)) + regs['ptr'].append(get_reg_str(inst.Op1)) + regs['nptr'].append(get_reg_str(inst.Op1)) # If we've found an instruction that overwrites a tracked register, stop tracking it - if GetMnem(item) in ["mov", "lea"] and GetOpType(item, 0) == o_reg: - if GetOpnd(item, 0) in regs['ptr'] and GetOpnd(item, 0) not in regs['nptr']: - print " - Untracking pointer register %s: " % GetOpnd(item, 0) + GetDisasm(item) - regs['ptr'].remove(GetOpnd(item, 0)) - elif GetOpnd(item, 0) in regs['hndl'] and GetOpnd(item, 0) not in regs['nhndl']: - print " - Untracking handle register %s: " % GetOpnd(item, 0) + GetDisasm(item) - regs['hndl'].remove(GetOpnd(item, 0)) + if inst.get_canon_mnem() in ["mov", "lea"] and inst.Op1 == o_reg: + if get_reg_str(inst.Op1) in regs['ptr'] and get_reg_str(inst.Op1) not in regs['nptr']: + print " - Untracking pointer register {}: ".format(get_reg_str(inst.Op1) + disasm(item)) + regs['ptr'].remove(get_reg_str(inst.Op1)) + elif get_reg_str(inst.Op1) in regs['hndl'] and get_reg_str(inst.Op1) not in regs['nhndl']: + print " - Untracking handle register {}: ".format(get_reg_str(inst.Op1) + disasm(item)) + regs['hndl'].remove(get_reg_str(inst.Op1)) # If we hit a call, just bail - if GetMnem(item) == "call": + if inst.get_canon_mnem() == "call": break # If we're not tracking any registers, bail @@ -268,33 +292,42 @@ def update_struct_offsets_for_xref(xref, struct_name): def rename_guids(): + labels = {} # Load GUIDs guids = dict((str(GUID(array=v)),k) for k, v in efiguids.GUIDs.iteritems()) guids.update(dict((v,k) for k, v in local_guids.iteritems())) # Find all the data segments in this binary - for seg in Segments(): - if isData(GetFlags(seg)): - print "Processing data segment at 0x%x" % seg + for seg_addr in idautils.Segments(): + seg = ida_segment.getseg(seg_addr) + if seg.type == ida_segment.SEG_DATA: + print "Processing data segment at 0x{:08x}".format(seg_addr) # Find any GUIDs we know about in the data segment - addr = seg - seg_end = SegEnd(seg) - while addr < seg_end: - d = [Dword(addr), Dword(addr+0x4), Dword(addr+0x8), Dword(addr+0xC)] + cur_addr = seg.start_ea + seg_end = seg.end_ea + while cur_addr < seg_end: + d = [ida_bytes.get_dword(cur_addr), ida_bytes.get_dword(cur_addr+0x4), ida_bytes.get_dword(cur_addr+0x8), ida_bytes.get_dword(cur_addr+0xC)] if (d[0] == 0 and d[1] == 0 and d[2] == 0 and d[3] == 0) or \ (d[0] == 0xFFFFFFFF and d[1] == 0xFFFFFFFF and d[2] == 0xFFFFFFFF and d[3] == 0xFFFFFFFF): pass else: guid = GUID(bytes=struct.pack(" rbx - """ - m = re.match(r'.*\[(.*)[\+\-]', displ) - return m.group(1) +def import_type(type_name): + if ida_typeinf.import_type(ida_typeinf.get_idati(), 0, type_name) == 0xffffffffffffffff: + return False + return True + + +def get_op_str(ea, op_no): + return ida_lines.tag_remove(ida_ua.print_operand(ea, op_no)) -def underscore_to_global(name): - return 'g'+''.join(list(s.capitalize() for s in name.lower().split('_'))) From 720ec9e7a17d1245fc054b290d7e8a0904f6e04e Mon Sep 17 00:00:00 2001 From: Frederic Vachon Date: Tue, 6 Mar 2018 11:27:39 -0500 Subject: [PATCH 02/13] Make efiutils.py PEP 8 compliant --- efiutils.py | 282 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 190 insertions(+), 92 deletions(-) diff --git a/efiutils.py b/efiutils.py index da75690..888fc0d 100644 --- a/efiutils.py +++ b/efiutils.py @@ -27,15 +27,15 @@ MAX_STACK_DEPTH = 1 -IMAGE_HANDLE_NAME = 'gImageHandle' -SYSTEM_TABLE_NAME = 'gSystemTable' -SYSTEM_TABLE_STRUCT = 'EFI_SYSTEM_TABLE' -BOOT_SERVICES_NAME = 'gBootServices' -BOOT_SERVICES_STRUCT = 'EFI_BOOT_SERVICES' -RUNTIME_SERVICES_NAME = 'gRuntimeServices' +IMAGE_HANDLE_NAME = 'gImageHandle' +SYSTEM_TABLE_NAME = 'gSystemTable' +SYSTEM_TABLE_STRUCT = 'EFI_SYSTEM_TABLE' +BOOT_SERVICES_NAME = 'gBootServices' +BOOT_SERVICES_STRUCT = 'EFI_BOOT_SERVICES' +RUNTIME_SERVICES_NAME = 'gRuntimeServices' RUNTIME_SERVICES_STRUCT = 'EFI_RUNTIME_SERVICES' -local_guids = { } +local_guids = {} class GUID: @@ -50,22 +50,26 @@ def __init__(self, default=None, bytes=None, string=None, array=None): elif isinstance(default, list): array = default - if bytes != None: - (self.Data1, self.Data2, self.Data3, d40, d41, d42, d43, d44, d45, d46, d47) = \ - struct.unpack("Q', int(''.join(s[3:]), 16))) - elif array != None: - (self.Data1, self.Data2, self.Data3, self.Data4) = (array[0], array[1], array[2], array[3:]) + self.Data4 = struct.unpack('BBBBBBBB', struct.pack( + '>Q', int(''.join(s[3:]), 16)) + ) + elif array is not None: + (self.Data1, self.Data2, self.Data3, self.Data4) = \ + (array[0], array[1], array[2], array[3:]) def __str__(self): - return "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" % tuple(self.array()) + return "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" % \ + tuple(self.array()) def bytes(self): return self.bytes @@ -75,7 +79,8 @@ def string(self): def array(self): d = self.Data4 - return [self.Data1, self.Data2, self.Data3, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]] + return [self.Data1, self.Data2, self.Data3, d[0], d[1], d[2], + d[3], d[4], d[5], d[6], d[7]] def go(): @@ -86,11 +91,14 @@ def go(): def rename_tables(): """ - Look at the entry point function and find where the SystemTable parameter is stored, along with the - RuntimeServices and BootServices tables. Rename the globals these are stored in. - The entry point for an EFI executable is called like this: - EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE * SystemTable) + Look at the entry point function and find where the SystemTable parameter + is stored, along with the RuntimeServices and BootServices tables. Rename + the globals these are stored in. + + The entry point for an EFI executable is called like this: EFI_STATUS + EFIAPI UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE * + SystemTable) ImageHandle is passed in rcx, SystemTable is passed in rdx. """ @@ -106,10 +114,15 @@ def rename_tables(): def rename_tables_internal(ea, regs, stackdepth=0): - names = {'im': IMAGE_HANDLE_NAME, 'st': SYSTEM_TABLE_NAME, 'bs': BOOT_SERVICES_NAME, 'rs': RUNTIME_SERVICES_NAME} + names = { + 'im': IMAGE_HANDLE_NAME, + 'st': SYSTEM_TABLE_NAME, + 'bs': BOOT_SERVICES_NAME, + 'rs': RUNTIME_SERVICES_NAME + } print "Processing function at 0x{:08x}".format(ea) - + for item in idautils.FuncItems(ea): inst = idautils.DecodeInstruction(item) @@ -119,9 +132,13 @@ def rename_tables_internal(ea, regs, stackdepth=0): print " - Hit stack depth limit, bailing!" return else: - if inst.Op1.type in [ida_ua.o_imm, ida_ua.o_far, ida_ua.o_near]: - # TODO : Currently assuming that the registry will be inaffected by calls - rename_tables_internal(inst.Op1.addr, copy.deepcopy(regs), stackdepth+1) + target_types = [ida_ua.o_imm, ida_ua.o_far, ida_ua.o_near] + if inst.Op1.type in target_types: + # TODO : Currently assuming that the registry will be + # inaffected by calls + rename_tables_internal( + inst.Op1.addr, copy.deepcopy(regs), stackdepth+1 + ) else: print " - Can't follow call, bailing!" return @@ -129,34 +146,53 @@ def rename_tables_internal(ea, regs, stackdepth=0): if inst.get_canon_mnem() in ["mov", "lea"]: # Rename data for key in names: - if get_reg_str(inst.Op2) in regs[key] and inst.Op1.type == ida_ua.o_mem: - print " - Found a copy to a memory address for {}, updating: {}".format(names[key], disasm(inst.ea)) + if get_reg_str(inst.Op2) in regs[key] and \ + inst.Op1.type == ida_ua.o_mem: + print(" - Found a copy to a memory address for {}, " + + "updating: {}").format(names[key], disasm(inst.ea)) ida_name.set_name(inst.Op1.addr, names[key]) break # Eliminate overwritten registers for key in names: - if get_reg_str(inst.Op1) in regs[key] and get_reg_str(inst.Op2) not in regs[key]: - print " - Untracking register {} for {}: {}".format(get_reg_str(inst.Op1), names[key], disasm(inst.ea)) + if get_reg_str(inst.Op1) in regs[key] and \ + get_reg_str(inst.Op2) not in regs[key]: + print " - Untracking register {} for {}: {}".format( + get_reg_str(inst.Op1), names[key], disasm(inst.ea) + ) regs[key].remove(get_reg_str(inst.Op1)) # Keep track of registers containing the EFI tables etc - if get_reg_str(inst.Op2) in regs['im'] and inst.Op1.type == ida_ua.o_reg and get_reg_str(inst.Op1) not in regs['im']: - # A tracked register was copied to a new register, track the new one - print " - Tracking register {} for image handle: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) + if get_reg_str(inst.Op2) in regs['im'] and \ + inst.Op1.type == ida_ua.o_reg and \ + get_reg_str(inst.Op1) not in regs['im']: + # A tracked register was copied to a new register, + # track the new one + print " - Tracking register {} for image handle: {}".format( + get_reg_str(inst.Op1), disasm(inst.ea) + ) regs['im'].append(get_reg_str(inst.Op1)) - if get_reg_str(inst.Op2) in regs['st'] and inst.Op1.type == ida_ua.o_reg and get_reg_str(inst.Op1) not in regs['st']: - # A tracked register was copied to a new register, track the new one - print " - Tracking register {} for system table: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) + if get_reg_str(inst.Op2) in regs['st'] and \ + inst.Op1.type == ida_ua.o_reg and \ + get_reg_str(inst.Op1) not in regs['st']: + # A tracked register was copied to a new register, + # track the new one + print " - Tracking register {} for system table: {}".format( + get_reg_str(inst.Op1), disasm(inst.ea) + ) regs['st'].append(get_reg_str(inst.Op1)) - if inst.Op2.type == ida_ua.o_displ and ida_idp.get_reg_name(inst.Op2.reg, 8) in regs['st']: - # A tracked register was used in a right operand with a displacement + if inst.Op2.type == ida_ua.o_displ and \ + ida_idp.get_reg_name(inst.Op2.reg, 8) in regs['st']: + # A tracked register was used in a right operand with a + # displacement offset = inst.Op2.addr if offset == 0x60: - print " - Tracking register {} for boot services table: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) + print " - Tracking register {} for boot services " + \ + "table: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) regs['bs'].append(get_reg_str(inst.Op1)) elif offset == 0x58: - print " - Tracking register {} for runtime services table: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) + print " - Tracking register {} for runtime services " + \ + "table: {}".format(get_reg_str(inst.Op1), disasm(inst.ea)) regs['rs'].append(get_reg_str(inst.Op1)) apply_struct_offset(inst, 1, SYSTEM_TABLE_STRUCT) @@ -164,21 +200,27 @@ def rename_tables_internal(ea, regs, stackdepth=0): def update_structs(): """Update xrefs to the major EFI tables to be struct offsets.""" - structs = {SYSTEM_TABLE_NAME: SYSTEM_TABLE_STRUCT, BOOT_SERVICES_NAME: BOOT_SERVICES_STRUCT, - RUNTIME_SERVICES_NAME: RUNTIME_SERVICES_STRUCT} + structs = { + SYSTEM_TABLE_NAME: SYSTEM_TABLE_STRUCT, + BOOT_SERVICES_NAME: BOOT_SERVICES_STRUCT, + RUNTIME_SERVICES_NAME: RUNTIME_SERVICES_STRUCT + } for key in structs: - addr = get_name_ea(0, key); + addr = get_name_ea(0, key) if addr == BADADDR: print "Couldn't find address for " + key else: - print "Updating structure references for {} ({})".format(key, structs[key]) + print "Updating structure references for {} ({})".format( + key, structs[key] + ) update_struct_offsets(addr, key, structs[key]) def update_struct_offsets(data_addr, struct_label, struct_name): """ - Find xrefs to a struct pointer and change all the offsets to be struct offsets. This is useful for updating - references to function pointers in EFI tables. + Find xrefs to a struct pointer and change all the offsets to be struct + offsets. This is useful for updating references to function pointers in + EFI tables. For example: mov rax, cs:qword_whatever @@ -200,13 +242,19 @@ def update_struct_offsets(data_addr, struct_label, struct_name): # Process xrefs for xref in xrefs: inst = idautils.DecodeInstruction(xref) - # We're only interested in xrefs in code where the left operand is a register, and the right operand is the - # memory address of our data structure. - if inst.Op1.type == o_reg and inst.Op2.type == o_mem and ida_name.get_name(inst.Op2.addr) == struct_label: - print "Processing xref from 0x{:08x}: {}".format(xref, disasm(xref)) + # We're only interested in xrefs in code where the left operand is a + # register, and the right operand is the memory address of our data + # structure. + if inst.Op1.type == o_reg and inst.Op2.type == o_mem and \ + ida_name.get_name(inst.Op2.addr) == struct_label: + print "Processing xref from 0x{:08x}: {}".format( + xref, disasm(xref) + ) update_struct_offsets_for_xref(xref, struct_name) else: - print "Too hard basket - xref from 0x{:08x}: {}".format(xref, disasm(xref)) + print "Too hard basket - xref from 0x{:08x}: {}".format( + xref, disasm(xref) + ) def update_struct_offsets_for_xref(xref, struct_name): @@ -218,10 +266,14 @@ def update_struct_offsets_for_xref(xref, struct_name): # Are we looking at a handle or a pointer? if inst.get_canon_mnem() == "mov": regs['ptr'].append(get_reg_str(inst.Op1)) - print " - Tracking pointer register {} at 0x{:08x}: {}".format(regs['ptr'][0], xref, disasm(xref)) + print " - Tracking pointer register {} at 0x{:08x}: {}".format( + regs['ptr'][0], xref, disasm(xref) + ) elif inst.get_canon_mnem() == "lea": regs['hndl'].append(get_reg_str(inst.Op1)) - print " - Tracking handle register {} at 0x{:08x}: {}".format(regs['hndl'][0], xref, disasm(xref)) + print " - Tracking handle register {} at 0x{:08x}: {}".format( + regs['hndl'][0], xref, disasm(xref) + ) # Get the rest of the instructions in this function items = list(idautils.FuncItems(xref)) @@ -241,7 +293,8 @@ def update_struct_offsets_for_xref(xref, struct_name): break cur = ida_bytes.next_head(cur, idaapi.cvar.inf.maxEA) - # Iterate through the rest of the instructions in this function looking for tracked registers with a displacement + # Iterate through the rest of the instructions in this function looking + # for tracked registers with a displacement for item in items: regs['nhndl'] = [] regs['nptr'] = [] @@ -251,36 +304,62 @@ def update_struct_offsets_for_xref(xref, struct_name): for op_no, op in enumerate(inst.Operands): if op_no == 2: break - if op.type == o_displ and ida_idp.get_reg_name(op.reg, 8) in regs['ptr']: - print " - Updating operand {} in instruction at 0x{:08x}: {}".format(get_op_str(inst.ea, op_no), item, disasm(item)) + if op.type == o_displ and \ + ida_idp.get_reg_name(op.reg, 8) in regs['ptr']: + print(" - Updating operand {} in instruction at " + + "0x{:08x}: {}").format( + get_op_str(inst.ea, op_no), item, disasm(item) + ) apply_struct_offset(inst, op_no, struct_name) - # If we find a mov instruction that copies a tracked register, track the new register + # If we find a mov instruction that copies a tracked register, track + # the new register if inst.get_canon_mnem() == 'mov': - if get_reg_str(inst.Op2) in regs['ptr'] and get_reg_str(inst.Op1) not in regs['ptr']: - print " - Copied tracked register at 0x{:08x}, tracking register {}".format(item, get_reg_str(inst.Op1)) + if get_reg_str(inst.Op2) in regs['ptr'] and \ + get_reg_str(inst.Op1) not in regs['ptr']: + print(" - Copied tracked register at 0x{:08x}, " + + "tracking register {}").format( + item, get_reg_str(inst.Op1) + ) regs['ptr'].append(get_reg_str(inst.Op1)) regs['nptr'].append(get_reg_str(inst.Op1)) - if get_reg_str(inst.Op2) in regs['hndl'] and get_reg_str(inst.Op1) not in regs['hndl']: - print " - Copied tracked register at 0x{:08x}, tracking register {}".format(item, get_reg_str(inst.Op1)) + if get_reg_str(inst.Op2) in regs['hndl'] and \ + get_reg_str(inst.Op1) not in regs['hndl']: + print(" - Copied tracked register at 0x{:08x}, " + + "tracking register {}").format( + item, get_reg_str(inst.Op1) + ) regs['hndl'].append(get_reg_str(inst.Op1)) regs['nhndl'].append(get_reg_str(inst.Op1)) - # If we find a mov instruction that dereferences a handle, track the destination register + # If we find a mov instruction that dereferences a handle, track the + # destination register for reg in regs['hndl']: - if get_reg_str(inst.Op2) == "[{}]".format(reg) and inst.get_canon_mnem() == 'mov' and get_reg_str(inst.Op1) not in regs['ptr']: - print " - Found a dereference at 0x{:08x}, tracking register {}".format(item, get_reg_str(inst.Op1)) + if get_reg_str(inst.Op2) == "[{}]".format(reg) and \ + inst.get_canon_mnem() == 'mov' and \ + get_reg_str(inst.Op1) not in regs['ptr']: + print(" - Found a dereference at 0x{:08x}, " + + "tracking register {}").format( + item, get_reg_str(inst.Op1) + ) regs['ptr'].append(get_reg_str(inst.Op1)) regs['nptr'].append(get_reg_str(inst.Op1)) - # If we've found an instruction that overwrites a tracked register, stop tracking it + # If we've found an instruction that overwrites a tracked register, + # stop tracking it if inst.get_canon_mnem() in ["mov", "lea"] and inst.Op1 == o_reg: - if get_reg_str(inst.Op1) in regs['ptr'] and get_reg_str(inst.Op1) not in regs['nptr']: - print " - Untracking pointer register {}: ".format(get_reg_str(inst.Op1) + disasm(item)) + if get_reg_str(inst.Op1) in regs['ptr']: + if get_reg_str(inst.Op1) not in regs['nptr']: + print " - Untracking pointer register {}: ".format( + get_reg_str(inst.Op1) + disasm(item) + ) regs['ptr'].remove(get_reg_str(inst.Op1)) - elif get_reg_str(inst.Op1) in regs['hndl'] and get_reg_str(inst.Op1) not in regs['nhndl']: - print " - Untracking handle register {}: ".format(get_reg_str(inst.Op1) + disasm(item)) - regs['hndl'].remove(get_reg_str(inst.Op1)) + elif get_reg_str(inst.Op1) in regs['hndl']: + if get_reg_str(inst.Op1) not in regs['nhndl']: + print " - Untracking handle register {}: ".format( + get_reg_str(inst.Op1) + disasm(item) + ) + regs['hndl'].remove(get_reg_str(inst.Op1)) # If we hit a call, just bail if inst.get_canon_mnem() == "call": @@ -294,8 +373,10 @@ def update_struct_offsets_for_xref(xref, struct_name): def rename_guids(): labels = {} # Load GUIDs - guids = dict((str(GUID(array=v)),k) for k, v in efiguids.GUIDs.iteritems()) - guids.update(dict((v,k) for k, v in local_guids.iteritems())) + guids = dict( + (str(GUID(array=v)), k) for k, v in efiguids.GUIDs.iteritems() + ) + guids.update(dict((v, k) for k, v in local_guids.iteritems())) # Find all the data segments in this binary for seg_addr in idautils.Segments(): @@ -307,23 +388,34 @@ def rename_guids(): cur_addr = seg.start_ea seg_end = seg.end_ea while cur_addr < seg_end: - d = [ida_bytes.get_dword(cur_addr), ida_bytes.get_dword(cur_addr+0x4), ida_bytes.get_dword(cur_addr+0x8), ida_bytes.get_dword(cur_addr+0xC)] + d = [ida_bytes.get_dword(cur_addr), + ida_bytes.get_dword(cur_addr+0x4), + ida_bytes.get_dword(cur_addr+0x8), + ida_bytes.get_dword(cur_addr+0xC)] if (d[0] == 0 and d[1] == 0 and d[2] == 0 and d[3] == 0) or \ - (d[0] == 0xFFFFFFFF and d[1] == 0xFFFFFFFF and d[2] == 0xFFFFFFFF and d[3] == 0xFFFFFFFF): - pass + ( + d[0] == 0xFFFFFFFF and d[1] == 0xFFFFFFFF and + d[2] == 0xFFFFFFFF and d[3] == 0xFFFFFFFF + ): + pass else: - guid = GUID(bytes=struct.pack(" Date: Mon, 5 Mar 2018 16:52:46 -0500 Subject: [PATCH 03/13] Resolve interfaces passed as LocateProtocol parameter --- efiutils.py | 270 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 196 insertions(+), 74 deletions(-) diff --git a/efiutils.py b/efiutils.py index 888fc0d..1f6c1bb 100644 --- a/efiutils.py +++ b/efiutils.py @@ -21,6 +21,7 @@ import ida_pro import ida_segment import ida_struct +import ida_typeinf import ida_ua import efiguids @@ -86,7 +87,8 @@ def array(self): def go(): rename_tables() update_structs() - rename_guids() + guid_labels = rename_guids() + update_protocols(guid_labels) def rename_tables(): @@ -216,7 +218,7 @@ def update_structs(): update_struct_offsets(addr, key, structs[key]) -def update_struct_offsets(data_addr, struct_label, struct_name): +def update_struct_offsets(data_addr, struct_label, struct_name, pad=0): """ Find xrefs to a struct pointer and change all the offsets to be struct offsets. This is useful for updating references to function pointers in @@ -237,8 +239,8 @@ def update_struct_offsets(data_addr, struct_label, struct_name): # Find all xrefs to this data in the code xrefs = list(idautils.DataRefsTo(data_addr)) - print "Found {} xrefs".format(len(xrefs)) - print "Struct name : {}".format(struct_name) + print "{}Found {} xrefs".format(' ' * pad, len(xrefs)) + print "{}Struct name : {}".format(' ' * pad, struct_name) # Process xrefs for xref in xrefs: inst = idautils.DecodeInstruction(xref) @@ -247,17 +249,17 @@ def update_struct_offsets(data_addr, struct_label, struct_name): # structure. if inst.Op1.type == o_reg and inst.Op2.type == o_mem and \ ida_name.get_name(inst.Op2.addr) == struct_label: - print "Processing xref from 0x{:08x}: {}".format( - xref, disasm(xref) + print "{}Processing xref from 0x{:08x}: {}".format( + ' ' * pad, xref, disasm(xref) ) - update_struct_offsets_for_xref(xref, struct_name) + update_struct_offsets_for_xref(xref, struct_name, pad) else: - print "Too hard basket - xref from 0x{:08x}: {}".format( - xref, disasm(xref) + print "{}Too hard basket - xref from 0x{:08x}: {}".format( + ' ' * pad, xref, disasm(xref) ) -def update_struct_offsets_for_xref(xref, struct_name): +def update_struct_offsets_for_xref(xref, struct_name, pad=0): regs = {} regs['hndl'] = [] regs['ptr'] = [] @@ -266,32 +268,16 @@ def update_struct_offsets_for_xref(xref, struct_name): # Are we looking at a handle or a pointer? if inst.get_canon_mnem() == "mov": regs['ptr'].append(get_reg_str(inst.Op1)) - print " - Tracking pointer register {} at 0x{:08x}: {}".format( - regs['ptr'][0], xref, disasm(xref) + print "{} - Tracking pointer register {} at 0x{:08x}: {}".format( + ' ' * pad, regs['ptr'][0], xref, disasm(xref) ) elif inst.get_canon_mnem() == "lea": regs['hndl'].append(get_reg_str(inst.Op1)) - print " - Tracking handle register {} at 0x{:08x}: {}".format( - regs['hndl'][0], xref, disasm(xref) + print "{} - Tracking handle register {} at 0x{:08x}: {}".format( + ' ' * pad, regs['hndl'][0], xref, disasm(xref) ) - # Get the rest of the instructions in this function - items = list(idautils.FuncItems(xref)) - if len(items): - idx = items.index(xref)+1 - items = items[idx:] - else: - print " - Xref at 0x{:08x} wasn't marked as a function".format(xref) - cur = ida_bytes.next_head(xref, idaapi.cvar.inf.maxEA) - while True: - if cur not in items: - items.append(cur) - print "adding 0x{:08x}: ".format(cur, disasm(cur)) - - inst = idautils.DecodeInstruction(cur) - if inst.get_canon_mnem() in ['call', 'jmp', 'retn']: - break - cur = ida_bytes.next_head(cur, idaapi.cvar.inf.maxEA) + items = get_func_items_from_xref(xref) # Iterate through the rest of the instructions in this function looking # for tracked registers with a displacement @@ -306,9 +292,10 @@ def update_struct_offsets_for_xref(xref, struct_name): break if op.type == o_displ and \ ida_idp.get_reg_name(op.reg, 8) in regs['ptr']: - print(" - Updating operand {} in instruction at " + + print("{} - Updating operand {} in instruction at " + "0x{:08x}: {}").format( - get_op_str(inst.ea, op_no), item, disasm(item) + ' ' * pad, get_op_str(inst.ea, op_no), + item, disasm(item) ) apply_struct_offset(inst, op_no, struct_name) @@ -317,17 +304,17 @@ def update_struct_offsets_for_xref(xref, struct_name): if inst.get_canon_mnem() == 'mov': if get_reg_str(inst.Op2) in regs['ptr'] and \ get_reg_str(inst.Op1) not in regs['ptr']: - print(" - Copied tracked register at 0x{:08x}, " + + print("{} - Copied tracked register at 0x{:08x}, " + "tracking register {}").format( - item, get_reg_str(inst.Op1) + ' ' * pad, item, get_reg_str(inst.Op1) ) regs['ptr'].append(get_reg_str(inst.Op1)) regs['nptr'].append(get_reg_str(inst.Op1)) if get_reg_str(inst.Op2) in regs['hndl'] and \ get_reg_str(inst.Op1) not in regs['hndl']: - print(" - Copied tracked register at 0x{:08x}, " + + print("{} - Copied tracked register at 0x{:08x}, " + "tracking register {}").format( - item, get_reg_str(inst.Op1) + ' ' * pad, item, get_reg_str(inst.Op1) ) regs['hndl'].append(get_reg_str(inst.Op1)) regs['nhndl'].append(get_reg_str(inst.Op1)) @@ -338,9 +325,9 @@ def update_struct_offsets_for_xref(xref, struct_name): if get_reg_str(inst.Op2) == "[{}]".format(reg) and \ inst.get_canon_mnem() == 'mov' and \ get_reg_str(inst.Op1) not in regs['ptr']: - print(" - Found a dereference at 0x{:08x}, " + + print("{} - Found a dereference at 0x{:08x}, " + "tracking register {}").format( - item, get_reg_str(inst.Op1) + ' ' * pad, item, get_reg_str(inst.Op1) ) regs['ptr'].append(get_reg_str(inst.Op1)) regs['nptr'].append(get_reg_str(inst.Op1)) @@ -350,14 +337,14 @@ def update_struct_offsets_for_xref(xref, struct_name): if inst.get_canon_mnem() in ["mov", "lea"] and inst.Op1 == o_reg: if get_reg_str(inst.Op1) in regs['ptr']: if get_reg_str(inst.Op1) not in regs['nptr']: - print " - Untracking pointer register {}: ".format( - get_reg_str(inst.Op1) + disasm(item) + print "{} - Untracking pointer register {}: ".format( + ' ' * pad, get_reg_str(inst.Op1) + disasm(item) ) regs['ptr'].remove(get_reg_str(inst.Op1)) elif get_reg_str(inst.Op1) in regs['hndl']: if get_reg_str(inst.Op1) not in regs['nhndl']: - print " - Untracking handle register {}: ".format( - get_reg_str(inst.Op1) + disasm(item) + print "{} - Untracking handle register {}: ".format( + ' ' * pad, get_reg_str(inst.Op1) + disasm(item) ) regs['hndl'].remove(get_reg_str(inst.Op1)) @@ -407,45 +394,129 @@ def rename_guids(): print " - Found GUID {} ({}) at 0x{:08x}".format( gstr, guids[gstr], cur_addr ) - struct_label = underscore_to_global(guids[gstr]) - if labels.get(struct_label) is None: - ida_name.set_name(cur_addr, struct_label) - labels[struct_label] = 1 - else: - labels[struct_label] += 1 - ida_name.set_name(cur_addr, "{}_{}".format( - struct_label, labels[struct_label]) - ) - + struct_label = get_next_unused_label( + underscore_to_global(guids[gstr]) + ) + ida_name.set_name(cur_addr, struct_label) + labels[struct_label] = (cur_addr, guids[gstr]) cur_addr += 0x08 else: print "Skipping non-data segment at 0x{:08x}".format(seg_addr) + return labels + -def update_protocols(): - pass +def update_protocols(guid_labels): + update_locate_protocols_interfaces(guid_labels) -def update_protocol(guid_addr, protocol): - pass - # # Find xrefs to this GUID - # xrefs = list(DataRefsTo(guid_addr)) - # print "Found %d xrefs to GUID %s" % \ - # (len(xrefs), str(guid_at_addr(guid_addr))) +def update_locate_protocols_interfaces(guid_labels): + """ + Find the interface parameter for LocateProtocol function. + Rename the Interface global variable. + Update struct offsets for the interface xref + + LocateProtocol call example: + ---- + mov rax, cs:gBootServices + lea r8, Interface ; Interface + lea rcx, gEfiHiiDatabaseProtocolGuid ; Protocol + xor edx, edx ; Registration + call [rax+EFI_BOOT_SERVICES.LocateProtocol + ---- + """ + print "Populating LocateProtocol interfaces" + for guid_label, values in guid_labels.iteritems(): + addr, guid_struct = values + xrefs = list(idautils.DataRefsTo(addr)) + for xref in xrefs: + if is_locate_protocol_param(xref): + interface_addr = get_interface_param_addr(xref) + if interface_addr is not None: + protocol_struct = \ + protocol_struct_from_guid_struct(guid_struct) + protocol_label = get_next_unused_label( + underscore_to_global(protocol_struct) + ) + ida_name.set_name(interface_addr, protocol_label) + print(" - Found interface of type '{}' at 0x{:08x} " + + "Updating offsets...").format( + protocol_struct, interface_addr + ) + update_struct_offsets( + interface_addr, protocol_label, protocol_struct, 6 + ) - # # Process xrefs - # for xref in xrefs: - # # We're only interested in xrefs in code where the left operand is a - # # register, and the right operand is the - # # memory address of our GUID. - # if GetOpType(xref, 0) == o_reg and \ - # GetOpType(xref, 1) == o_mem and \ - # GetOperandValue(xref, 1) == struct_name: - # print "Processing xref from 0x%x: %s" % (xref, disasm(xref)) - # update_struct_offsets_for_xref(xref, struct_name) - # else: - # print "Too hard basket - xref from 0x%x: %s" % \ - # (xref, disasm(xref)) + +def is_locate_protocol_param(xref): + """ + Returns True if the xref is the 'Protocol' parameter of this function : + typedef EFI_STATUS LocateProtocol ( + IN EFI_GUID *Protocol, + IN VOID *Registration OPTIONAL, + OUT VOID **Interface + ); + """ + inst = idautils.DecodeInstruction(xref) + + # Must be 'lea rcx, gSmtGuid' + if inst.get_canon_mnem() != 'lea' or inst.Op1.type != o_reg or \ + inst.Op1.reg != ida_idp.str2reg('rcx'): + return False + + # Look up to 5 instructions ahead to find: + # call [rax+EFI_BOOT_SERVICES.LocateProtocol] + for addr in get_func_items_from_xref(xref)[:5]: + inst = idautils.DecodeInstruction(addr) + if inst.get_canon_mnem() == 'call': + if inst.Op1.type == o_displ and \ + get_operand_struct_name(inst, 0) == 'EFI_BOOT_SERVICES' and \ + inst.Op1.addr == 0x140: + return True + else: + # Bail out if we hit a call that is not LocateProtocol + break + + return False + + +def get_interface_param_addr(xref): + """ + Find the interface address looking for this instruction : + lea r8, Interface + """ + # Check up to 3 instructions before + next_addr = xref + for i in range(3): + inst = idautils.DecodePreviousInstruction(next_addr) + if inst.get_canon_mnem() == 'call': + break + elif is_lea_r8_mem_inst(inst): + return inst.Op2.addr + next_addr = inst.ea + + # Check up to 3 instructions forward + for addr in get_func_items_from_xref(xref)[:3]: + inst = idautils.DecodeInstruction(addr) + if inst.get_canon_mnem() == 'call': + break + elif is_lea_r8_mem_inst(inst): + return inst.Op2.addr + + return None + + +def is_lea_r8_mem_inst(inst): + if inst.get_canon_mnem() == 'lea': + if inst.Op1.type == o_reg: + if inst.Op1.reg == ida_idp.str2reg('r8'): + if inst.Op2.type == o_mem: + return True + return False + + +def protocol_struct_from_guid_struct(struct_name): + return '_'.join(struct_name.split('_')[:-1]) def get_reg_str(operand): @@ -460,12 +531,23 @@ def disasm(ea): def apply_struct_offset(inst, operand_no, struct_name): + if not import_type(struct_name): + print "ERROR: Can't import '{}'".format(struct_name) + return path_len = 1 path = ida_pro.tid_array(path_len) path[0] = ida_struct.get_struc_id(struct_name) ida_bytes.op_stroff(inst, operand_no, path.cast(), path_len, 0) +def get_operand_struct_name(inst, op_no): + path_len = 1 + path = ida_pro.tid_array(path_len) + if ida_bytes.get_stroff_path(path.cast(), None, inst.ea, op_no): + return ida_struct.get_struc_name(path[0]) + return '' + + def find_struct_refs(): ida_bytes.is_stroff(ida_bytes.get_full_flags( ida_kernwin.get_screen_ea(), 0 @@ -485,5 +567,45 @@ def underscore_to_global(name): return 'g'+''.join(list(s.capitalize() for s in name.lower().split('_'))) +def import_type(type_name): + if ida_typeinf.import_type(ida_typeinf.get_idati(), 0, type_name) == \ + 0xffffffffffffffff: + return False + return True + + def get_op_str(ea, op_no): return ida_lines.tag_remove(ida_ua.print_operand(ea, op_no)) + + +def get_next_unused_label(label): + i = 2 + while True: + if ida_name.get_name_ea(0, label) == 0xffffffffffffffff: + return label + label = "{}_{}".format(label, i) + i += 1 + + +def get_func_items_from_xref(xref): + """ + Returns the rest of the instruction from a given xref + """ + items = list(idautils.FuncItems(xref)) + if len(items): + idx = items.index(xref)+1 + items = items[idx:] + else: + print " - Xref at 0x{:08x} wasn't marked as a function".format(xref) + cur = ida_bytes.next_head(xref, idaapi.cvar.inf.maxEA) + while True: + if cur not in items: + items.append(cur) + print "adding 0x{:08x}: ".format(cur, disasm(cur)) + + inst = idautils.DecodeInstruction(cur) + if inst.get_canon_mnem() in ['call', 'jmp', 'retn']: + break + cur = ida_bytes.next_head(cur, idaapi.cvar.inf.maxEA) + + return items From cde7c142c893a2ce7d34c2ff6f0cb4c3b3fc2c45 Mon Sep 17 00:00:00 2001 From: Frederic Vachon Date: Thu, 10 May 2018 11:58:35 -0400 Subject: [PATCH 04/13] Fix imports and namespaces --- efiutils.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/efiutils.py b/efiutils.py index 1f6c1bb..f4dc1fa 100644 --- a/efiutils.py +++ b/efiutils.py @@ -12,6 +12,7 @@ import idaapi import idautils +import ida_idaapi import ida_bytes import ida_entry import ida_idp @@ -208,8 +209,8 @@ def update_structs(): RUNTIME_SERVICES_NAME: RUNTIME_SERVICES_STRUCT } for key in structs: - addr = get_name_ea(0, key) - if addr == BADADDR: + addr = ida_name.get_name_ea(0, key) + if addr == ida_idaapi.BADADDR: print "Couldn't find address for " + key else: print "Updating structure references for {} ({})".format( @@ -247,7 +248,8 @@ def update_struct_offsets(data_addr, struct_label, struct_name, pad=0): # We're only interested in xrefs in code where the left operand is a # register, and the right operand is the memory address of our data # structure. - if inst.Op1.type == o_reg and inst.Op2.type == o_mem and \ + if inst.Op1.type == ida_ua.o_reg and \ + inst.Op2.type == ida_ua.o_mem and \ ida_name.get_name(inst.Op2.addr) == struct_label: print "{}Processing xref from 0x{:08x}: {}".format( ' ' * pad, xref, disasm(xref) @@ -290,7 +292,7 @@ def update_struct_offsets_for_xref(xref, struct_name, pad=0): for op_no, op in enumerate(inst.Operands): if op_no == 2: break - if op.type == o_displ and \ + if op.type == ida_ua.o_displ and \ ida_idp.get_reg_name(op.reg, 8) in regs['ptr']: print("{} - Updating operand {} in instruction at " + "0x{:08x}: {}").format( @@ -334,7 +336,8 @@ def update_struct_offsets_for_xref(xref, struct_name, pad=0): # If we've found an instruction that overwrites a tracked register, # stop tracking it - if inst.get_canon_mnem() in ["mov", "lea"] and inst.Op1 == o_reg: + if inst.get_canon_mnem() in ["mov", "lea"] and \ + inst.Op1 == ida_ua.o_reg: if get_reg_str(inst.Op1) in regs['ptr']: if get_reg_str(inst.Op1) not in regs['nptr']: print "{} - Untracking pointer register {}: ".format( @@ -460,7 +463,7 @@ def is_locate_protocol_param(xref): inst = idautils.DecodeInstruction(xref) # Must be 'lea rcx, gSmtGuid' - if inst.get_canon_mnem() != 'lea' or inst.Op1.type != o_reg or \ + if inst.get_canon_mnem() != 'lea' or inst.Op1.type != ida_ua.o_reg or \ inst.Op1.reg != ida_idp.str2reg('rcx'): return False @@ -469,7 +472,7 @@ def is_locate_protocol_param(xref): for addr in get_func_items_from_xref(xref)[:5]: inst = idautils.DecodeInstruction(addr) if inst.get_canon_mnem() == 'call': - if inst.Op1.type == o_displ and \ + if inst.Op1.type == ida_ua.o_displ and \ get_operand_struct_name(inst, 0) == 'EFI_BOOT_SERVICES' and \ inst.Op1.addr == 0x140: return True @@ -508,9 +511,9 @@ def get_interface_param_addr(xref): def is_lea_r8_mem_inst(inst): if inst.get_canon_mnem() == 'lea': - if inst.Op1.type == o_reg: + if inst.Op1.type == ida_ua.o_reg: if inst.Op1.reg == ida_idp.str2reg('r8'): - if inst.Op2.type == o_mem: + if inst.Op2.type == ida_ua.o_mem: return True return False From 5679e622c0f713a5bc2eeed169a7842572a0ffab Mon Sep 17 00:00:00 2001 From: Frederic Vachon Date: Thu, 10 May 2018 12:24:24 -0400 Subject: [PATCH 05/13] Update README --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7f66b99..722f26c 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ Becomes: Finds GUIDs in data segments and renames them. 470 protocol GUIDs were pulled out of the TianoCore source, and proprietary Apple (and other vendor) GUIDs will be added as they are encountered. +### `update_protocols()` + +Finds protocol's interfaces resolved using the `LocateProtocol` function and applies the struct offsets on calls made to its references. + ### `go()` Convenience method that does all of the above. @@ -65,13 +69,11 @@ Convenience method that does all of the above. 1. Load up your EFI binary in IDA Pro -2. Import `behemoth.h` to define the necessary data structures - -3. Add the structures from local types to your IDB +2. Run `efiguids.py` to add it to python's path (or do this by some other method) -4. Run `efiutils.py` to add it to python's path (or do this by some other method) +3. Run `efiutils.py` to add it to python's path (or do this by some other method) -5. Have a look at the code/docstrings, but probably: +4. Have a look at the code/docstrings, but probably: import efiutils; efiutils.go() From 567fb2fe023c6d3ed0891fd39b791585722b3158 Mon Sep 17 00:00:00 2001 From: Frederic Vachon Date: Thu, 10 May 2018 12:25:33 -0400 Subject: [PATCH 06/13] Format README --- README.md | 50 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 722f26c..32d343b 100644 --- a/README.md +++ b/README.md @@ -8,39 +8,52 @@ This package contains the following files: 2. `efiguids.py` - A collection of known GUIDs for EFI protocols -3. `efiguids_ami.py` - A collection of known GUIDs for protocols used in the AMI BIOS +3. `efiguids_ami.py` - A collection of known GUIDs for protocols used in the AMI +BIOS -4. `behemoth.h` - A giant header containing a collection of type and structure definitions for EFI +4. `behemoth.h` - A giant header containing a collection of type and structure +definitions for EFI -5. `structs.idc` - An IDC script containing some struct definitions (superseded by `behemoth.h`) +5. `structs.idc` - An IDC script containing some struct definitions (superseded +by `behemoth.h`) 6. `te_image.bt` - An 010 Editor template for TE binary images 7. `te_loader.py` - An IDA Pro loader script for TE binary images -This is my first attempt at IDA scripting, so please forgive me and let me know if I've reinvented wheels/done anything silly. +This is my first attempt at IDA scripting, so please forgive me and let me know +if I've reinvented wheels/done anything silly. ## Functions -The main useful functions are described below. See code and docstrings for more information on other functions. +The main useful functions are described below. See code and docstrings for more +information on other functions. ### `rename_tables()` -Finds the first entry point for the binary, tries to track the parameters that were passed to the entry point function and rename global variables in which the key EFI tables are stored. The following renaming operations are performed: +Finds the first entry point for the binary, tries to track the parameters that +were passed to the entry point function and rename global variables in which the +key EFI tables are stored. The following renaming operations are performed: 1. Global where `ImageHandle` ends up is renamed to `gImageHandle`. 2. Global where `SystemTable` ends up is renamed to `gSystemTable`. -3. Global where `SystemTable->BootServices` ends up is renamed to `gBootServices`. +3. Global where `SystemTable->BootServices` ends up is renamed to +`gBootServices`. -4. Global where `SystemTable->RuntimeServices` ends up is renamed to `gRuntimeServices`. +4. Global where `SystemTable->RuntimeServices` ends up is renamed to +`gRuntimeServices`. -Call instructions will only be followed one level deep, as most executables copy the table references in the entry point or a function called from the entry point. Change `MAX_STACK_DEPTH` if necessary. +Call instructions will only be followed one level deep, as most executables copy +the table references in the entry point or a function called from the entry +point. Change `MAX_STACK_DEPTH` if necessary. ### `update_structs()` -Finds cross-references to tables renamed above, and updates their names to be struct offsets from the appropriate structs. If `rename_tables()` failed you'll need to rename things manually as above for this to work properly. +Finds cross-references to tables renamed above, and updates their names to be +struct offsets from the appropriate structs. If `rename_tables()` failed you'll +need to rename things manually as above for this to work properly. For example: @@ -55,11 +68,14 @@ Becomes: ### `rename_guids()` -Finds GUIDs in data segments and renames them. 470 protocol GUIDs were pulled out of the TianoCore source, and proprietary Apple (and other vendor) GUIDs will be added as they are encountered. +Finds GUIDs in data segments and renames them. 470 protocol GUIDs were pulled +out of the TianoCore source, and proprietary Apple (and other vendor) GUIDs will +be added as they are encountered. ### `update_protocols()` -Finds protocol's interfaces resolved using the `LocateProtocol` function and applies the struct offsets on calls made to its references. +Finds protocol's interfaces resolved using the `LocateProtocol` function and +applies the struct offsets on calls made to its references. ### `go()` @@ -69,12 +85,16 @@ Convenience method that does all of the above. 1. Load up your EFI binary in IDA Pro -2. Run `efiguids.py` to add it to python's path (or do this by some other method) +2. Run `efiguids.py` to add it to python's path (or do this by some other +method) -3. Run `efiutils.py` to add it to python's path (or do this by some other method) +3. Run `efiutils.py` to add it to python's path (or do this by some other +method) 4. Have a look at the code/docstrings, but probably: import efiutils; efiutils.go() -To use the `te_loader.py` TE image loader, install it as you would any other loader. On OS X this is done by copying or symlinking it inside the loaders folder at `idaq.app/Contents/MacOS/loaders/`. +To use the `te_loader.py` TE image loader, install it as you would any other +loader. On OS X this is done by copying or symlinking it inside the loaders +folder at `idaq.app/Contents/MacOS/loaders/`. From 6790f77b6ef4ecfeecaa7ad0524bf5f5ef7cc942 Mon Sep 17 00:00:00 2001 From: Frederic Vachon Date: Mon, 14 May 2018 12:25:13 -0400 Subject: [PATCH 07/13] Process all segments to find GUID --- efiutils.py | 66 ++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/efiutils.py b/efiutils.py index f4dc1fa..6cd5a35 100644 --- a/efiutils.py +++ b/efiutils.py @@ -371,40 +371,38 @@ def rename_guids(): # Find all the data segments in this binary for seg_addr in idautils.Segments(): seg = ida_segment.getseg(seg_addr) - if seg.type == ida_segment.SEG_DATA: - print "Processing data segment at 0x{:08x}".format(seg_addr) - - # Find any GUIDs we know about in the data segment - cur_addr = seg.start_ea - seg_end = seg.end_ea - while cur_addr < seg_end: - d = [ida_bytes.get_dword(cur_addr), - ida_bytes.get_dword(cur_addr+0x4), - ida_bytes.get_dword(cur_addr+0x8), - ida_bytes.get_dword(cur_addr+0xC)] - if (d[0] == 0 and d[1] == 0 and d[2] == 0 and d[3] == 0) or \ - ( - d[0] == 0xFFFFFFFF and d[1] == 0xFFFFFFFF and - d[2] == 0xFFFFFFFF and d[3] == 0xFFFFFFFF - ): - pass - else: - guid = GUID(bytes=struct.pack( - " Date: Mon, 14 May 2018 14:37:41 -0400 Subject: [PATCH 08/13] Fix GUID.bytes method --- efiutils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/efiutils.py b/efiutils.py index 6cd5a35..f751c0f 100644 --- a/efiutils.py +++ b/efiutils.py @@ -56,7 +56,6 @@ def __init__(self, default=None, bytes=None, string=None, array=None): (self.Data1, self.Data2, self.Data3, d40, d41, d42, d43, d44, d45, d46, d47) = struct.unpack(" Date: Fri, 3 Aug 2018 12:45:08 -0400 Subject: [PATCH 09/13] Add GUIDs to efiguids --- efiguids.py | 1 + 1 file changed, 1 insertion(+) diff --git a/efiguids.py b/efiguids.py index 3bdc73d..5d634c8 100644 --- a/efiguids.py +++ b/efiguids.py @@ -190,6 +190,7 @@ 'EFI_EXT_SCSI_PASS_THRU_PROTOCOL_GUID':[0x143b7632, 0xb81b, 0x4cb7, 0xab, 0xd3, 0xb6, 0x25, 0xa5, 0xb9, 0xbf, 0xfe], 'EFI_FAULT_TOLERANT_WRITE_PROTOCOL_GUID':[0x3ebd9e82, 0x2c78, 0x4de6, 0x97, 0x86, 0x8d, 0x4b, 0xfc, 0xb7, 0xc8, 0x81], 'EFI_FFS_VOLUME_TOP_FILE_GUID':[0x1BA0062E, 0xC779, 0x4582, 0x85, 0x66, 0x33, 0x6A, 0xE8, 0xF7, 0x8F, 0x09], +'EFI_FILE_INFO_ID': [0x09576e92, 0x6d3f, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b], 'EFI_FILE_SYSTEM_INFO_ID_GUID':[0x9576e93, 0x6d3f, 0x11d2, 0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b], 'EFI_FILE_SYSTEM_VOLUME_LABEL_INFO_ID_GUID':[0xDB47D7D3, 0xFE81, 0x11d3, 0x9A, 0x35, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D], 'EFI_FIND_FV_PPI_GUID':[0x36164812, 0xa023, 0x44e5, 0xbd, 0x85, 0x5, 0xbf, 0x3c, 0x77, 0x0, 0xaa], From a704ead6f1dfdb3e116c5b2a3bb1b83a970e4f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Vachon?= Date: Fri, 25 Jan 2019 16:33:54 -0500 Subject: [PATCH 10/13] Remove stack depth limitation when renaming tables --- efiutils.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/efiutils.py b/efiutils.py index f751c0f..607d842 100644 --- a/efiutils.py +++ b/efiutils.py @@ -28,7 +28,6 @@ import efiguids -MAX_STACK_DEPTH = 1 IMAGE_HANDLE_NAME = 'gImageHandle' SYSTEM_TABLE_NAME = 'gSystemTable' SYSTEM_TABLE_STRUCT = 'EFI_SYSTEM_TABLE' @@ -112,10 +111,11 @@ def rename_tables(): entry = ida_entry.get_entry_ordinal(0) - rename_tables_internal(entry, regs) + visited = set() + rename_tables_internal(entry, regs, visited) -def rename_tables_internal(ea, regs, stackdepth=0): +def rename_tables_internal(ea, regs, visited): names = { 'im': IMAGE_HANDLE_NAME, 'st': SYSTEM_TABLE_NAME, @@ -124,23 +124,24 @@ def rename_tables_internal(ea, regs, stackdepth=0): } print "Processing function at 0x{:08x}".format(ea) + visited.add(ea) for item in idautils.FuncItems(ea): inst = idautils.DecodeInstruction(item) # Bail out if we hit a call if inst.get_canon_mnem() == "call": - if stackdepth == MAX_STACK_DEPTH: - print " - Hit stack depth limit, bailing!" + to_visit = inst.Op1.addr + if to_visit in visited: return else: target_types = [ida_ua.o_imm, ida_ua.o_far, ida_ua.o_near] if inst.Op1.type in target_types: - # TODO : Currently assuming that the registry will be - # inaffected by calls rename_tables_internal( - inst.Op1.addr, copy.deepcopy(regs), stackdepth+1 + to_visit, copy.deepcopy(regs), visited ) + # Keeps saved registers based on UEFI spec + regs = keep_saved_regs(regs) else: print " - Can't follow call, bailing!" return @@ -609,3 +610,11 @@ def get_func_items_from_xref(xref): cur = ida_bytes.next_head(cur, idaapi.cvar.inf.maxEA) return items + + +def keep_saved_regs(regs): + saved_regs = ['rbx', 'rbp', 'rdi', 'rsi', 'r12', 'r13', 'r14', 'r15'] + regs_out = {} + for k, registers in regs.iteritems(): + regs_out[k] = filter(lambda reg: reg in saved_regs, registers) + return regs_out From 3a9127fbc3efb06a8d31be14eadd9fdf16caf5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Vachon?= Date: Tue, 29 Jan 2019 14:46:56 -0500 Subject: [PATCH 11/13] Fix: GUID parsing --- efiutils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/efiutils.py b/efiutils.py index 607d842..8d0aa1e 100644 --- a/efiutils.py +++ b/efiutils.py @@ -386,7 +386,7 @@ def rename_guids(): d[0] == 0xFFFFFFFF and d[1] == 0xFFFFFFFF and d[2] == 0xFFFFFFFF and d[3] == 0xFFFFFFFF ): - pass + cur_addr += 0x10 else: guid = GUID(bytes=struct.pack( " Date: Tue, 29 Jan 2019 14:48:35 -0500 Subject: [PATCH 12/13] Fix is_locate_protocol_param --- efiutils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/efiutils.py b/efiutils.py index 8d0aa1e..18b12fc 100644 --- a/efiutils.py +++ b/efiutils.py @@ -461,6 +461,9 @@ def is_locate_protocol_param(xref): """ inst = idautils.DecodeInstruction(xref) + if inst is None: + return False + # Must be 'lea rcx, gSmtGuid' if inst.get_canon_mnem() != 'lea' or inst.Op1.type != ida_ua.o_reg or \ inst.Op1.reg != ida_idp.str2reg('rcx'): From 833dc80822daf34d8213dd4bea40de9f1ecbfe1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Vachon?= Date: Thu, 23 May 2019 14:45:49 -0400 Subject: [PATCH 13/13] Fix: Make sure tables are not renamed more than once --- efiutils.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/efiutils.py b/efiutils.py index 18b12fc..45e16dc 100644 --- a/efiutils.py +++ b/efiutils.py @@ -112,10 +112,11 @@ def rename_tables(): entry = ida_entry.get_entry_ordinal(0) visited = set() - rename_tables_internal(entry, regs, visited) + renamed = set() + rename_tables_internal(entry, regs, visited, renamed) -def rename_tables_internal(ea, regs, visited): +def rename_tables_internal(ea, regs, visited, renamed): names = { 'im': IMAGE_HANDLE_NAME, 'st': SYSTEM_TABLE_NAME, @@ -138,7 +139,7 @@ def rename_tables_internal(ea, regs, visited): target_types = [ida_ua.o_imm, ida_ua.o_far, ida_ua.o_near] if inst.Op1.type in target_types: rename_tables_internal( - to_visit, copy.deepcopy(regs), visited + to_visit, copy.deepcopy(regs), visited, renamed ) # Keeps saved registers based on UEFI spec regs = keep_saved_regs(regs) @@ -153,7 +154,13 @@ def rename_tables_internal(ea, regs, visited): inst.Op1.type == ida_ua.o_mem: print(" - Found a copy to a memory address for {}, " + "updating: {}").format(names[key], disasm(inst.ea)) - ida_name.set_name(inst.Op1.addr, names[key]) + to_rename = inst.Op1.addr + if to_rename not in renamed: + renamed.add(to_rename) + label = get_next_unused_label( + names[key] + ) + ida_name.set_name(to_rename, label) break # Eliminate overwritten registers