Skip to content

MtProtoKit: align FakeTLS ClientHello with current Chrome (JA3/JA4) + per-connection extension shuffle#168

Open
Lawliet2012 wants to merge 4 commits into
Swiftgram:masterfrom
Lawliet2012:fakeTls-chrome144-mimicry
Open

MtProtoKit: align FakeTLS ClientHello with current Chrome (JA3/JA4) + per-connection extension shuffle#168
Lawliet2012 wants to merge 4 commits into
Swiftgram:masterfrom
Lawliet2012:fakeTls-chrome144-mimicry

Conversation

@Lawliet2012

Copy link
Copy Markdown

Problem

Since 2026-06-05 RKN started dropping Telegram FakeTLS (ee-secret / SNI-fronted MTProxy) flows by ClientHello fingerprint (JA3 / JA4). The greeting is built in submodules/MtProtoKit/Sources/MTTcpConnection.mexecuteGenerationCode() (the byte-code template at line ~481) and sent at MTTcpConnection.m:1086-1111.

Two problems with the current template:

  1. It does not look like any current browser. Its JA4 is not in the real-browser population, so it is cheap to enumerate and block.
  2. It is static — only two < … > choice points (cipher list ×2, ALPN ×2), so the whole client base collapses to ~2 normalized fingerprints. One blocklist entry kills everyone.

Measured (from real packet captures)

Ground-truth ClientHellos taken from the reference captures in telemt/tdlib-obf (docs/Samples/Traffic dumps/Linux, desktop/clienthello-*.pcapng), normalized JA4:

Client JA4 JA3 (one capture)
Chrome 144.0.7559.109 t13d1516h2_8daaf6152771_d8a2da3f94cd 59e2ca82423690f86226a98c2ac63c51
Telegram Desktop 6.7.3 t13d1516h2_8daaf6152771_d8a2da3f94cd 6e6c9a8e99cf26cda448839203451bde
Firefox 148.0.2 t13d1717h2_5b57614c22b0_3cbfd9057e0d 6f7889b9fb1a62a9577e685c1fcfa919
MtProtoKit (this file), cipher variant A t13d1313h1_f57a46bbacb6_7f0f34a4126d 8527da8b8a640065e72ec6b6f99764f3
MtProtoKit (this file), cipher variant B t13d2013h2_a09f3c656075_7f0f34a4126d ecdf4f49dd59effc439639da29186671

Note Telegram Desktop already produces the exact Chrome 144 JA4 (it shuffles extension order per connection, so its JA3 differs from Chrome while JA4 is identical). This PR brings the iOS / MtProtoKit greeting to the same parity. Variant B above advertises 20 cipher suites mixing TLS 1.3 with legacy RSA/3DES (0x009d,0x009c,0x0035,0x002f,0xc008,0xc012,0x000a) — no shipping browser sends that; it is a fingerprint on its own.

What changes on the wire

Cipher suites (GREASE excluded):

before (A): 1302 1301 1303 c02c c030 c02b cca9 c02f cca8 c00a c009 c014 c013
after     : 1301 1302 1303 c02b c02f c02c c030 cca9 cca8 c013 c014 009c 009d 002f 0035   (== Chrome 144)

Extensions (types, GREASE excluded):

before: 0000 0017 ff01 000a 000b 0010 0005 000d 0012 0033 002d 002b 001b              (13)
after : 0000 0005 000a 000b 000d 0010 0012 0017 001b 0023 002b 002d 0033 44cd fe0d ff01 (16, == Chrome 144)

Added to match Chrome: session_ticket(0x0023), encrypted_client_hello(0xfe0d), application_settings(0x44cd); supported_groups trimmed to Chrome's {X25519MLKEM768, x25519, secp256r1, secp384r1}; signature_algorithms set to Chrome's 8; ALPN keeps h2,http/1.1.

Diff (two changes, MTTcpConnection.m only)

  1. Greeting interpreter — Shuffle op. Add { (item) (item) … }: collect the items (reusing the existing () alternative parsing), then emit them in a per-connection random order (Fisher–Yates over arc4random_uniform). Two new single-char opcodes { / }; existing opcodes untouched. ~25 lines, modeled on the existing BeginChoice case.
  2. executeGenerationCode template. Cipher list + extension set above, with the extension block wrapped in { … } so order — hence JA3 — varies per connection like BoringSSL/Chrome.

Net effect: JA4 = t13d1516h2_8daaf6152771_d8a2da3f94cd (Chrome 144) on every connection; JA3 different on every connection.

Verification (reproducible)

1. Fingerprint — the greeting interpreter from this file compiles standalone; ran the patched template 400×:

distinct JA4: 1 -> {'t13d1516h2_8daaf6152771_d8a2da3f94cd'}   # == Chrome 144, every run
distinct JA3: 400                                              # different every connection
malformed  : 0

2. Protocol — fed the generated greetings to a live MTProto proxy (telemt) over TCP, validating the FakeTLS contract end to end:

trial 0..4: sent 1550B hello -> ServerHello=True  resp_HMAC_valid=True
RESULT: 5/5 greetings accepted, valid ServerHello + HMAC

This is safe by construction: the server authenticates the greeting via HMAC-SHA256 over the record with the 32-byte ClientHello random zeroed (MTTcpConnection.m:1099,1109), so ClientHello content (ciphers/extensions/order) is fingerprint-only and never part of authentication. HMAC injection at offset 11 and the >= 513-byte guard (:1087) are unchanged; the new greeting is 1550 bytes.

Every JA4 above is reproducible from those captures with the public FoxIO ja4 tool; the patched greeting's bytes are produced by the interpreter in this file (re-implemented as a stdlib-only Python script and cross-checked against the in-file C interpreter compiled standalone with clang). Harness available on request.

3. Not done: running inside a full on-device app build. A test build / on-device confirmation would be very welcome — happy to help.

Scope

  • Touches only the proxy FakeTLS greeting (emulate_tls path). No effect on direct connections.
  • encrypted_client_hello (0xfe0d) is included as a minimal GREASE-shaped extension (type present, which is what JA3/JA4 key on). Making its inner bytes byte-faithful to Chrome's GREASE ECH is a sensible follow-up for deep-DPI realism.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants