# myTeamSpeak crypto — reverse-engineering progress & resume notes Status: **password-login and item crypto cracked (upload framing too); decryption implemented.** The item is `IV[16] || little-endian AES-CTR ciphertext || SHA512(plaintext)`; the old fixture was a capture truncated 37 bytes into its digest. See `CRYPTO_RE_SALT.md` section 0 for formulas, addresses, captured outputs, and the Java implementation. The notes below are retained as historical RE context. ## Confirmed facts - TeamSpeak's `teamcrypto` provides ONLY: AES-256-GCM, AES-CTR, SHA-256, SHA-512, CTR-DRBG. No PBKDF2/scrypt/Argon2/HKDF of its own (those strings in the binary are from statically-linked OpenSSL, unused by the sync code). => everything is buildable from SHA-2 + AES-GCM, so BouncyCastle/JCA can replicate it once the recipe is known. Nothing exotic to port. - Sync is end-to-end encrypted. Local settings.db stores items DECRYPTED; only the wire blobs are encrypted. - `cloud_sync_client/src/lib/Encryption.cpp` asserts `plain_hash.size() == teamcrypto::sha2::sha512_size` (64) => a SHA-512 "plain_hash" is central. `Item_Manager.cpp` asserts `!salt.empty()` => items use a salt. - **Login token is DETERMINISTIC** (verified: same email+password → identical 48-byte base64 token twice). So it is a reproducible KDF, not randomized. 48 bytes out. ## Ground-truth samples (test account; its credentials are not committed) Kept in the session scratchpad; re-capture via the harness if needed. Two login pairs (email, password, 48-byte token) — determinism confirmed. One LoginSession with: user_public_key 32B, encrypted_user_private_key 112B, my_teamspeak_id `01 20 <32B>`. One encrypted bookmark item_blob (274B, leading `0x11`) whose DECRYPTED plaintext is known (from local settings.db): `server.lixko.eu`, nick "niger", uid `+Tyg2JtxE8vRNZp+JiUBnmBh0MY=`. Recovery key = 32B. ## Ruled out (brute-forced against real samples) - Login token: NOT plain SHA-256/384/512, HMAC-*, PBKDF2, scrypt, Argon2 over any obvious email/password combo with common salts/peppers; NOT ~35 SHA-512 concat/HMAC/xor constructions tested against BOTH samples. => baked-in salt/pepper or non-obvious structure; must be read from code. - Item blob: NOT AES-256-GCM under {recovery key, sha256(rk), sha512(rk) halves} with nonce at offset 0/1, len 12/16, tag-last, and AAD ∈ {none, item_uuid, item_version, both, binary UUIDs, 0x11}. => item key is a derivation of the account data key (which is wrapped by the password-derived key and/or the recovery key), not the recovery key directly. ## Located in the binary (arm64 `re-android/.../libteamspeak_client.so`; file offset == vaddr in .rodata/.text) - Encryption.cpp SHA-512 assert string: vaddr 0x1baed6; referenced by code at **0xa8c280**; enclosing function starts at **0xa8c198** (stack 0x1e0). That function dispatches through a vtable (`ldr x9,[x8,#0x18]`/`[x8,#0x48]`; `blr x9`) — the SHA-512/AES-GCM are behind an interface object. - SHA-512 K-table @ vaddr **0x29f4d8**; SHA-256 K-table @ **0x29f318**; SHA-512 IV0 @ 0x241580. - Desktop x86-64 `ts3client_linux_amd64`: same assert string @ vaddr **0x2dcac1** (.rodata). - Login path entry: `Java_..._AccountManager_setupSyncAccount` @ arm64 0x8931a8 -> converts the 3 String args (email, password, device) -> calls Account_Manager_Impl vtable slot at `[vtable+0x18]`. ## Tools built (in session scratchpad) - `disasm.py ` — capstone arm64 disassembler, annotates adrp+add string loads and bl targets with .symtab/.dynsym names. - `xref.py ` / `xrefaddr.py [window]` — find code adrp(+add/ldr) xrefs. NOTE: capstone adrp op_str includes `#`; strip it when parsing (already fixed). ldr literal-pool (`ldr xN, #imm` PC-relative) xrefs are NOT yet handled — add this to find mbedtls SHA callers. ## Next steps (pick one, both bounded) 1. **Static:** from the SHA-512 K-table (0x29f4d8) find the compression fn (add literal-pool ldr xref handling), then its caller (sha512 one-shot wrapper), then THAT wrapper's callers — one is the login KDF (expected linear: reads password + a constant salt -> SHA-512 -> 48-byte token), another is Encryption. Read the login KDF to get the salt + truncation. Then read Encryption's item path (key derivation + GCM nonce/tag/AAD layout). Verify each against the ground-truth samples. 2. **Dynamic (faster to interpret):** gdb the running desktop client (harness in headless-ui-testing memory). Anchor: find the Encryption fn in the x86-64 binary via lea-xref to the assert string @0x2dcac1, break there; trigger a login (client decrypts the bookmark) and read the AES key/nonce/ AAD + the SHA-512 inputs from registers/stack. ptrace needs `sudo gdb` (yama scope=1 here) or ptrace_scope=0. libcrypto.so.1.1 EVP_PBE_scrypt/PKCS5_PBKDF2_HMAC are breakable but NOT used by the login KDF (it's teamcrypto), so break on the located Encryption fn, not on OpenSSL. ## Capture harness (working) DNS/connect LD_PRELOAD shim redirecting *.myteamspeak.com -> local TLS relay (client trusts an added CA via SSL_CERT_FILE/SSL_CERT_DIR); relay must speak http/1.1 upstream. Drive the official client under Xvfb+xdotool (licence: scroll to end then accept; dismiss the promo webview; fresh profile = clear AccountData+ProtobufItems in settings.db). See [[headless-ui-testing]].