"""What does the hub body actually contain? Read the sub-parser the hub root delegates to. CHAIN ESTABLISHED SO FAR: FutGetHubDataServerResponse literal 0x18022ce40 -> vtable 0x18022cd48 -> deser 0x1801738b0 (control FutSquadSave -> 0x180171a60 MATCHED in the same run). 0x1801738b0 spends TWO tokenizer calls and then tail-delegates to FUN_180139610. That is the /purchased shape: root spends 2, the sub-parser spends the 3rd and owns the key loop. We answer GET /hub with a FLAT two-key body {"clubPlayers":205,"auctionCount":0}. q_hub_1 scanned 152 deserializers for a direct comparison against clubPlayers (0x90) and found ZERO, with a passing control. auctionCount (0x33) got 7 hits, one of which is FUN_180139610 itself. WHY THAT IS NOT YET AN ANSWER. A source grep for `== 0x90` cannot see running-sum sub/dec ladder dispatch, which is common in this binary, so zero hits is suggestive and not conclusive. Read the ladder instead of grepping it. QUESTIONS Q1 print FUN_180139610 IN FULL and enumerate every atom its ladder handles, including any expressed as a running-sum sub/dec chain rather than an equality test Q2 is clubPlayers 0x90 among them, in ANY dispatch form Q3 does it consume a token before its loop (making the total 3, and therefore eating the first key/value pair of a flat body) or does it start looping immediately Q4 FUN_1801c8210(parser, 3, 1) is called by the hub root and not by the other roots read so far. Find out what it configures, since it may change the token semantics CONTROL: FUN_180139610 must show auctionCount 0x33 somewhere, since the q_hub_1 grep already found it there. If the atom enumeration below cannot see 0x33, the enumeration is broken and its verdict on 0x90 is void. """ import traceback SUB = 0x180139610 CFG = 0x1801C8210 A_CLUB = 0x90 A_AUCTION = 0x33 try: for va, title in ((SUB, "hub body sub-parser"), (CFG, "parser config called by the hub root")): f = func(va) src = dec(va) print("=" * 78) print("%#x %s body %d bytes / decompile %d chars (IN FULL)" % (va, title, f.getBody().getNumAddresses() if f else -1, len(src))) print("=" * 78) print(src) print() # Enumerate atoms mechanically from the instruction stream, which unlike a source # grep also catches sub/dec ladder steps. print("=" * 78) print("ATOMS REACHABLE IN %#x, read from the instruction stream" % SUB) print("=" * 78) f = func(SUB) imms, runsum = [], 0 for ad in f.getBody().getAddresses(True): ins = listing.getInstructionAt(ad) if ins is None: continue m = ins.getMnemonicString().lower() txt = str(ins) if m in ("cmp", "sub", "dec", "add", "mov"): for i in range(ins.getNumOperands()): for o in ins.getOpObjects(i): try: v = int(o.getValue()) except Exception: continue if 0 < v <= 0x400: imms.append((int(ad.getOffset()), m, v, txt)) print(" %d candidate immediates in range 1..0x400" % len(imms)) # running-sum reconstruction: consecutive sub/dec on the same register accumulate acc = 0 print("\n addr mnem imm running-sum disasm") for a, m, v, txt in imms: if m in ("sub", "dec"): acc += v print(" %#010x %-4s %#-6x %#-11x %s" % (a, m, v, acc, txt)) else: print(" %#010x %-4s %#-6x %-11s %s" % (a, m, v, "", txt)) seen = {v for _, m, v, _ in imms} sums = set() acc = 0 for _, m, v, _ in imms: if m in ("sub", "dec"): acc += v sums.add(acc) print("\n distinct raw immediates : %s" % " ".join("%#x" % v for v in sorted(seen))) print(" distinct running sums : %s" % " ".join("%#x" % v for v in sorted(sums))) print("\n CONTROL auctionCount %#x present? %s" % (A_AUCTION, "YES" if (A_AUCTION in seen or A_AUCTION in sums) else "NO -- enumeration broken, verdict void")) print(" clubPlayers %#x present? %s" % (A_CLUB, "YES" if (A_CLUB in seen or A_CLUB in sums) else "NO")) print("\n callees of %#x:" % SUB) for a, n in callees(SUB): print(" %#x %s" % (a, n)) except Exception: traceback.print_exc()