XIGNCODE3 getCookie2 — Reverse Engineering Report
Target: Albion Online Android v1.31.042.339912 |
Component: XIGNCODE3 Anti-Cheat Session Cookie |
Date: 2026-08-18
TL;DR: We have fully reverse engineered the Albion Online Android lobby protocol — from login to encrypted
Photon communication, including the XIGNCODE3 anti-cheat cookie generation. We can connect, authenticate, and
generate valid session cookies via RPC. The only remaining piece is a pure Python implementation of the
cookie signing algorithm, which is blocked by a custom obfuscated hash/MAC with a session-specific key.
1. What We've Achieved — Full Lobby Protocol #
┌──────────────────────────────────────────────────────────────────────────────┐
│ ALBION ONLINE LOBBY — FULL HANDSHAKE │
│ │
│ Phase 0 — Content Validation │
│ Client: AlbionMd5Check → scans all game files, computes MD5s │
│ Client: DeltaPatcher → downloads & applies binary patches if needed │
│ │
│ Phase 1 — Region Selection │
│ Client: Parallel TCP ping to 3 region servers │
│ Client: Selects best region (lowest latency) │
│ │
│ Phase 2 — Photon Connection (reliable-UDP, ENet-based, port 5055) │
│ Client → Server: CONNECT │
│ Server → Client: VERIFY_CONNECT │
│ │
│ Phase 3 — Diffie-Hellman Key Exchange │
│ Client ↔ Server: Oakley Group 1 (768-bit MODP, generator 22) │
│ Shared secret → SHA-256 → AES-256-CBC key (zero IV) │
│ Key is ephemeral — new for every connection │
│ │
│ Phase 4 — Encrypted Login (AES-256-CBC, GpBinaryV18 serialization) │
│ Client → Server: OP 1 { identity, device info, content MD5 } │
│ Server → Client: RES 1 { account data, character list } │
│ │
│ Phase 5 — XIGNCODE3 Handshake │
│ Server → Client: EVT 1 { param 0 = TARA challenge blob (~7000 chars) } │
│ Client: getCookie2(challenge) → cookie (76 chars) │
│ Client → Server: OP 1 { param 0 = cookie } │
│ Server: Validates cookie with Wellbia backend │
│ │
│ Phase 6 — Steady State │
│ ↻ PING heartbeat every ~5 seconds │
│ ↻ New challenge + cookie on every message │
│ ↻ All messages tunneled under OP 1 { param 255 = cmd, param 253 = corrId } │
└──────────────────────────────────────────────────────────────────────────────┘
Key Technical Findings
Transport & Encryption
| Protocol | Photon reliable-UDP (ENet derivative) |
| Port | 5055 |
| Serialization | GpBinaryV18 |
| Key Exchange | Diffie-Hellman (Oakley Group 1, 768-bit MODP, gen 22) |
| Encryption | AES-256-CBC, zero IV |
| Key Derivation | SHA-256(shared secret) |
| Key Lifetime | Ephemeral (per connection) |
Message Structure
| Command Type | param 255 |
| Correlation ID | param 253 |
| Cookie | param 0 (scookie2) |
| All messages | Tunneled under OP 1 |
| Login payload | Identity + device + content-MD5 |
| Challenge Source | EVT 1 { param 0 } |
What We Can Do (Working Tools)
Working albion-status.py
Independent Photon status client. Connects to region servers, performs DH key exchange, and measures latency — no game binary needed.
Working photon.py
Pure Python implementation of Photon reliable-UDP framing + GpBinaryV18 codec. Can encode/decode all message types.
Working net-log.js
Frida hook that captures all Photon traffic: OP/RES/EVT commands, DH key negotiation, AES-256-CBC decryption, GpBinaryV18 deserialization, and TARA blob decoding.
Working cookie.py
RPC client that spawns/attaches to the game, finds the XigncodeClientSystem instance, and calls getCookie2(challenge) to generate valid cookies. Requires emulator.
2. getCookie2 — The Anti-Cheat Cookie #
Every Photon message carries a session cookie (scookie2) generated by XIGNCODE3.
It's a challenge-response mechanism: the server sends a challenge, the client signs it, the server verifies.
Server → Client: EVT 1 { param 0 = challenge (~7000 char Z85 blob) }
Client: getCookie2(challenge) → cookie (76 char)
Client → Server: OP 1 { param 0 = cookie }
Challenge format: 6A848B92 mz86Br6:zF0@@r3cE>... (~7000 chars)
^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
timestamp Z85-encoded body (fixed per session)
Cookie format: 83 6A848B92 _ 1e4182a7c383570581e95ae300c05254 _ 8b1d1e635b7693842f76103f81e28768
^^ ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
prefix ts A = 16 bytes (deterministic) B = 16 bytes (keyed MAC)
3. How the RPC Client Works #
Architecture
Python (cookie.py)
│
│ Frida USB
▼
Android Emulator
│
│ Frida Server
▼
Albion Online process
│
│ Java.choose("XigncodeClientSystem")
▼
getCookie2(challenge) → cookie string
How It Works
1Frida attaches to the running game process (or spawns it)
2Java.choose finds the existing XigncodeClientSystem singleton
3Java.retain keeps a reference to prevent GC
4RPC endpoint exposes cookie2(challenge) to Python
5Python calls RPC → Java calls real getCookie2 → returns cookie
Verification
Challenge: 6A848B92mz86Br6:zF0@@r3cE>... (~7000 chars)
RPC Result: 836A848B92_1e4182a7c383570581e95ae300c05254_8b1d1e635b7693842f76103f81e28768
Real Call: 836A848B92_1e4182a7c383570581e95ae300c05254_8b1d1e635b7693842f76103f81e28768
Match: ✅
4. The Call Chain (5 Layers Deep) #
We traced the full execution path from the Java method down to the actual signing routine:
Layer 1 — Java
XigncodeClientSystem.getCookie2(String challenge)
│
▼
Layer 2 — JNI Wrapper (libxigncode.so @ 0x1eff0)
ZCWAVE_GetCookie2(JNIEnv*, jobject, jstring)
├─ g_ctx = .bss @ 0x8c0b0
├─ is_ready(ctx) check
└─ cookie_fn(32, out, 260, challenge) ← mode=32 = Cookie2
│
▼
Layer 3 — cookie_fn (xraphael_arm64.xem @ +0xd988)
├─ adrp + ldr → GLOBALS[0x40]
└─ blr x8 → dispatcher
│
▼
Layer 4 — Dispatcher + Jump Table (hash module, ~5 MB)
├─ mode - 17 = index → jump table
├─ mode=32 → index=15
└─ b → handler[15] → L5 wrapper
│
▼
Layer 5 — Sign Function (hash module @ +0x222ad0)
int sign_fn(ctx*, out, maxlen, challenge)
├─ 0x2228bc → compute A/B (calls sub-object vtable[0x1b8])
├─ 0x222bdc → compute A/B (calls sub-object vtable[0x1b8])
├─ 0x222438 → main entry (buffer allocation, ~5 KB stack)
├─ 0x21f75c → heavy crypto (1123 XOR/rotate/reverse ops)
└─ snprintf(out, "83%s_%s_%s", ts, hexA, hexB) [format string unconfirmed]
5. Where We're Stuck #
The core problem: The signing algorithm (functions that compute A and B from the challenge)
is a custom, obfuscated hash/MAC embedded in XIGNCODE's runtime-mapped native code.
It cannot be replicated with standard crypto libraries.
What We Tried
| Approach | Result |
| HMAC-MD5, HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 with extracted key | No match |
| MD5, SHA1, SHA256 of challenge/body/timestamp | No match |
| CRC32, custom XOR-rotate, AES-CMAC | No match |
| Static disassembly of sign function (128 KB ARM64 code) | Too complex |
| Stalker instruction trace (Frida) | ~500K instructions per call |
| Scan data pages for crypto constants (AES S-box, SHA IV) | None found |
| Search for embedded strings in hash module | Only import table, no key strings |
Why Static Analysis Fails
Obfuscated Code
- No recognizable crypto constants (AES S-box, SHA-256 IV, MD5 IV)
- 1123 XOR/rotate/reverse operations spread across multiple functions
- Data pages referenced by ADRP instructions contain exception handling tables, not key material
- The hash module is deleted from the filesystem after loading (anti-tamper)
The Numbers
- Sign function: 128 KB of ARM64 code
- Hash module: 5 MB total (4.9 MB code + 96 KB data)
- 1123 crypto operations (eor, ror, rev, rev16, rev32)
- ~500K instructions executed per call
- 252 ms execution time
- Estimated reversal effort: 3-7 days of manual ARM64 analysis
The Fundamental Issue
┌──────────────────────────────────────────────────────────────────┐
│ To implement getCookie2 in pure Python, we need: │
│ │
│ 1. The algorithm (F_a, F_b) │
│ → Custom, obfuscated, 1123 crypto ops │
│ → No standard library matches │
│ → Requires full ARM64 reversal │
│ │
│ 2. The secret key K (32 bytes) │
│ → Located at sub-context offset 0x100 │
│ → Changes every game launch (session-specific) │
│ → Generation mechanism unknown (device? server? random?) │
│ │
│ Even if we fully reverse the algorithm, we still need to │
│ extract the key from each session. The key generation │
│ itself is another unknown. │
└──────────────────────────────────────────────────────────────────┘
6. Key Material Details #
Location
Context Object
├─ 0x00: vtable ptr
├─ 0x08: ptr
├─ 0x10: ptr
├─ 0x18: sub-object ptr ──┐
├─ ... │
└─ 0x1c0: GUID │
"{32ACDE0F-F4BB-..." │
┌──────────────┘
▼
Sub-Object
├─ 0x00: vtable ptr
├─ ...
├─ 0x48: 0x31 (mode?)
├─ 0x50: 0x23 (size?)
├─ ...
└─ 0x100: 32-byte KEY
Properties
| Size | 32 bytes |
| Entropy | 16/16 unique bytes per half |
| Lifetime | Session-specific (new each spawn) |
| Session A | fc1d184e01d56b640c85... |
| Session B | c588159aadcd34bbccb9... |
| Source | Unknown |
7. What We Know vs What We Don't #
| Layer | Status | Notes |
| JNI wrapper (libxigncode.so @ 0x1eff0) |
Complete |
Full disassembly, all arguments mapped |
| Input/output format |
Complete |
Challenge format, cookie structure, validation rules |
| Call chain (5 layers) |
Complete |
Every function from Java to sign routine traced |
| Key material location |
Complete |
32 bytes at sub-context offset 0x100, session-specific |
| Module dumps |
Complete |
xraphael_arm64.xem (264 KB) + hash module (5 MB) |
| RPC client |
Working |
cookie.py spawns/attaches → calls getCookie2 → returns cookie |
| Algorithm behavior |
Characterized |
Deterministic, timestamp copy, strict validation, 252 ms |
| Algorithm implementation |
Blocked |
Custom obfuscated hash/MAC, 1123 crypto ops |
| Key generation source |
Unknown |
Device-derived? Server-provided? Random? |
| snprintf format string |
Unconfirmed |
Likely "83%s_%s_%s" but hook didn't capture it |
8. Files and Artifacts #
Source Code
cookie.py | RPC client (spawn/attach + cookie) |
cookie-rpc.js | Frida RPC script |
extract-key.js | Key material extraction |
algo-stalker.js | Stalker execution trace |
disasm-cookie.py | ARM64 disassembler (Capstone) |
Dumps
dumps/xraphael_arm64.xem | 264 KB |
dumps/hash_code.bin | 5 MB |
dumps/hash_data.bin | 96 KB |
cookie_fn_*.ndjson | 7 disassembly dumps |
Documentation
docs/xigncode-getcookie2.md | Full technical doc (342 lines) |
docs/STATUS.md | Status report (English) |
docs/README.md | Project overview (Turkish) |
Generated 2026-08-18 · Target: Albion Online Android v1.31.042.339912 · XIGNCODE3