"""Inspect explicit runtime-resolved getter RVAs, never search an assembly's types.""" import argparse import hashlib import json import pathlib import struct from capstone import Cs, CS_ARCH_X86, CS_MODE_64, CS_OP_MEM, CS_OP_REG def main(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('--binary', type=pathlib.Path, required=True) p.add_argument('--handles', type=pathlib.Path, required=True) p.add_argument('--output', type=pathlib.Path, required=True) args = p.parse_args() data = args.binary.read_bytes() pe = struct.unpack_from('8 or any(x.mnemonic not in ('mov','movzx','movss','ret','nop','endbr64') for x in leaf[:first_ret+1]): rows.append({**item,'direct_return_loads':[],'limitation':'No exception entry or proven straight-line leaf getter'}) continue end = leaf[first_ret].address+leaf[first_ret].size boundary = 'Runtime method entry, straight-line leaf return' instructions = list(decoder.disasm(data[offset(rva,end-rva):offset(rva,end-rva)+end-rva],rva)) loads = [] aliases = {'rcx'} for i, ins in enumerate(instructions): # A later dereference into _NodeParam must not be mistaken for # an offset on this. Track overwritten registers and call clobbers. before = set(aliases) if ins.mnemonic == 'call': aliases.difference_update({'rax','rcx','rdx','r8','r9','r10','r11'}) if ins.operands and ins.operands[0].type == CS_OP_REG: dest = ins.reg_name(ins.operands[0].reg) if ins.mnemonic == 'mov' and len(ins.operands)==2 and ins.operands[1].type==CS_OP_REG and ins.reg_name(ins.operands[1].reg) in before: aliases.add(dest) elif ins.mnemonic not in ('cmp','test','push'): aliases.discard(dest) if ins.mnemonic not in ('mov', 'movzx', 'movss') or len(ins.operands) != 2: continue src = ins.operands[1] if src.type != CS_OP_MEM or src.mem.index or ins.reg_name(src.mem.base) not in before: continue if ins.reg_name(ins.operands[0].reg) not in ('eax', 'rax', 'xmm0'): continue after = instructions[i+1:i+7] epilogue = [] for following in after: epilogue.append(following) if following.mnemonic == 'ret': break if not epilogue or epilogue[-1].mnemonic != 'ret' or any(x.mnemonic not in ('add','pop','ret','nop') for x in epilogue): continue loads.append({'offset':src.mem.disp,'size':src.size,'instruction':f'{ins.address:x}: {ins.mnemonic} {ins.op_str}'}) rows.append({**item,'boundary':boundary,'direct_return_loads':loads}) args.output.write_text(json.dumps({'assembly_sha256':hashlib.sha256(data).hexdigest(), 'scope':'Explicit named runtime getter handles; direct baseline return loads only, not computed getter semantics', 'getters':rows},indent=2)+'\n',encoding='utf-8') print(f'Inspected {len(rows)} named getters; {sum(len(x["direct_return_loads"])==1 for x in rows)} have a unique direct return load') if __name__ == '__main__': main()