import os import struct from io import BytesIO from PIL import Image def resize(image, size, format=None): output = BytesIO() back = Image.new('RGBA', size, (0,0,0,0)) if image.size[0] < size[0] or image.size[1] < size[1]: if image.height > image.width: factor = size[0] / image.height else: factor = size[1] / image.width image = image.resize((int(image.width * factor), int(image.height * factor)), Image.ANTIALIAS) else: image.thumbnail(size, Image.ANTIALIAS) offset = [0, 0] if image.size[0] > image.size[1]: offset[1] = int(back.size[1]/2-image.size[1]/2) elif image.size[0] < image.size[1]: offset[0] = int(back.size[0]/2-image.size[0]/2) else: offset[0] = int(back.size[0]/2-image.size[0]/2) offset[1] = int(back.size[1]/2-image.size[1]/2) back.paste(image, tuple(offset)) format = format or image.format back.save(output, format, sizes=[size]) contents = output.getvalue() output.close() return contents struct_symbols = {1:'B',#byte 2:'H',#word 4:'L',#long word 8:'Q' #double long word } endian_symbols = {'little':'<', 'big':'>'} name_dictionary = {'PEHeader_Machine': { 0:'IMAGE_FILE_MACHINE_UNKNOWN', 0x014c:'IMAGE_FILE_MACHINE_I386', 0x0162:'IMAGE_FILE_MACHINE_R3000', 0x0166:'IMAGE_FILE_MACHINE_R4000', 0x0168:'IMAGE_FILE_MACHINE_R10000', 0x0169:'IMAGE_FILE_MACHINE_WCEMIPSV2', 0x0184:'IMAGE_FILE_MACHINE_ALPHA', 0x01a2:'IMAGE_FILE_MACHINE_SH3', 0x01a3:'IMAGE_FILE_MACHINE_SH3DSP', 0x01a4:'IMAGE_FILE_MACHINE_SH3E', 0x01a6:'IMAGE_FILE_MACHINE_SH4', 0x01a8:'IMAGE_FILE_MACHINE_SH5', 0x01c0:'IMAGE_FILE_MACHINE_ARM', 0x01c2:'IMAGE_FILE_MACHINE_THUMB', 0x01c4:'IMAGE_FILE_MACHINE_ARMNT', 0x01d3:'IMAGE_FILE_MACHINE_AM33', 0x01f0:'IMAGE_FILE_MACHINE_POWERPC', 0x01f1:'IMAGE_FILE_MACHINE_POWERPCFP', 0x0200:'IMAGE_FILE_MACHINE_IA64', 0x0266:'IMAGE_FILE_MACHINE_MIPS16', 0x0284:'IMAGE_FILE_MACHINE_ALPHA64', 0x0284:'IMAGE_FILE_MACHINE_AXP64', # same 0x0366:'IMAGE_FILE_MACHINE_MIPSFPU', 0x0466:'IMAGE_FILE_MACHINE_MIPSFPU16', 0x0520:'IMAGE_FILE_MACHINE_TRICORE', 0x0cef:'IMAGE_FILE_MACHINE_CEF', 0x0ebc:'IMAGE_FILE_MACHINE_EBC', 0x8664:'IMAGE_FILE_MACHINE_AMD64', 0x9041:'IMAGE_FILE_MACHINE_M32R', 0xc0ee:'IMAGE_FILE_MACHINE_CEE' }, 'PEHeader_Characteristics':{ 0x0001:'IMAGE_FILE_RELOCS_STRIPPED', 0x0002:'IMAGE_FILE_EXECUTABLE_IMAGE', 0x0004:'IMAGE_FILE_LINE_NUMS_STRIPPED', 0x0008:'IMAGE_FILE_LOCAL_SYMS_STRIPPED', 0x0010:'IMAGE_FILE_AGGRESIVE_WS_TRIM', 0x0020:'IMAGE_FILE_LARGE_ADDRESS_AWARE', 0x0040:'IMAGE_FILE_16BIT_MACHINE', 0x0080:'IMAGE_FILE_BYTES_REVERSED_LO', 0x0100:'IMAGE_FILE_32BIT_MACHINE', 0x0200:'IMAGE_FILE_DEBUG_STRIPPED', 0x0400:'IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP', 0x0800:'IMAGE_FILE_NET_RUN_FROM_SWAP', 0x1000:'IMAGE_FILE_SYSTEM', 0x2000:'IMAGE_FILE_DLL', 0x4000:'IMAGE_FILE_UP_SYSTEM_ONLY', 0x8000:'IMAGE_FILE_BYTES_REVERSED_HI' }, 'OptionalHeader_Subsystem':{ 0:'IMAGE_SUBSYSTEM_UNKNOWN', 1:'IMAGE_SUBSYSTEM_NATIVE', 2:'IMAGE_SUBSYSTEM_WINDOWS_GUI', 3:'IMAGE_SUBSYSTEM_WINDOWS_CUI', 5:'IMAGE_SUBSYSTEM_OS2_CUI', 7:'IMAGE_SUBSYSTEM_POSIX_CUI', 8:'IMAGE_SUBSYSTEM_NATIVE_WINDOWS', 9:'IMAGE_SUBSYSTEM_WINDOWS_CE_GUI', 10:'IMAGE_SUBSYSTEM_EFI_APPLICATION', 11:'IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER', 12:'IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER', 13:'IMAGE_SUBSYSTEM_EFI_ROM', 14:'IMAGE_SUBSYSTEM_XBOX', 16:'IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION' }, 'OptionalHeader_DLL_Characteristics':{ 0x0001:'IMAGE_LIBRARY_PROCESS_INIT', # reserved 0x0002:'IMAGE_LIBRARY_PROCESS_TERM', # reserved 0x0004:'IMAGE_LIBRARY_THREAD_INIT', # reserved 0x0008:'IMAGE_LIBRARY_THREAD_TERM', # reserved 0x0020:'IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA', 0x0040:'IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE', 0x0080:'IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY', 0x0100:'IMAGE_DLLCHARACTERISTICS_NX_COMPAT', 0x0200:'IMAGE_DLLCHARACTERISTICS_NO_ISOLATION', 0x0400:'IMAGE_DLLCHARACTERISTICS_NO_SEH', 0x0800:'IMAGE_DLLCHARACTERISTICS_NO_BIND', 0x1000:'IMAGE_DLLCHARACTERISTICS_APPCONTAINER', 0x2000:'IMAGE_DLLCHARACTERISTICS_WDM_DRIVER', 0x4000:'IMAGE_DLLCHARACTERISTICS_GUARD_CF', 0x8000:'IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE' }, 'SectionHeader_Characteristics':{ 0x00000000:'IMAGE_SCN_TYPE_REG', # reserved 0x00000001:'IMAGE_SCN_TYPE_DSECT', # reserved 0x00000002:'IMAGE_SCN_TYPE_NOLOAD', # reserved 0x00000004:'IMAGE_SCN_TYPE_GROUP', # reserved 0x00000008:'IMAGE_SCN_TYPE_NO_PAD', # reserved 0x00000010:'IMAGE_SCN_TYPE_COPY', # reserved 0x00000020:'IMAGE_SCN_CNT_CODE', 0x00000040:'IMAGE_SCN_CNT_INITIALIZED_DATA', 0x00000080:'IMAGE_SCN_CNT_UNINITIALIZED_DATA', 0x00000100:'IMAGE_SCN_LNK_OTHER', 0x00000200:'IMAGE_SCN_LNK_INFO', 0x00000400:'IMAGE_SCN_LNK_OVER', # reserved 0x00000800:'IMAGE_SCN_LNK_REMOVE', 0x00001000:'IMAGE_SCN_LNK_COMDAT', 0x00004000:'IMAGE_SCN_MEM_PROTECTED', # obsolete 0x00004000:'IMAGE_SCN_NO_DEFER_SPEC_EXC', 0x00008000:'IMAGE_SCN_GPREL', 0x00008000:'IMAGE_SCN_MEM_FARDATA', 0x00010000:'IMAGE_SCN_MEM_SYSHEAP', # obsolete 0x00020000:'IMAGE_SCN_MEM_PURGEABLE', 0x00020000:'IMAGE_SCN_MEM_16BIT', 0x00040000:'IMAGE_SCN_MEM_LOCKED', 0x00080000:'IMAGE_SCN_MEM_PRELOAD', 0x00100000:'IMAGE_SCN_ALIGN_1BYTES', 0x00200000:'IMAGE_SCN_ALIGN_2BYTES', 0x00300000:'IMAGE_SCN_ALIGN_4BYTES', 0x00400000:'IMAGE_SCN_ALIGN_8BYTES', 0x00500000:'IMAGE_SCN_ALIGN_16BYTES', # default alignment 0x00600000:'IMAGE_SCN_ALIGN_32BYTES', 0x00700000:'IMAGE_SCN_ALIGN_64BYTES', 0x00800000:'IMAGE_SCN_ALIGN_128BYTES', 0x00900000:'IMAGE_SCN_ALIGN_256BYTES', 0x00A00000:'IMAGE_SCN_ALIGN_512BYTES', 0x00B00000:'IMAGE_SCN_ALIGN_1024BYTES', 0x00C00000:'IMAGE_SCN_ALIGN_2048BYTES', 0x00D00000:'IMAGE_SCN_ALIGN_4096BYTES', 0x00E00000:'IMAGE_SCN_ALIGN_8192BYTES', 0x00F00000:'IMAGE_SCN_ALIGN_MASK', 0x01000000:'IMAGE_SCN_LNK_NRELOC_OVFL', 0x02000000:'IMAGE_SCN_MEM_DISCARDABLE', 0x04000000:'IMAGE_SCN_MEM_NOT_CACHED', 0x08000000:'IMAGE_SCN_MEM_NOT_PAGED', 0x10000000:'IMAGE_SCN_MEM_SHARED', 0x20000000:'IMAGE_SCN_MEM_EXECUTE', 0x40000000:'IMAGE_SCN_MEM_READ', 0x80000000:'IMAGE_SCN_MEM_WRITE', }, } DEFAULT_ENDIAN = 'little' def read_data(file_data, offset, number_of_bytes, string_data=None): """Just reads the straight data with no endianness.""" if number_of_bytes > 0: data = file_data[offset:offset+number_of_bytes] #if len(data) != number_of_bytes: #print 'data out of bounds:', 'offset', hex(offset), 'data', data, 'data_len', len(data), 'num_bytes', number_of_bytes, 'total', hex(len(file_data)) return data else: return bytearray('') def read_bytes(file_data, offset, number_of_bytes, endian=None, string_data=None): """Returns a tuple of the data value and string representation. Will read 1,2,4,8 bytes with little endian as the default (value, string) """ if number_of_bytes > 0: endian = endian or DEFAULT_ENDIAN endian = endian_symbols[endian] data = bytes(file_data[offset:offset+number_of_bytes]) if len(data) != number_of_bytes: return 0, u'' return struct.unpack(endian+struct_symbols[number_of_bytes], data)[0], data else: return 0, u'' def value_to_byte_string(value, number_of_bytes, endian=None): endian = endian or DEFAULT_ENDIAN endian = endian_symbols[endian] return struct.pack(endian+struct_symbols[number_of_bytes], value) class ResourceTypes(object): Cursor = 1 Bitmap = 2 Icon = 3 Menu = 4 Dialog = 5 String = 6 Font_Directory = 7 Font = 8 Accelerator = 9 RC_Data = 10 Message_Table = 11 Group_Cursor = 12 Group_Icon = 14 Version_Info = 16 DLG_Include = 17 Plug_Play = 19 VXD = 20 Animated_Cursor = 21 Animated_Icon = 22 HTML = 23 Manifest = 24 resource_types = {1: 'Cursor', 2: 'Bitmap', 3: 'Icon', 4: 'Menu', 5: 'Dialog', 6: 'String', 7: 'Font Directory', 8: 'Font', 9: 'Accelerator', 10: 'RC Data', 11: 'Message Table', 12: 'Group Cursor', 14: 'Group Icon', 16: 'Version Info', 17: 'DLG Include', 19: 'Plug and Play', 20: 'VXD', 21: 'Animated Cursor', 22: 'Animated Icon', 23: 'HTML', 24: 'Manifest'} _32BIT_PLUS_MAGIC = 0x20b _32BIT_MAGIC = 0x10b _ROM_MAGIC = 0x107 def read_from_name_dict(obj, field_name): dict_field = '{}_{}'.format(obj.__class__.__name__, field_name) return name_dictionary.get(dict_field, {}) def test_bit(value, index): mask = 1 << index return (value & mask) def set_bit(value, index): mask = 1 << index return (value | mask) def clear_bit(value, index): mask = ~(1 << index) return (value & mask) def toggle_bit(value, index): mask = 1 << index return (value ^ mask) class PEFormatError(Exception): pass class Printable(object): def _attrs(self): a = [] for attr in dir(self): if not attr.startswith('_') and not callable(getattr(self, attr)): a.append(attr) return a def _dict_items(self): for a in reversed(self._attrs()): yield a, getattr(self,a) def _dict_string(self): vals = [] for key, val in self._dict_items(): try: vals.append(u'{}={}'.format(key, val)) except UnicodeDecodeError: vals.append(u'{}='.format(key)) return u', '.join(vals) def __repr__(self): return str(self) def __str__(self): return u'{} [{}]'.format(self.__class__.__name__, self._dict_string()) class Structure(Printable): _fields = {} def __init__(self, size=0, value=None, data=None, absolute_offset=0, name='', friendly_name='', *args, **kwargs): self._value = value self.size = size self.data = data self.name = name self.friendly_name = friendly_name self._absolute_offset = absolute_offset self._file_data = None for k, v in kwargs.items(): setattr(self, k, v) @property def absolute_offset(self): return self._absolute_offset @absolute_offset.setter def absolute_offset(self, abs_offset): self._absolute_offset = abs_offset for k, v in self._fields.items(): field = getattr(self, k) field.absolute_offset = self.absolute_offset + field.offset @property def value(self): return self._value @value.setter def value(self, value): if self._file_data is not None: self.data = value_to_byte_string(value, self.size) self._file_data[self.absolute_offset:self.absolute_offset+self.size] = bytearray(self.data) self._value = value def process_field(self, file_data, field_name, field_info): if hasattr(self, 'process_'+field_name) and callable(getattr(self, 'process_'+field_name)): getattr(self, 'process_'+field_name)(file_data, field_name, field_info) else: absolute_offset = field_info['offset'] + self.absolute_offset size = field_info['size'] self.size += size int_value, data = read_bytes(file_data, absolute_offset, size) field_name_dict = read_from_name_dict(self, field_name) name = field_name_dict.get(int_value, '') friendly_name = name.replace('_', ' ').capitalize() setattr(self, field_name, Structure(offset=field_info['offset'], size=size, value=int_value, data=data, absolute_offset=absolute_offset, name=name, friendly_name=friendly_name)) getattr(self, field_name)._file_data = file_data def process_Characteristics(self, file_data, field_name, field_info): absolute_offset = field_info['offset'] + self.absolute_offset size = field_info['size'] self.size += size int_value, data = read_bytes(file_data, absolute_offset, size) field_name_dict = read_from_name_dict(self, field_name) bit_length = len(bin(int_value))-2 characteristics = {} for i in range(bit_length): set_bit = test_bit(int_value, i) char_name = field_name_dict.get(set_bit, '') if set_bit != 0 and char_name: characteristics[char_name] = set_bit setattr(self, field_name, Structure(offset=field_info['offset'], size=size, value=int_value, data=data, absolute_offset=absolute_offset, values=characteristics, )) getattr(self, field_name)._file_data = file_data @classmethod def parse_from_data(cls, file_data, **cls_args): """Parses the Structure from the file data.""" self = cls(**cls_args) self._file_data = file_data for field_name, field_info in self._fields.items(): self.process_field(file_data, field_name, field_info) return self class DOSHeader(Structure): """The dos header of the PE file""" _fields = {'Signature':{'offset':0, 'size':2}, 'PEHeaderOffset':{'offset':0x3c, 'size':4} } class PEHeader(Structure): """PE signature plus the COFF header""" _fields = {'Signature':{'offset':0, 'size':4}, 'Machine':{'offset':4, 'size':2}, 'NumberOfSections':{'offset':6, 'size':2}, 'TimeDateStamp':{'offset':8, 'size':4}, 'PointerToSymbolTable':{'offset':12, 'size':4}, 'NumberOfSymbols':{'offset':16, 'size':4}, 'SizeOfOptionalHeader':{'offset':20, 'size':2}, 'Characteristics':{'offset':22, 'size':2} } class OptionalHeader(Structure): _fields_32_plus = {'Magic':{'offset':0, 'size':2}, 'MajorLinkerVersion':{'offset':2, 'size':1}, 'MinorLinkerVersion':{'offset':3, 'size':1}, 'SizeOfCode':{'offset':4, 'size':4}, 'SizeOfInitializedData':{'offset':8, 'size':4}, 'SizeOfUninitializedData':{'offset':12, 'size':4}, 'AddressOfEntryPoint':{'offset':16, 'size':4}, 'BaseOfCode':{'offset':20, 'size':4}, 'ImageBase':{'offset':24, 'size':8}, 'SectionAlignment':{'offset':32, 'size':4}, 'FileAlignment':{'offset':36, 'size':4}, 'MajorOperatingSystemVersion':{'offset':40, 'size':2}, 'MinorOperatingSystemVersion':{'offset':42, 'size':2}, 'MajorImageVersion':{'offset':44, 'size':2}, 'MinorImageVersion':{'offset':46, 'size':2}, 'MajorSubsystemVersion':{'offset':48, 'size':2}, 'MinorSubsystemVersion':{'offset':50, 'size':2}, 'Reserved':{'offset':52, 'size':4}, 'SizeOfImage':{'offset':56, 'size':4}, 'SizeOfHeaders':{'offset':60, 'size':4}, 'SizeOfHeaders':{'offset':60, 'size':4}, 'CheckSum': {'offset': 64, 'size':4}, 'Subsystem':{'offset':68, 'size':2}, 'DLL_Characteristics':{'offset':70, 'size':2}, 'SizeOfStackReserve':{'offset':72, 'size':8}, 'SizeOfStackCommit':{'offset':80, 'size':8}, 'SizeOfHeapReserve':{'offset':88, 'size':8}, 'SizeOfHeapCommit':{'offset':96, 'size':8}, 'LoaderFlags':{'offset':104, 'size':4}, 'NumberOfRvaAndSizes':{'offset':108, 'size':4}, 'ExportTableAddress':{'offset':112, 'size':4}, 'ExportTableSize':{'offset':116, 'size':4}, 'ImportTableAddress':{'offset':120, 'size':4}, 'ImportTableSize':{'offset':124, 'size':4}, 'ResourceTableAddress':{'offset':128, 'size':4}, 'ResourceTableSize':{'offset':132, 'size':4}, 'ExceptionTableAddress':{'offset':136, 'size':4}, 'ExceptionTableSize':{'offset':140, 'size':4}, 'CertificateTableAddress':{'offset':144, 'size':4}, 'CertificateTableSize':{'offset':148, 'size':4}, 'BaseRelocationTableAddress':{'offset':152, 'size':4}, 'BaseRelocationTableSize':{'offset':156, 'size':4}, 'DebugAddress':{'offset':160, 'size':4}, 'DebugSize':{'offset':164, 'size':4}, 'ArchitectureAddress':{'offset':168, 'size':4}, 'ArchitectureSize':{'offset':172, 'size':4}, 'GlobalPtrAddress':{'offset':176, 'size':8}, 'GlobalPtrSize':{'offset':184, 'size':0}, 'ThreadLocalStorageTableAddress':{'offset':184, 'size':4}, 'ThreadLocalStorageTableSize':{'offset':188, 'size':4}, 'LoadConfigTableAddress':{'offset':192, 'size':4}, 'LoadConfigTableSize':{'offset':196, 'size':4}, 'BoundImportAddress':{'offset':200, 'size':4}, 'BoundImportSize':{'offset':204, 'size':4}, 'ImportAddressTableAddress':{'offset':208, 'size':4}, 'ImportAddressTableSize':{'offset':212, 'size':4}, 'DelayImportDescriptorAddress':{'offset':216, 'size':4}, 'DelayImportDescriptorSize':{'offset':220, 'size':4}, 'COMRuntimeHeaderAddress':{'offset':224, 'size':4}, 'COMRuntimeHeaderSize':{'offset':228, 'size':4}, 'Reserved2':{'offset':232, 'size':8} } _fields_32 = {'Magic':{'offset':0, 'size':2}, 'MajorLinkerVersion':{'offset':2, 'size':1}, 'MinorLinkerVersion':{'offset':3, 'size':1}, 'SizeOfCode':{'offset':4, 'size':4}, 'SizeOfInitializedData':{'offset':8,# 'size':4}, 'SizeOfUninitializedData':{'offset':12, 'size':4}, 'AddressOfEntryPoint':{'offset':16, 'size':4}, 'BaseOfCode':{'offset':20, 'size':4}, 'BaseOfData':{'offset':24, 'size':4}, 'ImageBase':{'offset':28, 'size':4}, 'SectionAlignment':{'offset':32, 'size':4}, 'FileAlignment':{'offset':36, 'size':4}, 'MajorOperatingSystemVersion':{'offset':40, 'size':2}, 'MinorOperatingSystemVersion':{'offset':42, 'size':2}, 'MajorImageVersion':{'offset':44, 'size':2}, 'MinorImageVersion':{'offset':46, 'size':2}, 'MajorSubsystemVersion':{'offset':48, 'size':2}, 'MinorSubsystemVersion':{'offset':50, 'size':2}, 'Reserved':{'offset':52, 'size':4}, 'SizeOfImage':{'offset':56,# 'size':4}, 'SizeOfHeaders':{'offset':60, 'size':4}, 'CheckSum': {'offset': 64, 'size':4}, 'Subsystem':{'offset':68, 'size':2}, 'DLL_Characteristics':{'offset':70, 'size':2}, 'SizeOfStackReserve':{'offset':72, 'size':4}, 'SizeOfStackCommit':{'offset':76, 'size':4}, 'SizeOfHeapReserve':{'offset':80, 'size':4}, 'SizeOfHeapCommit':{'offset':84, 'size':4}, 'LoaderFlags':{'offset':88, 'size':4}, 'NumberOfRvaAndSizes':{'offset':92, 'size':4}, 'ExportTableAddress':{'offset':96, 'size':4}, 'ExportTableSize':{'offset':100, 'size':4}, 'ImportTableAddress':{'offset':104, 'size':4}, 'ImportTableSize':{'offset':108, 'size':4}, 'ResourceTableAddress':{'offset':112, 'size':4}, 'ResourceTableSize':{'offset':116,# 'size':4}, 'ExceptionTableAddress':{'offset':120, 'size':4}, 'ExceptionTableSize':{'offset':124, 'size':4}, 'CertificateTableAddress':{'offset':128, 'size':4}, 'CertificateTableSize':{'offset':132, 'size':4}, 'BaseRelocationTableAddress':{'offset':136,# 'size':4}, 'BaseRelocationTableSize':{'offset':140, 'size':4}, 'DebugAddress':{'offset':144, 'size':4}, 'DebugSize':{'offset':148, 'size':4}, 'ArchitectureAddress':{'offset':152, 'size':4}, 'ArchitectureSize':{'offset':156, 'size':4}, 'GlobalPtrAddress':{'offset':160, 'size':8}, 'GlobalPtrSize':{'offset':168, 'size':0}, 'ThreadLocalStorageTableAddress':{'offset':168, 'size':4}, 'ThreadLocalStorageTableSize':{'offset':172, 'size':4}, 'LoadConfigTableAddress':{'offset':176, 'size':4}, 'LoadConfigTableSize':{'offset':180, 'size':4}, 'BoundImportAddress':{'offset':184, 'size':4}, 'BoundImportSize':{'offset':188, 'size':4}, 'ImportAddressTableAddress':{'offset':192, 'size':4}, 'ImportAddressTableSize':{'offset':196, 'size':4}, 'DelayImportDescriptorAddress':{'offset':200, 'size':4}, 'DelayImportDescriptorSize':{'offset':204, 'size':4}, 'COMRuntimeHeaderAddress':{'offset':208, 'size':4}, 'COMRuntimeHeaderSize':{'offset':212, 'size':4}, 'Reserved2':{'offset':216, 'size':8}, } def process_DLL_Characteristics(self, file_data, field_name, field_info): self.process_Characteristics(file_data, field_name, field_info) def process_field(self, file_data, field_name, field_info): if hasattr(self, 'process_'+field_name) and callable(getattr(self, 'process_'+field_name)): getattr(self, 'process_'+field_name)(file_data, field_name, field_info) else: absolute_offset = field_info['offset'] + self.absolute_offset size = field_info['size'] self.size += size int_value, data = read_bytes(file_data, absolute_offset, size) field_name_dict = read_from_name_dict(self, field_name) name = field_name_dict.get(int_value, '') friendly_name = name.replace('_', ' ').capitalize() setattr(self, field_name, Structure(offset=field_info['offset'], size=size, value=int_value, data=data, absolute_offset=absolute_offset, name=name, friendly_name=friendly_name)) getattr(self, field_name)._file_data = file_data @classmethod def parse_from_data(cls, file_data, **cls_args): """Parses the Structure from the file data.""" self = cls(**cls_args) self._file_data = file_data magic, x = read_bytes(file_data, self.absolute_offset, 2) if magic == _32BIT_MAGIC: self._fields = self._fields_32 elif magic == _32BIT_PLUS_MAGIC: self._fields = self._fields_32_plus else: print(magic, _32BIT_MAGIC, _32BIT_PLUS_MAGIC) raise PEFormatError('Magic for Optional Header is invalid.') for field_name, field_info in self._fields.items(): self.process_field(file_data, field_name, field_info) return self class SectionHeader(Structure): """Section Header. Each section header is a row in the section table""" _fields = {'Name':{'offset':0, 'size':8}, 'VirtualSize':{'offset':8, #.rsrc 'size':4}, 'VirtualAddress':{'offset':12,#.reloc 'size':4}, 'SizeOfRawData':{'offset':16,#.rsrc 'size':4}, 'PointerToRawData':{'offset':20,#.reloc 'size':4}, 'PointerToRelocations':{'offset':24, 'size':4}, 'PointerToLineNumbers':{'offset':28, 'size':4}, 'NumberOfRelocations':{'offset':32, 'size':2}, 'NumberOfLineNumbers':{'offset':34, 'size':2}, 'Characteristics':{'offset':36, 'size':4} } class ResourceDirectoryTable(Structure): _fields = {'Characteristics':{'offset':0, 'size':4}, 'TimeDateStamp':{'offset':4, 'size':4}, 'MajorVersion':{'offset':8, 'size':2}, 'MinorVersion':{'offset':10, 'size':2}, 'NumberOfNameEntries':{'offset':12, 'size':2}, 'NumberOfIDEntries':{'offset':14, 'size':2} } def __init__(self, *args, **kwargs): self.name_entries = [] self.id_entries = [] self.subdirectory_tables = [] self.data_entries = [] super(ResourceDirectoryTable, self).__init__(*args, **kwargs) class ResourceDirectoryEntryName(Structure): _fields = {'NameRVA':{'offset':0, 'size':4}, 'DataOrSubdirectoryEntryRVA':{'offset':4, #high bit 1 for subdir RVA 'size':4} } directory_string = None def is_data_entry(self): return (test_bit(self.DataOrSubdirectoryEntryRVA.value, 31) == 0) def data_rva_empty(self): return self.get_data_or_subdirectory_rva() == 0 def get_data_or_subdirectory_rva(self, virtual_to_physical=0): return clear_bit(self.DataOrSubdirectoryEntryRVA.value-virtual_to_physical, 31) def get_data_or_subdirectory_absolute_offset(self): return self.get_data_or_subdirectory_rva() + self._section_header.PointerToRawData.value def get_name_absolute_offset(self): return clear_bit(self.NameRVA.value, 31) + self._section_header.PointerToRawData.value class ResourceDirectoryEntryID(Structure): _fields = {'IntegerID':{'offset':0, 'size':4}, 'DataOrSubdirectoryEntryRVA':{'offset':4,#high bit 1 for Subdir RVA 'size':4} } def is_data_entry(self): return (test_bit(self.DataOrSubdirectoryEntryRVA.value, 31) == 0) def data_rva_empty(self): return self.get_data_or_subdirectory_rva() == 0 def get_data_or_subdirectory_rva(self, virtual_to_physical=0): return clear_bit(self.DataOrSubdirectoryEntryRVA.value-virtual_to_physical, 31) def get_data_or_subdirectory_absolute_offset(self, vtp=0): return self.get_data_or_subdirectory_rva(vtp) + self._section_header.PointerToRawData.value class ResourceDirectoryString(Structure): _fields = {'Length':{'offset':0, 'size':2}, #String : offset=2, len=Length } @classmethod def parse_from_data(cls, file_data, **cls_args): """Parses the Structure from the file data.""" self = cls(**cls_args) self._file_data = file_data str_len, _ = read_bytes(file_data, self.absolute_offset, 2) self._fields['String'] = {'offset':2, 'size':str_len} for field_name, field_info in self._fields.items(): self.process_field(file_data, field_name, field_info) return self def process_String(self, file_data, field_name, field_info): absolute_offset = field_info['offset'] + self.absolute_offset size = field_info['size'] self.size += size data = u'' for i in range(size): val, dat = read_bytes(file_data, absolute_offset+i*2,2) data += str(dat, 'utf-8') setattr(self, field_name, Structure(offset=field_info['offset'], size=size, data=data, absolute_offset=absolute_offset, )) class ResourceDataEntry(Structure): _fields = {'DataRVA':{'offset':0, 'size':4}, 'Size':{'offset':4, 'size':4}, 'Codepage':{'offset':8, 'size':4}, 'Reserved':{'offset':12, 'size':4}, } def get_data_absolute_offset(self): return self._section_header.PointerToRawData.value - self._section_header.VirtualAddress.value + self.DataRVA.value def process_field(self, file_data, field_name, field_info): if hasattr(self, 'process_'+field_name) and callable(getattr(self, 'process_'+field_name)): getattr(self, 'process_'+field_name)(file_data, field_name, field_info) else: absolute_offset = field_info['offset'] + self.absolute_offset size = field_info['size'] self.size += size int_value, data = read_bytes(file_data, absolute_offset, size) field_name_dict = read_from_name_dict(self, field_name) name = field_name_dict.get(int_value, '') friendly_name = name.replace('_', ' ').capitalize() setattr(self, field_name, Structure(offset=field_info['offset'], size=size, value=int_value, data=data, absolute_offset=absolute_offset, name=name, friendly_name=friendly_name)) getattr(self, field_name)._file_data = file_data @classmethod def parse_from_data(cls, file_data, **cls_args): """Parses the Structure from the file data.""" self = cls(**cls_args) self._file_data = file_data for field_name, field_info in self._fields.items(): self.process_field(file_data, field_name, field_info) self.data = read_data(file_data, self.get_data_absolute_offset(), self.Size.value) return self class ResourceHeader(Structure): _fields = {'DataSize':{'offset':0, 'size':4}, 'HeaderSize':{'offset':4, 'size':4}, 'Type':{'offset':8, 'size':4}, 'Name':{'offset':12, 'size':4}, 'DataVersion':{'offset':16, 'size':4}, 'MemoryFlags':{'offset':20, 'size':2}, 'LanguageID':{'offset':22, 'size':2}, 'Version':{'offset':24, 'size':4}, 'Characteristics':{'offset':28, 'size':4}, } def get_name(self): return resource_types[self.Type.value] def set_name(self, value): for k,v in resource_types.items(): if v == value: self.Type.value = k return class IconHeader(Structure): _fields = {'Reserved':{'offset':0, 'size':2}, 'ImageType':{'offset':2,#1 for ICO, 2 for CUR, others invalid 'size':2}, 'ImageCount':{'offset':4, 'size':2}, } def copy_from(self, group_header): self.Reserved.value = group_header.Reserved.value self.ImageType.value = group_header.ResourceType.value self.ImageCount.value = group_header.ResourceCount.value self.entries = [] entry_offset = 0 self.total_size = self.size for group_entry in group_header.entries: icon_entry = IconEntry.parse_from_data(bytearray(''), absolute_offset=self.absolute_offset+self.size+entry_offset, offset=entry_offset) icon_entry._file_data = self._file_data icon_entry.copy_from(group_entry) icon_entry.number = group_entry.number self.entries.append(icon_entry) entry_offset += icon_entry.size self.total_size += icon_entry.size @classmethod def parse_from_data(cls, file_data, **cls_args): """Parses the Structure from the file data.""" self = cls(**cls_args) self._file_data = file_data for field_name, field_info in self._fields.items(): self.process_field(file_data, field_name, field_info) self.entries = [] entry_offset = 0 self.total_size = self.size for i in range(self.ImageCount.value): entry = IconEntry.parse_from_data(file_data, absolute_offset=self.absolute_offset+self.size+entry_offset, offset=entry_offset) entry.number = i + 1 self.entries.append(entry) entry_offset += entry.size self.total_size += entry.size return self class GroupHeader(Structure): _fields = {'Reserved':{'offset':0, 'size':2}, 'ResourceType':{'offset':2,#1 for ICO, 2 for CUR, others invalid 'size':2}, 'ResourceCount':{'offset':4, 'size':2}, } def copy_from(self, icon_header): self.Reserved._file_data = self._file_data self.ResourceType._file_data = self._file_data self.ResourceCount._file_data = self._file_data self.Reserved.value = icon_header.Reserved.value self.ResourceType.value = icon_header.ImageType.value self.ResourceCount.value = icon_header.ImageCount.value self.entries = [] entry_offset = 0 self.total_size = self.size for icon_entry in icon_header.entries: group_entry = GroupEntry.parse_from_data(bytearray(b''), absolute_offset=self.absolute_offset+self.size+entry_offset, offset=entry_offset) group_entry._file_data = self._file_data group_entry.copy_from(icon_entry) group_entry.number = icon_entry.number self.entries.append(group_entry) entry_offset += group_entry.size self.total_size += group_entry.size @classmethod def parse_from_data(cls, file_data, **cls_args): """Parses the Structure from the file data.""" self = cls(**cls_args) self._file_data = file_data for field_name, field_info in self._fields.items(): self.process_field(file_data, field_name, field_info) self.entries = [] entry_offset = 0 self.total_size = self.size for i in range(self.ResourceCount.value): entry = GroupEntry.parse_from_data(file_data, absolute_offset=self.absolute_offset+self.size+entry_offset, offset=entry_offset) entry.number = i + 1 self.entries.append(entry) entry_offset += entry.size self.total_size += entry.size return self class IconEntry(Structure): _fields = {'Width':{'offset':0, 'size':1}, 'Height':{'offset':1, 'size':1}, 'ColorCount':{'offset':2, 'size':1}, 'Reserved':{'offset':3, 'size':1}, 'ColorPlanes':{'offset':4, 'size':2}, 'BitCount':{'offset':6, #bits per pixel 'size':2}, 'DataSize':{'offset':8, 'size': 4}, 'OffsetToData':{'offset':12, #from start of file 'size':4}, } def copy_from(self, group_entry, entries): self.Width.value = group_entry.Width.value self.Height.value = group_entry.Height.value self.ColorCount.value = group_entry.ColorCount.value self.Reserved.value = group_entry.Reserved.value self.ColorPlanes.value = group_entry.ColorPlanes.value self.BitCount.value = group_entry.BitCount.value self.DataSize.value = group_entry.DataSize.value self.OffsetToData.value = self._get_entry_offset(group_entry, entries) def _get_entry_offset(self, group_entry, group_entries): offset = 6 #Default icon header size offset += self.size * len(group_entries) for i in range(group_entry.number-1): offset += group_entries[i].DataSize.value return offset @classmethod def parse_from_data(cls, file_data, **cls_args): """Parses the Structure from the file data.""" self = cls(**cls_args) self._file_data = file_data for field_name, field_info in self._fields.items(): self.process_field(file_data, field_name, field_info) self.data = read_data(file_data, self.OffsetToData.value, self.DataSize.value) return self class GroupEntry(Structure): _fields = {'Width':{'offset':0, 'size':1}, 'Height':{'offset':1, 'size':1}, 'ColorCount':{'offset':2, 'size':1}, 'Reserved':{'offset':3, 'size':1}, 'ColorPlanes':{'offset':4, 'size':2}, 'BitCount':{'offset':6, 'size':2}, 'DataSize':{'offset':8, 'size': 4}, 'IconCursorId':{'offset':12, 'size':2}, } def copy_from(self, icon_entry): self.Width._file_data = self._file_data self.Height._file_data = self._file_data self.ColorCount._file_data = self._file_data self.Reserved._file_data = self._file_data self.ColorPlanes._file_data = self._file_data self.BitCount._file_data = self._file_data self.DataSize._file_data = self._file_data self.IconCursorId._file_data = self._file_data self.Width.value = icon_entry.Width.value self.Height.value = icon_entry.Height.value self.ColorCount.value = icon_entry.ColorCount.value self.Reserved.value = icon_entry.Reserved.value self.ColorPlanes.value = icon_entry.ColorPlanes.value self.BitCount.value = icon_entry.BitCount.value self.DataSize.value = icon_entry.DataSize.value self.IconCursorId.value = icon_entry.number class PEFile(Printable): """Reads a portable exe file in either big or little endian. Right now this only reads the .rsrc section. """ signature = b'MZ' dos_header = None def __init__(self, file_path, endian='little'): self.file_path = os.path.abspath(os.path.expanduser(file_path)) self.endian = endian if not self.is_PEFile(): raise PEFormatError('File is not a proper portable executable formatted file!') self.pe_file_data = bytearray(open(self.file_path,'rb').read()) self.dos_header = DOSHeader.parse_from_data(self.pe_file_data) self.pe_header = PEHeader.parse_from_data(self.pe_file_data, absolute_offset=self.dos_header.PEHeaderOffset.value) self.optional_header = OptionalHeader.parse_from_data(self.pe_file_data, absolute_offset=self.pe_header.size+self.pe_header.absolute_offset) number_of_sections = self.pe_header.NumberOfSections.value section_size = 40 section_offset = self.pe_header.size+self.pe_header.absolute_offset+self.pe_header.SizeOfOptionalHeader.value self.sections = {} for section_number in range(number_of_sections): section_header = SectionHeader.parse_from_data(self.pe_file_data, absolute_offset=section_offset) section_offset += section_size header_name = str(section_header.Name.data, 'utf-8').strip('\x00') self.sections[header_name] = section_header if section_header.PointerToLineNumbers.value != 0: print('{} section contains line number COFF table, which is not implemented yet.'.format(section_header.Name)) if section_header.PointerToRelocations.value != 0: print('{} section contains relocation table, which is not implemented yet.'.format(section_header.Name)) if section_header.Name.data == b'.rsrc\x00\x00\x00': current_table_pointer = section_header.PointerToRawData.value current_resource_directory_table = ResourceDirectoryTable.parse_from_data(self.pe_file_data, absolute_offset=current_table_pointer, _section_header=section_header, type=None) self.resource_directory_table = current_resource_directory_table cur_level = 0 stack = [(current_resource_directory_table, cur_level)] delta = section_header.VirtualAddress.value - section_header.PointerToRawData.value while stack: resource_directory_table, level = stack.pop() num_name_entries = resource_directory_table.NumberOfNameEntries.value num_id_entries = resource_directory_table.NumberOfIDEntries.value current_offset = resource_directory_table.absolute_offset + resource_directory_table.size for i in range(num_name_entries): name_entry = ResourceDirectoryEntryName.parse_from_data(self.pe_file_data, absolute_offset=current_offset, _section_header=section_header) current_offset += name_entry.size string_offset = name_entry.get_name_absolute_offset() name_entry.directory_string = ResourceDirectoryString.parse_from_data(self.pe_file_data, absolute_offset=string_offset, _section_header=section_header) offset = name_entry.get_data_or_subdirectory_absolute_offset() if not name_entry.data_rva_empty(): if name_entry.is_data_entry(): rd = ResourceDataEntry.parse_from_data(self.pe_file_data, absolute_offset=offset, _section_header=section_header) resource_directory_table.data_entries.append(rd) else: rd = ResourceDirectoryTable.parse_from_data(self.pe_file_data, absolute_offset=offset, _section_header=section_header, type=None) resource_directory_table.subdirectory_tables.append(rd) stack.append((rd, level+1)) resource_directory_table.name_entries.append(name_entry) for i in range(num_id_entries): id_entry = ResourceDirectoryEntryID.parse_from_data(self.pe_file_data, absolute_offset=current_offset, _section_header=section_header) current_offset += id_entry.size offset = id_entry.get_data_or_subdirectory_absolute_offset() if id_entry.is_data_entry(): rd = ResourceDataEntry.parse_from_data(self.pe_file_data, absolute_offset=offset, _section_header=section_header) resource_directory_table.data_entries.append(rd) else: id_entry.name = str(id_entry.IntegerID.value) if level+1 == 1: id_entry.name = resource_types[id_entry.IntegerID.value] rd = ResourceDirectoryTable.parse_from_data(self.pe_file_data, absolute_offset=offset, _section_header=section_header, type=id_entry.IntegerID.value) resource_directory_table.subdirectory_tables.append(rd) stack.append((rd, level+1)) resource_directory_table.id_entries.append(id_entry) def replace_icon(self, icon_path): """Replaces an icon in the pe file with the one specified. This only replaces the largest icon and resizes the input image to match so that the data is undisturbed. I tried to update the pointers automatically by moving the data to the end of the file, but that did not work. Comments were left as history to what I attempted. """ icon_path = os.path.expanduser(icon_path) #this needs to be a string and not unicode if not os.path.exists(icon_path): raise Exception('Icon {} does not exist'.format(icon_path)) resource_section = self.sections['.rsrc'] g_icon_dir = self.get_directory_by_type(ResourceTypes.Group_Icon) g_icon_data_entry = g_icon_dir.subdirectory_tables[0].data_entries[0] icon_dir = self.get_directory_by_type(ResourceTypes.Icon) icon_data_entry = icon_dir.subdirectory_tables[0].data_entries[0] group_header = GroupHeader.parse_from_data(self.pe_file_data, absolute_offset=g_icon_data_entry.get_data_absolute_offset()) g_entry = group_header.entries[0] icon = Image.open(icon_path) width = g_entry.Width.value height = g_entry.Height.value if width == 0: width = 256 if height == 0: height = 256 i_data = resize(icon, (width, height), format='ico') new_icon_size = len(i_data) icon_file_size = g_entry.DataSize.value+group_header.size+g_entry.size+2 #9662 is the exact length of the icon in nw.exe extra_size = icon_file_size-new_icon_size if extra_size < 0: extra_size = 0 icon_data = bytearray(i_data) + bytearray(extra_size) icon_header = IconHeader.parse_from_data(icon_data, absolute_offset=0) #group_header.absolute_offset = len(self.pe_file_data) #g_icon_data_entry.DataRVA.value = len(self.pe_file_data) - resource_section.PointerToRawData.value + resource_section.VirtualAddress.value #padding = 6+14*len(icon_header.entries) #g_icon_data_entry.Size.value = padding #self.pe_file_data += bytearray(padding) group_header._file_data = self.pe_file_data group_header.copy_from(icon_header) #icon_data_entry.DataRVA.value = len(self.pe_file_data) - resource_section.PointerToRawData.value + resource_section.VirtualAddress.value #print hex(icon_data_entry.DataRVA.value), hex(len(self.pe_file_data)), hex(icon_data_entry.get_data_absolute_offset()) #print hex(read_bytes(self.pe_file_data[icon_data_entry.DataRVA.absolute_offset:icon_data_entry.DataRVA.absolute_offset+icon_data_entry.DataRVA.size],0, icon_data_entry.DataRVA.size)[0]) #data = bytearray() #for entry in icon_header.entries: #data += entry.data #self.pe_file_data += entry.data #icon_data_entry.Size.value = len(data) #self.optional_header.SizeOfImage.value = self.optional_header.SizeOfImage.value + len(data) + padding #self.optional_header.ResourceTableSize.value = self.optional_header.ResourceTableSize.value + len(data) + padding #self.optional_header.SizeOfInitializedData.value = self.optional_header.SizeOfInitializedData.value + len(data) + padding #resource_section.SizeOfRawData.value = resource_section.SizeOfRawData.value + len(data) + padding #resource_section.VirtualSize.value = resource_section.VirtualSize.value + len(data) + padding #print icon_header.total_size data_address = icon_data_entry.get_data_absolute_offset() data_size = icon_data_entry.Size.value self.pe_file_data = self.pe_file_data[:data_address] + icon_data[icon_header.total_size:] + self.pe_file_data[data_address+data_size:] def write(self, file_name): with open(file_name, 'wb+') as f: f.write(self.pe_file_data) def get_directory_by_type(self, type): """Gets the directory by resource type.""" for d in self.resource_directory_table.subdirectory_tables: if d.type == type: return d def is_PEFile(self): """Checks if the file is a proper PE file""" signature = None try: with open(self.file_path,'rb') as f: signature = f.read(2) except IOError as e: raise e finally: return signature == self.signature