"""DIMENSION 4, pass 12: name the two endpoints in the loan-signing chain, and name the response class whose deserializer is 0x1801293d0 (one of the four duplicateItemIdList consumers). Chain: -> FUN_18009c360 (HTTP 403 -> "GotoAlreadySignedPopup") -> manager vt+0x158 request, vtable 0x1801ed690, continuation FUN_18009bc40 -> FUN_18009bc40 branches on item->duplicateItemId not dup -> vtable 0x1801f3158 request = PUT item {"itemData":[{"id":.., "pile":"club","swap":0,"tradeId":0}]} (atoms 0x16b/0x15c/ 0x226/0x87/0x2fe/0x331 -- decoded from FUN_180127cc0) dup -> UI command "GotoNewItems", no request at all So: dump vtable 0x1801ed690 (its +0x38 URL builder and +0x10 body serializer), and resolve the class name of the response whose deser is 0x1801293d0 by taking the vtable that holds it at slot +0x08 (data ref 0x180220ba8 => vtable base 0x180220ba0) and looking for the RS4: literal referenced by its factory. CONTROL: for the RS4 resolution, also run the same procedure on the known CreatePack deser 0x180162880 (whose class RS4:FutCreatePackServerResponse is already established) and check it comes out right. """ import traceback OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/store/dup12_out.txt" def w(fh, s=""): fh.write(str(s) + "\n") def dump(fh, a, label): s = dec(a) w(fh, "") w(fh, "#" * 70) w(fh, "# %s %#x len(src)=%d FULL" % (label, a, len(s))) w(fh, "#" * 70) w(fh, s) def rs4_for_deser(fh, deser, label): """find vtables holding `deser` at slot +8, then the RS4: name near a factory""" w(fh, "") w(fh, "=== RS4 resolution for deser %#x (%s) ===" % (deser, label)) for frm, typ, fn, ent in xrefs_to(deser): if typ != "DATA": continue vt_base = frm - 8 w(fh, " data ref at %#x -> candidate vtable %#x" % (frm, vt_base)) try: q0 = qword(vt_base) except Exception: continue f0 = fm.getFunctionAt(addr(q0)) if 0x180000000 <= q0 < 0x181000000 else None w(fh, " slot0 = %#x %s" % (q0, f0.getName() if f0 else "(not a function)")) for frm2, typ2, fn2, ent2 in xrefs_to(vt_base): w(fh, " vtable referenced from %#x in %s @ %#x" % (frm2, fn2, ent2)) if not ent2: continue f = func(ent2) if f is None: continue it = listing.getInstructions(f.getBody(), True) while it.hasNext(): ins = it.next() for r in ins.getReferencesFrom(): t = int(r.getToAddress().getOffset()) try: s = rd_str(t, 70) except Exception: continue if s.startswith("RS4:"): w(fh, " -> %s (at %#x)" % (s, t)) try: fh = open(OUT, "w") w(fh, "=== vtable 0x1801ed690 (the request made after signing) ===") for off, tgt, nm in vtable(0x1801ED690, 12): w(fh, " +%#05x -> %#x %s" % (off, tgt, nm)) for off in (0x10, 0x38): t = qword(0x1801ED690 + off) if fm.getFunctionAt(addr(t)) is not None: dump(fh, t, "vtable 0x1801ed690 slot +%#04x" % off) w(fh, "") w(fh, "=== builders that use vtable 0x1801ed690 ===") for frm, typ, fn, ent in xrefs_to(0x1801ED690): w(fh, " %#x %s in %s @ %#x" % (frm, typ, fn, ent)) rs4_for_deser(fh, 0x1801293D0, "one of the four dup consumers") rs4_for_deser(fh, 0x18013BD40, "another dup consumer") rs4_for_deser(fh, 0x180162880, "CONTROL: CreatePack deser") fh.close() print("WROTE", OUT) except Exception: traceback.print_exc()