Sign in to myTeamSpeak and import what the account synchronises
The myTeamSpeak client protocol and its end-to-end encryption are reverse-engineered (docs/myteamspeak): an HTTP POST of a framed protobuf, a PBKDF2-HMAC-SHA512 login token and account key, an AES-GCM wrapped item key and AES-CTR items with a SHA-512 trailer. Options on desktop and Settings on Android gain a myTeamSpeak page: sign in and out, and the account's bookmarks and identities, each picked for import and marked when already here. The client stays signed in the way the official one does, keeping the derived token and key rather than the password; each read signs in afresh, and a sign-in the server no longer takes signs out. Nothing is written to the account. Items are the same Item_Data as the local TeamSpeak store, so they go through the existing decoder and importer; a bookmark chosen alone brings the identity it connects with, found by UUID or, as some name it, by the identity's name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
68
ts3-client/docs/myteamspeak/CRYPTO_RE.md
Normal file
68
ts3-client/docs/myteamspeak/CRYPTO_RE.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 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 <lib> <vaddr> <len>` — capstone arm64 disassembler, annotates adrp+add string loads and
|
||||
bl targets with .symtab/.dynsym names.
|
||||
- `xref.py <lib> <string>` / `xrefaddr.py <lib> <vaddr> [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]].
|
||||
Reference in New Issue
Block a user