Manual/Workbench/Capturing & decoding traffic
v1.0.364 notes · build 2026-11-07 12:10 UTC

Capturing & decoding traffic

plain-TCP pipeline · reassembly · session recovery
#workbench #pcap

Basis: the SFS2X game socket is plain TCP, no TLS — no MITM, pinning bypass, or interception proxy needed. Every live claim on these notes came from this pipeline.

1 — Capture

tcpdump -i en0 -w capture.pcap 'tcp and not port 443 and not port 22'. Port 443 excluded = all TLS undecodable (incl. GSL, so at.time/rt never appear in captures). Let the app settle 10–15 s after login before acting (a rushed capture = only the bootstrap burst). Known hosts: 104.17.133.15, 104.17.132.15, 172.65.210.24, 3.33.246.23, 15.197.233.176, 34.145.128.94, 136.107.113.253, 166.117.204.28; ports 19292, 17783, 6088, 25092, 10783. The host list outlives the port list: a server merge moved account zone APS783 → APS8092, port 17783 → 25092; merged-zone host lastwar-game-cf.lastwarapp.net → 172.65.210.24; new IPs may need re-resolving via the login-response serverInfo.

2 — Find the game socket

List TCP streams in the capture; the gateway race can open three gateways on the zone port (two IPv4, one Cloudflare IPv6), each sent an a=29 probe; the first to answer got the login, the others closed ~30 ms later. Detect the client side automatically (initiator or private-address endpoint).

3 — Reassemble + decode

Reassemble by TCP sequence number (reordering/retransmits handled). Example decoded line: [c2s] #233 @offset 25561: {c=1, a=13, p={c=al.science.donate, r=-1, p={scienceId=10011900, option=1, _id=229}}}. Reading the output: c=1 extension call, c=0 system/heartbeat; a = sub-action; p.c = command name; p.p = params; _id = the ever-incrementing per-connection request id echoed by responses.

Rotated-token recovery (ec=28, ep=[E011])

  1. tcpdump a login capture; 2. cold-start the real app, wait for the main screen, quit, stop tcpdump; 3. extract the session from the capture; 4. verify with a reconnect + collect. Extraction: search plain streams, pick the accepted Login (c=0, a=1) whose reply has no ec; write ip, port, zone, gameUid, deviceId, shumeiBoxId, accessToken, iosMode (from platform="0") + appVersion/versionCode; mode 0600; append the other probe-answering gateways as |-delimited fallbacks; refuse if multiple accepted Logins exist (no timestamps to disambiguate). shumeiBoxId optional — empty reconnect [live 2026-10-04]. Tokens are bound to appVersion/versionCode. The output file holds live credentials — keep it out of any repo.

Known limitation — partial decodes

Server→client streams sometimes fail with expected top-level tag 18 (SFS_OBJECT), got ... or zstd decode: ..., often mid bootstrap burst; client→server is never affected. DecodeObject errors are non-fatal (packet skipped); zstd decode errors are fatal to the rest of the stream. Theory: a reassembly edge case, not a codec bug — a capture that the old tshark-fed Python reassembler only partly decoded was fully decoded by a pure-Go reassembler (~35 extra s2c packets). Workaround for cut-off responses: replay live.

notes build 2026-11-07 12:10 UTC · lw-notes@9c47e1b · sources: own capture/analysis cross-checked against the public dossier