Reverse Engineering Asked on April 10, 2021
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.
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.
Correct answer by Igor Skochinsky on April 10, 2021
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(' ')
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]
Answered by Orwellophile on April 10, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP