"""VERIFY-2. Attack the ABSENCE claims by asm-level immediate enumeration. HYPOTHESES UNDER ATTACK: H6 extPrice finalPrice (0x180139070) / originalPrice (0x18013aae0) read ONLY atom 0x11a (externalPriceId). They do NOT read 0x1b (amount) or 0xc4 (currency). H7 currency element deser 0x180138bd0 reads ONLY 0x1d0/0x134/0x124, stride 0x30. H8 FutCreatePackServerResponse deser 0x180162880 has arms ONLY for 0x16e,0x1dd,0x264,0xec -- no reason/errorCode/state. H9 FUN_18002c3c0 has exactly ONE caller (0x1800150d0) and does pure copies of +0x144..+0x154. H10 the 0x20f..0x298 jump table in 0x18013af30 really does bind 0x26b quantity, 0x298 saleType, 0x20f packType to real arms. METHOD: instead of reading the decompiler, I enumerate EVERY scalar immediate that appears in a CMP/SUB/LEA/MOV inside each function's real instruction listing. If an atom id is nowhere in that set, it cannot be dispatched on. This is an exhaustive upper bound over the function body and is a different method from reading C output. CONTROL: 0x18013af30 must yield 0x20c, 0x2e3, 0x35d in its immediate set (known present) and must yield the 5 packContentInfo ids. If the technique misses those, it is broken and every absence below is void. """ import traceback OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres/" try: def all_scalars(entry): f = func(entry) if f is None: return None, None out = {} it = listing.getInstructions(f.getBody(), True) n = 0 while it.hasNext(): ins = it.next(); n += 1 for i in range(ins.getNumOperands()): try: sc = ins.getScalar(i) except Exception: sc = None if sc is None: continue v = int(sc.getUnsignedValue()) out.setdefault(v, []).append((int(ins.getAddress().getOffset()), str(ins))) return out, n def dump(entry, tag): src = dec(entry) p = OUT + "v2_%s_%x.txt" % (tag, entry) open(p, "w").write(src) print("[%s] %#x len(src)=%d lines=%d -> %s" % (tag, entry, len(src), src.count("\n"), p)) return src def asm(entry, tag): f = func(entry) lines = [] it = listing.getInstructions(f.getBody(), True) while it.hasNext(): i = it.next() lines.append("%010x %s" % (int(i.getAddress().getOffset()), str(i))) p = OUT + "v2_%s_%x.asm" % (tag, entry) open(p, "w").write("\n".join(lines) + "\n") return p ATOMS = {0x1b: "amount", 0xc4: "currency", 0x11a: "externalPriceId", 0x124: "finalFunds", 0x134: "funds", 0x1d0: "name", 0x16e: "itemList", 0x1dd: "numberItems", 0x264: "purchasedPackId", 0xec: "duplicateItemIdList", 0x2eb: "state", 0x28b: "reason", 0x20b: "packId", 0x127: "firstPartyStoreId", 0x26b: "quantity", 0x298: "saleType", 0x20f: "packType", 0x176: "isPremium", 0x15c: "id", 0x20c: "packContentInfo", 0x2e3: "start", 0x35d: "unopened", 0x63: "bronzeQuantity", 0x149: "goldQuantity", 0x170: "itemQuantity", 0x273: "rareQuantity", 0x2c6: "silverQuantity", 0x240: "points", 0x265: "purchaseLimit", 0x261: "purchaseCount", 0x37d: "visible", 0x36a: "useDefaultImage", 0x102: "end", 0xcc: "dealType", 0x2cb: "sortPriority", 0x33a: "transactionId", 0x367: "useAuth", 0x368: "useCount", 0x375: "useTime", 0x369: "useCredits", 0x36b: "usePreOrder", 0x258: "productId", 0x14e: "groupName", 0x266: "purchasePackType", 0x260: "purchase"} for entry, tag in [(0x18013af30, "CONTROL_packelem"), (0x180139070, "finalPrice"), (0x18013aae0, "originalPrice"), (0x180138bd0, "currencyElem"), (0x180162880, "createpack_deser"), (0x180162530, "createpack_reqser"), (0x1801269f0, "purchaseitems_deser")]: sc, n = all_scalars(entry) if sc is None: print("!! no function at %#x" % entry); continue present = sorted(a for a in ATOMS if a in sc) print() print("=" * 74) print("%s %#x instructions=%d distinct scalars=%d" % (tag, entry, n, len(sc))) print(" ATOM IDS PRESENT AS IMMEDIATES:") for a in present: sites = sc[a][:3] print(" %-6s %-22s %s" % (hex(a), ATOMS[a], "; ".join("%010x %s" % s for s in sites))) missing = sorted(a for a in ATOMS if a not in sc) print(" ABSENT: " + ", ".join("%s(%s)" % (hex(a), ATOMS[a]) for a in missing)) print() print("=" * 74) print("H10: jump table behind LEA EAX,[R15-0x20f]; CMP EAX,0x89") # find R13 base f = func(0x18013af30) it = listing.getInstructions(f.getBody(), True) while it.hasNext(): i = it.next() s = str(i) if "R13" in s and i.getMnemonicString() in ("LEA", "MOV") and s.split(',')[0].endswith("R13"): print(" R13 set:", "%010x %s" % (int(i.getAddress().getOffset()), s)) idx = read_bytes(0x18013bcb4, 0x8a) print(" index table @0x18013bcb4 (%d bytes):" % len(idx), idx.hex()) offs = [dword(0x18013bc98 + 4 * k) for k in range(max(idx) + 1)] print(" offset table @0x18013bc98:", ["%08x" % o for o in offs]) print(" atom -> target:") for k in range(0x8a): atom = 0x20f + k t = (offs[idx[k]] + 0x180000000) & 0xFFFFFFFFFFFF nm = ATOMS.get(atom, "") if nm or t != (offs[idx[0x8a - 1]] + 0x180000000): pass # group atoms by target from collections import defaultdict g = defaultdict(list) for k in range(0x8a): g[offs[idx[k]] + 0x180000000].append(0x20f + k) for t in sorted(g): ats = g[t] named = [("%s=%s" % (hex(a), ATOMS[a])) for a in ats if a in ATOMS] print(" target %010x n=%-3d %s" % (t, len(ats), ", ".join(named) if named else "")) print() print("=" * 74) print("H9: xrefs to FUN_18002c3c0") for r in xrefs_to(0x18002c3c0): print(" from %010x %-14s in %s (%010x)" % (r[0], r[1], r[2], r[3])) s = dump(0x18002c3c0, "adapter") import re print(" --- lines mentioning 0x14[4-9c]/0x15[04]/0xb4/0xcd ---") for ln in s.split("\n"): if any(k in ln for k in ("0x144", "0x148", "0x14c", "0x150", "0x154", "+ 0xb4", "+ 0xcd")): print(" ", ln.strip()) print() print("=" * 74) print("STRING LITERALS referenced by the pack element deser") for a in (0x1801e98c8, 0x180223228, 0x1801fd44c, 0x180223238, 0x180221c04, 0x1801efea0, 0x1801ec008): print(" %010x = %r" % (a, rd_str(a, 40))) for e, t in [(0x180139070, "finalPrice"), (0x18013aae0, "originalPrice"), (0x180138bd0, "currencyElem"), (0x180162880, "createpack_deser")]: dump(e, t); asm(e, t) except Exception: traceback.print_exc()