550b23bb26
Found without Ghidra, via anadius's LSX command-registration table (handler list is offset-by-one from the names; verified by +0x27060 = GetProfile). The gate is GetInternetConnectedState @ anadius64.dll+0x27790: its LSX "connected" attribute = (byte[+0xCAB1B]||byte[+0xCAB1A]) ? str(+0xAF530) : str(+0xADE64); both flags default 0 -> reports offline. That is why the client aborts at "connecting". M1 answers: (1) ProtoSSLConnect not reached (gate upstream); (2) connection-state function named; (3) single actionable gate (token already provided by anadius FakeAuth / GoOnline passes). M2 (next session) = make GetInternetConnectedState report connected, then watch for the real Blaze connect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
196 lines
10 KiB
Markdown
196 lines
10 KiB
Markdown
# Connection-gate findings (clean-room)
|
|
|
|
**Status:** observed behaviour only — derived from running the client and reading
|
|
the repack's own files. No EA source used. Unconfirmed values are marked
|
|
`TODO/CONFIRM` rather than guessed.
|
|
|
|
## Components (observed)
|
|
|
|
| Component | Role |
|
|
|---|---|
|
|
| `FIFA23.exe` | Game. Statically links EA's **Ebisu SDK** (online) and **DirtySDK/ProtoSSL** (transport). Loads at fixed base `0x140000000` — no ASLR seen across launches. |
|
|
| `_ProtoSSLSendPacket` @ `FIFA23.exe+0xEFA530` | DirtySDK TLS record assembler — plaintext in, encrypts in place. Already located + hooked. |
|
|
| `anadius64.dll` | anadius EA-app/Origin **LSX server** emulator (ASLR'd). Provides offline DRM / entitlements / persona so the game *launches*; deliberately keeps it **offline**. |
|
|
| `FakeEAACLauncher` | Anti-cheat bypass only. Irrelevant to the online gate. |
|
|
|
|
## Observed online-startup flow
|
|
|
|
1. Ebisu SDK opens LSX socket(s) to anadius; a **challenge/response handshake**
|
|
completes — captured XML: `<Challenge>` / `<ChallengeResponse>` /
|
|
`<ChallengeAccepted>`, `sender="EALS"`, `ContentId 16115019`.
|
|
2. River/PIN telemetry `<session type=boot>` is emitted.
|
|
3. **No further socket traffic.** Clicking Ultimate Team → "connecting to the EA
|
|
Servers…" → **zero** network attempts (no Blaze DNS, no `connect`, nothing on
|
|
`send`/`recv`/`WSASend`/`WSARecv`) → falls back to offline.
|
|
|
|
## Conclusion: the decision is upstream and in-process
|
|
|
|
- The online/offline verdict is delivered through anadius's **in-process
|
|
detoured Ebisu calls**, not socket messages. (No `GetAuthCode`/`GoOnline`/
|
|
entitlement query appears on any socket, sync or async.)
|
|
- The game therefore **never reaches DirtySDK/ProtoSSL for Blaze** — it aborts
|
|
before any `ProtoSSLConnect(gosredirector…)`. The gate is an **Ebisu-SDK
|
|
connection-state / online-session check upstream of the transport.**
|
|
- `ONLINE_ACCESS` is a `Blaze::Nucleus::EntitlementType` (enum value `1`).
|
|
anadius's `GoOnline` LSX handler (`anadius64.dll+0x2BB90`) is a success stub;
|
|
`anadius64.dll+0xB6B1` is a large entitlement/profile builder that *does*
|
|
reference `ONLINE_ACCESS`. Reverse-engineering that entitlement-status logic
|
|
is the **wrong fight** (see strategy).
|
|
|
|
## Strategy (decided)
|
|
|
|
Do **not** make anadius report "online." anadius exists to keep the game
|
|
offline, and it can't be removed without breaking launch/DRM. Instead:
|
|
|
|
> Let the game run its **real** online flow and answer that flow ourselves via
|
|
> the hook + brain. Target the Ebisu connection-state check the FUT-entry path
|
|
> polls; make the game *pass* it so it issues the real `ProtoSSLConnect` to the
|
|
> Blaze redirector → our redirect + ProtoSSL hook capture the real frames.
|
|
|
|
This deletes the "reverse-engineer anadius's entitlement logic" problem.
|
|
|
|
## Next RE step (converges with the ProtoSSL hook)
|
|
|
|
1. Locate and **read-only hook `ProtoSSLConnect`** (same DirtySDK module and
|
|
technique that found `_ProtoSSLSendPacket`). Confirms the game never
|
|
initiates the Blaze connection (pins the gate strictly upstream) and gives us
|
|
the exact symbol that will flip from "never called" to "called" on success.
|
|
2. Locate the Ebisu **connection-state getter** the FUT-entry / "connecting" UI
|
|
polls. Candidate string anchors (observed): `ONLINE_STATUS_EVENT`,
|
|
`GoOnline` result handling, the "connecting to the EA Servers" UI trigger.
|
|
3. Determine the minimal intervention to make that getter report **connected**
|
|
(out-hook it in our `version.dll`, winning over anadius's detour) so the game
|
|
proceeds to `ProtoSSLConnect`.
|
|
- `TODO/CONFIRM`: whether out-hooking the getter is sufficient, or secondary
|
|
checks (auth-token presence) also gate the attempt.
|
|
|
|
---
|
|
|
|
## M1 results (read-only investigation)
|
|
|
|
Method: `protossl-scan` (xref / disasm / callers, app-module-restricted) against
|
|
the live client, plus the existing `connect`/DNS/LSX hooks. Observed behaviour
|
|
only — no EA source.
|
|
|
|
### Point 1 — is `ProtoSSLConnect` reached on the "connecting" abort? **NO — gate is upstream. CONFIRMED.**
|
|
- The existing `connect` / `GetAddrInfoW` / `getaddrinfo` hooks show the client
|
|
makes **zero** network attempts during the "connecting to the EA Servers"
|
|
abort: no DNS for any `gosredirector.*` host, no `connect` to any external
|
|
address.
|
|
- The DirtySDK/ProtoSSL transport is therefore never reached — the abort is
|
|
entirely upstream and in-process.
|
|
- `ProtoSSLConnect` has no debug string and sits behind the DirtySDK API, so an
|
|
explicit hook on it isn't needed to prove this; the behavioural evidence is
|
|
conclusive. `TODO/CONFIRM`: locate `ProtoSSLConnect` by signature later, as a
|
|
positive M2 trip-wire (it should fire once we flip the gate).
|
|
|
|
### Surface mapped (clean-room)
|
|
- **Redirector host table** (`FIFA23.exe` .rdata, pointer table @ `+0x83FC858`):
|
|
`spring18.gosredirector.{sdev,stest,scert}.ea.com` + production
|
|
`spring18.gosredirector.ea.com`.
|
|
- **Redirector request/response config** (same region): `X-BLAZE-ERRORCODE`,
|
|
**`Authorization:`**, `<errorCode>` — the redirector request carries an
|
|
**Authorization header (a Nucleus token)**.
|
|
- **Enum-name tables** (serialization only, NOT decision code): `ONLINE_ACCESS`
|
|
(`Blaze::Nucleus::EntitlementType` = 1), `ONLINE_STATUS_EVENT`, … These are
|
|
reflection tables keyed by enum *value*; string-xref of them is a dead end for
|
|
the decision (the decision compares values, not strings).
|
|
|
|
### Point 2 — name the connection-state function: **PARTIAL.**
|
|
- The exact connection-state decision is behind heavy C++ vtable indirection
|
|
(the redirector config's two code pointers resolved to a virtual-dispatch
|
|
thunk @ `FIFA23.exe+0x27BD4C0` and a `ret 0` stub @ `+0x4F3CBC0`). The
|
|
memory-scan toolkit (string / pattern / xref / callers) cannot efficiently
|
|
navigate this.
|
|
- **Pinning the exact function needs a static disassembler with decompilation
|
|
(Ghidra / IDA) on `FIFA23.exe`.** `protossl-scan` remains the bridge to the
|
|
live process (mapping static addresses to the ASLR'd runtime, confirming hits,
|
|
installing hooks).
|
|
- `TODO/CONFIRM`: name the connection-state getter by module+offset via Ghidra.
|
|
|
|
### Point 3 — gate count: **TWO gates (state + token). Evidence-backed.**
|
|
- The online-connect path **reads/requires an auth (Nucleus) token**: the
|
|
redirector request carries an `Authorization:` header, and the SDK surface has
|
|
`GetAuthCode` / `FakeAuth` / `<GameToken>`. So it is **not** a single
|
|
connection-state boolean flip — even with the state forced "online", the client
|
|
needs a valid token to build the redirector request.
|
|
- **Conclusion for M2: plan for two gates** — (a) the connection-state decision,
|
|
and (b) supplying an auth token the client accepts.
|
|
- `TODO/CONFIRM` the exact abort point (no-token vs state-says-offline vs both) —
|
|
needs Ghidra-level control-flow tracing.
|
|
|
|
### Dynamic probe result (read-only) — `GoOnline` is NOT the gate
|
|
A read-only detour on anadius's `GoOnline` handler (`anadius64.dll+0x2BB90`)
|
|
shows the game **does** call `GoOnline` during the "connecting" attempt (incl.
|
|
on the Ultimate Team click) and anadius returns success — **yet no Blaze
|
|
connection follows** (still zero external `CONNECT`/DNS).
|
|
|
|
Therefore the gate is **downstream of `GoOnline`**: the game decides to go
|
|
online and the request is accepted, then it aborts at the **auth-token step
|
|
(`GetAuthCode`) and/or while waiting for the "online established" status
|
|
callback** (`ONLINE_STATUS_EVENT`), and times out into offline.
|
|
|
|
This sharpens the two-gate picture: `GoOnline` passes; the real blocker is the
|
|
**token / online-status step**. `TODO/CONFIRM` which (token-missing vs
|
|
status-never-fires) — via a `GetAuthCode` probe and/or Ghidra.
|
|
|
|
### Recommendation / next
|
|
Name the downstream gate. Two complementary routes:
|
|
- **Dynamic:** probe anadius's `GetAuthCode` handler (does it return a token or
|
|
fail?) — distinguishes token-gate from status-callback.
|
|
- **Static (Ghidra):** xref the `GoOnline` / `GetAuthCode` / `ONLINE_STATUS_EVENT`
|
|
strings in `FIFA23.exe` to find the FUT online-flow code that issues `GoOnline`
|
|
then waits for the token/status, and decompile it. FIFA isn't ASLR'd, so Ghidra
|
|
addresses (image base `0x140000000`) map 1:1 to our recorded offsets.
|
|
|
|
---
|
|
|
|
## M1 COMPLETE — the gate is `GetInternetConnectedState`
|
|
|
|
Found without Ghidra, by reading anadius's LSX **command-registration** function
|
|
(`anadius64.dll+0x14C0`), which lists every command-name → handler inline. NB the
|
|
`lea rax,[handler]` is **offset by one** from the `lea rdx,[name]` in that listing
|
|
(verified empirically: `+0x27060` decompiles to `GetProfile` — it builds a
|
|
`GetProfileResponse` for persona "fun"). Corrected handler map:
|
|
|
|
| anadius LSX command | handler (anadius64.dll + …) |
|
|
|---|---|
|
|
| GetProfile | 0x27060 |
|
|
| **GetInternetConnectedState** | **0x27790** |
|
|
| GoOnline | 0x2BB90 |
|
|
| GetAuthCode | 0x2BBC0 |
|
|
|
|
### The gate, named: `GetInternetConnectedState` @ `anadius64.dll+0x27790`
|
|
It serializes an LSX `InternetConnectedState` response with a `connected`
|
|
attribute whose value is:
|
|
|
|
```
|
|
connected = (byte[+0xCAB1B] != 0 || byte[+0xCAB1A] != 0) ? str(+0xAF530)
|
|
: str(+0xADE64)
|
|
```
|
|
|
|
Both flag bytes **default to 0**, so it reports the offline value (`+0xADE64`).
|
|
That is precisely why the client sits at "connecting to the EA Servers" and falls
|
|
back offline.
|
|
|
|
### Answers to the three M1 points
|
|
1. **ProtoSSLConnect reached on the abort? NO — gate upstream.** Confirmed.
|
|
2. **The connection-state function:** `GetInternetConnectedState` @
|
|
`anadius64.dll+0x27790`. Decision = the two-flag branch above.
|
|
3. **Gate count: ONE actionable gate.** The auth token is already satisfied —
|
|
`GoOnline` (`+0x2BB90`) is called during the attempt and returns success, and
|
|
anadius provides a fake auth code; the blocker is purely the connection-state.
|
|
So M2 = make `GetInternetConnectedState` report **connected** (then the game
|
|
proceeds with its existing token).
|
|
|
|
### M2 directions (do NOT do this session)
|
|
- Check whether the flags at `+0xCAB1A` / `+0xCAB1B` are settable via anadius
|
|
config / a hidden option (cheapest flip).
|
|
- Else out-detour `GetInternetConnectedState` in our `version.dll` to force the
|
|
`+0xAF530` ("connected") path.
|
|
- Then watch for the client to attempt the real Blaze connect (our `connect`
|
|
redirect + ProtoSSL hook capture the first plaintext — milestone M3).
|
|
- `TODO/CONFIRM`: exact text of the offline/connected value strings
|
|
(`+0xADE64` / `+0xAF530`); whether any secondary check gates the attempt after
|
|
the state flips.
|