1# SPDX-License-Identifier: BSD-3-Clause 2# Copyright (c) 2020 Dmitry Kozlyuk <dmitry.kozliuk@gmail.com> 3 4import ctypes 5 6# x86_64 little-endian 7COFF_MAGIC = 0x8664 8 9# Names up to this length are stored immediately in symbol table entries. 10COFF_NAMELEN = 8 11 12# Special "section numbers" changing the meaning of symbol table entry. 13COFF_SN_UNDEFINED = 0 14COFF_SN_ABSOLUTE = -1 15COFF_SN_DEBUG = -2 16 17 18class CoffFileHeader(ctypes.LittleEndianStructure): 19 _pack_ = True 20 _fields_ = [ 21 ("magic", ctypes.c_uint16), 22 ("section_count", ctypes.c_uint16), 23 ("timestamp", ctypes.c_uint32), 24 ("symbol_table_offset", ctypes.c_uint32), 25 ("symbol_count", ctypes.c_uint32), 26 ("optional_header_size", ctypes.c_uint16), 27 ("flags", ctypes.c_uint16), 28 ] 29 30 31class CoffName(ctypes.Union): 32 class Reference(ctypes.LittleEndianStructure): 33 _pack_ = True 34 _fields_ = [ 35 ("zeroes", ctypes.c_uint32), 36 ("offset", ctypes.c_uint32), 37 ] 38 39 Immediate = ctypes.c_char * 8 40 41 _pack_ = True 42 _fields_ = [ 43 ("immediate", Immediate), 44 ("reference", Reference), 45 ] 46 47 48class CoffSection(ctypes.LittleEndianStructure): 49 _pack_ = True 50 _fields_ = [ 51 ("name", CoffName), 52 ("physical_address", ctypes.c_uint32), 53 ("physical_address", ctypes.c_uint32), 54 ("size", ctypes.c_uint32), 55 ("data_offset", ctypes.c_uint32), 56 ("relocations_offset", ctypes.c_uint32), 57 ("line_numbers_offset", ctypes.c_uint32), 58 ("relocation_count", ctypes.c_uint16), 59 ("line_number_count", ctypes.c_uint16), 60 ("flags", ctypes.c_uint32), 61 ] 62 63 64class CoffSymbol(ctypes.LittleEndianStructure): 65 _pack_ = True 66 _fields_ = [ 67 ("name", CoffName), 68 ("value", ctypes.c_uint32), 69 ("section_number", ctypes.c_int16), 70 ("type", ctypes.c_uint16), 71 ("storage_class", ctypes.c_uint8), 72 ("auxiliary_count", ctypes.c_uint8), 73 ] 74 75 76class Symbol: 77 def __init__(self, image, symbol: CoffSymbol): 78 self._image = image 79 self._coff = symbol 80 81 @property 82 def name(self): 83 if self._coff.name.reference.zeroes: 84 return decode_asciiz(bytes(self._coff.name.immediate)) 85 86 offset = self._coff.name.reference.offset 87 offset -= ctypes.sizeof(ctypes.c_uint32) 88 return self._image.get_string(offset) 89 90 def get_value(self, offset): 91 section_number = self._coff.section_number 92 93 if section_number == COFF_SN_UNDEFINED: 94 return None 95 96 if section_number == COFF_SN_DEBUG: 97 return None 98 99 if section_number == COFF_SN_ABSOLUTE: 100 return bytes(ctypes.c_uint32(self._coff.value)) 101 102 section_data = self._image.get_section_data(section_number) 103 section_offset = self._coff.value + offset 104 return section_data[section_offset:] 105 106 107class Image: 108 def __init__(self, data): 109 header = CoffFileHeader.from_buffer_copy(data) 110 header_size = ctypes.sizeof(header) + header.optional_header_size 111 112 sections_desc = CoffSection * header.section_count 113 sections = sections_desc.from_buffer_copy(data, header_size) 114 115 symbols_desc = CoffSymbol * header.symbol_count 116 symbols = symbols_desc.from_buffer_copy(data, header.symbol_table_offset) 117 118 strings_offset = header.symbol_table_offset + ctypes.sizeof(symbols) 119 strings = Image._parse_strings(data[strings_offset:]) 120 121 self._data = data 122 self._header = header 123 self._sections = sections 124 self._symbols = symbols 125 self._strings = strings 126 127 @staticmethod 128 def _parse_strings(data): 129 full_size = ctypes.c_uint32.from_buffer_copy(data) 130 header_size = ctypes.sizeof(full_size) 131 return data[header_size : full_size.value] 132 133 @property 134 def symbols(self): 135 i = 0 136 while i < self._header.symbol_count: 137 symbol = self._symbols[i] 138 yield Symbol(self, symbol) 139 i += symbol.auxiliary_count + 1 140 141 def get_section_data(self, number): 142 # section numbers are 1-based 143 section = self._sections[number - 1] 144 base = section.data_offset 145 return self._data[base : base + section.size] 146 147 def get_string(self, offset): 148 return decode_asciiz(self._strings[offset:]) 149 150 151def decode_asciiz(data): 152 index = data.find(b'\x00') 153 end = index if index >= 0 else len(data) 154 return data[:end].decode() 155