FoxiGram/TMessagesProj/jni/tgnet/StealthTlsHello.cpp
instant992 8e79f2ee9c FoxiGram: Telegram client with built-in Xray VLESS proxy
Based on Nekogram. Key additions:
- Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app)
- Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so
- Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI
- Auto-restore proxy on restart, rebind to active network (LTE/WiFi)
- Server credentials externalized to git-ignored XrayServers.java (+ template)
- libxray Go source included; compiled .so, keystore, google-services.json ignored
2026-06-08 16:41:07 +04:00

633 lines
21 KiB
C++

/*
* StealthTlsHello.cpp
* Ported from telemt/tdlib-obf (MIT License, Copyright 2026 telemt community)
* Adapted for tgnet — C++17, OpenSSL only.
*/
#include "StealthTlsHello.h"
#include <openssl/rand.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <algorithm>
#include <atomic>
#include <cassert>
#include <cstring>
#include <vector>
namespace stealth {
// ---------------------------------------------------------------------------
// Per-run salt — initialised once, survives for the app lifetime.
// Ensures same destination picks different profiles across restarts.
// ---------------------------------------------------------------------------
static std::atomic<uint32_t> g_run_salt{0};
void initStealthSalt() {
uint32_t s = 0;
RAND_bytes(reinterpret_cast<uint8_t *>(&s), 4);
g_run_salt.store(s, std::memory_order_relaxed);
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
static void rand_bytes(uint8_t *buf, size_t n) {
RAND_bytes(buf, static_cast<int>(n));
}
static uint32_t rand_u32() {
uint32_t v;
rand_bytes(reinterpret_cast<uint8_t *>(&v), 4);
return v;
}
static uint32_t bounded(uint32_t n) {
if (n <= 1) return 0;
uint32_t t = static_cast<uint32_t>(-n) % n;
for (;;) { uint32_t v = rand_u32(); if (v >= t) return v % n; }
}
static void write_u16be(uint8_t *p, uint16_t v) {
p[0] = v >> 8; p[1] = v & 0xff;
}
// Simple append-only buffer over a caller-supplied array
struct Buf {
uint8_t *base;
uint32_t pos{0};
void u8(uint8_t v) { base[pos++] = v; }
void u16(uint16_t v) { base[pos++]=v>>8; base[pos++]=v&0xff; }
void u24(uint32_t v) { base[pos++]=(v>>16)&0xff; base[pos++]=(v>>8)&0xff; base[pos++]=v&0xff; }
void bytes(const void *p, size_t n) { memcpy(base+pos, p, n); pos+=n; }
void rnd(size_t n) { rand_bytes(base+pos, n); pos+=n; }
void zero(size_t n) { memset(base+pos, 0, n); pos+=n; }
uint32_t mark() { return pos; }
// patch big-endian u16 length at offset m (not counting the 2-byte field itself)
void patch16(uint32_t m) { write_u16be(base+m, static_cast<uint16_t>(pos-m-2)); }
// patch big-endian u24 length at offset m
void patch24(uint32_t m) {
uint32_t len = pos - m - 3;
base[m]=(len>>16)&0xff; base[m+1]=(len>>8)&0xff; base[m+2]=len&0xff;
}
};
// ---------------------------------------------------------------------------
// GREASE (RFC 8701) — 7 slots, Chrome-style
// ---------------------------------------------------------------------------
struct Grease {
uint16_t v[7];
Grease() {
uint8_t raw[7];
rand_bytes(raw, 7);
for (int i = 0; i < 7; i++)
v[i] = static_cast<uint16_t>(((raw[i] & 0xF0) | 0x0A) * 0x0101);
for (int i = 1; i < 7; i += 2)
if (v[i] == v[i-1]) v[i] ^= 0x1010;
}
};
// ---------------------------------------------------------------------------
// Shared cipher lists
// ---------------------------------------------------------------------------
// Chrome 131/133/147 cipher suite list
static const uint16_t CHROME_CIPHERS[] = {
0x1301, 0x1302, 0x1303,
0xc02b, 0xc02f, 0xc02c, 0xc030,
0xcca9, 0xcca8,
0xc013, 0xc014,
0x009c, 0x009d,
0x002f, 0x0035,
};
static const int CHROME_CIPHERS_N = sizeof(CHROME_CIPHERS)/sizeof(CHROME_CIPHERS[0]);
// Firefox 148/149 cipher suite list
static const uint16_t FIREFOX_CIPHERS[] = {
0x1301, 0x1302, 0x1303,
0xc02b, 0xc02f, 0xc02c, 0xc030,
0xcca9, 0xcca8,
0xc013, 0xc014,
0x009c, 0x009d,
0x002f, 0x0035, 0x000a,
};
static const int FIREFOX_CIPHERS_N = sizeof(FIREFOX_CIPHERS)/sizeof(FIREFOX_CIPHERS[0]);
// Safari / iOS cipher suite list
static const uint16_t SAFARI_CIPHERS[] = {
0x1301, 0x1302, 0x1303,
0xc02c, 0xc02b, 0xc030, 0xc02f,
0xcca9, 0xcca8,
0xc00a, 0xc009, 0xc014, 0xc013,
0x009d, 0x009c, 0x0035, 0x002f, 0x000a,
};
static const int SAFARI_CIPHERS_N = sizeof(SAFARI_CIPHERS)/sizeof(SAFARI_CIPHERS[0]);
// Android OkHttp cipher suite list (conservative)
static const uint16_t OKHTTP_CIPHERS[] = {
0x1301, 0x1302, 0x1303,
0xc02b, 0xc02f, 0xcca9, 0xcca8,
0xc02c, 0xc030, 0xc013, 0xc014,
0x009c, 0x009d, 0x002f, 0x0035,
};
static const int OKHTTP_CIPHERS_N = sizeof(OKHTTP_CIPHERS)/sizeof(OKHTTP_CIPHERS[0]);
// ---------------------------------------------------------------------------
// Key material: HMAC-SHA256(secret, domain || unix_time_be) → 32 bytes
// Used in the key_share "K" slot of the ClientHello, matching tdlib-obf.
// ---------------------------------------------------------------------------
static void compute_key_material(
const uint8_t *secret, size_t secret_len,
const std::string &domain, int32_t unix_time,
uint8_t out[32]) {
uint8_t tb[4] = {
uint8_t(unix_time >> 24), uint8_t(unix_time >> 16),
uint8_t(unix_time >> 8), uint8_t(unix_time),
};
HMAC_CTX *ctx = HMAC_CTX_new();
HMAC_Init_ex(ctx, secret, (int)secret_len, EVP_sha256(), nullptr);
HMAC_Update(ctx, (const uint8_t*)domain.data(), domain.size());
HMAC_Update(ctx, tb, 4);
unsigned int len = 32;
HMAC_Final(ctx, out, &len);
HMAC_CTX_free(ctx);
}
// ---------------------------------------------------------------------------
// Profile builders
// ---------------------------------------------------------------------------
// Chrome 133 / 131 / 147 (Chromium family)
// has_pq=true (X25519MLKEM768, group 0x11EC), ALPS 0x44CD (133/147) or 0x4469 (131)
static uint32_t build_chrome(Buf &b, const std::string &domain,
const uint8_t *secret, size_t slen,
int32_t unix_time, uint16_t alps_type, bool has_pq) {
Grease g;
uint8_t km[32]; // key material (X25519 public key slot)
compute_key_material(secret, slen, domain, unix_time, km);
uint8_t ml_kem[1216]; // ML-KEM-768 public key placeholder (random)
rand_bytes(ml_kem, sizeof(ml_kem));
// TLS record header: \x16\x03\x01 <length u16>
b.u8(0x16); b.u8(0x03); b.u8(0x01);
uint32_t rec_len = b.mark(); b.u16(0); // patched later
// Handshake: ClientHello type=1, length u24
b.u8(0x01);
uint32_t hs_len = b.mark(); b.u24(0);
// client_version
b.u8(0x03); b.u8(0x03);
// client_random: 32 zeros (HMAC written into bytes[11..42] later by caller)
b.zero(32);
// session_id: 32 random bytes
b.u8(0x20); b.rnd(32);
// cipher_suites: GREASE + chrome list
uint32_t cs_len = b.mark(); b.u16(0);
b.u16(g.v[0]); // GREASE
for (int i = 0; i < CHROME_CIPHERS_N; i++) b.u16(CHROME_CIPHERS[i]);
b.patch16(cs_len);
// compression: null only
b.u8(0x01); b.u8(0x00);
// extensions
uint32_t ext_len = b.mark(); b.u16(0);
// GREASE extension
b.u16(g.v[2]); b.u16(0x00);
// SNI (0x0000)
b.u16(0x0000);
uint32_t sni_ext = b.mark(); b.u16(0);
uint32_t sni_list = b.mark(); b.u16(0);
b.u8(0x00); // host_name type
uint32_t sni_name = b.mark(); b.u16(0);
b.bytes(domain.data(), domain.size());
b.patch16(sni_name); b.patch16(sni_list); b.patch16(sni_ext);
// extended_master_secret (0x0017)
b.u16(0x0017); b.u16(0x0000);
// renegotiation_info (0xff01)
b.u16(0xff01); b.u16(0x0001); b.u8(0x00);
// supported_groups (0x000a): GREASE + X25519MLKEM768 + x25519 + secp256r1 + secp384r1
b.u16(0x000a);
uint32_t sg_ext = b.mark(); b.u16(0);
uint32_t sg_list = b.mark(); b.u16(0);
b.u16(g.v[4]); // GREASE
if (has_pq) b.u16(0x11EC); // X25519MLKEM768
b.u16(0x001d); b.u16(0x0017); b.u16(0x0018);
b.patch16(sg_list); b.patch16(sg_ext);
// ec_point_formats (0x000b)
b.u16(0x000b); b.u16(0x0002); b.u8(0x01); b.u8(0x00);
// session_ticket (0x0023)
b.u16(0x0023); b.u16(0x0000);
// ALPN (0x0010): h2 + http/1.1
b.u16(0x0010);
uint32_t alpn_ext = b.mark(); b.u16(0);
uint32_t alpn_list = b.mark(); b.u16(0);
b.u8(0x02); b.bytes("h2", 2);
b.u8(0x08); b.bytes("http/1.1", 8);
b.patch16(alpn_list); b.patch16(alpn_ext);
// status_request (0x0005)
b.u16(0x0005); b.u16(0x0005);
b.u8(0x01); b.u16(0x0000); b.u16(0x0000);
// signature_algorithms (0x000d)
b.u16(0x000d); b.u16(0x0012); b.u16(0x0010);
b.u16(0x0403); b.u16(0x0804); b.u16(0x0401); b.u16(0x0503);
b.u16(0x0805); b.u16(0x0501); b.u16(0x0806); b.u16(0x0601);
// signed_cert_timestamp (0x0012)
b.u16(0x0012); b.u16(0x0000);
// key_share (0x0033)
b.u16(0x0033);
uint32_t ks_ext = b.mark(); b.u16(0);
uint32_t ks_list = b.mark(); b.u16(0);
if (has_pq) {
b.u16(0x11EC); // X25519MLKEM768
b.u16(0x04C0); // key length 1216
b.bytes(ml_kem, 1216);
}
b.u16(0x001d); // x25519
b.u16(0x0020); // key length 32
b.bytes(km, 32);
b.patch16(ks_list); b.patch16(ks_ext);
// psk_key_exchange_modes (0x002d)
b.u16(0x002d); b.u16(0x0002); b.u8(0x01); b.u8(0x01);
// supported_versions (0x002b): GREASE + TLS1.3 + TLS1.2
b.u16(0x002b); b.u16(0x0007); b.u8(0x06);
b.u16(g.v[6]); b.u16(0x0304); b.u16(0x0303);
// compress_certificate (0x001b)
b.u16(0x001b); b.u16(0x0003); b.u8(0x02); b.u16(0x0002);
// GREASE extension (trailing)
b.u16(g.v[3]); b.u16(0x0001); b.u8(0x00);
// ALPS (application_settings) — type varies by Chrome version
if (alps_type != 0) {
b.u16(alps_type); b.u16(0x0005); b.u8(0x00); b.u8(0x03); b.u8(0x02);
b.bytes("h2", 2);
}
// padding (0x0015) — vary per build to defeat static record-size hashing
{
// target ~517 bytes for ClientHello body; add random entropy
uint32_t pad = (uint32_t)bounded(128);
b.u16(0x0015);
uint32_t pad_len = b.mark(); b.u16(0);
b.zero(pad);
b.patch16(pad_len);
}
b.patch16(ext_len);
b.patch24(hs_len);
b.patch16(rec_len);
return b.pos;
}
// Firefox 148 / 149
static uint32_t build_firefox(Buf &b, const std::string &domain,
const uint8_t *secret, size_t slen,
int32_t unix_time) {
Grease g;
uint8_t km[32];
compute_key_material(secret, slen, domain, unix_time, km);
uint8_t ml_kem[1216];
rand_bytes(ml_kem, sizeof(ml_kem));
b.u8(0x16); b.u8(0x03); b.u8(0x01);
uint32_t rec_len = b.mark(); b.u16(0);
b.u8(0x01);
uint32_t hs_len = b.mark(); b.u24(0);
b.u8(0x03); b.u8(0x03);
b.zero(32);
b.u8(0x20); b.rnd(32);
// ciphers (no leading GREASE for Firefox)
uint32_t cs_len = b.mark(); b.u16(0);
for (int i = 0; i < FIREFOX_CIPHERS_N; i++) b.u16(FIREFOX_CIPHERS[i]);
b.patch16(cs_len);
b.u8(0x01); b.u8(0x00);
uint32_t ext_len = b.mark(); b.u16(0);
// SNI
b.u16(0x0000);
uint32_t sni_ext = b.mark(); b.u16(0);
uint32_t sni_list = b.mark(); b.u16(0);
b.u8(0x00);
uint32_t sni_name = b.mark(); b.u16(0);
b.bytes(domain.data(), domain.size());
b.patch16(sni_name); b.patch16(sni_list); b.patch16(sni_ext);
// extended_master_secret
b.u16(0x0017); b.u16(0x0000);
// renegotiation_info
b.u16(0xff01); b.u16(0x0001); b.u8(0x00);
// supported_groups: X25519MLKEM768 + x25519 + secp256r1 + secp384r1 + secp521r1
b.u16(0x000a);
uint32_t sg_ext = b.mark(); b.u16(0);
uint32_t sg_list = b.mark(); b.u16(0);
b.u16(0x11EC); b.u16(0x001d); b.u16(0x0017); b.u16(0x0018); b.u16(0x0019);
b.patch16(sg_list); b.patch16(sg_ext);
// ec_point_formats
b.u16(0x000b); b.u16(0x0002); b.u8(0x01); b.u8(0x00);
// session_ticket
b.u16(0x0023); b.u16(0x0000);
// ALPN: http/1.1 only (Firefox proxy mode)
b.u16(0x0010);
uint32_t alpn_ext = b.mark(); b.u16(0);
uint32_t alpn_list = b.mark(); b.u16(0);
b.u8(0x08); b.bytes("http/1.1", 8);
b.patch16(alpn_list); b.patch16(alpn_ext);
// status_request
b.u16(0x0005); b.u16(0x0005);
b.u8(0x01); b.u16(0x0000); b.u16(0x0000);
// signature_algorithms
b.u16(0x000d); b.u16(0x0014); b.u16(0x0012);
b.u16(0x0403); b.u16(0x0804); b.u16(0x0401);
b.u16(0x0503); b.u16(0x0805); b.u16(0x0501);
b.u16(0x0806); b.u16(0x0601); b.u16(0x0201);
// key_share: X25519MLKEM768 + x25519
b.u16(0x0033);
uint32_t ks_ext = b.mark(); b.u16(0);
uint32_t ks_list = b.mark(); b.u16(0);
b.u16(0x11EC); b.u16(0x04C0); b.bytes(ml_kem, 1216);
b.u16(0x001d); b.u16(0x0020); b.bytes(km, 32);
b.patch16(ks_list); b.patch16(ks_ext);
// psk_key_exchange_modes
b.u16(0x002d); b.u16(0x0002); b.u8(0x01); b.u8(0x01);
// supported_versions: TLS 1.3 + 1.2
b.u16(0x002b); b.u16(0x0005); b.u8(0x04);
b.u16(0x0304); b.u16(0x0303);
// compress_certificate
b.u16(0x001b); b.u16(0x0003); b.u8(0x02); b.u16(0x0002);
// delegated_credentials (Firefox-specific, 0x0022)
b.u16(0x0022); b.u16(0x0012); b.u16(0x0010);
b.u16(0x0403); b.u16(0x0804); b.u16(0x0401);
b.u16(0x0503); b.u16(0x0805); b.u16(0x0501);
b.u16(0x0806); b.u16(0x0601); b.u16(0x0201); // 9 pairs = 18 bytes = 0x0012... correct
// record_size_limit (0x001c) — Firefox advertises 0x4001
b.u16(0x001c); b.u16(0x0002); b.u16(0x4001);
// padding
uint32_t pad = (uint32_t)bounded(96);
b.u16(0x0015);
uint32_t pad_len = b.mark(); b.u16(0);
b.zero(pad);
b.patch16(pad_len);
b.patch16(ext_len);
b.patch24(hs_len);
b.patch16(rec_len);
return b.pos;
}
// Safari 26 / iOS 14 (Apple TLS stack)
static uint32_t build_safari(Buf &b, const std::string &domain,
const uint8_t *secret, size_t slen,
int32_t unix_time) {
uint8_t km[32];
compute_key_material(secret, slen, domain, unix_time, km);
uint8_t ml_kem[1216];
rand_bytes(ml_kem, sizeof(ml_kem));
b.u8(0x16); b.u8(0x03); b.u8(0x01);
uint32_t rec_len = b.mark(); b.u16(0);
b.u8(0x01);
uint32_t hs_len = b.mark(); b.u24(0);
b.u8(0x03); b.u8(0x03);
b.zero(32);
b.u8(0x20); b.rnd(32);
uint32_t cs_len = b.mark(); b.u16(0);
for (int i = 0; i < SAFARI_CIPHERS_N; i++) b.u16(SAFARI_CIPHERS[i]);
b.patch16(cs_len);
b.u8(0x01); b.u8(0x00);
uint32_t ext_len = b.mark(); b.u16(0);
b.u16(0x0000);
uint32_t sni_ext = b.mark(); b.u16(0);
uint32_t sni_list = b.mark(); b.u16(0);
b.u8(0x00);
uint32_t sni_name = b.mark(); b.u16(0);
b.bytes(domain.data(), domain.size());
b.patch16(sni_name); b.patch16(sni_list); b.patch16(sni_ext);
b.u16(0x0017); b.u16(0x0000);
b.u16(0x000a);
uint32_t sg_ext = b.mark(); b.u16(0);
uint32_t sg_list = b.mark(); b.u16(0);
b.u16(0x11EC); b.u16(0x001d); b.u16(0x0017); b.u16(0x0018); b.u16(0x0019);
b.patch16(sg_list); b.patch16(sg_ext);
b.u16(0x000b); b.u16(0x0002); b.u8(0x01); b.u8(0x00);
b.u16(0x0023); b.u16(0x0000);
b.u16(0x0010);
uint32_t alpn_ext = b.mark(); b.u16(0);
uint32_t alpn_list = b.mark(); b.u16(0);
b.u8(0x08); b.bytes("http/1.1", 8);
b.patch16(alpn_list); b.patch16(alpn_ext);
b.u16(0x0005); b.u16(0x0005); b.u8(0x01); b.u16(0x0000); b.u16(0x0000);
b.u16(0x000d); b.u16(0x0012); b.u16(0x0010);
b.u16(0x0403); b.u16(0x0804); b.u16(0x0401);
b.u16(0x0503); b.u16(0x0805); b.u16(0x0501);
b.u16(0x0806); b.u16(0x0601);
b.u16(0x0033);
uint32_t ks_ext = b.mark(); b.u16(0);
uint32_t ks_list = b.mark(); b.u16(0);
b.u16(0x11EC); b.u16(0x04C0); b.bytes(ml_kem, 1216);
b.u16(0x001d); b.u16(0x0020); b.bytes(km, 32);
b.patch16(ks_list); b.patch16(ks_ext);
b.u16(0x002d); b.u16(0x0002); b.u8(0x01); b.u8(0x01);
b.u16(0x002b); b.u16(0x0005); b.u8(0x04); b.u16(0x0304); b.u16(0x0303);
b.u16(0xff01); b.u16(0x0001); b.u8(0x00);
uint32_t pad = (uint32_t)bounded(64);
b.u16(0x0015);
uint32_t pad_len = b.mark(); b.u16(0);
b.zero(pad);
b.patch16(pad_len);
b.patch16(ext_len); b.patch24(hs_len); b.patch16(rec_len);
return b.pos;
}
// Android OkHttp (no ECH, no PQ)
static uint32_t build_okhttp(Buf &b, const std::string &domain,
const uint8_t *secret, size_t slen,
int32_t unix_time) {
uint8_t km[32];
compute_key_material(secret, slen, domain, unix_time, km);
b.u8(0x16); b.u8(0x03); b.u8(0x01);
uint32_t rec_len = b.mark(); b.u16(0);
b.u8(0x01);
uint32_t hs_len = b.mark(); b.u24(0);
b.u8(0x03); b.u8(0x03);
b.zero(32);
b.u8(0x00); // no session_id
uint32_t cs_len = b.mark(); b.u16(0);
for (int i = 0; i < OKHTTP_CIPHERS_N; i++) b.u16(OKHTTP_CIPHERS[i]);
b.patch16(cs_len);
b.u8(0x01); b.u8(0x00);
uint32_t ext_len = b.mark(); b.u16(0);
b.u16(0x0000);
uint32_t sni_ext = b.mark(); b.u16(0);
uint32_t sni_list = b.mark(); b.u16(0);
b.u8(0x00);
uint32_t sni_name = b.mark(); b.u16(0);
b.bytes(domain.data(), domain.size());
b.patch16(sni_name); b.patch16(sni_list); b.patch16(sni_ext);
b.u16(0x000a);
uint32_t sg_ext = b.mark(); b.u16(0);
uint32_t sg_list = b.mark(); b.u16(0);
b.u16(0x001d); b.u16(0x0017); b.u16(0x0018);
b.patch16(sg_list); b.patch16(sg_ext);
b.u16(0x000b); b.u16(0x0002); b.u8(0x01); b.u8(0x00);
b.u16(0x0023); b.u16(0x0000);
b.u16(0x0010);
uint32_t alpn_ext = b.mark(); b.u16(0);
uint32_t alpn_list = b.mark(); b.u16(0);
b.u8(0x08); b.bytes("http/1.1", 8);
b.patch16(alpn_list); b.patch16(alpn_ext);
b.u16(0x000d); b.u16(0x000c); b.u16(0x000a);
b.u16(0x0403); b.u16(0x0401); b.u16(0x0503);
b.u16(0x0501); b.u16(0x0603); b.u16(0x0601);
b.u16(0x0033);
uint32_t ks_ext = b.mark(); b.u16(0);
uint32_t ks_list = b.mark(); b.u16(0);
b.u16(0x001d); b.u16(0x0020); b.bytes(km, 32);
b.patch16(ks_list); b.patch16(ks_ext);
b.u16(0x002d); b.u16(0x0002); b.u8(0x01); b.u8(0x01);
b.u16(0x002b); b.u16(0x0005); b.u8(0x04); b.u16(0x0304); b.u16(0x0303);
b.patch16(ext_len); b.patch24(hs_len); b.patch16(rec_len);
return b.pos;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
uint32_t buildTlsHelloForProfile(
const std::string &domain,
const uint8_t *secret, size_t secret_len,
int32_t unix_time,
BrowserProfile profile,
uint8_t *out) {
Buf b{out, 0};
switch (profile) {
case BrowserProfile::Chrome133:
return build_chrome(b, domain, secret, secret_len, unix_time, 0x44CD, true);
case BrowserProfile::Chrome131:
return build_chrome(b, domain, secret, secret_len, unix_time, 0x4469, true);
case BrowserProfile::Chrome120:
return build_chrome(b, domain, secret, secret_len, unix_time, 0x4469, false);
case BrowserProfile::Chrome147_Windows:
case BrowserProfile::Chrome147_IOSChromium:
return build_chrome(b, domain, secret, secret_len, unix_time, 0x44CD, true);
case BrowserProfile::Firefox148:
case BrowserProfile::Firefox149_Windows:
return build_firefox(b, domain, secret, secret_len, unix_time);
case BrowserProfile::Safari26_3:
case BrowserProfile::IOS14:
return build_safari(b, domain, secret, secret_len, unix_time);
case BrowserProfile::Android11_OkHttp:
return build_okhttp(b, domain, secret, secret_len, unix_time);
default:
return build_chrome(b, domain, secret, secret_len, unix_time, 0x44CD, true);
}
}
BrowserProfile pickProfile(const std::string &destination, int32_t unix_time,
uint32_t per_run_salt) {
static const struct { BrowserProfile p; uint32_t w; } TABLE[] = {
{ BrowserProfile::Chrome133, 50 },
{ BrowserProfile::Chrome131, 20 },
{ BrowserProfile::Chrome120, 15 },
{ BrowserProfile::Chrome147_Windows, 10 },
{ BrowserProfile::Chrome147_IOSChromium, 30 },
{ BrowserProfile::Firefox148, 15 },
{ BrowserProfile::Firefox149_Windows, 10 },
{ BrowserProfile::Safari26_3, 5 },
{ BrowserProfile::IOS14, 5 },
{ BrowserProfile::Android11_OkHttp, 30 },
};
static const uint32_t TOTAL = 190;
uint32_t daily = static_cast<uint32_t>(unix_time / 86400);
uint32_t h = per_run_salt;
h ^= daily * 2654435761u;
for (char c : destination) h = h * 31 + static_cast<uint8_t>(c);
h ^= h >> 16; h *= 0x45d9f3b; h ^= h >> 16;
uint32_t slot = h % TOTAL, acc = 0;
for (auto &e : TABLE) {
acc += e.w;
if (slot < acc) return e.p;
}
return BrowserProfile::Chrome133;
}
uint32_t buildStealthTlsHello(
const std::string &domain,
const uint8_t *secret, size_t secret_len,
int32_t unix_time,
uint8_t *out) {
uint32_t salt = g_run_salt.load(std::memory_order_relaxed);
if (salt == 0) {
RAND_bytes(reinterpret_cast<uint8_t *>(&salt), 4);
g_run_salt.store(salt, std::memory_order_relaxed);
}
BrowserProfile profile = pickProfile(domain, unix_time, salt);
return buildTlsHelloForProfile(domain, secret, secret_len, unix_time, profile, out);
}
} // namespace stealth