Sequence
- Client generates a random 20-character salt (from an 84-char alphabet, non-cryptographic
System.Random, the exact PRNG doesn’t matter to the server, only the resulting bytes do). - Salt is RSA-PKCS1v1.5-encrypted against the server’s public key (delivered fresh every check-version response as base64 DER, no PEM armor) → base64 → URL-safe → this is the
uuidPOST field. - The AES key is derived as
ASCII(lowercase_hex(MD5(salt))), 32 ASCII bytes, not the raw 16-byte MD5 digest. - The request body (form-encoded device/session fields) is AES-256-ECB-PKCS7 encrypted with that key → base64 → URL-safe → the
dataPOST field. (Effective key size is 256 bits despite the source visually settingKeySize = 128, .NET’sKeysetter silently overrides it based on the 32-byte key length actually assigned.) - Response: if the top-level JSON has a non-empty
binfield, decrypt it the same way (same salt-derived key) and re-parse as the same response shape.
Go implementation
Same key/AES-ECB scheme, reused elsewhere. The identical
AESDecrypt function is reused with a hardcoded key 7a7611b0efc334a7cc229fe5d89c5997 to decrypt a server-pushed Lua snippet that’s fed directly to eval-equivalent (GameEntry.Lua.Env.DoString). Elsewhere the same client also uses a properly-chained AES-CBC-with-explicit-IV helper for a Zendesk webchat URL, so the ECB choice for GSL is a deliberate (if weak) design decision for that one path, not a general “forgot the IV” bug.