#!/usr/bin/python # # Copyright (c) 2013 - 2016 Apple Inc. All rights reserved # # Dump out a PE/COFF, PE/COFF+, or TE file using the EfiPeCoff class # # Read from memory in lldb # T=pecoff.EfiPeCoff(lldb.target, 0x86c7d000) # # Read from a Python file object # T=pecoff.EfiPeCoff(file) # # Read from a Python string # T=pecoff.EfiPeCoff(file.read()) # import sys import struct import collections import optparse import commands import os import platform #---------------------------------------------------------------------- # Code that auto imports LLDB #---------------------------------------------------------------------- try: # Just try for LLDB in case PYTHONPATH is already correctly setup import lldb except ImportError: lldb_python_dirs = list() # lldb is not in the PYTHONPATH, try some defaults for the current platform platform_system = platform.system() if platform_system == 'Darwin': # On Darwin, try the currently selected Xcode directory xcode_dir = commands.getoutput("xcode-select --print-path") if xcode_dir: lldb_python_dirs.append(os.path.realpath(xcode_dir + '/../SharedFrameworks/LLDB.framework/Resources/Python')) lldb_python_dirs.append(xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python') lldb_python_dirs.append('/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python') success = False for lldb_python_dir in lldb_python_dirs: if os.path.exists(lldb_python_dir): if not (sys.path.__contains__(lldb_python_dir)): sys.path.append(lldb_python_dir) try: import lldb except ImportError: pass else: success = True break if not success: print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly" sys.exit(1) class ReadOnlyFile: '''Abstract reading data from an object: Duck type an lldb.SBTarget, string (output of file.read()), or Python File object. ''' def __init__(self, readAbstraction, address = 0): # Python file object self.file = None # offset for FAT binaries. self.offset = None # lldb SBTarget self.address = None self.startingAddress = None self.SBTarget = None self.SBError = None # Python string (file.read()) self.data = None self.dataIndex = 0 if isinstance(readAbstraction, lldb.SBTarget): # duck type lldb memory reads self.address = address self.startingAddress = address self.SBTarget = readAbstraction self.SBError = lldb.SBError() elif isinstance(readAbstraction, file): # duck type to a Python file self.file = readAbstraction self.offset = address elif isinstance(readAbstraction, str): # string, like the result of reading the file in via Python self.data = readAbstraction self.dataIndex = 0 else: raise SyntaxError('Unsupported type for readAbstraction') def Read (self, size, offset=None): if offset is not None: self.Seek (offset) if self.file: return self.file.read(size) if self.address: data = self.SBTarget.process.ReadMemory (self.address, size, self.SBError) self.address += size return bytearray(data) if self.data: data = self.data[self.dataIndex:self.dataIndex+size] self.dataIndex += size return data def ReadCString (self, offset=None, maxSize=512): if offset: self.Seek (offset) if self.file: data = self.file.read(maxSize) str = data.split('\x00')[0] # seek to end of string self.file.seek (-(maxSize - len(str)), os.SEEK_CUR) return data if self.address: data = self.SBTarget.process.ReadCStringFromMemory (self.address, maxSize, self.SBError) self.address += len(data) return data if self.data: data = self.data[self.dataIndex:self.dataIndex+maxSize] str = data.split('\x00')[0] self.dataIndex += len(str) return str def Seek (self, offset, whence = os.SEEK_SET): if self.file: return self.file.seek(offset, whence) if self.address: if whence == os.SEEK_SET: self.address = self.startingAddress + offset elif whence == os.SEEK_CUR: self.address = self.address + offset elif whence == os.SEEK_END: raise SyntaxError('whence does not support SEEK_END due to memory not having an end') else: raise SyntaxError('illegal whence value') if self.data: if whence == os.SEEK_SET: self.dataIndex = offset elif whence == os.SEEK_CUR: self.dataIndex = self.dataIndex + offset elif whence == os.SEEK_END: raise SyntaxError('whence does not support SEEK_END due to memory not having an end') else: raise SyntaxError('illegal whence value') def Tell (self): if self.file: return self.file.tell() if self.address: return self.address if self.data: return self.dataIndex def __del__(self): if self.file: self.file.close() class EfiPeCoff: ''' class to abstract PE/COFF walking''' # PE/COFF class definitions # # typedef struct { # UINT32 VirtualAddress; # UINT32 Size; # } EFI_IMAGE_DATA_DIRECTORY; # # typedef struct { # UINT16 Signature; ///< The signature for TE format = "VZ". # UINT16 Machine; ///< From the original file header. # UINT8 NumberOfSections; ///< From the original file header. # UINT8 Subsystem; ///< From original optional header. # UINT16 StrippedSize; ///< Number of bytes we removed from the header. # UINT32 AddressOfEntryPoint; ///< Offset to entry point -- from original optional header. # UINT32 BaseOfCode; ///< From original image -- required for ITP debug. # UINT64 ImageBase; ///< From original file header. # EFI_IMAGE_DATA_DIRECTORY DataDirectory[2]; ///< Only base relocation and debug directory. # } EFI_TE_IMAGE_HEADER; # EFI_TE_IMAGE_HEADER_fmt = ' e_res2[10] EFI_IMAGE_DOS_HEADER_fmt = 'PeCoffHeaderOffset + sizeof (UINT32) + sizeof (EFI_IMAGE_FILE_HEADER) + Hdr.Pe32->FileHeader.SizeOfOptionalHeader; self.PeSections = offset + 4 + 20 + self.PeHdr.SizeOfOptionalHeader def PeCoffDumpHdr (self): PeHdr = self.PeHdr if self.TeHdr is None else self.TeHdr Width = max (len (s) for s in PeHdr._fields) return "\n".join('{0} = {1:#0{2}x}'.format(s.ljust(Width), getattr(PeHdr, s), FmtStrToWidth(self.PeHdrFmt[i+1])+2) for i, s in enumerate (PeHdr._fields)) def PeCoffZeroInfo (self): # Return the files offsets and number of bytes that need to get zero'ed return self.ZeroList def NumberOfSections (self): if self.PeHdr is not None: return self.PeHdr.NumberOfSections elif self.TeHdr is not None: return self.TeHdr.NumberOfSections else: return 0 def PeCoffGetSection (self, index): offset = self.PeSections + (index * EfiPeCoff.PeCoffSectionLength) data = self.f.Read(EfiPeCoff.PeCoffSectionLength, offset) return (data[0:8].split('\x00')[0], EfiPeCoff.EFI_IMAGE_SECTION_HEADER._make (struct.Struct(EfiPeCoff.EFI_IMAGE_SECTION_HEADER_fmt).unpack_from (data))) def PeCoffDumpSectionHdr (self, Name, Section): Width = max (len (s) for s in Section._fields) result = '' for i, s in enumerate (Section._fields): result += '{0} = '.format(s.ljust(Width)) if i == 0 and Name != '': # print name as a string, not a hex value result += Name + '\n' else: result += '{0:#0{1}x}\n'.format(getattr(Section, s), FmtStrToWidth(EfiPeCoff.EFI_IMAGE_SECTION_HEADER_fmt[i+1])+2) return result def PeCoffDumpSection (self, Name, Section): data = self.f.Read (Section.SizeOfRawData, Section.VirtualAddress) result = [] Address = Section.VirtualAddress for i in xrange (0, Section.SizeOfRawData, 16): HexStr = ' '.join(["%02X"%ord(x) for x in data[i:i+16]]) TextStr = ''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in data[i:i+16]]) result.append("%08X %-*s |%s|\n" % (Address + i, 16*3, HexStr, TextStr)) return ''.join(result) def PeCoffGetPdePointer (self, DebugEntry = 0, DebugEntrySize = 0, adjust = 0): # if DebugEntrySize == 0: if self.PeHdr is not None: DebugEntry = self.PeHdr.DataDirVirt_Debug DebugEntrySize = self.PeHdr.DataDirSize_Debug elif self.TeHdr is not None: DebugEntry = self.TeHdr.DataDirVirt_Debug DebugEntrySize = self.TeHdr.DataDirSize_Debug adjust = self.TeAdjust else: return ('','') offset = DebugEntry + adjust data = self.f.Read(DebugEntrySize, offset) DirectoryEntry = EfiPeCoff.EFI_IMAGE_DEBUG_DIRECTORY_ENTRY._make (struct.Struct(EfiPeCoff.EFI_IMAGE_DEBUG_DIRECTORY_ENTRY_fmt).unpack_from (data)) offset = DirectoryEntry.FileOffset + adjust data = self.f.Read(4, offset) guid = '' if data == 'MTOC': data = self.f.Read(16) tup = struct.unpack (EfiPeCoff.EFI_GUID_fmt, data) guid = '{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}'.format(*tup) Str = self.f.ReadCString () elif data == 'NB10': Str = self.f.ReadCString (offset + 16) elif data == 'RSDS': Str = self.f.ReadCString (offset + 24) else: Str = "\x00" # Python is more that happy to print out a NULL return (Str.split('\x00')[0], guid) def PeCoffDumpRelocations (self, offset, size): data = self.f.Read(size, offset) base = 0 baseEnd = size - EfiPeCoff.BaseRelocationLength value = '' while base < baseEnd: (VirtualAddress, SizeOfBlock) = struct.unpack_from (EfiPeCoff.EFI_IMAGE_BASE_RELOCATION_fmt, data[base:base + EfiPeCoff.BaseRelocationLength]) if SizeOfBlock == 0 or SizeOfBlock > size: break reloc = base + EfiPeCoff.BaseRelocationLength relocEnd = base + SizeOfBlock value += '0x%08x SizeOfBlock 0x%x\n' % (VirtualAddress, SizeOfBlock) while reloc < relocEnd: rel, = struct.unpack_from ('> 12, ) reloc += 2 base = relocEnd return value def PeCoffDumpCert (self, offset, size): data = self.f.Read(size, offset) value = '\n' if size > 8: (dwLength, wRevision, wCertificateType) = struct.unpack_from (EfiPeCoff.WIN_CERTIFICATE_fmt, data) value += "dwLength = 0x%04x wRevision = 0x%02x wCertificateType = 0x%02x\n" % (dwLength, wRevision, wCertificateType) # UEFI Scheme for i in range(struct.calcsize(EfiPeCoff.WIN_CERTIFICATE_fmt), size, 0x10): value += "0x{:04x}:".format(i), value += " ".join("{:02x}".format(ord(c)) for c in data[i:i+0x10]) value += '\n' else: # Loki Scheme start = 0 while start < size: (VirtualAddress, SizeOfBlock) = struct.unpack_from (EfiPeCoff.EFI_IMAGE_BASE_RELOCATION_fmt, data[start: start + struct.calcsize(EfiPeCoff.EFI_IMAGE_BASE_RELOCATION_fmt)]) start += struct.calcsize(EfiPeCoff.EFI_IMAGE_BASE_RELOCATION_fmt) value += "CERT: 0x%X size 0x%x\n" % (VirtualAddress, SizeOfBlock) cert = self.f.Read(SizeOfBlock, VirtualAddress) for i in range(0, SizeOfBlock, 0x10): value += "0x{:04x}:".format(i) value += " ".join("{:02x}".format(ord(c)) for c in cert[i:i+0x10]) value += '\n' return value def __str__(self): return self.PeCoffDumpHdr() def FmtStrToWidth (c): c = c.upper() if c== 'B': return 1*2 if c== 'H': return 2*2 if c== 'I' or c=='L': return 4*2 if c== 'Q': return 8*2 return 0 #define EFI_FAT_BINARY_MAGIC 0x0ef1fab9 # typedef struct _EFI_FAT_BINARY_HEADER { # UINT32 magic; /* FAT_MAGIC */ # UINT32 nfat_arch; /* number of structs that follow */ # } EFI_FAT_BINARY_HEADER; # typedef struct _EFI_FAT_BINARY_ARCH { # UINT32 cputype; /* cpu specifier (int) */ # UINT32 cpusubtype; /* machine specifier (int) */ # UINT32 offset; /* file offset to this object file */ # UINT32 size; /* size of this object file */ # UINT32 align; /* alignment as a power of 2 */ # } EFI_FAT_BINARY_ARCH; EFI_FAT_BINARY_ARCH_fmt = ' Section.VirtualSize: sizeDiff = Section.SizeOfRawData - Section.VirtualSize offset = pecoff.TeAdjust + Section.PointerToRawData + Section.SizeOfRawData - sizeDiff pecoff.ZeroList.append( [offset, sizeDiff] ) # The .debug section also contains a timestamp if '.debug' in Name: pecoff.ZeroList.append([pecoff.TeAdjust + Section.PointerToRawData + struct.calcsize(EfiPeCoff.EFI_IMAGE_DEBUG_DIRECTORY_ENTRY_fmt[0:2]), 4]) for patch, size in pecoff.PeCoffZeroInfo(): #print 'patching 0x%x for 0x%x bytes' % (patch, size) if patch != 0: if size == -1: # -1 means to the end of the file f.seek(0,2) size = f.tell() - patch f.seek(patch) f.write(bytearray(size))