#!/usr/bin/env python3 """Forge a FifaOnline::FirstPartyAuthCodeFutureImpl node and enqueue it so DoTick (@0x146f199c0) fires GetAuthCode over LSX. Clean-room; from our own RE (ENQUEUE_PLAN.md, adversarially verified). ptrace_scope=0 required. LSX responder MUST be answering GetAuthCode first (the auth call is synchronous with a 15s timeout). Two gates (both live-verified 0): the retriever queue slot AND OriginSDK[+0x3a0] default-user. This sets BOTH. Treat the first run as a PROBE: setting the default user flips ~15 other GetDefaultUser consumers; `--revert` restores the originals. Usage: python3 forge_node.py # forge + enqueue python3 forge_node.py --revert # restore SDK default-user + clear the slot """ import sys, struct, glob, os, json ONLINEMGR_PP = 0x1448a3b20 # *-> OnlineManager ; retriever = +0x4e98 RETR_OFF = 0x4e98 GUARD = 0x1448a3ac3 # enqueue guard byte (must be 1) SDK_PP = 0x144b7c7a0 # *-> OriginSDK SDK_DEFUSER = 0x3a0 # default-user slot (BLOCKER) -- set +0x3a0 AND +0x3a8 SDK_DEREF = 0x3b0 # deref'd unconditionally downstream; must stay non-null VPTR = 0x1438f5d58 # node primary vtable (AddRef/Release/dtor/GetStatus/GetResult) VPTR2 = 0x1438f5d90 # node secondary vtable -- MUST be this, never 0 (Release calls [this+8]->[0]) NODE_VA = 0x14300a380 # validated zero/unreferenced scratch (ENQUEUE_PLAN ยง4) -- re-checked below CLIENTID = b"FIFA17PC" # only proven constraint: non-empty SAVE = "/tmp/forge_node_orig.json" def pid(): for d in glob.glob('/proc/[0-9]*'): try: if open(d+'/comm').read().strip()=='FIFA17.exe': return int(d.split('/')[-1]) except Exception: pass raise SystemExit("FIFA17.exe not running") def build_node(): b = bytearray(0xF0) struct.pack_into(' survives DoTick's Release, never freed b[0x18:0x18+len(CLIENTID)] = CLIENTID # inline clientId, NUL-terminated return bytes(b) def main(): p = pid(); mp = f"/proc/{p}/mem" f = open(mp, "r+b") def rq(va): f.seek(va); return struct.unpack(' {orig.get('defuser',0):#x}/{orig.get('defuser8',0):#x}, slot -> {orig.get('slot',0):#x}") return # --- preconditions (verify, do not assume) --- assert rd(GUARD,1)[0] == 1, "guard byte != 1" assert rq(sdk+SDK_DEREF) != 0, "SDK+0x3b0 is NULL (would fault downstream) -- abort" assert rq(slot) == 0, f"queue slot already non-zero ({rq(slot):#x}) -- abort" scratch = rd(NODE_VA, 0xF0) assert all(x==0 for x in scratch), "scratch NODE_VA not zero -- abort" assert rq(VPTR) == 0x147e8f160, "node vtable[0] mismatch -- wrong build?" # save originals for --revert json.dump({"defuser": rq(sdk+SDK_DEFUSER), "defuser8": rq(sdk+0x3a8), "slot": rq(slot)}, open(SAVE,"w")) # 1) forge the node into scratch (BEFORE anything is armed) wr(NODE_VA, build_node()) assert rd(NODE_VA,0xF0) == build_node(), "node write-back mismatch" print(f"[+] node forged @ {NODE_VA:#x} clientId={CLIENTID.decode()} refcount=2") # 2) gate 2: set the Origin default user (both slots, like the real SDK) wr(sdk+SDK_DEFUSER, struct.pack(' {sdk:#x} (default user)") # 3) gate 1 (the TRIGGER, set last): enqueue the node wr(slot, struct.pack(' {NODE_VA:#x} *** ENQUEUED ***") print(" Watch /tmp/lsx.log for . Then node+0xE8 -> 1,") print(" node+0xE0=200 = error (read node+0x58 msg). --revert to undo.") if __name__ == "__main__": main()