sots-re/verify/harness/profiling/funcs.py

38 lines
1.7 KiB
Python

#!/usr/bin/env python3
"""Heuristic function-start table for the SotS exe (no symbols): scan .text for MSVC prologues
and call targets. Output: funcs.txt with one 'ghidra_addr' per line, sorted."""
import struct, sys
data = open(sys.argv[1], 'rb').read()
pe = struct.unpack_from('<I', data, 0x3c)[0]
nsec = struct.unpack_from('<H', data, pe + 6)[0]
optsz = struct.unpack_from('<H', data, pe + 20)[0]
imgbase = struct.unpack_from('<I', data, pe + 0x34)[0]
secs = []
for i in range(nsec):
o = pe + 24 + optsz + i * 40
name = data[o:o+8].rstrip(b'\0').decode(errors='replace')
vsz, va, rsz, rp = struct.unpack_from('<IIII', data, o + 8)
secs.append((name, va, vsz, rp, rsz))
text = [s for s in secs if s[0] == '.text'][0]
_, tva, tvsz, trp, trsz = text
lo, hi = imgbase + tva, imgbase + tva + trsz
code = data[trp:trp+trsz]
starts = set()
# call rel32 targets inside .text
for i in range(len(code) - 5):
if code[i] == 0xE8:
rel = struct.unpack_from('<i', code, i + 1)[0]
tgt = lo + i + 5 + rel
if lo <= tgt < hi:
starts.add(tgt)
# prologues: push ebp; mov ebp,esp (55 8B EC) preceded by ret/int3/nop/padding
for i in range(1, len(code) - 3):
if code[i] == 0x55 and code[i+1] == 0x8B and code[i+2] == 0xEC and code[i-1] in (0xC3, 0xCC, 0x90, 0xC2, 0x00) :
starts.add(lo + i)
# also CC-padded starts of any kind: prev byte CC and this byte not CC
if code[i-1] == 0xCC and code[i] != 0xCC and code[i] in (0x55, 0x53, 0x56, 0x57, 0x83, 0x81, 0x8B, 0x6A, 0x68, 0xB8, 0x33, 0xE9, 0xA1, 0x51, 0x52):
starts.add(lo + i)
with open('funcs.txt', 'w') as f:
for a in sorted(starts):
f.write('%08x\n' % a)
print('imgbase %x text %x-%x funcs %d' % (imgbase, lo, hi, len(starts)))