0

I am trying to move some of my PE parsing into IDApython, i know how to do this with libraries like lief, but is it possible to parse the PE headers using IDA python, just like lief? i want to get all the info from the header like is debug info present and is the file signed and the compilation time and so on.

how to parse PE headers using IDApython? any guide? tried googling but there is nothing.

1

The PE header area is not loaded into the database by default but you can do it by enabling “manual load” in the initial load dialog. However, the copy of the header is saved in the database and can be accessed using the idautils.peutils_t class.

0

And if you ever feel the need to do it manually or in an environment other than IDA, you might find this useful.

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

class section_header(object):
    packstring = '8BIIIIIIHHI'
    packcount = 16
    packchunk = 17
    packinfo = [
        [ 'B', 'Name', 8, 'string' ],
        [ 'I', 'VirtualSize' ],
        [ 'I', 'VirtualAddress' ],
        [ 'I', 'SizeOfRawData' ],
        [ 'I', 'PointerToRawData' ],
        [ 'I', 'PointerToRelocations' ],
        [ 'I', 'PointerToLinenumbers' ],
        [ 'H', 'NumberOfRelocations' ],
        [ 'H', 'NumberOfLinenumbers' ],
        [ 'I', 'Characteristics' ]
    ]

    def __str__(self):
        return "{:32} {} - {}".format(self.name(), 
                hex(self.base + self.data.VirtualAddress), 
                hex(self.base + self.data.VirtualAddress + self.data.VirtualSize)) 

    def __repr__(self):
        return "<{} '{}'>".format(str(__class__)[1:-2].split('.', 2)[1], self.name())

    def __init__(self, base, data):
        self.base = base
        self.data = AttrDict()
        for i in range(len(self.packinfo)):
            count = 1
            if len(self.packinfo[i]) > 2:
                count = self.packinfo[i][2]
                l = []
                for unused in range(count):
                    l.append(data.pop(0))
                if len(self.packinfo[i]) > 3:
                    iteratee = self.packinfo[i][3]
                    fn = getattr(self, iteratee)
                    result = (fn(l))
                else:
                    result = (l)
            else:
                result = (data.pop(0))

            self.data[self.packinfo[i][1]] = result

    def name(self):
        return self.data.Name

    def empty(self):
        return self.data.VirtualSize == 0 and self.data.VirtualAddress == 0

    def string(self, data):
        return ''.join([chr(x) for x in data]).rstrip('\0')

class data_directory(section_header):
    names = [
        "Export Directory", "Import Directory", "Resource Directory",
        "Exception Directory", "Security Directory", "Base Relocation Table",
        "Debug Directory", "Architecture Specific Data", "RVA of GP", 
        "TLS Directory", "Load Configuration Directory", 
        "Bound Import Directory", "Import Address Table", 
        "Delay Load Import Descriptors", "COM Runtime descriptor"
    ]
    packstring = 'II'
    packcount = 16
    packchunk = 2
    packinfo = [
        [ 'I', 'VirtualAddress' ],
        [ 'I', 'Size' ],
    ]

    def __init__(self, base, data, number):
        super(data_directory, self).__init__(base, data)
        if number < len(self.names):
            self.data.Name = self.names[number]
        else:
            self.data.Name = 'Unknown'

    def __str__(self):
        return "{:32} {} - {}".format(self.name(), 
                hex(self.base + self.data.VirtualAddress), 
                hex(self.base + self.data.VirtualAddress + self.data.Size)) 

    def empty(self):
        return self.data.Size == 0 and self.data.VirtualAddress == 0


