"""Read-only native evidence for local navigation; never attaches to or invokes the game."""
import argparse, bisect, hashlib, json, struct
from pathlib import Path
import pefile
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
from capstone.x86 import X86_OP_MEM, X86_OP_IMM, X86_REG_RIP
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('image', type=Path)
p.add_argument('--rva', action='append', type=lambda s: int(s, 0), required=True)
p.add_argument('--output', type=Path, required=True)
a = p.parse_args()
raw = a.image.read_bytes()
pe = pefile.PE(data=raw, fast_load=True)
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXCEPTION']])
rows = sorted((e.struct.BeginAddress, e.struct.EndAddress, e.struct.UnwindData) for e in pe.DIRECTORY_ENTRY_EXCEPTION)
starts = [r[0] for r in rows]
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
decoder.detail = True
parents={}
def parent(entry):
if entry[0] in parents:return parents[entry[0]]
first=entry[0]
for _ in range(16):
header=pe.get_data(entry[2],4)
if not (header[0]>>3)&4:break
entry=struct.unpack('<III',pe.get_data(entry[2]+4+((header[2]+1)&~1)*2,12))
parents[first]=entry[0]
return entry[0]
groups={}
for row in rows:groups.setdefault(parent(row),[]).append(row)
def text_at(rva):
if not 0 <= rva < pe.OPTIONAL_HEADER.SizeOfImage:
return None
try:
b = pe.get_data(rva, 160).split(b'\0')[0]
except pefile.PEFormatError:
return None
return b.decode('ascii') if 2 < len(b) < 160 and all(32 <= c < 127 for c in b) else None
def function(rva):
i = bisect.bisect_right(starts, rva)-1
if i < 0 or not rows[i][0] <= rva < rows[i][1]:
leaf=list(decoder.disasm(pe.get_data(rva,128),rva))
stop=next((n for n,x in enumerate(leaf) if x.mnemonic in ('ret','jmp')),None)
if stop is None:raise ValueError('Missing bounded leaf at '+hex(rva))
end=leaf[stop].address+leaf[stop].size;begin=rva;fragments=[(begin,end,0)]
else:
begin=parent(rows[i]);fragments=groups[begin];end=max(r[1] for r in fragments)
lines=[]
for ins in (ins for start,finish,_ in fragments for ins in decoder.disasm(pe.get_data(start,min(finish-start,65536)),start)):
annotations=[]
for op in ins.operands:
if op.type == X86_OP_MEM and op.mem.base == X86_REG_RIP:
at = ins.address+ins.size+op.mem.disp
value = text_at(at)
try:
b = pe.get_data(at, 8)
except pefile.PEFormatError:
b = b''
annotations.append(hex(at)+(' "'+value+'"' if value else (' f32='+str(struct.unpack('<f',b[:4])[0]) if len(b)>=4 else '')))
elif op.type == X86_OP_IMM and ins.mnemonic == 'call':
annotations.append('call '+hex(op.imm))
lines.append(hex(ins.address)+' '+ins.mnemonic+' '+ins.op_str+(' ; '+' | '.join(annotations) if annotations else ''))
return {'requested':hex(rva), 'begin':hex(begin), 'end':hex(end), 'fragments':[(hex(r[0]),hex(r[1])) for r in fragments], 'lines':lines}
result={'imageSha256':hashlib.sha256(raw).hexdigest(), 'scope':'Static PE evidence only. A symbol reference does not validate an object layout or invocation ABI.', 'functions':[function(r) for r in a.rva]}
a.output.parent.mkdir(parents=True, exist_ok=True)
a.output.write_text(json.dumps(result, indent=2), encoding='utf-8')
print(json.dumps({'output':str(a.output), 'functions':[(f['begin'],len(f['lines'])) for f in result['functions']]}))
"""Read-only native evidence for local navigation; never attaches to or invokes the game."""
import argparse, bisect, hashlib, json, struct
from pathlib import Path
import pefile
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
from capstone.x86 import X86_OP_MEM, X86_OP_IMM, X86_REG_RIP
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('image', type=Path)
p.add_argument('--rva', action='append', type=lambda s: int(s, 0), required=True)
p.add_argument('--output', type=Path, required=True)
a = p.parse_args()
raw = a.image.read_bytes()
pe = pefile.PE(data=raw, fast_load=True)
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXCEPTION']])
rows = sorted((e.struct.BeginAddress, e.struct.EndAddress, e.struct.UnwindData) for e in pe.DIRECTORY_ENTRY_EXCEPTION)
starts = [r[0] for r in rows]
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
decoder.detail = True
parents={}
def parent(entry):
if entry[0] in parents:return parents[entry[0]]
first=entry[0]
for _ in range(16):
header=pe.get_data(entry[2],4)
if not (header[0]>>3)&4:break
entry=struct.unpack('<III',pe.get_data(entry[2]+4+((header[2]+1)&~1)*2,12))
parents[first]=entry[0]
return entry[0]
groups={}
for row in rows:groups.setdefault(parent(row),[]).append(row)
def text_at(rva):
if not 0 <= rva < pe.OPTIONAL_HEADER.SizeOfImage:
return None
try:
b = pe.get_data(rva, 160).split(b'\0')[0]
except pefile.PEFormatError:
return None
return b.decode('ascii') if 2 < len(b) < 160 and all(32 <= c < 127 for c in b) else None
def function(rva):
i = bisect.bisect_right(starts, rva)-1
if i < 0 or not rows[i][0] <= rva < rows[i][1]:
leaf=list(decoder.disasm(pe.get_data(rva,128),rva))
stop=next((n for n,x in enumerate(leaf) if x.mnemonic in ('ret','jmp')),None)
if stop is None:raise ValueError('Missing bounded leaf at '+hex(rva))
end=leaf[stop].address+leaf[stop].size;begin=rva;fragments=[(begin,end,0)]
else:
begin=parent(rows[i]);fragments=groups[begin];end=max(r[1] for r in fragments)
lines=[]
for ins in (ins for start,finish,_ in fragments for ins in decoder.disasm(pe.get_data(start,min(finish-start,65536)),start)):
annotations=[]
for op in ins.operands:
if op.type == X86_OP_MEM and op.mem.base == X86_REG_RIP:
at = ins.address+ins.size+op.mem.disp
value = text_at(at)
try:
b = pe.get_data(at, 8)
except pefile.PEFormatError:
b = b''
annotations.append(hex(at)+(' "'+value+'"' if value else (' f32='+str(struct.unpack('<f',b[:4])[0]) if len(b)>=4 else '')))
elif op.type == X86_OP_IMM and ins.mnemonic == 'call':
annotations.append('call '+hex(op.imm))
lines.append(hex(ins.address)+' '+ins.mnemonic+' '+ins.op_str+(' ; '+' | '.join(annotations) if annotations else ''))
return {'requested':hex(rva), 'begin':hex(begin), 'end':hex(end), 'fragments':[(hex(r[0]),hex(r[1])) for r in fragments], 'lines':lines}
result={'imageSha256':hashlib.sha256(raw).hexdigest(), 'scope':'Static PE evidence only. A symbol reference does not validate an object layout or invocation ABI.', 'functions':[function(r) for r in a.rva]}
a.output.parent.mkdir(parents=True, exist_ok=True)
a.output.write_text(json.dumps(result, indent=2), encoding='utf-8')
print(json.dumps({'output':str(a.output), 'functions':[(f['begin'],len(f['lines'])) for f in result['functions']]}))