"""Q: who READS the parsed item record's discard fields, item+0x38 (the wire discardValue) and item+0x3c (the client's own fcc_discardcoins result)? WHY q2's CONTROL FAILED, and why that was the control's fault: inside FUN_18013fe00 the item is a STACK STRUCT at RBP+0x160, so the guard reads [RBP+0x198] and the store writes [RBP+0x19c]. A scan for the displacements 0x38 and 0x3c can never see them. The control was invalid, not the scan. NEW METHOD -- FINGERPRINT THE STRUCT, NOT THE OFFSET. The item record has several displacements that are rare in general code: +0x146 (preferredPosition, u16), +0x148 (nation), +0x154 (leagueId), +0x94 (teamid), +0xb4 (rating). Any function that dereferences a pointer at two or more of those is handling an item record. Collect the displacement set per function from the instruction text, select the item handlers, and then report their +0x38 / +0x3c usage. SECOND TEST, independent of the fingerprint: find every place in .text where a dword is READ at [reg+0x38] and, within 0x40 bytes and off the SAME base register, a dword is READ at [reg+0x3c]. That is the shape of a "server value else computed value" selector. H1 (consumer reads +0x38 only) predicts no such selector on an item; H2 predicts one. CONTROL for this run: the fingerprint must select FUN_18013fe00 itself when the frame register RBP is allowed, because that function demonstrably touches RBP+0x2a6 (0x146+0x160), RBP+0x2a8, RBP+0x2b4 and RBP+0x214. I print the frame-relative fingerprint hits separately for exactly that reason. """ import re import traceback OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/q3_out.txt" RE_MEM = re.compile(r"\[(R[A-Z0-9]+) \+ (-?0x[0-9a-f]+)\]") try: lines = [] def P(*a): lines.append(" ".join(str(x) for x in a)) ITEM_MARKS = (0x146, 0x148, 0x154, 0x94, 0xb4, 0x8c) FRAME_MARKS = tuple(m + 0x160 for m in ITEM_MARKS) per_fn = {} # entry -> {'name', 'disp': {d: [(addr, base, txt)]}} reads38 = [] # (addr, base, fnentry, txt) reads3c = [] n_ins = 0 it = listing.getInstructions(True) while it.hasNext(): ins = it.next() n_ins += 1 txt = str(ins) if "[" not in txt: continue ms = RE_MEM.findall(txt) if not ms: continue a = int(ins.getAddress().getOffset()) f = fm.getFunctionContaining(ins.getAddress()) ent = int(f.getEntryPoint().getOffset()) if f else 0 rec = per_fn.setdefault(ent, {"name": f.getName() if f else "?", "disp": {}}) for base, dtxt in ms: d = int(dtxt, 16) rec["disp"].setdefault(d, []).append((a, base, txt)) if d == 0x38 and txt.startswith("MOV E") and "dword ptr [" + base in txt: reads38.append((a, base, ent, txt)) if d == 0x3c and txt.startswith("MOV E") and "dword ptr [" + base in txt: reads3c.append((a, base, ent, txt)) P("instructions scanned: %d ; functions with memory operands: %d" % (n_ins, len(per_fn))) # ---- CONTROL: the frame-relative fingerprint must select FUN_18013fe00 P("") P("=== CONTROL: frame-relative item fingerprint (marks + 0x160) ===") ctl = [] for ent, rec in per_fn.items(): got = [m for m in FRAME_MARKS if m in rec["disp"]] if len(got) >= 3: ctl.append((ent, rec["name"], [hex(g) for g in got])) for ent, nm, got in sorted(ctl): P(" %-18s %#x marks %s %s" % (nm, ent, got, "<== FUN_18013fe00" if ent == 0x18013FE00 else "")) P(" control %s" % ("PASS" if any(e == 0x18013FE00 for e, _, _ in ctl) else "FAIL -- fingerprint cannot see the known item handler")) # ---- pointer-relative fingerprint: the real search P("") P("=== ITEM HANDLERS BY POINTER-RELATIVE FINGERPRINT (>=2 of %s) ===" % [hex(m) for m in ITEM_MARKS]) cands = [] for ent, rec in per_fn.items(): got = [] for m in ITEM_MARKS: for (a, base, txt) in rec["disp"].get(m, []): if base not in ("RSP", "RBP"): got.append(m) break if len(got) >= 2: cands.append((ent, rec["name"], got)) P("candidates: %d" % len(cands)) for ent, nm, got in sorted(cands): rec = per_fn[ent] h38 = [(a, b, t) for (a, b, t) in rec["disp"].get(0x38, []) if b not in ("RSP", "RBP")] h3c = [(a, b, t) for (a, b, t) in rec["disp"].get(0x3c, []) if b not in ("RSP", "RBP")] P("") P(" %-18s %#x marks %s +0x38:%d +0x3c:%d" % (nm, ent, [hex(g) for g in got], len(h38), len(h3c))) for a, b, t in h38: P(" 38 %#x %s" % (a, t)) for a, b, t in h3c: P(" 3c %#x %s" % (a, t)) # ---- selector shape P("") P("=== SELECTOR SHAPE: dword read [reg+0x38] then dword read [SAME reg+0x3c] within 0x40 ===") idx3c = {} for a, base, ent, txt in reads3c: idx3c.setdefault(base, []).append((a, ent, txt)) nsel = 0 for a, base, ent, txt in reads38: for a2, ent2, txt2 in idx3c.get(base, []): if 0 < a2 - a <= 0x40: nsel += 1 nm = per_fn.get(ent, {}).get("name", "?") P(" %s @ %#x : %#x %s -> %#x %s" % (nm, ent, a, txt, a2, txt2)) P(" selectors found: %d" % nsel) P(" (reads at +0x38: %d, reads at +0x3c: %d, over the whole .text)" % (len(reads38), len(reads3c))) with open(OUT, "w") as fh: fh.write("\n".join(lines)) print("wrote %s (%d lines)" % (OUT, len(lines))) except Exception: traceback.print_exc()