Comprehensive Reverse-Engineering & Protocol Analysis • APK v1.31.20 (build 269)
This report documents the reverse-engineering of Century Game's White Out Survival (com.gof.global, v1.31.20) network APIs, protocol schema, and security mechanisms. The investigation combines static APK analysis with live API testing to map the game's communication infrastructure.
{
"fid": 250893802,
"nickname": "FADL2",
"kid": 2007,
"stove_lv": 31,
"stove_lv_content": 30,
"avatar_image": "https://gof-formal-avatar.akamaized.net/avatar/2026/04/18/nmAP54_1776551552.png",
"total_recharge_amount": 0
}
Note: The stove_lv of 31 maps to Furnace level "30-1" (FC1 tier entry). The stove_lv_content of 30 indicates the displayed content level. See the Furnace Level Mapping section for the complete translation table.
| Property | Value |
|---|---|
| Package Name | com.gof.global |
| Version | v1.31.20 (version_code: 269) |
| Engine | Unity IL2CPP |
| Scripting Bridge | tolua (C# ↔ Lua via tolua.so) |
| Protocol | sproto binary over WebSocket |
| Lua Runtime | LuaJIT (luapackage_64 bytecode) |
| Library | Size | Purpose | Security Role |
|---|---|---|---|
libil2cpp.so | 56 MB | IL2CPP compiled C# runtime | Core game logic |
libunity.so | 20 MB | Unity Engine native | Rendering & engine |
libNetHTProtect.so | 4.4 MB | HTProtect anti-tamper | SSL Pinning + HTPX |
libtolua.so | 1.8 MB | Lua/C bridge + sproto | Protocol encode/decode |
libnesec.so | 1 MB | NetEase Shell packer | Code packing |
libsigner.so | 961 KB | APK signature verification | Integrity checks |
POST https://wos-giftcode-api.centurygame.com/api/player
Auth: MD5 sign with secret "tB87#kPtkxqOS2"
Sign = MD5("fid={fid}&time={ts}tB87#kPtkxqOS2")
Body (form-data):
fid: 250893802
time: {timestamp_ms}
sign: {md5_hash}
Returns: fid, nickname, kid, stove_lv, stove_lv_content, avatar_image, total_recharge_amount
POST https://wos-giftcode-api.centurygame.com/api/gift_code_config Returns: Banner configuration for the gift code web portal
GET https://platform-config-prod.centurygame.com/config.json No auth required Returns: FPCS hosts, acc regions, CDN hosts, source UUID Key data: - fpcs hosts (3 endpoints) - acc regions (default: APA, US/AU/JP/KR/TW: GA) - cs_cdn_host (3 CDN mirrors) - age_compliance host
GET https://fpcs-prod-apa.centurygame.com/api/location
No auth required
Returns: { country_code, ip, cmp_region }
POST https://gof-report-api-formal.centurygame.com/api/web_notice Returns: In-game notice/announcement data
POST https://fpcs-prod-apa.centurygame.com/api/config Requires: game_id (20121), version, platform Issue: Version validation currently fails (server-side mapping issue) The server does not recognize the v1.31.20 version string, likely requires a specific version code mapping that differs from the APK version_code (269).
209 protocol messages were extracted from the APK's LuaJIT bytecode (luapackage_64). The game uses the sproto serialization format (originally from the Cloudwu/skynet framework) transported over WebSocket connections with AES-192 encryption.
-- power_rank type
.power_rank {
id 0
abbr 2
gflag 3
__bg 4
leader 5
uid 6
language 8
power_rank 9
}
-- soldier_map type
.soldier_map {
soldier_map 7 : *battle_(id)(quality)
}
-- finish_ts field
.finish_ts 4 : integer(0)
-- vcount field
.vcount 5
-- expert_info type
.expert_info {
-- includes effect_source field
}
-- powerFight field (found in UI context with formatnumberthousands)
Server-push notification messages extracted from the sproto schema. These are sent from the game server to the client without a corresponding request.
Algorithm: MD5 hash Formula: MD5(sorted_query_params + secret) Secret: "tB87#kPtkxqOS2" Example: params: fid=250893802, time=1700000000000 string: "fid=250893802&time=1700000000000tB87#kPtkxqOS2" sign: MD5(above) = <32-char hex>
Algorithm: MD5 hash with prefix
Formula: MD5("UxXkyv4g9nmvK8gP:" + timestamp_ms)
Used for: platform-config-prod.centurygame.com requests
Algorithm: AES-192
Key: Jm93LUl9xqWd/1Ar+7QXeApCZw== (Base64, 24 bytes)
Transport: WebSocket (binary frames)
Encoding: sproto binary (pack/unpack)
Flow: App ↔ FPCS Gateway ↔ Game Server
Packet structure:
[AES-192 encrypted payload]
→ decrypt
→ sproto_unpack
→ decoded protocol message
| Protection | Library | Mechanism | Status |
|---|---|---|---|
| NetEase Shell | libnesec.so |
Code packing / runtime unpacking | ACTIVE |
| HTProtect | libNetHTProtect.so |
Metadata encryption (HTPX format, XOR-based) | ACTIVE |
| SSL Pinning | libNetHTProtect.so |
Certificate validation on all network calls | ACTIVE |
| Metadata Encryption | HTPX magic | global-metadata.dat encrypted (0x58505448) | ACTIVE |
| Asset Encryption | Unity3D custom | LZ4-compressed bundles with custom encryption | ACTIVE |
⚠ HTPX Format: The global-metadata.dat file begins with the HTPX magic bytes 0x58505448 ("HTPX" in ASCII) instead of the standard Unity magic 0xFAB11BAF. The decryption uses XOR-based obfuscation implemented in libNetHTProtect.so.
The stove_lv field from the API uses a numeric mapping where values above 30 represent "Furnace Core" (FC) tiers. The tested player (FID 250893802) has stove_lv=31, which maps to Level 30-1 (entry into FC1).
| Code | Meaning | Notes |
|---|---|---|
| 0 | Success | Request completed successfully |
| 40001 | Role not exist | FID does not map to a valid player |
| 40002 | Already claimed | Gift code already used by this player |
| 40003 | Gift code expired | The gift code validity period has ended |
| 40004 | Claim limit reached | Maximum redemptions for this code reached |
| 40005 | Furnace level too low | Player's stove_lv below required threshold |
| 40006 | Gift code not found | Invalid gift code string |
| 40007 | Same type once | One redemption per type per player |
| 40008 | Unknown error | Generic server error |
| 40009 | Not logged in | Authentication required |
| 40010 | Server busy | Rate-limited or server overloaded |
| 40011 | Too frequent | Request rate limit exceeded |
| 40012 | Account age requirement | Age compliance restriction |
| 40013 | Captcha too frequent | Captcha request rate limited |
| 40014 | Captcha incorrect | Wrong captcha answer |
| 40015 | Captcha expired | Captcha session timed out |
| 40016 | Network busy | Network congestion |
| 40018 | Leader Concierge only | Restricted to alliance leaders |
| Host | Region | Protocol |
|---|---|---|
https://fpcs-apa.centurygames.net | Asia-Pacific (Primary) | HTTPS / WSS |
https://fpcs-prod-wf.centurygame.com | West (Failover) | HTTPS / WSS |
https://fpcs-prod-apa.centurygame.com | Asia-Pacific (Alt) | HTTPS / WSS |
Source UUID: 98937a85-0774-439f-889f-8510fb11634a
| Host | CDN Provider |
|---|---|
https://dis-customer-hub-res-ak.centurygame.com | Akamai |
https://dis-customer-hub-res-ct.centurygame.com | China Telecom |
https://dis-customer-hub-res-cf.centurygame.com | Cloudflare |
| Region | Countries | Default Server |
|---|---|---|
| APA | Asia-Pacific (default) | fpcs-apa |
| GA | US, AU, JP, KR, TW | fpcs-prod-wf |
import hashlib
import time
import requests
def lookup_player(fid):
"""Look up a White Out Survival player by their FID."""
ts = str(int(time.time() * 1000))
secret = 'tB87#kPtkxqOS2'
# Compute MD5 signature
sign_str = f'fid={fid}&time={ts}{secret}'
sign = hashlib.md5(sign_str.encode()).hexdigest()
# Make the request
resp = requests.post(
'https://wos-giftcode-api.centurygame.com/api/player',
data={
'fid': str(fid),
'time': ts,
'sign': sign,
},
headers={
'User-Agent': 'Mozilla/5.0 (Linux; Android 14) Chrome/125.0.0.0',
'Origin': 'https://wos-giftcode.centurygame.com',
'Referer': 'https://wos-giftcode.centurygame.com/',
},
)
return resp.json()
# --- Test ---
result = lookup_player(250893802)
print(result)
# Expected output:
# {
# "code": 0,
# "data": {
# "fid": 250893802,
# "nickname": "FADL2",
# "kid": 2007,
# "stove_lv": 31,
# "stove_lv_content": 30,
# "avatar_image": "https://gof-formal-avatar.akamaized.net/...",
# "total_recharge_amount": 0
# }
# }
def decode_furnace_level(stove_lv):
"""Convert numeric stove_lv to human-readable furnace level."""
if stove_lv <= 30:
return f"Level {stove_lv}"
fc_map = {35: "FC1", 40: "FC2", 45: "FC3", 50: "FC4", 55: "FC5", 60: "FC6", 65: "FC7"}
# FC milestone levels
if stove_lv in fc_map:
return fc_map[stove_lv]
# Find which FC tier this falls into
milestones = sorted(fc_map.keys(), reverse=True)
for ms in milestones:
if stove_lv > ms:
offset = stove_lv - ms
tier_num = ms // 5 - 6 # FC1=1, FC2=2, etc.
return f"FC{tier_num}-{offset}"
# Between 30 and FC1 (31-34)
if 31 <= stove_lv <= 34:
return f"30-{stove_lv - 30}"
return f"Unknown ({stove_lv})"
# Examples:
print(decode_furnace_level(31)) # "30-1"
print(decode_furnace_level(35)) # "FC1"
print(decode_furnace_level(37)) # "FC1-2"
print(decode_furnace_level(50)) # "FC4"
print(decode_furnace_level(65)) # "FC7"
# Fetch platform config (no auth needed)
curl -s https://platform-config-prod.centurygame.com/config.json | python -m json.tool
# GeoIP lookup
curl -s https://fpcs-prod-apa.centurygame.com/api/location | python -m json.tool
# Player lookup with signature
FID=250893802
TS=$(date +%s%3N)
SIGN=$(echo -n "fid=${FID}&time=${TS}tB87#kPtkxqOS2" | md5sum | cut -d' ' -f1)
curl -s -X POST https://wos-giftcode-api.centurygame.com/api/player \
-d "fid=${FID}&time=${TS}&sign=${SIGN}" \
-H "Origin: https://wos-giftcode.centurygame.com" \
-H "Referer: https://wos-giftcode.centurygame.com/" | python -m json.tool
Install HTTP Canary or Packet Capture from the Play Store. These create a local VPN to intercept HTTPS traffic and can capture FPCS gateway requests. However, SSL pinning will block HTTPS decryption of game traffic. Works for non-pinned connections only.
Install Termux from F-Droid and run mitmproxy inside it. Configure the device proxy to localhost:8080. Provides full HTTP inspection but SSL pinning still blocks game traffic. Better for analyzing web API calls.
Use Parallel Space to run a Frida-Gadget injected version of the APK. No root required but the setup is complex: extract APK, inject frida-gadget.so into lib/arm64, repack, sign, and run in Parallel Space. Can hook SSL pinning bypasses at runtime.
Decompile the APK on a server, extract sproto schemas from Lua bytecode, reconstruct protocol from code analysis. This is the approach being used for this investigation. It works entirely offline and doesn't require interacting with the game client at all.
Contact Century Game for a developer partnership and request access to player data APIs. This is the legitimate route but requires business justification and is unlikely to be granted for individual researchers.
| Phase | Objective | Status | Notes |
|---|---|---|---|
| Phase 1 | APK decompilation & structure analysis | COMPLETE | IL2CPP + LuaJIT bytecode extracted |
| Phase 2 | Web API discovery & authentication | COMPLETE | 5 endpoints verified, 1 partial |
| Phase 3 | sproto schema extraction | COMPLETE | 209 messages, 77 NOTIFY types identified |
| Phase 4 | AES key & crypto mechanism recovery | COMPLETE | AES-192 key recovered, MD5 secrets found |
| Phase 5 | Full sproto schema reconstruction | IN PROGRESS | Partial types recovered, need complete field mapping |
| Phase 6 | Live WebSocket protocol capture | BLOCKED | Requires SSL pinning bypass (root or Frida) |
| Phase 7 | Protocol message decoding & validation | BLOCKED | Depends on Phase 6 completion |
| Phase 8 | Tool development & automation | PLANNED | sproto encoder/decoder, player data viewer |