# -*- coding: utf-8 -*- """ADVERSARIAL VERIFY 2: who WRITES the gate byte, and who else READS slot +0x2e0. WHY. Live memory (pid 4048, read-only) says FutDataManagerImpl+0x1fd45 is currently 01, while utas_server.py is running with FUT_SETTINGS unset, i.e. it serves {"configs": []}. So either the settings struct default-initialises those fields to 1 and the applier runs anyway, or something other than FUN_18011dc50 writes the byte. Both possibilities contradict the reviewed report's premise that "the byte defaults to zero". H1 FUN_18011dc50 is the ONLY writer of +0x1fd45 / +0x1fd3a in CardsDLL .text. (disassembly scan for any memory operand with displacement 0x1fd3a..0x1fd48) H2 the settings struct handed to the applier is default-constructed with 1s. (callers of FUN_18013c6d0, and the allocation site) H3 of the 8 CALL [reg+0x2e0] sites, only 0x1800aaa43 is a FutDataManagerImpl accessor. ATTACK: decompile all 8 and look at what object they call it on. CONTROL: the same write-scan for 0x1fd3a must find FUN_18011dc50 too. """ import traceback, re, struct OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres" def dump(n, s): p = "%s/v_%s.txt" % (OUT, n) open(p, "w").write(s) print("[wrote %s %d chars]" % (p, len(s))) try: from ghidra.program.model.address import AddressSet blk = [b for b in mem.getBlocks() if b.getName() == ".text"][0] aset = AddressSet(blk.getStart(), blk.getEnd()) print("=" * 78) print("H1: every instruction in .text whose operand displacement is 0x1fd28..0x1fd50") it = listing.getInstructions(aset, True) hits = {} n = 0 rx = re.compile(r"0x1fd([0-9a-f]{2})") while it.hasNext(): ins = it.next() n += 1 t = str(ins) m = rx.search(t) if not m: continue d = int("1fd" + m.group(1), 16) if not (0x1fd28 <= d <= 0x1fd50): continue a = int(ins.getAddress().getOffset()) hits.setdefault(d, []).append((a, fname(a), t)) print(" instructions walked: %d" % n) for d in sorted(hits): print(" --- disp %#x : %d sites ---" % (d, len(hits[d]))) for a, f, t in hits[d]: print(" %#x %-24s %s" % (a, f, t)) print("=" * 78) print("H2: callers of the applier FUN_18011dc50 and of the settings deser FUN_18013c6d0") for tgt in (0x18011dc50, 0x18013c6d0): print(" callers(%#x):" % tgt) try: cs = callers(tgt) except Exception as e: cs = "EXC %s" % e print(" ", cs) try: for r in xrefs_to(tgt): print(" xref", r, fname(r[0]) if isinstance(r, tuple) else "") except Exception as e: print(" xrefs_to EXC", e) print("=" * 78) print("H2b: FULL decompile of every caller of the applier") seen = set() try: cs = callers(0x18011dc50) except Exception: cs = [] for c in cs: va = c if isinstance(c, int) else int(c) if va in seen: continue seen.add(va) s = dec(va) print("----- caller %#x (%s) len=%d -----" % (va, fname(va), len(s))) print(s) dump("q2_applier_caller_%x" % va, s) print("=" * 78) print("H3: decompile every CALL [reg+0x2e0] site's containing function, show the line") SITES = [(0x1800522ac, 0x180051cd0), (0x18006b3dd, 0x18006ac20), (0x18008918b, 0x180088cb0), (0x18008bcc0, 0x18008b6e0), (0x1800aaa43, 0x1800aa440), (0x1800d06a4, 0x1800d0600), (0x18011a61d, 0x18011a5c0), (0x18011cfdc, 0x18011cfa0)] for site, fn in SITES: s = dec(fn) print("--- site %#x in %s : decompile %d chars ---" % (site, fname(fn), len(s))) dump("q2_site_%x" % fn, s) for i, ln in enumerate(s.split("\n")): if "0x2e0" in ln: print(" L%-4d %s" % (i + 1, ln.strip())) # what object? print 12 instructions before the call a = addr(site) ins = listing.getInstructionAt(a) back = [] for _ in range(14): ins = ins.getPrevious() if ins else None if ins is None: break back.append(" %#x %s" % (int(ins.getAddress().getOffset()), ins)) for l in reversed(back): print(l) print(" %#x %s <== the call" % (site, listing.getInstructionAt(a))) print("=" * 78) print("H3b: is 0x18011cfa0 / 0x18011a5c0 operating on the same vtable? print them fully") for fn in (0x18011cfa0, 0x18011a5c0, 0x1800d0600): s = dec(fn) print("===== %#x %s (%d chars) =====" % (fn, fname(fn), len(s))) print(s) except Exception: traceback.print_exc() print("QUERY DONE")