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
ProtocolPhoton reliable-UDP (ENet derivative)
Port5055
SerializationGpBinaryV18
Key ExchangeDiffie-Hellman (Oakley Group 1, 768-bit MODP, gen 22)
EncryptionAES-256-CBC, zero IV
Key DerivationSHA-256(shared secret)
Key LifetimeEphemeral (per connection)
Message Structure
Command Typeparam 255
Correlation IDparam 253
Cookieparam 0 (scookie2)
All messagesTunneled under OP 1
Login payloadIdentity + device + content-MD5
Challenge SourceEVT 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) ├─ 0x21f75cheavy 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

ApproachResult
HMAC-MD5, HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 with extracted keyNo match
MD5, SHA1, SHA256 of challenge/body/timestampNo match
CRC32, custom XOR-rotate, AES-CMACNo 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 moduleOnly import table, no key strings

Why Static Analysis Fails

Obfuscated Code
The Numbers

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
Size32 bytes
Entropy16/16 unique bytes per half
LifetimeSession-specific (new each spawn)
Session Afc1d184e01d56b640c85...
Session Bc588159aadcd34bbccb9...
SourceUnknown

7. What We Know vs What We Don't #

LayerStatusNotes
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.pyRPC client (spawn/attach + cookie)
cookie-rpc.jsFrida RPC script
extract-key.jsKey material extraction
algo-stalker.jsStalker execution trace
disasm-cookie.pyARM64 disassembler (Capstone)
Dumps
dumps/xraphael_arm64.xem264 KB
dumps/hash_code.bin5 MB
dumps/hash_data.bin96 KB
cookie_fn_*.ndjson7 disassembly dumps
Documentation
docs/xigncode-getcookie2.mdFull technical doc (342 lines)
docs/STATUS.mdStatus report (English)
docs/README.mdProject overview (Turkish)

Generated 2026-08-18  ·  Target: Albion Online Android v1.31.042.339912  ·  XIGNCODE3