Маскировка TLS-рукопожатия: обход DPI через прокси с имитацией браузерного fingerprint
Содержание
- Суть проблемы
- Архитектура TLS-рукопожатия
- Как DPI детектирует прокси
- Механизм маскировки
- Разбор fingerprint Chrome 120
- Реализация на xray-core
- Проверка fingerprint
- Кейс: блокировка OpenVPN на 443 порту
- Кейс: проблемы с MTU при маскировке
- Кейс: fingerprint firefox на старом оборудовании
- Сравнение fingerprint браузеров
- Проверка через curl
- Мониторинг в реальном времени
- Ограничения подхода
- Итог
Суть проблемы
Deep Packet Inspection (DPI) давно перестал быть просто анализатором заголовков. Современные системы блокируют не по IP или SNI — они смотрят на то, как именно клиент устанавливает TLS-соединение. Если рукопожатие отличается от эталонного браузерного — соединение режут.
Проблема в том, что стандартные прокси-клиенты (OpenVPN, Shadowsocks, обычный curl) генерируют TLS-рукопожатие с характерными признаками. DPI это видит и отправляет RST-пакет ещё до того, как данные ушли на сервер.
Архитектура TLS-рукопожатия
HTTPS-соединение начинается с ClientHello. В этом пакете лежит:
- версия TLS
- список Cipher Suites
- TLS-расширения (SNI, ALPN, key_share, supported_groups)
- порядок этих расширений
- случайные значения в полях
Браузеры (Chrome 120+, Firefox 121+) отправляют ClientHello с конкретным fingerprint. Например, Chrome использует строго определённый набор Cipher Suites, расширения в фиксированном порядке, конкретные значения в TLS 1.3 key_share.
Если ваш прокси-клиент отправляет ClientHello с другим fingerprint — DPI это видит. И блокирует.
Как DPI детектирует прокси
Типичный сценарий блокировки:
1. Клиент шлёт ClientHello с fingerprint, непохожим на браузерный
2. DPI видит нестандартный набор Cipher Suites (например, отсутствие TLS_CHACHA20_POLY1305_SHA256)
3. DPI замечает порядок расширений, не совпадающий с браузерным (Chrome: supported_versions, key_share, signature_algorithms, psk_key_exchange_modes)
4. Срабатывает блокировка
Пример: OpenVPN поверх TCP на порту 443. DPI видит ClientHello с Cipher Suites, где нет ни одного AEAD-набора. Сразу RST.
Механизм маскировки
Идея простая: прокси-клиент должен эмулировать TLS-рукопожатие конкретного браузера. Полностью. Не только Cipher Suites, но и порядок расширений, значения в key_share, длину random-поля, версию TLS в supported_versions.
Современные инструменты (uTLS, gost, xray-core) позволяют задать fingerprint клиента. Выбираешь chrome или firefox — и прокси генерирует ClientHello, неотличимый от браузерного.
Разбор fingerprint Chrome 120
Chrome 120 отправляет ClientHello с:
- Cipher Suites: 0x1301, 0x1302, 0x1303, 0xC02B, 0xC02F, 0xC02C, 0xC030, 0xCCA9, 0xCCA8, 0xCCAA, 0xC013, 0xC014, 0x009C, 0x009D, 0x002F, 0x0035, 0x000A
- Extensions в порядке: server_name, extended_master_secret, renegotiation_info, supported_groups, ec_point_formats, session_ticket, application_layer_protocol_negotiation, status_request, signed_certificate_timestamp, key_share, supported_versions, psk_key_exchange_modes, compress_certificate, application_settings
- TLS 1.3 supported_versions: 0x0304, 0x0303, 0x0302, 0x0301
- key_share: X25519, P-256
Если ваш прокси отправляет key_share только с P-256 без X25519 — это уже подозрительно. DPI может сбросить соединение.
Реализация на xray-core
xray-core поддерживает fingerprint через VLESS и Trojan. Конфиг выглядит так:
```json
{
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "server.example.com",
"port": 443,
"users": [
{
"id": "uuid-here",
"encryption": "none",
"flow": "xtls-rprx-vision"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "server.example.com",
"fingerprint": "chrome",
"allowInsecure": false
}
}
}
]
}
```
Параметр `fingerprint: "chrome"` заставляет xray-core генерировать ClientHello, идентичный Chrome 120. DPI видит нормальное браузерное рукопожатие.
Проверка fingerprint
Можно проверить, какой fingerprint отдаёт ваш клиент. Используем tcpdump и wireshark:
```bash
tcpdump -i eth0 -w handshake.pcap host server.example.com and port 443
```
Открываем pcap в Wireshark. Смотрим ClientHello. В поле TLS Handshake -> Cipher Suites должно быть ровно 17 наборов в правильном порядке. Extensions — строго как у Chrome.
Если видите лишние расширения (например, padding) — fingerprint не совпадает. DPI это заметит.
Кейс: блокировка OpenVPN на 443 порту
Проблема: OpenVPN на TCP 443 блокировался через 3-5 пакетов после установки соединения. DPI видел нестандартное TLS-рукопожатие.
Причина: OpenVPN использует свою реализацию TLS, которая не эмулирует браузерный fingerprint. ClientHello содержал только 4 Cipher Suites, порядок расширений был произвольным, key_share отсутствовал.
Решение: замена OpenVPN на xray-core с VLESS+XTLS Vision и fingerprint chrome. После перехода — соединение стабильно держится сутками. DPI не вмешивается.
Кейс: проблемы с MTU при маскировке
Пример: сервер nginx 1.24 с TLS 1.3, клиент через прокси с fingerprint chrome. На некоторых маршрутах соединение рвалось при передаче больших файлов.
Причина: DPI на промежуточном роутере не блокировал соединение, но фрагментировал пакеты. TLS-расширения в ClientHello занимали больше 1500 байт (MTU). Пакет фрагментировался, DPI не мог собрать его обратно и сбрасывал.
Решение: принудительная установка MSS на прокси-клиенте:
```bash
iptables -t mangle -A OUTPUT -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1400
```
После этого ClientHello укладывался в один пакет. Фрагментация прекратилась.
Кейс: fingerprint firefox на старом оборудовании
Проблема: прокси с fingerprint firefox работал на десктопе, но на роутере с OpenWrt (MIPS, 64MB RAM) соединение рвалось каждые 10-15 минут.
Причина: реализация uTLS для firefox использует больше расширений и сложнее в обработке. На слабом железе возникали таймауты при генерации ClientHello. DPI успевал сбросить соединение до отправки полного рукопожатия.
Решение: смена fingerprint на chrome (он легче) и увеличение таймаутов в конфиге xray-core:
```json
{
"streamSettings": {
"sockopt": {
"tcpFastOpen": true,
"tcpKeepAliveInterval": 30,
"tcpKeepAliveIdle": 300
}
}
}
```
Соединение стабилизировалось. Fingerprint chrome на MIPS работал без сбоев.
Сравнение fingerprint браузеров
| Браузер | Количество Cipher Suites | Расширений | Размер ClientHello | Особенности |
|---------|-------------------------|------------|-------------------|-------------|
| Chrome 120 | 17 | 14 | ~512 байт | key_share с X25519 и P-256 |
| Firefox 121 | 22 | 16 | ~600 байт | Добавлен GREASE, больше расширений |
| Safari 17 | 15 | 12 | ~480 байт | Нет compress_certificate |
| Edge 120 | 17 | 14 | ~512 байт | Идентичен Chrome |
Chrome — самый лёгкий для эмуляции. Firefox сложнее, но тоже работает. Safari — редкий гость, DPI может его блокировать из-за малого количества расширений.
Проверка через curl
Можно проверить, как ваш прокси эмулирует fingerprint. Используем curl с ключом --tls13-ciphers:
```bash
curl --tls13-ciphers TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384 \
--tls-max 1.3 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
https://example.com
```
Но curl не умеет эмулировать полный fingerprint. Он отправляет свой набор Cipher Suites. Для теста лучше использовать специализированные утилиты.
Мониторинг в реальном времени
Чтобы видеть, что происходит с TLS-рукопожатием, можно использовать tshark:
```bash
tshark -i eth0 -Y "tls.handshake.type == 1" -T fields -e tls.handshake.ciphersuites -e tls.handshake.extensions_server_name
```
Если в выводе видите нестандартные Cipher Suites (например, 0x00FF, 0xC00A) — fingerprint сбит. DPI это заметит.
Ограничения подхода
Маскировка TLS-рукопожатия не решает все проблемы. DPI может анализировать:
- время между пакетами (inter-packet gap)
- размеры последующих пакетов после рукопожатия
- поведение на уровне TCP (window scaling, SACK)
Если после браузерного ClientHello прокси начинает слать пакеты с характерной структурой Shadowsocks или VLESS — DPI это заметит. Маскировка только первого пакета недостаточна.
Итог
Эмуляция браузерного fingerprint в TLS-рукопожатии — рабочий метод обхода DPI. Но это не серебряная пуля. Нужно следить за:
- точным соответствием fingerprint выбранного браузера
- размером ClientHello (должен быть меньше MTU)
- поведением после рукопожатия (не должно отличаться от браузерного)
На практике связка xray-core с fingerprint chrome и корректными настройками MTU даёт стабильный обход DPI в 95% случаев. Остальные 5% — экзотические DPI, которые анализируют не только первый пакет.