class WinPE(object):
    """
    example usage:

        w = WinPE(64)
        print(w.nt.SizeOfCode)
        print(w.dos.e_lfanew)
        print(w.get_rva(w.nt.BaseOfCode))
    """

    _nt_nam_32 = [ "Signature", "Machine", "NumberOfSections",
        "TimeDateStamp", "PointerToSymbolTable", "NumberOfSymbols",
        "SizeOfOptionalHeader", "Characteristics", "Magic", "MajorLinkerVersion",
        "MinorLinkerVersion", "SizeOfCode", "SizeOfInitializedData",
        "SizeOfUninitializedData", "AddressOfEntryPoint", "BaseOfCode",
        "BaseOfData", "ImageBase", "SectionAlignment", "FileAlignment",
        "MajorOperatingSystemVersion", "MinorOperatingSystemVersion",
        "MajorImageVersion", "MinorImageVersion", "MajorSubsystemVersion",
        "MinorSubsystemVersion", "Win32VersionValue", "SizeOfImage",
        "SizeOfHeaders", "CheckSum", "Subsystem", "DllCharacteristics",
        "SizeOfStackReserve", "SizeOfStackCommit", "SizeOfHeapReserve",
        "SizeOfHeapCommit", "LoaderFlags", "NumberOfRvaAndSizes" ]
    _nt_nam_64 = _nt_nam_32.copy()
    _nt_nam_64.remove('BaseOfData')
    _dos_nam = ['e_magic', 'e_cblp', 'e_cp', 'e_crlc', 'e_cparhdr',
        'e_minalloc', 'e_maxalloc', 'e_ss', 'e_sp', 'e_csum', 'e_ip', 'e_cs',
        'e_lfarlc', 'e_ovno', 'e_res_0', 'e_res_1', 'e_res_2', 'e_res_3',
        'e_oemid', 'e_oeminfo', 'e_res2_0', 'e_res2_1', 'e_res2_2', 'e_res2_3',
        'e_res2_4', 'e_res2_5', 'e_res2_6', 'e_res2_7', 'e_res2_8', 'e_res2_9',
        'e_lfanew' ]
    _dos_fmt = 'HHHHHHHHHHHHHH4HHH10Hi'
    _sig_fmt = 'I'
    _img_fmt = 'HHIIIHH'
    _dir_fmt = data_directory.packstring * data_directory.packcount
    _sec_fmt = section_header.packstring * section_header.packcount

    _nt_fmt_32 = _sig_fmt + _img_fmt + "HBBIIIIIIIIIHHHHHHIIIIHHIIIIII" \
            + _dir_fmt + _sec_fmt
    _nt_fmt_64 = _sig_fmt + _img_fmt + "HBBIIIIIQIIHHHHHHIIIIHHQQQQII"  \
            + _dir_fmt + _sec_fmt

    def __init__(self, bits=64, base=None):
        if bits not in (32, 64):
            raise RuntimeError("bits must be 32 or 64")
        if base is None:
            import idaapi
            base = idaapi.cvar.inf.min_ea

        self.bits = bits
        self.base = base
        self.dos = self.unpack(self.get_rva(0), self._dos_nam, self._dos_fmt)
        self.nt  = self.unpack(
                self.get_rva(self.dos.e_lfanew),
                getattr(self, "_nt_nam_%i" % bits),
                getattr(self, "_nt_fmt_%i" % bits))

        t2s = data_directory.packcount * data_directory.packchunk
        t4s = section_header.packcount * section_header.packchunk

        self.dirs = [y for y in [data_directory(base, x[1], x[0]) \
                for x in enumerate(chunk_list(self._overspill[0:t2s], \
                data_directory.packchunk))] \
                if not y.empty()]

        self.sections = [y for y in [section_header(base, x) \
                for x in chunk_list(self._overspill[t2s:t2s+t4s], \
                section_header.packchunk)] \
                if not y.empty()]

        self.end  = self.base + self.nt.SizeOfCode;
        self.size = self.nt.SizeOfImage;

        print("-- DOS Header --")
        for k, s in self.dos.items(): print("{:32} {}".format(k, hex(s)))
        print("-- NT Header --")
        for k, s in self.nt.items(): print("{:32} {}".format(k, hex(s)))
        print("-- Directories --")
        for s in self.dirs: print(s)
        print("-- Segments --")
        for s in self.sections: print(s)

    def get_rva(self, offset):
        return self.base + offset

    def zipObject(self, keys, values):
        result = {}
        for x in zip(keys, values):
            result[x[0]] = x[1]
        return result

    def unpack(self, ea, names, fmt):
        d = struct.unpack(fmt, idc.get_bytes(ea, struct.calcsize(fmt)))
        o = self.zipObject(names, d)
        self._overspill = list(d[len(names):])
        return AttrDict(o)

def chunk_list(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.