#!/usr/bin/env python # -*- coding: utf-8 -*- # # gpefile.py # # Copyright 2012 Mauro A. Meloni # http://www.maurom.com/ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # # Version history: # # 2012.03.28 0.19 Agregado despliegue de RT_STRING, GIF y PNG # 2012.03.28 0.18 Agregado despliegue de RT_GROUP_CURSOR y RT_GROUP_ICON # 2012.03.27 0.17 Agregado despliegue de StringFileInfo y VarFileInfo # 2012.03.23 0.16 Agregado inicipiente despliegue de RT_BITMAP # 2012.03.20 0.15 Agregado despliegue de Resource Data # 2012.03.19 0.14 Agregado despliegue de Relocations # 2012.03.19 0.13 Agregado despliegue de Bound Import Tables # 2012.03.09 0.12 Agregadas las vistas de offset en formato RVA y VA # 2012.03.08 0.11 Agregadas las vistas de offset en formato pView # 2012.03.01 0.10 Primera version publica # import ConfigParser from os.path import basename, expanduser from struct import calcsize, pack, unpack, unpack_from import sys import time try: import pefile except ImportError: print "This program requires the python-pefile library. Sorry!\n" sys.exit(1) try: import pygtk pygtk.require('2.0') import gtk except ImportError: print "This program requires the python-gtk2 library. Sorry!\n" sys.exit(1) APP_NAME = 'gPEfile' APP_VERSION = '0.19' APP_URL = 'http://www.maurom.com/' CONFIG_FILE = expanduser('~/.gpefile') ADDR_FMT_FILE_OFFSET = 1 ADDR_FMT_VIEW_OFFSET = 2 ADDR_FMT_RELATIVE_VA = 3 ADDR_FMT_VIRTUAL_ADD = 4 dwFileFlags = [ ('VS_FF_DEBUG', 0x00000001L), ('VS_FF_INFOINFERRED', 0x00000010L), ('VS_FF_PATCHED', 0x00000004L), ('VS_FF_PRERELEASE', 0x00000002L), ('VS_FF_PRIVATEBUILD', 0x00000008L), ('VS_FF_SPECIALBUILD', 0x00000020L) ] FILE_FLAGS = dict([(e[1], e[0]) for e in dwFileFlags] + dwFileFlags) dwFileOS = [ ('VOS_DOS', 0x00010000L), ('VOS_NT', 0x00040000L), ('VOS__WINDOWS16', 0x00000001L), ('VOS__WINDOWS32', 0x00000004L), ('VOS_OS216', 0x00020000L), ('VOS_OS232', 0x00030000L), ('VOS__PM16', 0x00000002L), ('VOS__PM32', 0x00000003L), ('VOS_UNKNOWN', 0x00000000L) ] FILE_OS = dict([(e[1], e[0]) for e in dwFileOS] + dwFileOS) dwFileType = [ ('VFT_APP', 0x00000001L), ('VFT_DLL', 0x00000002L), ('VFT_DRV', 0x00000003L), ('VFT_FONT', 0x00000004L), ('VFT_STATIC_LIB', 0x00000007L), ('VFT_UNKNOWN', 0x00000000L), ('VFT_VXD', 0x00000005L) ] FILE_TYPE = dict([(e[1], e[0]) for e in dwFileType] + dwFileType) dwFileSubtype = [ ('VFT2_DRV_COMM', 0x0000000AL), ('VFT2_DRV_DISPLAY', 0x00000004L), ('VFT2_DRV_INSTALLABLE',0x00000008L), ('VFT2_DRV_KEYBOARD', 0x00000002L), ('VFT2_DRV_LANGUAGE', 0x00000003L), ('VFT2_DRV_MOUSE', 0x00000005L), ('VFT2_DRV_NETWORK', 0x00000006L), ('VFT2_DRV_PRINTER', 0x00000001L), ('VFT2_DRV_SOUND', 0x00000009L), ('VFT2_DRV_SYSTEM', 0x00000007L), ('VFT2_DRV_VERSIONED_PRINTER', 0x0000000CL), ('VFT2_UNKNOWN', 0x00000000L) ] FILE_SUBTYPE = dict([(e[1], e[0]) for e in dwFileSubtype] + dwFileSubtype) dwFileSubtypeFont = [ ('VFT2_FONT_RASTER', 0x00000001L), ('VFT2_FONT_TRUETYPE', 0x00000003L), ('VFT2_FONT_VECTOR', 0x00000002L), ('VFT2_UNKNOWN', 0x00000000L) ] FILE_SUBTYPE_FONT = dict([(e[1], e[0]) for e in dwFileSubtypeFont] + dwFileSubtypeFont) def format_data (instr): 'Return a string with non-printable characters masked as dots' outstr = '' for char in instr: if ord(char) >= 32 and ord(char) < 127: outstr += char else: outstr += '.' # outstr = outstr.decode('latin1').encode('utf-8') return outstr def format_timestamp (timestamp): 'Return a string representing the given timestamp, or empty when ts is 0' if not timestamp: return '' return time.asctime(time.gmtime(timestamp)) + ' UTC' def message_dialog (parent, msg, _type=gtk.MESSAGE_ERROR, \ buttons=gtk.BUTTONS_OK): 'Show a message in a dialog box on screen' dialog = gtk.MessageDialog(parent, \ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, \ _type, buttons, msg) response = dialog.run() dialog.destroy() return response def section_contains_offset (section, offset): 'Return True if the given offset is between that section boundaries' section_end = section.PointerToRawData + section.SizeOfRawData return section.PointerToRawData <= offset < section_end def section_by_offset (peobj, offset): 'Return a section of a PE file which contains the given offset' for section in peobj.sections: if section_contains_offset(section, offset): return section return None def get_rva_from_offset (peobj, offset): 'Return a Relative Virtual Address given an offset on file' section = section_by_offset(peobj, offset) if section is None: raise pefile.PEFormatError("specified offset (0x%x) doesn't belong to any section." % offset) return offset - section.PointerToRawData + section.VirtualAddress def get_va_from_offset (peobj, offset): 'Return an Absolute Virtual Address given an offset on file' try: return get_rva_from_offset(peobj, offset) + \ peobj.OPTIONAL_HEADER.ImageBase except pefile.PEFormatError: return offset + peobj.OPTIONAL_HEADER.ImageBase class BMPFILE: 'Represents a Bitmap File' # http://msdn.microsoft.com/en-us/library/dd183374%28v=vs.85%29.aspx class BITMAPFILEHEADER: def __init__ (self): self.format = '=2sIHHI' self.bfType = 'BM' self.bfSize = 0 self.bfReserved1 = 0 self.bfReserved2 = 0 self.bfOffBits = 0 def sizeof (self): return calcsize(self.format) def pack (self): return pack(self.format, self.bfType, self.bfSize, \ self.bfReserved1, self.bfReserved2, self.bfOffBits) def __init__ (self, data): self.data = data self.bfheader = BMPFILE.BITMAPFILEHEADER() self.bfheader.bfSize = len(data) + self.bfheader.sizeof() biSize = unpack_from('I', data, 0)[0] biBitCount = unpack_from('H', data, 14)[0] biClrUsed = unpack_from('I', data, 32)[0] if biBitCount > 8: num = 0 elif biClrUsed == 0: num = 2 ** biBitCount else: num = biClrUsed self.bfheader.bfOffBits = self.bfheader.sizeof() + biSize + num * 4 def export (self, filename): 'Export this RT_BITMAP resource to a file' fout = open(filename, 'wb') fout.write(self.bfheader.pack()) fout.write(self.data) fout.close() class ICOCURFILE: 'Represents a Icon/Cursor File' # see http://www.devsource.com/c/a/Architecture/Resources-From-PE-I/2/ # and http://www.devsource.com/c/a/Architecture/Retrieve-Resources-PE-II/ class HEADER: def __init__ (self, data): self.format = '=HHH' self.wReserved, self.wResID, self.wNumImages = \ unpack_from(self.format, data, 0) def sizeof (self): return calcsize(self.format) class SMALLDIRENTRY: def __init__ (self, data): self.format = '=HHHHIH' self.wWidth, self.wHeight, self.wPlanes, self.wBitCount, \ self.dwBytesInImage, self.wID = \ unpack_from(self.format, data, 0) def sizeof (self): return calcsize(self.format) class BIGDIRENTRY: def __init__ (self): self.format = '=BBBBHHII' self.bWidth = 0 self.bHeight = 0 self.bColorCount = 0 self.bReserved = 0 # wHotspotX for RT_CURSOR # wPlanes for RT_ICON self.wHotspotX = 0 # wHotspotX for RT_CURSOR # wBitCount for RT_ICON self.wHotspotY = 0 self.dwBytesInImage = 0 self.dwImageOffset = 0 def sizeof (self): return calcsize(self.format) def pack (self): return pack(self.format, self.bWidth, self.bHeight, self.bColorCount, self.bReserved, self.wHotspotX, self.wHotspotY, self.dwBytesInImage, self.dwImageOffset) def __init__ (self, peobj, data, is_cursor): self.peobj = peobj self.data = data self.is_cursor = is_cursor self.header = ICOCURFILE.HEADER(data) offset = self.header.sizeof() self.smalldes = [] self.bigdes = {} dwOffset = self.header.sizeof() dwOffset+= self.header.wNumImages * ICOCURFILE.BIGDIRENTRY().sizeof() for i in xrange(self.header.wNumImages): smallde = ICOCURFILE.SMALLDIRENTRY(data[offset:]) self.smalldes.append(smallde) offset += smallde.sizeof() bigde = ICOCURFILE.BIGDIRENTRY() bigde.bWidth = smallde.wWidth bigde.bHeight = smallde.wHeight / 2 if self.is_cursor: rt_offset, rt_size = self.__find_resource_data__(1, smallde.wID) data = self.peobj.get_data(rt_offset, 4) bigde.wHotspotX, bigde.wHotspotY = unpack('=HH', data) bigde.dwBytesInImage = smallde.dwBytesInImage - 4 else: self.wHotspotX = smallde.wPlanes self.wHotspotY = smallde.wBitCount bigde.dwBytesInImage = smallde.dwBytesInImage bigde.dwImageOffset = dwOffset dwOffset += bigde.dwBytesInImage self.bigdes[smallde.wID] = bigde def __find_resource_data__ (self, parent_id, child_id): 'Return offset and size of data for a given parent and children id' resdir = self.peobj.DIRECTORY_ENTRY_RESOURCE rt_parent = None for entry in resdir.entries: if entry.struct.Name == parent_id: rt_parent = entry if rt_parent is None: return None for entry in rt_parent.directory.entries: if entry.id == child_id: struct = entry.directory.entries[0].data.struct return struct.OffsetToData, struct.Size return None def export (self, filename): 'Export this RT_GROUP_CURSOR resource to a file' hdrlen = self.header.sizeof() fout = open(filename, 'wb') fout.write(self.data[0:hdrlen]) for i in xrange(self.header.wNumImages): fout.write(self.bigdes[self.smalldes[i].wID].pack()) for i in xrange(self.header.wNumImages): if self.is_cursor: rt_offset, rt_size = \ self.__find_resource_data__(1, self.smalldes[i].wID) data = self.peobj.get_data(rt_offset + 4, rt_size - 4) else: rt_offset, rt_size = \ self.__find_resource_data__(3, self.smalldes[i].wID) data = self.peobj.get_data(rt_offset, rt_size) fout.write(data) fout.close() class PEModel: 'Represents the PE file and provides models of its structures' def __init__ (self): self.datasize = 4 self.address_format = ADDR_FMT_FILE_OFFSET self.filename = None self.peobj = None self.image = None self.last_error = None self.res_types = {} def clear (self): 'Clear most of the atributes and reset the model' self.filename = None self.peobj = None self.image = None self.last_error = None def get_last_error (self): 'Return the last error registered when trying to analyze the PE file' string = self.last_error self.last_error = None return string def load_file (self, filename): 'Load and analyse the PE file given' try: newpe = pefile.PE(filename) except pefile.PEFormatError, exeption: self.last_error = str(exeption) return False self.filename = filename self.peobj = newpe self.image = open(filename, 'rb').read() return True def fmtaddr (self, address): 'Format an address given address_format' return '%08X' % address def get_fmt_address (self, offset): 'Return an initial address according to the selected format' if self.address_format == ADDR_FMT_FILE_OFFSET: return offset elif self.address_format == ADDR_FMT_VIEW_OFFSET: return 0 elif self.address_format == ADDR_FMT_RELATIVE_VA: try: return get_rva_from_offset(self.peobj, offset) except pefile.PEFormatError: return offset elif self.address_format == ADDR_FMT_VIRTUAL_ADD: return get_va_from_offset(self.peobj, offset) def get_data (self, offset=0, count=None): 'Return a list-model in hex format of data starting at offset' liststore = gtk.ListStore(str, str, str) i = 0 if count is None: count = len(self.image) else: count = min(count, self.image) base = self.get_fmt_address(offset) col1 = self.fmtaddr(base + i) col2 = '' col3 = '' while i < count: char = self.image[offset + i] i += 1 col2 += '%02x' % ord(char) col3 += format_data(char) if (i % self.datasize) == 0: col2 += ' ' if self.datasize < 4 and (i % 8) == 0: col2 += ' ' if (i % 0x10) == 0: liststore.append([col1, col2, col3]) col1 = self.fmtaddr(base + i) col2 = '' col3 = '' if col2: liststore.append([col1, col2, col3]) return liststore def get_dos_header (self): 'Return a list-model of the DOS header of the PE file' liststore = gtk.ListStore(str, str, str, str) struct = self.peobj.DOS_HEADER offset = self.get_fmt_address(struct.get_file_offset()) if struct.e_magic == pefile.IMAGE_DOS_SIGNATURE: e_magic_info = 'IMAGE_DOS_SIGNATURE %s' elif struct.e_magic == pefile.IMAGE_DOSZM_SIGNATURE: e_magic_info = 'IMAGE_DOSZM_SIGNATURE %s' elif struct.e_magic == pefile.IMAGE_NE_SIGNATURE: e_magic_info = 'IMAGE_NE_SIGNATURE %s' elif struct.e_magic == pefile.IMAGE_LE_SIGNATURE: e_magic_info = 'IMAGE_LE_SIGNATURE %s' elif struct.e_magic == pefile.IMAGE_LX_SIGNATURE: e_magic_info = 'IMAGE_LX_SIGNATURE %s' else: e_magic_info = '%s' liststore.append([ self.fmtaddr(offset + 0x00), '%04x' % struct.e_magic, 'Signature', e_magic_info % format_data(pack('H', struct.e_magic)) ]) liststore.append([self.fmtaddr(offset + 0x02), '%04x' % struct.e_cblp, 'Bytes on Last Page of File', None]) liststore.append([self.fmtaddr(offset + 0x04), '%04x' % struct.e_cp, 'Pages in File', None]) liststore.append([self.fmtaddr(offset + 0x06), '%04x' % struct.e_crlc, 'Relocations', None]) liststore.append([self.fmtaddr(offset + 0x08), '%04x' % struct.e_cparhdr, 'Size of Header in Paragraphs', None]) liststore.append([self.fmtaddr(offset + 0x0A), '%04x' % struct.e_minalloc, 'Minimum Extra Paragraphs', None]) liststore.append([self.fmtaddr(offset + 0x0C), '%04x' % struct.e_maxalloc, 'Maximum Extra Paragraphs', None]) liststore.append([self.fmtaddr(offset + 0x0E), '%04x' % struct.e_ss, 'Initial (relative) SS', None]) liststore.append([self.fmtaddr(offset + 0x10), '%04x' % struct.e_sp, 'Initial SP', None]) liststore.append([self.fmtaddr(offset + 0x12), '%04x' % struct.e_csum, 'Checksum', None]) liststore.append([self.fmtaddr(offset + 0x14), '%04x' % struct.e_ip, 'Initial IP', None]) liststore.append([self.fmtaddr(offset + 0x16), '%04x' % struct.e_cs, 'Initial (relative) CS', None]) liststore.append([self.fmtaddr(offset + 0x18), '%04x' % struct.e_lfarlc, 'Offset to Relocation Table', None]) liststore.append([self.fmtaddr(offset + 0x1A), '%04x' % struct.e_ovno, 'Overlay Number', None]) liststore.append([self.fmtaddr(offset + 0x1C), format_data(struct.e_res), 'Reserved', None]) liststore.append([self.fmtaddr(offset + 0x24), '%04x' % struct.e_oemid, 'OEM Identifier', None]) liststore.append([self.fmtaddr(offset + 0x26), '%04x' % struct.e_oeminfo , 'OEM Information', None]) liststore.append([self.fmtaddr(offset + 0x28), format_data(struct.e_res2), 'Reserved', None]) liststore.append([self.fmtaddr(offset + 0x3C), '%08x' % struct.e_lfanew , 'Offset to New EXE Header', None]) return liststore def get_signature (self): 'Return a list-model of the Signature header of the PE file' liststore = gtk.ListStore(str, str, str, str) struct = self.peobj.NT_HEADERS offset = self.get_fmt_address(struct.get_file_offset()) if struct.Signature == pefile.IMAGE_NT_SIGNATURE: info = 'IMAGE_NT_SIGNATURE %s' else: info = '%s' liststore.append([ self.fmtaddr(offset), '%08x' % struct.Signature, 'Signature', info % format_data(pack('I', struct.Signature)) ]) return liststore def get_file_header (self): 'Return a list-model of the FILE header of the PE file' liststore = gtk.ListStore(str, str, str, str) struct = self.peobj.FILE_HEADER offset = self.get_fmt_address(struct.get_file_offset()) liststore.append([ self.fmtaddr(offset + 0x00), '%04x' % struct.Machine, 'Machine', pefile.MACHINE_TYPE[struct.Machine] ]) liststore.append([self.fmtaddr(offset + 0x02), '%04x' % struct.NumberOfSections, 'Number of Sections', None]) liststore.append([self.fmtaddr(offset + 0x04), '%08x' % struct.TimeDateStamp, 'Time Date Stamp', format_timestamp(struct.TimeDateStamp)]) liststore.append([self.fmtaddr(offset + 0x08), '%08x' % struct.PointerToSymbolTable, 'Pointer to Symbol Table', None]) liststore.append([self.fmtaddr(offset + 0x0C), '%08x' % struct.NumberOfSymbols, 'Number of Symbols', None]) liststore.append([self.fmtaddr(offset + 0x10), '%04x' % struct.SizeOfOptionalHeader, 'Size of Optional Header', None]) liststore.append([self.fmtaddr(offset + 0x12), '%04x' % struct.Characteristics, 'Characteristics', None]) dicc = pefile.IMAGE_CHARACTERISTICS char = struct.Characteristics for mask in dicc.keys(): if (isinstance(mask, int) or isinstance(mask, long)) \ and char & mask: liststore.append([None, None, '%04x' % mask, dicc[mask]]) return liststore def get_optional_header (self): 'Return a list-model of the OPTIONAL header of the PE file' liststore = gtk.ListStore(str, str, str, str) struct = self.peobj.OPTIONAL_HEADER offset = self.get_fmt_address(struct.get_file_offset()) liststore.append([self.fmtaddr(offset + 0x00), '%04x' % struct.Magic, 'Magic', None]) liststore.append([self.fmtaddr(offset + 0x02), '%02x' % struct.MajorLinkerVersion, 'Major Linker Version' , None]) liststore.append([self.fmtaddr(offset + 0x03), '%02x' % struct.MinorLinkerVersion, 'Minor Linker Version' , None]) liststore.append([self.fmtaddr(offset + 0x04), '%08x' % struct.SizeOfCode, 'Size of Code', None]) liststore.append([self.fmtaddr(offset + 0x08), '%08x' % struct.SizeOfInitializedData, 'Size of Initialized Data', None]) liststore.append([self.fmtaddr(offset + 0x0C), '%08x' % struct.SizeOfUninitializedData, 'Size of Uninitialized Data', None]) liststore.append([self.fmtaddr(offset + 0x10), '%08x' % struct.AddressOfEntryPoint, 'Address of Entry Point', None]) liststore.append([self.fmtaddr(offset + 0x14), '%08x' % struct.BaseOfCode, 'Base of Code', None]) liststore.append([self.fmtaddr(offset + 0x18), '%08x' % struct.BaseOfData, 'Base of Data', None]) liststore.append([self.fmtaddr(offset + 0x1C), '%08x' % struct.ImageBase, 'Image Base', None]) liststore.append([self.fmtaddr(offset + 0x20), '%08x' % struct.SectionAlignment, 'Section Alignment', None]) liststore.append([self.fmtaddr(offset + 0x24), '%08x' % struct.FileAlignment, 'File Alignment', None]) liststore.append([self.fmtaddr(offset + 0x28), '%04x' % struct.MajorOperatingSystemVersion, 'Major O/S Version', None]) liststore.append([self.fmtaddr(offset + 0x2A), '%04x' % struct.MinorOperatingSystemVersion, 'Minor O/S Version', None]) liststore.append([self.fmtaddr(offset + 0x2C), '%04x' % struct.MajorImageVersion , 'Major Image Version' , None]) liststore.append([self.fmtaddr(offset + 0x2E), '%04x' % struct.MinorImageVersion , 'Minor Image Version' , None]) liststore.append([self.fmtaddr(offset + 0x30), '%04x' % struct.MajorSubsystemVersion, 'Major Subsystem Version', None]) liststore.append([self.fmtaddr(offset + 0x32), '%04x' % struct.MinorSubsystemVersion, 'Minor Subsystem Version', None]) liststore.append([self.fmtaddr(offset + 0x34), '%08x' % struct.Reserved1, 'Win32 Version Value', None]) liststore.append([self.fmtaddr(offset + 0x38), '%08x' % struct.SizeOfImage, 'Size of Image', None]) liststore.append([self.fmtaddr(offset + 0x3C), '%08x' % struct.SizeOfHeaders, 'Size of Headers', None]) liststore.append([self.fmtaddr(offset + 0x40), '%08x' % struct.CheckSum, 'Checksum', None]) liststore.append([self.fmtaddr(offset + 0x44), '%04x' % struct.Subsystem, 'Subsystem', pefile.SUBSYSTEM_TYPE[struct.Subsystem] ]) liststore.append([self.fmtaddr(offset + 0x46), '%04x' % struct.DllCharacteristics, 'DLL Characteristics' , None]) dicc = pefile.DLL_CHARACTERISTICS char = struct.DllCharacteristics for mask in dicc.keys(): if (isinstance(mask, int) or isinstance(mask, long)) \ and char & mask: liststore.append([None, None, '%04x' % mask, dicc[mask]]) liststore.append([self.fmtaddr(offset + 0x48), '%08x' % struct.SizeOfStackReserve, 'Size of Stack Reserve', None]) liststore.append([self.fmtaddr(offset + 0x4C), '%08x' % struct.SizeOfStackCommit, 'Size of Stack Commit' , None]) liststore.append([self.fmtaddr(offset + 0x50), '%08x' % struct.SizeOfHeapReserve, 'Size of Heap Reserve' , None]) liststore.append([self.fmtaddr(offset + 0x54), '%08x' % struct.SizeOfHeapCommit, 'Size of Heap Commit' , None]) liststore.append([self.fmtaddr(offset + 0x58), '%08x' % struct.LoaderFlags, 'Loader Flags', None]) liststore.append([self.fmtaddr(offset + 0x5C), '%08x' % struct.NumberOfRvaAndSizes, 'Number of Data Directories', None]) for entry in struct.DATA_DIRECTORY: offset = entry.get_file_offset() liststore.append(['---', None, None, None]) liststore.append([self.fmtaddr(offset + 0), '%08x' % entry.VirtualAddress, 'RVA', entry.name]) liststore.append([self.fmtaddr(offset + 4), '%08x' % entry.Size, 'Size', None]) liststore.append(['---', None, None, None]) return liststore def get_section_header (self, index): 'Return a list-model of the section given by index argument' liststore = gtk.ListStore(str, str, str, str) struct = self.peobj.sections[index] offset = self.get_fmt_address(struct.get_file_offset()) name = struct.Name.ljust(8, chr(0)) name32 = unpack('>II', name) liststore.append([self.fmtaddr(offset + 0x00), '%08x' % name32[0], 'Name', struct.Name]) liststore.append([self.fmtaddr(offset + 0x04), '%08x' % name32[1], None, None]) liststore.append([self.fmtaddr(offset + 0x08), '%08x' % struct.Misc_VirtualSize, 'Virtual Size', None]) liststore.append([self.fmtaddr(offset + 0x0C), '%08x' % struct.VirtualAddress, 'RVA', None]) liststore.append([self.fmtaddr(offset + 0x10), '%08x' % struct.SizeOfRawData, 'Size of Raw Data', None]) liststore.append([self.fmtaddr(offset + 0x14), '%08x' % struct.PointerToRawData, 'Pointer to Raw Data', None]) liststore.append([self.fmtaddr(offset + 0x18), '%08x' % struct.PointerToRelocations, 'Pointer to Relocations', None]) liststore.append([self.fmtaddr(offset + 0x1C), '%08x' % struct.PointerToLinenumbers, 'Pointer to Line Numbers', None]) liststore.append([self.fmtaddr(offset + 0x20), '%04x' % struct.NumberOfRelocations, 'Number of Relocations', None]) liststore.append([self.fmtaddr(offset + 0x22), '%04x' % struct.NumberOfLinenumbers, 'Number of Line Numbers', None]) liststore.append([self.fmtaddr(offset + 0x24), '%08x' % struct.Characteristics, 'Characteristics', None]) dicc = pefile.SECTION_CHARACTERISTICS char = struct.Characteristics for mask in dicc.keys(): if (isinstance(mask, int) or isinstance(mask, long)) \ and (char & mask) == mask: liststore.append([None, None, '%08x' % mask, dicc[mask]]) return liststore def get_import_dir_table (self): 'Return a list-model of the import directory table' liststore = gtk.ListStore(str, str, str, str) offset = 0 for entry in self.peobj.DIRECTORY_ENTRY_IMPORT: struct = entry.struct if self.address_format != ADDR_FMT_VIEW_OFFSET: offset = self.get_fmt_address(struct.get_file_offset()) liststore.append([self.fmtaddr(offset + 0x00), '%08x' % struct.OriginalFirstThunk, 'Import Name Table RVA', None]) liststore.append([self.fmtaddr(offset + 0x04), '%08x' % struct.TimeDateStamp, 'Time Date Stamp', format_timestamp(struct.TimeDateStamp)]) liststore.append([self.fmtaddr(offset + 0x08), '%08x' % struct.ForwarderChain, 'Forwarder Chain', None]) liststore.append([self.fmtaddr(offset + 0x0C), '%08x' % struct.Name, 'Name RVA', entry.dll]) liststore.append([self.fmtaddr(offset + 0x10), '%08x' % struct.FirstThunk, 'Import Address Table RVA', None]) liststore.append(['---', None, None, None]) offset += 0x14 for i in range(5): liststore.append([ self.fmtaddr(offset + (i * 4)), '%08x' % 0, None, None ]) liststore.append(['---', None, None, None]) return liststore def get_import_table (self, idt): 'Return a list-model of the import address/names table' liststore = gtk.ListStore(str, str, str, str) offset = 0 for entry in self.peobj.DIRECTORY_ENTRY_IMPORT: if self.address_format == ADDR_FMT_FILE_OFFSET: if idt: pfile = entry.struct.OriginalFirstThunk else: pfile = entry.struct.FirstThunk offset = self.peobj.get_offset_from_rva(pfile) elif self.address_format == ADDR_FMT_RELATIVE_VA: if idt: offset = entry.struct.OriginalFirstThunk else: offset = entry.struct.FirstThunk elif self.address_format == ADDR_FMT_VIRTUAL_ADD: if idt: offset = self.peobj.OPTIONAL_HEADER.BaseOfCode + \ entry.struct.OriginalFirstThunk else: offset = self.peobj.OPTIONAL_HEADER.BaseOfCode + \ entry.struct.FirstThunk for symbol in entry.imports: if symbol.ordinal: liststore.append([self.fmtaddr(offset), '%08x' % (0x80000000 + symbol.ordinal), 'Ordinal', '%04X' % symbol.ordinal]) else: liststore.append([self.fmtaddr(offset), '%08x' % symbol.hint_name_table_rva, 'Hint/Name RVA', '%04X %s' % (symbol.hint, symbol.name)]) offset += 0x04 liststore.append([self.fmtaddr(offset), '%08x' % 0x00, 'End of Imports', entry.dll]) liststore.append(['---', None, None, None]) if self.address_format == ADDR_FMT_VIEW_OFFSET: offset += 0x04 else: offset += 0x04 return liststore def get_import_names_range (self): 'Return start and end of the block containing import names' min_addr = None max_addr = None for entry in self.peobj.DIRECTORY_ENTRY_IMPORT: offset = entry.struct.OriginalFirstThunk table = self.peobj.get_import_table(offset) offset = entry.struct.get_file_offset() name_rva = self.peobj.get_dword_from_offset(offset + 0x0C) pfile = self.peobj.get_offset_from_rva(name_rva) if min_addr is None or pfile < min_addr: min_addr = pfile if max_addr is None or pfile > max_addr: max_addr = pfile for thunk in table: if thunk.AddressOfData & pefile.IMAGE_ORDINAL_FLAG: continue pfile = self.peobj.get_offset_from_rva(thunk.AddressOfData) if min_addr is None or pfile < min_addr: min_addr = pfile if max_addr is None or pfile > max_addr: max_addr = pfile return min_addr, max_addr - min_addr def get_bound_table (self): 'Return a list-model of the bound imports table' liststore = gtk.ListStore(str, str, str, str) offset = 0 for entry in self.peobj.DIRECTORY_ENTRY_BOUND_IMPORT: struct = entry.struct if self.address_format != ADDR_FMT_VIEW_OFFSET: offset = self.get_fmt_address(struct.get_file_offset()) liststore.append([self.fmtaddr(offset + 0x00), '%08x' % struct.TimeDateStamp, 'Time Date Stamp', format_timestamp(struct.TimeDateStamp)]) liststore.append([self.fmtaddr(offset + 0x04), '%04x' % struct.OffsetModuleName, 'Offset to Module Name', entry.name]) liststore.append([self.fmtaddr(offset + 0x06), '%04x' % struct.NumberOfModuleForwarderRefs, 'Number of Module Forwarder Refs', None]) liststore.append(['---', None, None, None]) offset += 0x08 liststore.append([self.fmtaddr(offset + 0x00), '%08x' % 0, None, None]) liststore.append([self.fmtaddr(offset + 0x04), '%04x' % 0, None, None]) liststore.append([self.fmtaddr(offset + 0x06), '%04x' % 0, None, None]) liststore.append(['---', None, None, None]) return liststore def get_bound_names_range (self): 'Return start and end of the block containing bound import names' min_addr = None max_addr = None for entry in self.peobj.DIRECTORY_ENTRY_BOUND_IMPORT: pfile = entry.struct.get_file_offset() + \ entry.struct.OffsetModuleName if min_addr is None or pfile < min_addr: min_addr = pfile if max_addr is None or pfile > max_addr: max_addr = pfile + len(entry.name) + 1 return min_addr, max_addr - min_addr def get_relocations (self): 'Return a list-model of the relocation table' liststore = gtk.ListStore(str, str, str, str) offset = 0 for entry in self.peobj.DIRECTORY_ENTRY_BASERELOC: struct = entry.struct if self.address_format != ADDR_FMT_VIEW_OFFSET: offset = self.get_fmt_address(struct.get_file_offset()) liststore.append([self.fmtaddr(offset + 0x00), '%08x' % struct.VirtualAddress, 'RVA of Block', None]) liststore.append([self.fmtaddr(offset + 0x04), '%08x' % struct.SizeOfBlock, 'Size of Block', None]) offset += 0x08 for reldata in entry.entries: data = (reldata.type << 12) | (reldata.rva & 0x0fff) liststore.append([self.fmtaddr(offset), '%04x' % data, 'Type RVA', '%08X %s' % (reldata.rva, pefile.RELOCATION_TYPE[reldata.type]) ]) offset += 0x2 liststore.append(['---', None, None, None]) return liststore def get_resource (self, entry): 'Return a list-model of the resource given by index argument' liststore = gtk.ListStore(str, str, str, str) struct = entry.struct offset = self.get_fmt_address(struct.get_file_offset()) if isinstance(entry, pefile.ResourceDirData): liststore.append([self.fmtaddr(offset + 0x00), '%08x' % struct.Characteristics, 'Characteristics', None]) liststore.append([self.fmtaddr(offset + 0x04), '%08x' % struct.TimeDateStamp, 'Time Date Stamp', format_timestamp(struct.TimeDateStamp)]) liststore.append([self.fmtaddr(offset + 0x08), '%04x' % struct.MajorVersion, 'Major Version', None]) liststore.append([self.fmtaddr(offset + 0x0A), '%04x' % struct.MinorVersion, 'Minor Version', None]) liststore.append([self.fmtaddr(offset + 0x0C), '%04x' % struct.NumberOfNamedEntries, 'Number of Named Entries', None]) liststore.append([self.fmtaddr(offset + 0x0E), '%04x' % struct.NumberOfIdEntries, 'Number Of ID Entries', None]) liststore.append(['---', None, None, None]) offset += 0x10 for child in entry.entries: try: name = pefile.RESOURCE_TYPE[child.struct.Id] except KeyError: name = None liststore.append([self.fmtaddr(offset + 0x00), '%08x' % child.struct.Id, 'ID', None]) if child.struct.DataIsDirectory: liststore.append([self.fmtaddr(offset + 0x04), '%08x' % child.struct.OffsetToDirectory, 'Offset to DIRECTORY', name]) else: liststore.append([self.fmtaddr(offset + 0x04), '%08x' % child.struct.OffsetToData, 'Offset to DATA', name]) liststore.append(['---', None, None, None]) offset += 0x08 return liststore def get_resource_data (self, entry): 'Return start and end of data block given a resource entry' offset = self.peobj.get_offset_from_rva(entry.struct.OffsetToData) return offset, entry.struct.Size def get_version_info (self): 'Return a list-model of the VS_VERSIONINFO structure' # http://msdn.microsoft.com/en-us/library/windows/desktop/ # ms647001%28v=vs.85%29.aspx liststore = gtk.ListStore(str, str, str, str) struct = self.peobj.VS_VERSIONINFO offset = self.get_fmt_address(struct.get_file_offset()) if struct.Type == 0: typedata = 'Binary data' elif struct.Type == 1: typedata = 'Text data' else: typedata = None liststore.append([self.fmtaddr(offset + 0x00), '%04x' % struct.Length, 'Length', None]) liststore.append([self.fmtaddr(offset + 0x02), '%04x' % struct.ValueLength, 'Value Length', None]) liststore.append([self.fmtaddr(offset + 0x04), '%04x' % struct.Type, 'Type', typedata]) liststore.append([self.fmtaddr(offset + 0x06), format_data(struct.Key), 'Key', '(wchar)']) return liststore def get_fixed_file_info (self): 'Return a list-model of the VS_FIXEDFILEINFO structure' # http://msdn.microsoft.com/en-us/library/windows/desktop/ # ms646997%28v=vs.85%29.aspx liststore = gtk.ListStore(str, str, str, str) struct = self.peobj.VS_FIXEDFILEINFO offset = self.get_fmt_address(struct.get_file_offset()) liststore.append([self.fmtaddr(offset + 0x00), '%08x' % struct.Signature, 'Signature', None]) liststore.append([self.fmtaddr(offset + 0x04), '%08x' % struct.StrucVersion, 'Struct Version', None]) liststore.append([self.fmtaddr(offset + 0x08), '%08x' % struct.FileVersionMS, 'File Version MS', None]) liststore.append([self.fmtaddr(offset + 0x0C), '%08x' % struct.FileVersionLS, 'File Version LS', None]) liststore.append([self.fmtaddr(offset + 0x10), '%08x' % struct.ProductVersionMS, 'Product Version MS', None]) liststore.append([self.fmtaddr(offset + 0x14), '%08x' % struct.ProductVersionLS, 'Product Version LS', None]) liststore.append([self.fmtaddr(offset + 0x18), '%08x' % struct.FileFlagsMask, 'File Flags Mask', None]) liststore.append([self.fmtaddr(offset + 0x1C), '%08x' % struct.FileFlags, 'File Flags', None]) dicc = FILE_FLAGS flags = struct.FileFlags for mask in dicc.keys(): if (isinstance(mask, int) or isinstance(mask, long)) \ and flags & mask: liststore.append([None, None, '%08x' % mask, dicc[mask]]) liststore.append([self.fmtaddr(offset + 0x20), '%08x' % struct.FileOS, 'File OS', None]) dicc = FILE_OS flags = struct.FileOS for mask in dicc.keys(): if (isinstance(mask, int) or isinstance(mask, long)) \ and flags & mask: liststore.append([None, None, '%08x' % mask, dicc[mask]]) liststore.append([self.fmtaddr(offset + 0x24), '%08x' % struct.FileType, 'File Type', FILE_TYPE[struct.FileType]]) # depends on the previous liststore.append([self.fmtaddr(offset + 0x28), '%08x' % struct.FileSubtype, 'File Subtype', FILE_TYPE[struct.FileSubtype]]) liststore.append([self.fmtaddr(offset + 0x2C), '%08x' % struct.FileDateMS, 'File Date MS', format_timestamp(struct.FileDateMS)]) liststore.append([self.fmtaddr(offset + 0x30), '%08x' % struct.FileDateLS, 'File Date LS', format_timestamp(struct.FileDateLS)]) return liststore def get_version_file_info (self, string_table): 'Return a list-model of a StringFileInfo or VarFileInfo structure' # http://msdn.microsoft.com/en-us/library/windows/desktop/ # ms646994%28v=vs.85%29.aspx liststore = gtk.ListStore(str, str, str, str) struct = None for current in self.peobj.FileInfo: if string_table and current.name == 'StringFileInfo': struct = current break elif not string_table and current.name == 'VarFileInfo': struct = current break if struct is None: return liststore offset = self.get_fmt_address(struct.get_file_offset()) typedata = '(binary data)' if struct.Type == 0 else '(text data)' liststore.append([self.fmtaddr(offset + 0x00), '%04x' % struct.Length, 'Length', None]) liststore.append([self.fmtaddr(offset + 0x02), '%04x' % struct.ValueLength, 'Value Length', None]) liststore.append([self.fmtaddr(offset + 0x04), '%04x' % struct.Type, 'Type', typedata]) liststore.append(['---', None, None, None]) if hasattr(struct, 'StringTable'): for elem in struct.StringTable: offset = self.get_fmt_address(elem.get_file_offset()) typedata = '(binary data)' if elem.Type == 0 else '(text data)' liststore.append([self.fmtaddr(offset + 0x00), '%04x' % elem.Length, 'Length', None]) liststore.append([self.fmtaddr(offset + 0x02), '%04x' % elem.ValueLength, 'Value Length', None]) liststore.append([self.fmtaddr(offset + 0x04), '%04x' % elem.Type, 'Type', typedata]) liststore.append([self.fmtaddr(offset + 0x00), elem.LangID, 'LangID', None]) liststore.append(['---', None, None, None]) offsets = elem.entries_offsets for key, val in elem.entries.items(): liststore.append([self.fmtaddr(offsets[key][0]), None, 'Key', key]) liststore.append([self.fmtaddr(offsets[key][1]), None, 'Value', val]) liststore.append(['---', None, None, None]) elif hasattr(struct, 'Var'): for elem in struct.Var: offset = self.get_fmt_address(elem.get_file_offset()) typedata = '(binary data)' if elem.Type == 0 else '(text data)' liststore.append([self.fmtaddr(offset + 0x00), '%04x' % elem.Length, 'Length', None]) liststore.append([self.fmtaddr(offset + 0x02), '%04x' % elem.ValueLength, 'Value Length', None]) liststore.append([self.fmtaddr(offset + 0x04), '%04x' % elem.Type, 'Type', typedata]) liststore.append(['---', None, None, None]) for key, val in elem.entry.items(): liststore.append([None, None, 'Key', key]) liststore.append([None, None, 'Value', val]) liststore.append(['---', None, None, None]) return liststore class GPEFile: 'Represent this application' def __init__ (self): self.model = PEModel() self.tree_callbacks = {} self.config = ConfigParser.RawConfigParser({ 'font': 'monospace regular 8', 'datasize': 4, 'address_format': ADDR_FMT_FILE_OFFSET, 'show_toolbar': 1, 'show_statusbar': 1, 'show_treeview': 1, 'show_header': 1, 'show_warnings': 1, }) self.config.read(CONFIG_FILE) self.model.datasize = int(self.config.get('DEFAULT', 'datasize')) self.model.address_format = \ int(self.config.get('DEFAULT', 'address_format')) # relevant window widgets self.window = None self.handlebox = None self.treestore = None self.treeview = None self.listview = None self.scroll_left = None self.scroll_right = None self.expander = None self.warnings = None self.statusbar = None # widgets for resource display self.res_image = None self.res_frame = None self.res_text = None self.__create_window() def load_file (self, filename): 'Load the given file, analyse it, and display it on the screen' if not self.model.load_file(filename): message_dialog(self.window, self.model.get_last_error()) return self.window.set_title('%s - %s' % (APP_NAME, filename)) self.__rebuild_tree() self.treeview.expand_row(0, False) self.treeview.get_selection().unselect_all() self.treeview.get_selection().select_path(0) warnings = self.model.peobj.get_warnings() self.warnings.get_buffer().set_text('\n'.join(warnings) + '\n') self.expander.set_expanded(len(warnings) != 0) def __add_sections (self): 'Add file sections as tree rows' treestore = self.treestore root = treestore.get_iter_root() peobj = self.model.peobj # offsets to tables addr = peobj.OPTIONAL_HEADER.DATA_DIRECTORY[1].VirtualAddress idt_offset = None if addr == 0 else peobj.get_offset_from_rva(addr) # imports if hasattr(peobj, 'DIRECTORY_ENTRY_IMPORT'): addr = peobj.DIRECTORY_ENTRY_IMPORT[0].struct.OriginalFirstThunk ilt_offset = None if addr == 0 else peobj.get_offset_from_rva(addr) addr = peobj.DIRECTORY_ENTRY_IMPORT[0].struct.FirstThunk iat_offset = None if addr == 0 else peobj.get_offset_from_rva(addr) names_offset, _ = self.model.get_import_names_range() else: ilt_offset = iat_offset = names_offset = None # relocations rel_offset = None if hasattr(peobj, 'DIRECTORY_ENTRY_BASERELOC'): addr = peobj.OPTIONAL_HEADER.DATA_DIRECTORY[5].VirtualAddress rel_offset = None if addr == 0 else peobj.get_offset_from_rva(addr) # section headers for i, section in enumerate(peobj.sections): elem = treestore.append(root, ['%s %s' % (section.name, section.Name)]) path = treestore.get_path(elem) self.tree_callbacks[path] = (self.show_section_header, i) # bound imports if hasattr(peobj, 'DIRECTORY_ENTRY_BOUND_IMPORT'): child = treestore.append(root, ['BOUND IMPORT Directory Table']) child_path = treestore.get_path(child) self.tree_callbacks[child_path] = self.show_bound_import_table child = treestore.append(root, ['BOUND IMPORT DLL Names']) child_path = treestore.get_path(child) self.tree_callbacks[child_path] = self.show_bound_names # section data for i, section in enumerate(peobj.sections): elem = treestore.append(root, ['SECTION %s' % (section.Name)]) path = treestore.get_path(elem) self.tree_callbacks[path] = (self.show_section_data, i) # insert tables as childs of the corresponding section if idt_offset and section_contains_offset(section, idt_offset): child = treestore.append(elem, ['IMPORT Directory Table']) child_path = treestore.get_path(child) self.tree_callbacks[child_path] = self.show_import_dir_table if ilt_offset and section_contains_offset(section, ilt_offset): child = treestore.append(elem, ['IMPORT Name Table']) child_path = treestore.get_path(child) self.tree_callbacks[child_path] = self.show_import_name_table if iat_offset and section_contains_offset(section, iat_offset): child = treestore.append(elem, ['IMPORT Address Table']) child_path = treestore.get_path(child) self.tree_callbacks[child_path] = self.show_import_addr_table if names_offset and section_contains_offset(section, names_offset): child = treestore.append(elem, ['IMPORT Hints/Names & DLL Names']) child_path = treestore.get_path(child) self.tree_callbacks[child_path] = self.show_import_names if rel_offset and section_contains_offset(section, rel_offset): child = treestore.append(elem, ['IMAGE_BASE_RELOCATION']) child_path = treestore.get_path(child) self.tree_callbacks[child_path] = self.show_relocations def __traverse_resources (self, root, name, entry, parents): 'Add file resources as tree rows' treestore = self.treestore depth = len(parents) if depth == 2: try: name = pefile.RESOURCE_TYPE[name] except KeyError: pass if depth == 0: title = 'IMAGE_ENTRY_DIRECTORY' else: try: title = '%04X' % name except TypeError: title = name if isinstance(entry, pefile.ResourceDirData): elem = treestore.append(root, [title]) path = treestore.get_path(elem) self.tree_callbacks[path] = (self.show_resource, entry) for child in entry.entries: self.__traverse_resources(elem, 0, child, parents + [entry]) elif isinstance(entry, pefile.ResourceDirEntryData): name = entry.name if entry.name is not None else entry.id if entry.struct.DataIsDirectory: self.__traverse_resources(root, name, entry.directory, \ parents + [entry]) else: self.__traverse_resources(root, name, entry.data, parents) elif isinstance(entry, pefile.ResourceDataEntryData): # memorize resource type for later self.model.res_types[entry] = parents[1].struct.Name elem = treestore.append(root, [title]) path = treestore.get_path(elem) self.tree_callbacks[path] = (self.show_resource_data, entry) def __rebuild_tree (self): 'Rebuild the left tree model used to select the header or section view' treestore = self.treestore treestore.clear() self.tree_callbacks = {} root = treestore.append(None, [basename(self.model.filename)]) self.tree_callbacks[treestore.get_path(root)] = self.show_data elem = treestore.append(root, ['IMAGE_DOS_HEADER']) self.tree_callbacks[treestore.get_path(elem)] = self.show_dos_header elem = treestore.append(root, ['MS-DOS Stub Program']) self.tree_callbacks[treestore.get_path(elem)] = self.show_dos_stub # nt headers parent = treestore.append(root, ['IMAGE_NT_HEADERS']) self.tree_callbacks[treestore.get_path(parent)] = self.show_nt_header elem = treestore.append(parent, ['Signature']) self.tree_callbacks[treestore.get_path(elem)] = self.show_signature elem = treestore.append(parent, ['IMAGE_FILE_HEADER']) self.tree_callbacks[treestore.get_path(elem)] = self.show_file_header elem = treestore.append(parent, ['IMAGE_OPTIONAL_HEADER']) self.tree_callbacks[treestore.get_path(elem)]=self.show_optional_header self.__add_sections() # resources if hasattr(self.model.peobj, 'DIRECTORY_ENTRY_RESOURCE'): self.__traverse_resources(root, 0, self.model.peobj.DIRECTORY_ENTRY_RESOURCE, []) # version information if hasattr(self.model.peobj, 'VS_VERSIONINFO'): parent = treestore.append(root, ['VS_VERSIONINFO']) self.tree_callbacks[treestore.get_path(parent)] = \ self.show_version_info elem = treestore.append(parent, ['VS_FIXEDFILEINFO']) self.tree_callbacks[treestore.get_path(elem)] = \ self.show_fixed_file_info elem = treestore.append(parent, ['StringFileInfo']) self.tree_callbacks[treestore.get_path(elem)] = \ (self.show_version_file_info, True) elem = treestore.append(parent, ['VarFileInfo']) self.tree_callbacks[treestore.get_path(elem)] = \ (self.show_version_file_info, False) def __clear_listview (self): 'Delete all the columns of the right table (header)' self.listview.set_model() for col in self.listview.get_columns(): self.listview.remove_column(col) def __add_standard_columns (self): 'Add columns for a header view in the right table (header)' self.__clear_listview() font = self.config.get('DEFAULT', 'font') cell = gtk.CellRendererText() cell.set_property('font', font) cell.set_property('xpad', 6) if self.model.address_format == ADDR_FMT_FILE_OFFSET: self.listview.insert_column_with_attributes( \ -1, 'pView', cell, text=0) elif self.model.address_format == ADDR_FMT_RELATIVE_VA: self.listview.insert_column_with_attributes(-1, 'RVA', cell, text=0) elif self.model.address_format == ADDR_FMT_VIRTUAL_ADD: self.listview.insert_column_with_attributes(-1, 'VA', cell, text=0) else: self.listview.insert_column_with_attributes( \ -1, 'pFile', cell, text=0) cell = gtk.CellRendererText() cell.set_property('xalign', 0.5) cell.set_property('font', font) cell.set_property('xpad', 6) self.listview.insert_column_with_attributes(-1, 'Data', cell, text=1) cell = gtk.CellRendererText() cell.set_property('font', font) cell.set_property('xpad', 6) self.listview.insert_column_with_attributes( \ -1, 'Description', cell, text=2) cell = gtk.CellRendererText() cell.set_property('font', font) cell.set_property('xpad', 6) self.listview.insert_column_with_attributes(-1, 'Value', cell, text=3) def __add_hex_columns (self): 'Add columns for a hex view in the right table (header)' self.__clear_listview() font = self.config.get('DEFAULT', 'font') cell = gtk.CellRendererText() cell.set_property('font', font) cell.set_property('xpad', 6) if self.model.address_format == ADDR_FMT_VIEW_OFFSET: self.listview.insert_column_with_attributes( \ -1, 'pView', cell, text=0) elif self.model.address_format == ADDR_FMT_RELATIVE_VA: self.listview.insert_column_with_attributes(-1, 'RVA', cell, text=0) elif self.model.address_format == ADDR_FMT_VIRTUAL_ADD: self.listview.insert_column_with_attributes(-1, 'VA', cell, text=0) else: self.listview.insert_column_with_attributes( \ -1, 'pFile', cell, text=0) cell = gtk.CellRendererText() cell.set_property('font', font) cell.set_property('xpad', 6) self.listview.insert_column_with_attributes( \ -1, 'Raw Data', cell, text=1) cell = gtk.CellRendererText() cell.set_property('font', font) cell.set_property('xpad', 6) self.listview.insert_column_with_attributes(-1, 'Value', cell, text=2) def show_data (self, offset=0, count=None): 'Display an hex view of the PE file starting at offset' self.__add_hex_columns() self.listview.set_model(self.model.get_data(offset, count)) def show_file (self): 'Display an hex view of the PE file starting at offset 0' self.show_data() def show_dos_header (self): 'Display the DOS header of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_dos_header()) def show_dos_stub (self): 'Display an hex view of the DOS stub of the PE file' offset = self.model.peobj.DOS_HEADER.sizeof() size = 0x40 self.show_data(offset, size) def show_nt_header (self): 'Display an hex view of the NT header of the PE file' peobj = self.model.peobj offset = peobj.NT_HEADERS.get_file_offset() size = peobj.NT_HEADERS.sizeof() + peobj.FILE_HEADER.sizeof() + \ peobj.OPTIONAL_HEADER.sizeof() self.show_data(offset, size) def show_signature (self): 'Display the Signature header of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_signature()) def show_file_header (self): 'Display the FILE header of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_file_header()) def show_optional_header (self): 'Display the OPTIONAL header of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_optional_header()) def show_section_header (self, index): 'Display a section of the PE file given by index argument' self.__add_standard_columns() self.listview.set_model(self.model.get_section_header(index)) def show_section_data (self, index): 'Display a section of the PE file given by index argument' offset = self.model.peobj.sections[index].PointerToRawData size = self.model.peobj.sections[index].SizeOfRawData self.show_data(offset, size) def show_resource (self, entry): 'Display the resource entry given as argument' self.__add_standard_columns() self.listview.set_model(self.model.get_resource(entry)) def show_resource_data (self, entry): 'Display the resource data block given as argument' offset, size = self.model.get_resource_data(entry) self.show_data(offset, size) buffr = self.res_text.get_buffer() buffr.set_text('') res_type = self.model.res_types[entry] data = self.model.image[offset:offset+size] if res_type == 2: # RT_BITMAP obj = BMPFILE(data) filename = '/tmp/resource.bmp' obj.export(filename) self.res_image.set_from_file(filename) elif res_type == 6: # RT_STRING offset = 0 size = len(data) strings = '' while offset < size: ustr_length = unpack_from('H', data, offset)[0] offset += 2 if ustr_length == 0: continue strings += data[offset:offset + ustr_length * 2].decode('utf-16').encode('utf-8') strings += "\n" offset += ustr_length * 2 buffr.set_text(strings) self.res_frame.set_visible(True) elif res_type == 12: # RT_GROUP_CURSOR obj = ICOCURFILE(self.model.peobj, data, True) filename = '/tmp/resource.cur' obj.export(filename) self.res_image.set_from_file(filename) elif res_type == 14: # RT_GROUP_ICON obj = ICOCURFILE(self.model.peobj, data, False) filename = '/tmp/resource.ico' obj.export(filename) self.res_image.set_from_file(filename) elif res_type == 24: # RT_MANIFEST buffr.set_text(data) self.res_frame.set_visible(True) else: filename = None if data.startswith('GIF8'): filename = '/tmp/resource.gif' elif data.startswith('\x89PNG'): filename = '/tmp/resource.png' if filename: fout = open(filename, 'wb') fout.write(data) fout.close() self.res_image.set_from_file(filename) def show_import_dir_table (self): 'Display the Import Directory Table of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_import_dir_table()) def show_import_name_table (self): 'Display the Import Names Table of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_import_table(idt=True)) def show_import_addr_table (self): 'Display the Import Address Table table of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_import_table(idt=False)) def show_import_names (self): 'Display the section of the PE file which hosts the import strings' offset, size = self.model.get_import_names_range() self.show_data(offset, size) def show_bound_import_table (self): 'Display the Bound Import Table of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_bound_table()) def show_bound_names (self): 'Display the section of the PE file containing the bound import strings' offset, size = self.model.get_bound_names_range() self.show_data(offset, size) def show_relocations (self): 'Display the relocation table of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_relocations()) def show_version_info (self): 'Display the VS_VERSIONINFO structure of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_version_info()) def show_fixed_file_info (self): 'Display the VS_FIXEDFILEINFO structure of the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_fixed_file_info()) def show_version_file_info (self, string_table): 'Display a StringFileInfo or VarFileInfo structure from the PE file' self.__add_standard_columns() self.listview.set_model(self.model.get_version_file_info(string_table)) def cb_file_open (self, item): 'Callback for the File/Open menu command' chooser = gtk.FileChooserDialog('Open...', self.window, \ gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, \ gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) filters = [ ('Executable (*.exe)', '*.exe'), ('Dynamic Link Library (*.dll)', '*.dll'), ('System (*.sys)', '*.sys'), ('ActiveX Control (*.ocx)', '*.ocx'), ('Control Panel Extension (*.cpl)', '*.cpl'), ('Screen Saver (*.scr)', '*.scr'), ('ActiveX Control (*.ocx)', '*.ocx'), ('Object (*.obj)', '*.obj'), ('Symbol (*.dbg)', '*.dbg'), ('Library (*.lib)', '*.lib'), ('Type Library (*.tlb;*.olb)', ('*.tlb', '*.olb')), ('All Files (*.*)', '*.*'), ] for row in filters: flt = gtk.FileFilter() flt.set_name(row[0]) if isinstance(row[1], str): flt.add_pattern(row[1]) else: for pat in row[1]: flt.add_pattern(pat) chooser.add_filter(flt) response = chooser.run() if response == gtk.RESPONSE_OK: self.load_file(chooser.get_filename()) chooser.destroy() def cb_file_export (self, item): 'Callback for the File/Export menu command' if self.model.filename is None: return chooser = gtk.FileChooserDialog('Export...', self.window, \ gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, \ gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) response = chooser.run() if response == gtk.RESPONSE_OK: filename = chooser.get_filename() info = self.model.peobj.dump_info() fh1 = open(filename, 'w') fh1.write(info) fh1.close() chooser.destroy() def cb_file_close (self, item): 'Callback for the File/Close menu command' self.model.clear() self.treestore.clear() self.listview.set_model() def cb_file_quit (self, item): 'Callback for the File/Quit menu command' self.config.write(open(CONFIG_FILE, 'w')) gtk.main_quit() def cb_view_toolbar (self, action): 'Callback for the View/Toolbar menu command' value = action.get_active() self.config.set('DEFAULT', 'show_toolbar', int(value)) self.handlebox.set_visible(value) def cb_view_statusbar (self, action): 'Callback for the View/Statusbar menu command' value = action.get_active() self.config.set('DEFAULT', 'show_statusbar', int(value)) self.statusbar.set_visible(value) def cb_view_treeview (self, action): 'Callback for the View/Treeview menu command' value = action.get_active() self.config.set('DEFAULT', 'show_treeview', int(value)) self.scroll_left.set_visible(value) def cb_view_header (self, action): 'Callback for the View/Header menu command' value = action.get_active() self.config.set('DEFAULT', 'show_header', int(value)) self.scroll_right.set_visible(value) def cb_view_warnings (self, action): 'Callback for the View/Warnings menu command' value = action.get_active() self.config.set('DEFAULT', 'show_warnings', int(value)) self.expander.set_visible(value) def cb_view_address (self, action, current): 'Callback for the View/Address menu selector' value = action.get_current_value() self.config.set('DEFAULT', 'address_format', int(value)) self.model.address_format = value self.cb_selection_changed(self.treeview.get_selection()) def cb_view_rawdata (self, action, current): 'Callback for the View/RawData menu selector' value = action.get_current_value() self.config.set('DEFAULT', 'datasize', int(value)) self.model.datasize = value self.cb_selection_changed(self.treeview.get_selection()) def cb_view_font (self, item): 'Callback for the View/Font menu command' chooser = gtk.FontSelectionDialog(None) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_font_name(self.config.get('DEFAULT', 'font')) response = chooser.run() if response == gtk.RESPONSE_OK: self.config.set('DEFAULT', 'font', chooser.get_font_name()) chooser.destroy() self.cb_cursor_changed(self.treeview) def cb_help_about (self, item): 'Callback for the Help/About menu command' #gtk.about_dialog_set_url_hook(lambda dialog, link: open_url(link)) dialog = gtk.AboutDialog() dialog.set_comments('PE/COFF file viewer') dialog.set_authors(['Mauro A. Meloni ']) dialog.set_copyright('Copyright (C) 2012 Mauro A. Meloni') dialog.set_license('GNU General Public License version 2 or later.\nSee http://www.gnu.org/licenses/gpl-2.0.txt') dialog.set_name(APP_NAME) # TRANSLATORS: Maintain the names of translators here. # Launchpad does this automatically for translations # typed in Launchpad. This is a special string shown # in the 'About' box. #dialog.set_translator_credits(_("translator-credits")) dialog.set_version(APP_VERSION) dialog.set_website(APP_URL) dialog.set_transient_for(self.window) #if appicon_path and os.path.exists(appicon_path): # icon = gtk.gdk.pixbuf_new_from_file(appicon_path) pixbuf = self.window.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_DIALOG) dialog.set_logo(pixbuf) dialog.run() dialog.destroy() def cb_selection_changed (self, selection): 'Callback for treeview selection changes' model, iterator = selection.get_selected() if iterator is None: return self.res_image.clear() self.res_frame.set_visible(False) path = model.get_path(iterator) if path in self.tree_callbacks: val = self.tree_callbacks[path] if isinstance(val, tuple): # function with one argument val[0](val[1]) else: # function without arguments val() def __create_uimanager (self): 'Return an UIManager with MenuBar and ToolBar objects' menu_items = ''' ''' uimanager = gtk.UIManager() # Create the base ActionGroup actiongroup = gtk.ActionGroup('UIMergeExampleBase') actiongroup.add_actions([ ('File', None, '_File'), ('View', None, '_View'), ('Go', None, '_Go'), ('Help', None, '_Help'), ('Open', None, '_Open...', 'O', None, self.cb_file_open), ('Export', None, '_Export...', None, None, self.cb_file_export), ('Close', None, '_Close', 'F4', None, self.cb_file_close), ('Exit', None, 'E_xit', None, None, self.cb_file_quit), ('Address', None, '_Address'), ('Raw Data', None, 'Raw _Data'), ('Split', None, '_Split'), ('Font', None, '_Font...', None, None, self.cb_view_font), ('Options', None, '_Options...'), ('Back', None, '_Back', 'leftarrow'), ('Forward', None, '_Forward', 'rightarrow'), ('Next', None, '_Next', 'downarrow'), ('Previous', None, '_Previous', 'uparrow'), ('Save Position', None, '_Save Position', 'Return'), ('About', None, '_About', None, None, self.cb_help_about), ]) actiongroup.add_actions([ ('tbOpen', gtk.STOCK_OPEN, None, None, 'Open', self.cb_file_open), ('tbBack', gtk.STOCK_GO_BACK, None, None, 'Back'), ('tbForward', gtk.STOCK_GO_FORWARD, None, None, 'Forward'), ('tbNext', gtk.STOCK_GO_DOWN, None, None, 'Next'), ('tbPrevious', gtk.STOCK_GO_UP, None, None, 'Previous'), ]) actiongroup.add_toggle_actions([ ('Toolbar', None, '_Toolbar', None, None, self.cb_view_toolbar), ('Status Bar', None, '_Status Bar', None, None, self.cb_view_statusbar), ('TreeView', None, 'Tree_View', None, None, self.cb_view_treeview), ('Header', None, '_Header', None, None, self.cb_view_header), ('Warnings', None, '_Warnings', None, None, self.cb_view_warnings), ]) actiongroup.add_radio_actions([ ('BYTE', None, '_BYTE', None, None, 1), ('WORD', None, '_WORD', None, None, 2), ('DWORD', None, '_DWORD', None, None, 4), ], 4, self.cb_view_rawdata) actiongroup.add_radio_actions([ ('File Offset', None, '_File Offset', \ None, None, ADDR_FMT_FILE_OFFSET), ('View Offset', None, 'View _Offset', \ None, None, ADDR_FMT_VIEW_OFFSET), ('Relative Virtual Address', None, '_Relative Virtual Address', \ None, None, ADDR_FMT_RELATIVE_VA), ('Virtual Address', None, '_Virtual Address', \ None, None, ADDR_FMT_VIRTUAL_ADD), ], ADDR_FMT_FILE_OFFSET, self.cb_view_address) uimanager.insert_action_group(actiongroup, 0) uimanager.add_ui_from_string(menu_items) return uimanager def __create_tree (self): 'Create the treestore and treeview objects for view selection (left)' self.treestore = gtk.TreeStore(str) self.treeview = gtk.TreeView(self.treestore) tvcolumn = gtk.TreeViewColumn() self.treeview.append_column(tvcolumn) cell = gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 0) self.treeview.set_search_column(0) #self.treeview.connect('cursor-changed', self.cb_cursor_changed) selection = self.treeview.get_selection() selection.connect('changed', self.cb_selection_changed) def __row_separator_func (self, model, itr, data): 'Indicates if the current row is a ListView row separator' return model.get_value(itr, 0) == '---' def __create_window (self): 'Create the main app window and its elements' # Create a new window self.window = gtk.Window() self.window.connect('destroy', self.cb_file_quit) pixbuf = self.window.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_DIALOG) self.window.set_size_request(640, 480) self.window.set_title(APP_NAME) self.window.set_icon(pixbuf) #self.window.set_resizable(True) #self.window.set_border_width(10) #self.window.connect("delete_event", self.delete_event) vbox = gtk.VBox() self.window.add(vbox) uimanager = self.__create_uimanager() accelgroup = uimanager.get_accel_group() self.window.add_accel_group(accelgroup) # menu [ File, Edit, View, Go, Help ] menubar = uimanager.get_widget('/MenuBar') vbox.pack_start(menubar, False) # toolbar self.handlebox = gtk.HandleBox() vbox.pack_start(self.handlebox, False) toolbar = uimanager.get_widget('/Toolbar') self.handlebox.add(toolbar) # split between data and warnings #bigpane = gtk.VPaned() #vbox.pack_start(bigpane, True) # split main window pane = gtk.HPaned() vbox.pack_start(pane, True) #bigpane.pack1(pane, True, True) # add tree to the left self.__create_tree() #frame1 = gtk.Frame() #frame1.add(self.treeview) #pane.pack1(frame1) self.scroll_left = gtk.ScrolledWindow() self.scroll_left.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scroll_left.add(self.treeview) pane.pack1(self.scroll_left) # add table to the right_box self.listview = gtk.TreeView() #self.listview.horizontal_separator = 10 #self.listview.set_property('fixed_height_mode', True) self.listview.set_row_separator_func(self.__row_separator_func, None) self.scroll_right = gtk.ScrolledWindow() self.scroll_right.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scroll_right.add(self.listview) pane.pack2(self.scroll_right, True, True) pane.set_position(300) # widgets for resource display self.res_image = gtk.Image() vbox.pack_start(self.res_image, False) self.res_frame = gtk.Frame() self.res_text = gtk.TextView() self.res_text.set_editable(False) self.res_frame.add(self.res_text) vbox.pack_start(self.res_frame, False) # textview for warnings self.warnings = gtk.TextView() self.warnings.set_editable(False) frame2 = gtk.Frame() frame2.add(self.warnings) self.expander = gtk.Expander('Warnings') self.expander.add(frame2) vbox.pack_start(self.expander, False) #bigpane.pack2(frame2) # statusbar self.statusbar = gtk.Statusbar() vbox.pack_end(self.statusbar, False) # enable widgets in view menu uimanager.get_widget('/MenuBar/View/Toolbar').set_active( int(self.config.get('DEFAULT', 'show_toolbar')) ) uimanager.get_widget('/MenuBar/View/Status Bar').set_active( int(self.config.get('DEFAULT', 'show_statusbar')) ) uimanager.get_widget('/MenuBar/View/TreeView').set_active( int(self.config.get('DEFAULT', 'show_treeview')) ) uimanager.get_widget('/MenuBar/View/Header').set_active( int(self.config.get('DEFAULT', 'show_header')) ) uimanager.get_widget('/MenuBar/View/Warnings').set_active( int(self.config.get('DEFAULT', 'show_warnings')) ) # TODO #uimanager.get_widget('/MenuBar/View/Address').set_active( # int(self.config.get('DEFAULT', 'address_format')) #) #uimanager.get_widget('/MenuBar/View/Raw Data').set_active( # int(self.config.get('DEFAULT', 'datasize')) #) self.window.show_all() self.res_frame.set_visible(False) if __name__ == "__main__": GUI = GPEFile() if len(sys.argv) > 1: GUI.load_file(sys.argv[1]) gtk.main()