66 lines
3.4 KiB
Python
66 lines
3.4 KiB
Python
"""Read-only disassembly of an explicit PE RVA range; no process attachment."""
|
|
import argparse
|
|
import hashlib
|
|
import pathlib
|
|
import struct
|
|
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--binary', type=pathlib.Path, required=True)
|
|
parser.add_argument('--rva', type=lambda s: int(s, 0), required=True)
|
|
parser.add_argument('--size', type=lambda s: int(s, 0), required=True)
|
|
parser.add_argument('--output', type=pathlib.Path, required=True)
|
|
parser.add_argument('--containing-function', action='store_true', help='Use the AMD64 exception-table bounds containing the selected RVA')
|
|
args = parser.parse_args()
|
|
if not 0 < args.size <= 65536:
|
|
raise SystemExit('Select a range of 1..65536 bytes')
|
|
data = args.binary.read_bytes()
|
|
if data[:2] != b'MZ':
|
|
raise SystemExit('Expected PE image')
|
|
pe = struct.unpack_from('<I', data, 0x3c)[0]
|
|
if data[pe:pe+4] != b'PE\0\0' or struct.unpack_from('<H', data, pe+4)[0] != 0x8664:
|
|
raise SystemExit('Expected AMD64 PE image')
|
|
section_count = struct.unpack_from('<H', data, pe+6)[0]
|
|
optional_size = struct.unpack_from('<H', data, pe+20)[0]
|
|
def file_offset(address, length):
|
|
for i in range(section_count):
|
|
off = pe+24+optional_size+i*40
|
|
_, rva, size, raw = struct.unpack_from('<IIII', data, off+8)
|
|
if rva <= address and address+length <= rva+size and raw+address-rva+length <= len(data):
|
|
return raw+address-rva
|
|
raise ValueError('Range is not contained in a raw section')
|
|
if args.containing_function:
|
|
if struct.unpack_from('<H', data, pe+24)[0] != 0x20b:
|
|
raise SystemExit('Expected PE32+ optional header')
|
|
exception_rva, exception_size = struct.unpack_from('<II', data, pe+24+112+3*8)
|
|
base = file_offset(exception_rva, exception_size)
|
|
matches = []
|
|
for off in range(base, base+exception_size, 12):
|
|
begin, end, _ = struct.unpack_from('<III', data, off)
|
|
if begin <= args.rva < end:
|
|
matches.append((begin, end))
|
|
if len(matches) != 1 or not 0 < matches[0][1]-matches[0][0] <= 65536:
|
|
raise SystemExit('No unique bounded exception-table function entry')
|
|
args.rva, end = matches[0]
|
|
args.size = end-args.rva
|
|
for i in range(section_count):
|
|
off = pe+24+optional_size+i*40
|
|
_, rva, size, raw = struct.unpack_from('<IIII', data, off+8)
|
|
if rva <= args.rva and args.rva+args.size <= rva+size:
|
|
start = raw+args.rva-rva
|
|
if start+args.size > len(data):
|
|
raise SystemExit('Truncated section')
|
|
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
|
|
scope = 'AMD64 exception-table function bounds' if args.containing_function else 'explicit range; function boundaries unproven'
|
|
lines = [f'; SHA256 {hashlib.sha256(data).hexdigest()}',
|
|
f'; RVA {args.rva:#x}, length {args.size:#x}; {scope}']
|
|
lines += [f'{ins.address:x}: {ins.mnemonic} {ins.op_str}' for ins in decoder.disasm(data[start:start+args.size], args.rva)]
|
|
args.output.write_text('\n'.join(lines)+'\n', encoding='utf-8')
|
|
print(f'Wrote {len(lines)-2} instructions to {args.output}')
|
|
return
|
|
raise SystemExit('RVA range is not contained in a raw section')
|
|
|
|
if __name__ == '__main__':
|
|
main()
|