"""ADVERSARIAL BATCH 5 -- consequences of the one serve-changing action, and the service gate the other agent left open. 1. untradeable:false flips item+0x49 to 1 on EVERY card. Besides TO_TRADE_PILE that byte feeds FUN_1800bc580, which counts untradeable members of the 11-slot active squad. Who consumes that count, and does flipping it change anything else? 2. FUN_1801a7260's other gate: slot +0x270 of the service FUN_180009c80 resolves. Identify the service vtable and that slot if possible. """ import traceback, struct OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/adv2/q5_raw.txt" try: f = open(OUT, "w") def P(*a): f.write(" ".join(str(x) for x in a) + "\n") P("=" * 25, "1. consumers of the untradeable-squad count", "=" * 25) for a in (0x1800BB2A0, 0x1800BBA10): d = dec(a, timeout=600) P("### %#x len=%d" % (a, len(d))) P(d) P() P("=" * 25, "2. the service locator used by FUN_1801a7260", "=" * 25) for nm, a in (("FUN_1800d7170", 0x1800D7170), ("FUN_180009c80", 0x180009C80), ("FUN_180018bd0", 0x180018BD0), ("FUN_180009b60", 0x180009B60)): P("### " + nm) P(dec(a)) P() P("=" * 25, "3. any vtable with >= 0x280 bytes containing plausible slot 0x270", "=" * 25) # find .rdata runs of >= 0x50 consecutive .text pointers; report those long enough for b in mem.getBlocks(): if b.getName() != ".rdata" or not b.isInitialized(): continue s = int(b.getStart().getOffset()) e = int(b.getEnd().getOffset()) a = (s + 7) & ~7 run_start = None while a + 8 <= e: try: v = qword(a) except Exception: break ok = 0x180001000 <= v < 0x1801E5000 if ok and run_start is None: run_start = a elif not ok and run_start is not None: ln = a - run_start if ln >= 0x280: P(" vtable-ish run %#x..%#x len %#x slot+0x270 -> %#x %s" % (run_start, a, ln, qword(run_start + 0x270), (lambda fn: fn.getName() if fn else "")(fm.getFunctionAt(addr(qword(run_start + 0x270)))))) run_start = None a += 8 f.close() print("WROTE", OUT) except Exception: traceback.print_exc()