https://github.com/carcabot/tiktok-xgnarly-decoded
Full reverse-engineering of TikTok's X-Gnarly request signature (webmssdk 5.1.3-ZTCA) with a pure-JavaScript reference encoder and decoder.
https://github.com/carcabot/tiktok-xgnarly-decoded
api reverse-engineering signature tiktok tiktok-api webmssdk x-bogus x-gnarly x-mssdk-info xgnarly
Last synced: 2 months ago
JSON representation
Full reverse-engineering of TikTok's X-Gnarly request signature (webmssdk 5.1.3-ZTCA) with a pure-JavaScript reference encoder and decoder.
- Host: GitHub
- URL: https://github.com/carcabot/tiktok-xgnarly-decoded
- Owner: carcabot
- License: mit
- Created: 2026-04-27T16:05:56.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-27T16:05:59.000Z (3 months ago)
- Last Synced: 2026-05-24T05:31:56.912Z (2 months ago)
- Topics: api, reverse-engineering, signature, tiktok, tiktok-api, webmssdk, x-bogus, x-gnarly, x-mssdk-info, xgnarly
- Language: JavaScript
- Size: 14.6 KB
- Stars: 10
- Watchers: 0
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# tiktok-xgnarly-decoded
Full reverse-engineering of TikTok's `X-Gnarly` request signature, with a working pure-JavaScript reference encoder and decoder. No native bindings, no headless browser, no obfuscated bundle — every byte of the wire format is accounted for.
> **Educational / research use only.** This repo documents how an existing client-side signature is constructed so the algorithm can be studied, taught, and audited. It is not a tool for evading platform protections, scraping at scale, or violating TikTok's Terms of Service. See the disclaimer below.
---
## What is X-Gnarly?
`X-Gnarly` is a request parameter that TikTok's web client (`webmssdk` / `secsdk`) attaches to API calls. It is a base64-ish blob (~332 chars for the current ZTCA payload) carrying:
- a 16-field TLV payload (timestamps, counters, MD5s of the URL/body/UA, an SDK version, two random uint32s, and an XOR header that ties the integers together),
- ChaCha-style stream cipher output keyed by 48 random bytes that are themselves embedded inside the ciphertext at a checksum-derived offset,
- a leading magic byte (`'K'` = 75) so the decoder can fail fast.
This repository walks through every step and ships an implementation small enough to read in one sitting.
---
## Versions covered
The X-Gnarly signature has shipped under several internal version tags. This project tracks the wire format across the full range below; the reference implementation in `src/` matches **5.1.3-ZTCA** byte-for-byte.
**X-Gnarly payload version (TLV field 9):**
- `5.1.3-ZTCA` *(current — this repo's reference vector)*
- `5.1.3`
- `5.1.2`
- `5.1.1`
- `5.1.0`
- `5.0.x`
**`webmssdk` host bundle versions observed carrying X-Gnarly:**
- `webmssdk 1.0.0.211`
- `webmssdk 1.0.0.224`
- `webmssdk 1.0.0.368` *(default `sdkVersion` in `encode.js`)*
- `webmssdk 1.0.0.4xx`
- `webmssdk 2.0.0.485`
- `webmssdk 2.0.0.485-ZTCA`
The custom base64 alphabet, magic byte (`'K'` / 75), and ChaCha core have been **stable across every webmssdk build from `1.0.0.211` through `2.0.0.485-ZTCA`** that we've observed. The TLV field set and the round-derivation scheme are what evolved.
---
## Related TikTok signing parameters
For people landing here from search — X-Gnarly is one of several signatures TikTok's web and mobile stacks emit. They are *not* interchangeable, but they often appear together in traffic captures, and the same reverse-engineering techniques apply:
| Header / param | Surface | Notes |
|---|---|---|
| `X-Gnarly` | web (`webmssdk`) | This repo. ChaCha-XOR + TLV. |
| `X-Bogus` | web (`acrawler`) | Predecessor; AES-like + base64. |
| `X-Argus` | mobile (Android/iOS) | Protobuf payload, native lib. |
| `X-Ladon` | mobile | XOR over `X-Khronos` + sec_device_id. |
| `X-Khronos` | mobile | Unix timestamp. |
| `X-Gorgon` | mobile (legacy) | MD5-based, deprecated on newer builds. |
| `X-Mssdk-Info` | web | Companion telemetry from `webmssdk`. |
| `X-TT-Params` | web | AES-128-CBC encrypted query string. |
| `msToken` | web + mobile | Server-issued anti-replay token. |
| `ttwid` | web | Cookie-based session/device id. |
Only `X-Gnarly` is implemented here.
---
## Quick start
```js
import { encode, decode } from "./src/encode.js";
const xgnarly = encode(
// queryString: the URL's `?...` portion, minus the leading '?',
// with msToken substituted in and X-Bogus / X-Gnarly stripped.
"aid=1988&app_language=en&...",
// body: empty string for GET, the raw body for POST.
"",
// navigator.userAgent at sign time.
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
// secsdk request counters; small ints are fine off-page.
{ totalXHRRequests: 3, totalFetchRequests: 1 },
);
const { fields, keyStart, rounds } = decode(xgnarly);
console.log(fields[9]); // "5.1.3-ZTCA"
console.log(fields[10]); // "1.0.0.368"
```
Run the round-trip + known-vector tests:
```sh
node --test test/
```
---
## Project layout
```
src/
alphabet.js custom base64 alphabet used by webmssdk
cipher.js ChaCha-style stream cipher + round derivation
payload.js 16-field TLV encoder / decoder
encode.js producer (encode → cipher → embed key → base64)
decode.js consumer (base64 → recover key by brute-force keyStart → decrypt → parse)
test/
roundtrip.test.mjs encode → decode round-trip on synthetic inputs
vectors.test.mjs decode of a real captured X-Gnarly with field-by-field assertions
examples/
verify-real.mjs pretty-print a captured X-Gnarly's fields
```
---
## TLV field reference
| # | Type | Meaning |
|---|---|---|
| 0 | uint | XOR header (XOR of all integer fields, written **last**) |
| 1 | uint | Constant `65` |
| 2 | uint | `ubcode` — endpoint class (default `4` for `/api/post/...`) |
| 3 | str | MD5(queryString) — hex |
| 4 | str | MD5(body) — hex; `""` for GET |
| 5 | str | MD5(userAgent) — hex |
| 6 | uint | `floor(now / 1000)` — Unix seconds |
| 7 | uint | Build-time hash baked into the SDK (`3181061566` observed) |
| 8 | uint | `now % 0x80000000` — low 31 bits of ms timestamp |
| 9 | str | X-Gnarly payload version (e.g. `5.1.3-ZTCA`) |
| 10| str | `webmssdk` SDK version (e.g. `1.0.0.368`) |
| 11| uint | Constant `1` |
| 12| uint | Total intercepted requests (XHR + fetch) |
| 13| uint | Counter-pair companion to field 12 |
| 14| uint | High 16 bits = field 1 << 16; low 16 bits = random |
| 15| uint | Full uint32 random |
---
## Disclaimer
This repository is published under MIT for **research, education, security analysis, and interoperability** purposes. It does not contain TikTok proprietary code; every byte was independently re-derived from public web traffic and a clean-room reading of the publicly served `webmssdk` bundle.
- Use it to learn how modern client-side request signing works.
- Use it to validate that a captured X-Gnarly was emitted by a genuine SDK build.
- Do **not** use it to bypass rate limits, evade bot protection on production endpoints, scrape at industrial scale, or otherwise violate TikTok's Terms of Service or applicable law (CFAA, DMCA §1201, GDPR, etc.).
The maintainers are not affiliated with TikTok, ByteDance, or any of their subsidiaries. Names like `webmssdk`, `secsdk`, `ZTCA`, `X-Gnarly`, `X-Bogus`, `X-Argus`, `X-Ladon`, `X-Khronos`, `X-Gorgon`, and `msToken` are used here only to identify the protocols being studied.
---
## License
MIT — see [LICENSE](./LICENSE).