git.zapret.moe в России заблокирован по имени, как и wiki.zapret.moe. Ссылки на Forgejo во всех заметках, футерах, wizard.html и главной заменены на git2.zapret.moe (включая закодированные адреса значков shields.io). Какое имя рабочее, теперь решают реестры хоста /etc/zapret-*-domains.conf через zapret-site-names: pre-commit хук vault отклоняет добавленные строки с заблокированным именем, сборка Quartz сама переводит ссылки и baseUrl на рабочие имена. Правило в AGENTS.md переписано под этот механизм. scripts/wiki-stats.py: переходы с любых имён вики и git (wikiN, gitN) больше не считаются внешними сайтами. content-check: проверка зеркал сторонних проектов узнаёт любое имя git. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 KiB
| date | tags | aliases | description | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-02-17 |
|
|
Функция hostfakesplit в zapret2/nfqws2: разрез TCP-сегментов по границам hostname с фейковыми SNI, аргументы host, midhost, disorder_after, примеры. |
[!mirror] Резервное зеркало Актуальная версия этой страницы — на основной вики: wiki2.zapret.moe/Zapret2/desync/hostfakesplit
hostfakesplit — TCP-сегментация с фейковым hostname (zapret2 / nfqws2)
Коротко и по-простому
Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке Host: запроса. ТСПУ (Технические средства противодействия угрозам — оборудование DPI/DPI, то есть глубокой инспекции трафика, которое стоит у провайдера) ищет это имя в первом пакете соединения и по нему решает, что делать с соединением дальше.
Имя сайта занимает в запросе небольшой участок: перед ним идут служебные данные запроса, после него — остальное. В HTTP-запросе имя стоит после Host:, в ClientHello лежит внутри поля SNI. Границы имени hostfakesplit находит сама, по названиям-маркерам. Маркер — это название места в данных, которое движок вычисляет по устройству запроса: host — первый байт имени, endhost — байт сразу за его последним байтом. Так это выглядит в HTTP (метки под строкой показывают границы имени):
GET /watch HTTP/1.1\r\nHost: www.youtube.com\r\n...
^host ^endhost
Эти маркеры работают, только если движок распознал запрос как HTTP или как TLS ClientHello; общий список маркеров — в разделе Zapret2/desync/multisplit#Относительные маркеры статьи про multisplit, а как hostfakesplit их использует — в разделе #Основные точки разреза: host и endhost.
HTTP-запросы и TLS-соединения передаются по протоколу TCP, а TCP отправляет данные кусками — сегментами. У каждого байта в соединении есть порядковый номер (sequence number, коротко seq), и сервер раскладывает принятые куски в свой буфер строго по этим номерам. Поэтому ему безразлично, пришли данные одним пакетом или несколькими и в каком порядке куски доехали: исходный поток он всё равно соберёт. Почему это гарантирует именно TCP, разобрано в нюансе Zapret2/desync/multisplit#1. Работает только с TCP — и почему статьи про multisplit.
Ещё одно понятие: фейк. Это сегмент с ложными данными, здесь — с подложным именем сайта. Чтобы сервер не принял фейк за настоящие данные и не сломал соединение, фейк «портят» так, чтобы сервер его отбросил, а DPI мог прочитать: такая порча называется fooling (Zapret2/ts-and-fooling#Что такое fooling). В команде из быстрого старта это tcp_md5: к сегменту добавляется TCP-опция с подписью MD5 (16 байт, по умолчанию случайные), и Linux-сервер обычно такой сегмент отбрасывает. Способ не универсальный, другие собраны в Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов. Без fooling сервер примет фейки и получит два противоречивых имени на одном месте потока, поэтому для hostfakesplit он обязателен.
Теперь сама техника, по шагам. hostfakesplit перехватывает исходящий ClientHello или HTTP-запрос и находит в нём границы имени. Затем генерирует фейковое имя той же длины, что и настоящее: функция genhost собирает случайный домен вроде u9a7bk2.org, а если подсказать шаблон (host=vk.com), получится поддомен вида e8nzn.vk.com. После этого функция отправляет пять сегментов (это базовый вариант, без midhost и disorder_after): данные до имени, фейковое имя, настоящее имя, ещё раз то же фейковое имя и данные после имени. Фейки уходят с fooling и с тем же seq, что и настоящее имя; настоящим сегментам fooling не достаётся (исключение — опция tcp_ts_up). В конце функция выбрасывает перехваченный оригинал (если не задан nodrop), чтобы те же данные не ушли второй раз. Если границы имени найти нельзя (например, это не HTTP и не ClientHello), она ничего не делает.
Если fooling сработал, сервер отбрасывает оба фейка, принимает настоящие сегменты и собирает ровно тот запрос, который отправил браузер. DPI на месте имени видит три кандидата с одним и тем же seq и одной и той же длиной: фейк, настоящее имя, фейк. Длина у всех трёх одинаковая, поэтому по размеру настоящее имя не отличить, и DPI приходится выбирать, какое из имён «правильное». Он может принять за настоящее одно из фейковых имён; если он выберет фейк, обход удался. Что видит каждая сторона, показано в разделе #Порядок отправки сегментов.
Команда из быстрого старта hostfakesplit:tcp_md5 находит в ClientHello границы имени сайта (маркеры host и endhost) и отправляет пять сегментов: данные до имени, фейковое имя, настоящее имя youtube.com, ещё раз фейковое имя и данные после имени. Фейковое имя (на схеме u9a7bk2.org) той же длины и ложится на тот же seq, поэтому на месте имени ТСПУ видит три кандидата; сервер отбрасывает фейки по неверной подписи TCP MD5. Как читать схему: строки — пакеты в порядке отправки, подписанные в тех же обозначениях, что и таблицы в этой статье; по горизонтали — место данных в TCP-потоке (номер последовательности, seq); в колонке справа — что сделал с пакетом сервер; внизу — буфер сервера, в который принятые куски встают по своим seq.
!desync-anim-hostfakesplit.svg
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5
Строка --payload=tls_client_hello значит «срабатывать только на TLS ClientHello», а --lua-desync=hostfakesplit:tcp_md5 включает функцию с fooling tcp_md5. Обе строки пишут в Zapret2/profile#Анатомия профиля — набор правил Zapret 2 для определённого трафика. Остальные готовые команды собраны в разделах #Быстрый старт и #Практические примеры. К этой схеме можно добавить несколько опций. Аргумент midhost= режет настоящее имя ещё на две части, например посередине домена второго уровня (midhost=midsld; у www.youtube.com домен второго уровня — youtube): вместо одного настоящего сегмента с именем уйдут два (#midhost — разрез внутри hostname). Аргумент disorder_after режет данные после имени ещё на две части и отправляет их в обратном порядке (#disorder_after — обратный порядок хвоста). Флаги nofake1 и nofake2 отключают первый или второй фейк, а repeats=N отправляет каждый фейк N раз (настоящие сегменты — по одному разу). Все аргументы разобраны в разделе #Полный список аргументов.
Чем hostfakesplit отличается от соседей. Zapret2/desync/multisplit#Зачем нужен multisplit только режет настоящие данные и фейков не шлёт; места разреза там задаёт аргумент pos, а у hostfakesplit pos нет: границы всегда стоят по имени. Zapret2/desync/fakedsplit#Зачем нужен fakedsplit режет по одной произвольной позиции и заливает фейки паттерном (нулями и т.п.), а hostfakesplit знает, где стоит имя, и подмешивает осмысленное фейковое имя той же длины. Zapret2/desync/fakeddisorder#Зачем нужен fakeddisorder и Zapret2/desync/multidisorder#Зачем нужен multidisorder отправляют куски в обратном порядке; у hostfakesplit обратный порядок необязательный и касается только данных после имени. Все отличия сведены в таблицу в разделе #Отличия от других функций сегментации.
Когда техника не сработает. Функция понимает только HTTP-запрос и TLS ClientHello: для QUIC (это не TCP) и других протоколов границ имени не найти, и она тихо ничего не делает. Приёма seqovl (скрытого фейка внутри сегмента, Zapret2/desync/multisplit#seqovl — скрытый фейк внутри сегмента) у неё нет. Обход удаётся, только если DPI выберет из трёх кандидатов фейк, а не настоящее имя, поэтому рабочую комбинацию для своего провайдера подбирают опытным путём и проверяют по Zapret2/verify-strategy#Короткий чек-лист честной проверки. В боевых примерах hostfakesplit ставят после Zapret2/desync/fake#Зачем нужен fake — отдельного пакета-обманки с ложным ClientHello; см. примеры #12. Комбинация: fake → hostfakesplit (два инстанса) и #13. Боевой пример для YouTube (TLS).
Оглавление
- Коротко и по-простому
- Справка: где функция в коде и родственники
- Зачем нужен hostfakesplit
- Быстрый старт
- Откуда берутся данные
- Основные точки разреза: host и endhost
- Генерация фейкового hostname (genhost)
- Дополнительные точки разреза
- Полный список аргументов
- Порядок отправки сегментов
- Разделение opts: оригиналы vs фейки
- Поведение при replay / reasm
- Место в общем скелете
- Псевдокод алгоритма
- Нюансы и подводные камни
- Отличия от других функций сегментации
- Миграция с nfqws1
- Практические примеры
- 📚 См. также
Справка: где функция в коде и родственники
Паспорт функции для тех, кто сверяется с исходниками zapret2: в каком файле и на какой строке лежит код, какой ключ первой версии zapret она заменяет и как объявлена.
Файл: lua/zapret-antidpi.lua:707
nfqws1 эквивалент: --dpi-desync=hostfakesplit
Сигнатура: function hostfakesplit(ctx, desync)
hostfakesplit — специализированная функция TCP-сегментации с замешиванием фейковых hostname-сегментов. Она предназначена исключительно для payload, содержащих имя хоста (http_req и tls_client_hello). Функция автоматически определяет границы hostname в payload, генерирует фейковый hostname той же длины (через genhost), и отправляет последовательность из реальных и фейковых TCP-сегментов. После успешной отправки выносит VERDICT_DROP, чтобы оригинальный пакет не ушёл.
[!note] Проще говоря Payload — полезные данные пакета без сетевых заголовков: сам HTTP-запрос или сам ClientHello.
http_reqиtls_client_hello— Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока), то есть названия распознанных протоколов. Hostname — имя сайта, то самое, которое ищет DPI.genhost— функция, которая генерирует фейковое имя нужной длины (#Генерация фейкового hostname (genhost)).VERDICT_DROP— решение «перехваченный пакет не отправлять»; как движок складывает такие решения от разных функций, описано в Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация.
Родственные функции: multisplit (базовая сегментация), multidisorder (обратный порядок), fakedsplit (фейки по произвольной позиции), fakeddisorder (фейки + обратный порядок), tcpseg (диапазон), oob (urgent byte).
Общий для всех техник дурения порядок работы — восемь стадий от отсева чужого транспорта до вердикта — разобран в жизненный цикл desync-функции; здесь описано только то, чем hostfakesplit от этого скелета отличается.
Зачем нужен hostfakesplit
DPI ищет hostname в TCP-потоке: заголовок Host: в HTTP, поле SNI в TLS ClientHello. Если DPI работает попакетно (не реассемблирует TCP), достаточно разрезать payload по границам hostname. Но продвинутые DPI умеют реассемблировать поток и собрать hostname из нескольких сегментов.
hostfakesplit решает обе проблемы одновременно:
- Разрез по границам hostname: payload разрезается точно по маркерам
hostиendhost, так что ни один сегмент не содержит полный hostname в "чистом" виде - Замешивание фейков: между реальными сегментами вставляются фейковые TCP-сегменты с тем же TCP sequence, но с другим hostname (сгенерированным
genhost). DPI, пытающийся реассемблировать поток, может принять фейковый hostname за настоящий - Fooling на фейках: фейковые сегменты отправляются с fooling-опциями (TTL, md5sig, badseq и т.д.), поэтому сервер их отбрасывает, а DPI — нет
Сервер корректно собирает поток: фейки отбрасываются благодаря fooling, реальные сегменты доставляются через стандартный TCP-механизм.
[!note] Проще говоря DPI ищет имя сайта в пакетах. Самый простой DPI разбирает каждый пакет отдельно и не склеивает их обратно в поток (склейка называется реассемблированием, от английского reassembly), поэтому его сбивает обычный разрез. Продвинутый DPI поток склеивает и может собрать имя из нескольких сегментов. Против него
hostfakesplitподмешивает к настоящим сегментам фейковые с ложным именем: на одном месте потока лежат разные имена, и DPI приходится выбирать. Fooling здесь — способ испортить фейки так, чтобы сервер их выбросил; подробно — Zapret2/ts-and-fooling#Что такое fooling, короткие описания способов — Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов.
Ключевое отличие от fakedsplit: hostfakesplit автоматически знает где hostname, генерирует осмысленный фейковый hostname той же длины и имеет опцию midhost для дополнительного разреза внутри hostname. fakedsplit работает по произвольной позиции и заливает фейк паттерном (0x00 и т.п.).
Как отправляет пакеты fakedsplit и что при этом видит DPI, показано в разделе Zapret2/desync/fakedsplit#Последовательность отправки (6 пакетов). Про обычный разрез без фейков — Zapret2/desync/multisplit#Зачем нужен multisplit. Сводная таблица отличий — в разделе #Отличия от других функций сегментации.
Быстрый старт
Ниже четыре готовые строки для Zapret2/profile#Анатомия профиля Zapret 2, от самой короткой к более сложным. Флаг --payload=… работает как фильтр для всех вызовов --lua-desync, которые идут после него: они срабатывают только на данных указанного типа. Поэтому --payload пишут перед --lua-desync (подробно — в последовательность аргументов). Опция tcp_md5 в каждой строке — fooling для фейков (Zapret2/ts-and-fooling#Что такое fooling); без него функция вредна, см. нюанс #2. Fooling обязателен.
Минимально (fooling обязателен для фейков!):
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5
Функция срабатывает только на TLS ClientHello, режет его по границам имени сайта и вставляет два фейка с fooling tcp_md5.
С шаблоном для фейкового hostname:
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:host=google.com
Шаблон host=google.com подсказывает, на что должно быть похоже фейковое имя: например, k7z2a.google.com. Длина фейка по-прежнему равна длине настоящего имени (#Генерация фейкового hostname (genhost)).
С дополнительным разрезом посередине hostname:
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:midhost=midsld
Настоящее имя режется посередине домена второго уровня и уходит двумя сегментами (#midhost — разрез внутри hostname).
С disorder хвостовой части:
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:disorder_after
Данные после имени режутся ещё на две части и уходят в обратном порядке: сначала последний байт, потом остальное (#disorder_after — обратный порядок хвоста).
Откуда берутся данные
Внутри hostfakesplit данные (data) выбираются в следующем порядке приоритетов:
1. blob_or_def(desync, desync.arg.blob) — если задан blob= и он существует
2. desync.reasm_data — если есть реассемблированные данные
3. desync.dis.payload — текущий пакет (fallback)
Следствие: маркеры host, endhost, midsld и прочие работают по тем данным, которые реально выбраны. Если задан blob=myblob, маркеры разрешатся только если myblob содержит валидный TLS ClientHello или HTTP-запрос, который zapret может распознать.
[!note] Проще говоря Функция сначала спрашивает: «мне явно подсунули blob (заготовленные данные вместо настоящих, см. blob)? — беру его». Если нет: «движок собрал запрос из нескольких пакетов (reasm)? — беру собранное». Если и этого нет: «беру тело текущего пакета». Маркеры
hostиendhostищут имя в тех данных, которые выиграли этот выбор. Та же цепочка общая для большинства функций дурения: Zapret2/жизненный цикл desync-функции#Стадия 4. Выбор данных: blob → reasm → payload; на примереmultisplitона разобрана в разделе Zapret2/desync/multisplit#Откуда берутся данные для нарезки.
Основные точки разреза: host и endhost
hostfakesplit не принимает произвольный pos=. Вместо этого две основные точки разреза определяются автоматически через вызов:
local pos = resolve_range(data, desync.l7payload, "host,endhost-1", true)
Это разрешает два маркера с strict=true:
pos[1]= host — первый байт имени хоста (началоHost:значения в HTTP, начало SNI в TLS)pos[2]= endhost-1 — последний байт имени хоста (байт передendhost)
Если хотя бы один маркер не разрешается (например, payload = unknown или quic_initial), функция логирует "host range cannot be resolved" и ничего не делает.
Таким образом: hostfakesplit работает только с http_req и tls_client_hello.
Разрешить маркер — значит превратить его название в конкретный номер байта в выбранных данных. Вот как выглядят разрешённые позиции на HTTP-запросе:
GET /watch HTTP/1.1\r\nHost: www.youtube.com\r\n...
^host ^endhost
pos[1] и pos[2] — первая и вторая позиции из результата вызова: начало имени и его последний байт. Запись endhost-1 — это Zapret2/desync/multisplit#Арифметика маркеров: «на один байт левее endhost». Какие типы бывают у payload и почему unknown или quic_initial не подходят, разобрано в заметке про Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока); все маркеры — в разделе «Zapret2/desync/multisplit#Относительные маркеры».
[!note] Проще говоря Функция сама находит начало и конец имени сайта в запросе. Если запрос не похож ни на HTTP, ни на TLS ClientHello, хотя бы один из двух маркеров не найдётся, и она ничего не сделает.
Генерация фейкового hostname (genhost)
Фейковый hostname генерируется вызовом:
fakehost = genhost(pos[2] - pos[1] + 1, desync.arg.host)
- Длина фейка всегда равна длине реального hostname (
endhost - host). Это критически важно: DPI видит hostname той же длины на том же TCP sequence, и не может отличить по размеру - С шаблоном (
host=vk.com): генерируется случайный поддомен, напримерe8nzn.vk.com. Если реальный hostname короче шаблона — шаблон обрезается слева - Без шаблона: генерируется случайный домен с одним из стандартных TLD (
com,org,net,edu,gov,biz). Если длина < 7 — случайная строка без точек
[!note] Проще говоря Фейк должен занимать ровно столько же байт, сколько настоящее имя, поэтому его собирает
genhost: ей передают нужную длину и, если есть, шаблон. Шаблон — необязательная подсказка (host=vk.com): фейк становится случайным поддоменом этого домена. TLD (top-level domain, домен верхнего уровня) — последняя часть имени, вродеcomилиorg; без шаблона функция выбирает один из стандартных. Не путайте аргументhost=(шаблон фейка) с маркеромhost(начало настоящего имени в запросе): названия совпадают, но это разные вещи.
Примеры генерации (шаблон google.com, реальный hostname длиной 16):
h82aj.google.com (len=16, template="google.com")
Примеры без шаблона:
k3x.net (len=7)
b8c54a (len=6, без TLD)
u9a7bk2.org (len=11)
Дополнительные точки разреза
midhost — разрез внутри hostname
midhost задает позицию для дополнительного разреза внутри реального hostname. Реальный hostname отправляется не одним сегментом, а двумя.
midhost=<posmarker>
Маркер разрешается через resolve_pos. Типичные значения: midsld, sld, endsld, host+5, и т.д.
Ограничение: разрешенная позиция midhost должна быть строго внутри hostname:
host + 1 <= midhost <= endhost - 1
Если позиция выходит за эти границы (т.е. midhost <= pos[1] или midhost > pos[2]), разрез внутри hostname не происходит, и hostname отправляется одним сегментом. Функция логирует "midhost is not inside the host range".
Без midhost:
Один сегмент: [весь реальный hostname]
С midhost=midsld:
- сегмент 3a: от
hostдоmidhost-1— первая часть hostname; - сегмент 3b: от
midhostдоendhost-1— вторая часть hostname.
[!note] Проще говоря Обычно настоящее имя уходит одним сегментом. С
midhostоно уходит двумя, и в каждом лежит только часть имени. Значенияmidsld,sld,endsldиhost+5— те же маркеры, что описаны в разделе «Zapret2/desync/multisplit#Относительные маркеры», а+5— Zapret2/desync/multisplit#Арифметика маркеров.resolve_pos— вызов, который превращает маркер в номер байта. Если позиция выходит за пределы имени, разреза не будет, см. нюанс #5. midhost за пределами hostname молча игнорируется.
disorder_after — обратный порядок хвоста
disorder_after задает позицию для дополнительного разреза хвостовой части (всё что после hostname) и отправки двух результирующих частей в обратном порядке.
disorder_after=<posmarker>
Особый случай: если маркер — пустая строка (disorder_after без =значения, т.е. просто :disorder_after), используется маркер "-1" (последний байт payload).
disorder_after_pos = resolve_pos(data, desync.l7payload,
desync.arg.disorder_after == "" and "-1" or desync.arg.disorder_after)
Ограничение: разрешенная позиция должна быть строго больше pos[2] + 1 (т.е. после endhost):
disorder_after_pos > pos[2] + 1
Если это условие не выполняется, disorder не происходит, и хвост отправляется одним сегментом в прямом порядке.
Без disorder_after:
Один сегмент: [endhost → конец данных]
С disorder_after (порядок отправки перевернут!):
- сначала уходит последняя часть: от
disorder_afterдо конца данных; - затем средняя: от
endhostдоdisorder_after-1.
[!note] Проще говоря «Хвост» — всё, что стоит в запросе после имени.
disorder_afterрежет его ещё на две части и отправляет вторую раньше первой. Такой обратный порядок (Zapret2/desync/multidisorder#Обратный порядок отправки — суть disorder) может помешать DPI, который ждёт данные по порядку. Отрицательные маркеры вроде-1и-20отсчитываются от конца данных (Zapret2/desync/multisplit#Типы маркеров). Про пустое значение см. нюанс 6.
DPI, ожидающий данные по порядку, может не собрать хвост корректно.
Полный список аргументов
Формат вызова:
--lua-desync=hostfakesplit[:arg1[=val1][:arg2[=val2]]...]
Все val приходят в Lua как строки. Если =val не указан, значение = пустая строка "" (в Lua это truthy), поэтому флаги пишутся просто как :optional, :nodrop, :nofake1.
[!note] Проще говоря Аргументы делятся на собственные (раздел A) и стандартные (разделы B–G): стандартные понимают и другие функции дурения, общий обзор есть в desync. Каждый аргумент отделяется от имени функции двоеточием, а флаг пишется без
=значения.
A) Собственные аргументы hostfakesplit
host
- Формат:
host=<str> - Тип: строка (шаблон hostname)
- По умолчанию: не задан (случайный домен со стандартным TLD)
- Описание: Шаблон для генерации фейкового hostname через
genhost. Фейк будет выглядеть какrandom.template, например дляhost=vk.comи реального hostname длиной 12 символов:e8nzn.vk.com - Рекомендация: задавайте домен, похожий на реальный (тот же TLD, популярный сервис), чтобы фейк выглядел правдоподобно для DPI-эвристик
- Не путать: аргумент
host=— шаблон для фейка, а маркерhost— начало настоящего имени в запросе; это разные вещи с одинаковым названием - Примеры:
host=google.com— фейк видаk7z2a.google.comhost=vk.com— фейк видаe8nzn.vk.com- без
host=— фейк видаr4k2m.netилиb8c54a(случайный)
midhost
- Формат:
midhost=<posmarker> - Тип: строка (маркер позиции)
- По умолчанию: не задан (реальный hostname отправляется одним сегментом)
- Описание: Дополнительный разрез реального hostname на два сегмента. Маркер разрешается через
resolve_pos. Должен быть строго внутриhost+1..endhost-1, иначе игнорируется - Примеры:
midhost=midsld— разрез посередине SLD (самый популярный вариант)midhost=sld— разрез по началу SLDmidhost=endsld— разрез по концу SLDmidhost=host+5— 5 байт от начала hostname
nofake1
- Формат:
nofake1(флаг, без значения) - Описание: Не отправлять первый фейковый сегмент (fake1, перед реальным hostname). Полезно для экспериментов: некоторые DPI реагируют только на один из фейков
nofake2
- Формат:
nofake2(флаг, без значения) - Описание: Не отправлять второй фейковый сегмент (fake2, после реального hostname). Аналогично
nofake1, но для второго фейка - Подробнее: такие же флаги есть у Zapret2/desync/fakedsplit#nofake1..nofake4 — отключение отдельных фейков (там фейков до четырёх)
disorder_after
- Формат:
disorder_after[=<posmarker>] - Тип: строка (маркер позиции) или пустая строка
- По умолчанию: не задан (хвост отправляется одним сегментом в прямом порядке)
- Описание: Дополнительный разрез хвостовой части (после hostname) и отправка в обратном порядке. Если значение = пустая строка (
:disorder_afterбез=...), используется маркер"-1"(последний байт payload). Позиция должна быть >endhost, иначе игнорируется - Примеры:
disorder_after— разрез по-1(последний байт), disorder всего хвостаdisorder_after=-10— разрез за 10 байт до конца payloaddisorder_after=sniext+50— разрез через 50 байт после начала SNI extension data
blob
- Формат:
blob=<blobName> - Тип: имя blob-переменной
- По умолчанию: не задан
- Описание: Заменить текущий payload/reasm на указанный blob. Blob должен содержать валидный HTTP-запрос или TLS ClientHello, иначе маркеры
host/endhostне разрешатся - Подробнее: что такое blob и как его объявлять — в заметке blob; что внутри стандартных blob-ов — в разделе Zapret2/desync/fake#Стандартные blob-ы статьи про
fake - Примеры:
blob=fake_default_tls— стандартный TLS-фейкblob=my_custom_ch— предзагруженный blob с модифицированным ClientHello
optional
- Формат:
optional(флаг, без значения) - Описание: Мягкий режим:
- Если задан
blob=...и blob отсутствует —hostfakesplitничего не делает (тихий skip, без ошибок)
- Если задан
- Использование: защита от ошибок при использовании blob, которые могут отсутствовать
nodrop
- Формат:
nodrop(флаг, без значения) - Описание: После успешной отправки сегментов не выносить
VERDICT_DROP(вместо этого вернутьVERDICT_PASS). Оригинальный пакет тоже будет отправлен - Предупреждение: в боевых профилях
nodropнежелателен — оригинал создаст дублирование и может ухудшить обход - Подробнее: #9. nodrop создает дублирование; то же для
multisplit— Zapret2/desync/multisplit#4. nodrop создаёт дублирование;VERDICT_PASSиVERDICT_DROP— Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация
B) Standard direction
| Параметр | Значения | По умолчанию |
|---|---|---|
dir |
in, out, any |
out |
Фильтр по направлению пакета. hostfakesplit по умолчанию работает только с исходящими (out).
Направление говорит, в какую сторону идёт пакет: от вашего компьютера к серверу (исходящий) или обратно (входящий). При первом вызове функция отсекает себя от противоположного направления: Zapret2/жизненный цикл desync-функции#Стадия 2. Отсечение противоположного направления.
dir=out— только исходящие (от клиента к серверу)dir=in— только входящие (от сервера к клиенту)dir=any— оба направления
C) Standard payload
| Параметр | Значения | По умолчанию |
|---|---|---|
payload |
список типов через запятую | known |
Фильтр по типу payload на уровне Lua. Это дополнительный фильтр к --payload=... на уровне профиля.
Проще говоря: тип payload — название распознанного протокола (Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)). Основной отбор лучше делать флагом --payload=… на уровне профиля: он работает в быстром ядре программы, а аргумент payload= проверяется уже в Lua (Zapret2/жизненный цикл desync-функции#Стадия 5. Три гварда).
payload=known— только распознанные протоколыpayload=tls_client_hello,http_req— конкретные типыpayload=all— любой payload, включаяunknown
Важно: hostfakesplit требует payload с hostname (http_req, tls_client_hello). Даже при payload=all, если payload = unknown, маркеры host/endhost не разрешатся и функция ничего не сделает. Поэтому фильтр payload здесь влияет только на то, дойдет ли код до попытки resolve_range.
D) Standard fooling
Модификации L3/L4 заголовков. В hostfakesplit fooling и repeats применяются только к фейковым сегментам. К оригиналам — только tcp_ts_up.
Fooling — намеренная порча заголовков пакета, чтобы сервер отбросил фейк, а DPI его прочитал (Zapret2/ts-and-fooling#Что такое fooling). L3/L4 — это заголовки IP (сетевой уровень) и TCP (транспортный). Таблица ниже — полный набор опций; как убивают фейк и где ломаются основные из них, описано в разделе «Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов».
| Параметр | Описание | Пример |
|---|---|---|
ip_ttl=N |
Установить IPv4 TTL | ip_ttl=6 |
ip6_ttl=N |
Установить IPv6 Hop Limit | ip6_ttl=6 |
ip_autottl=delta,min-max |
Автоматический TTL (delta от серверного TTL) | ip_autottl=-2,40-64 |
ip6_autottl=delta,min-max |
Аналогично для IPv6 | ip6_autottl=-2,40-64 |
ip6_hopbyhop[=HEX] |
Вставить extension header hop-by-hop | ip6_hopbyhop |
ip6_hopbyhop2[=HEX] |
Второй hop-by-hop header | ip6_hopbyhop2 |
ip6_destopt[=HEX] |
Destination options header | ip6_destopt |
ip6_destopt2[=HEX] |
Второй destination options | ip6_destopt2 |
ip6_routing[=HEX] |
Routing header | ip6_routing |
ip6_ah[=HEX] |
Authentication header | ip6_ah |
tcp_seq=N |
Сместить TCP sequence (+ или -) | tcp_seq=-10000 |
tcp_ack=N |
Сместить TCP ack (+ или -) | tcp_ack=-66000 |
tcp_ts=N |
Сместить TCP timestamp | tcp_ts=-100 |
tcp_md5[=HEX] |
Добавить TCP MD5 option (16 байт) | tcp_md5 |
tcp_flags_set=LIST |
Установить TCP-флаги | tcp_flags_set=FIN,PUSH |
tcp_flags_unset=LIST |
Снять TCP-флаги | tcp_flags_unset=ACK |
tcp_ts_up |
Поднять TCP timestamp option в начало заголовка | tcp_ts_up |
tcp_nop_del |
Удалить все TCP NOP опции | tcp_nop_del |
fool=<func> |
Кастомная Lua-функция fooling | fool=my_fooler |
Критически важно: fooling обязателен для hostfakesplit. Без fooling фейковые hostname будут приняты сервером, что вызовет ошибку соединения. Минимально рекомендуемый fooling: tcp_md5 или ip_ttl=1.
[!note] Проще говоря Фейки — это ложные данные. Если сервер их не отбросит, он получит два противоречивых имени на одном месте потока. Fooling делает так, что сервер выбрасывает именно фейки, а настоящие сегменты принимает: настоящим fooling не достаётся, кроме
tcp_ts_up.
Заметка про tcp_ts_up: это единственная fooling-опция, которая применяется и к оригинальным сегментам. Она перемещает TCP timestamp option в начало заголовка, что улучшает совместимость с badseq-fooling на серверах с Linux TCP-стеком.
Подробнее про эту опцию — в заметке Zapret2/ts-and-fooling.
E) Standard ipid
IP ID — идентификатор в заголовке IPv4-пакета. Эти параметры задают, какие значения получат отправляемые сегменты.
| Параметр | Описание | По умолчанию |
|---|---|---|
ip_id=seq |
Последовательные IP ID | seq |
ip_id=rnd |
Случайные IP ID | — |
ip_id=zero |
Нулевые IP ID | — |
ip_id=none |
Не менять IP ID | — |
ip_id_conn |
Сквозная нумерация IP ID в рамках соединения | — |
ip_id применяется и к фейкам, и к оригиналам (через ipid = desync.arg в обоих opts).
F) Standard reconstruct
Реконструкция — сборка готового raw-пакета («сырого», со всеми заголовками) перед отправкой.
| Параметр | Описание |
|---|---|
badsum |
Испортить L4 (TCP) checksum при реконструкции raw-пакета |
Важно: reconstruct (включая badsum) применяется только к фейкам. У оригиналов reconstruct = {} (пустая таблица). Это логично: если испортить checksum на оригинале, сервер его отбросит.
G) Standard rawsend
Rawsend — отправка готовых сегментов в сеть самой функцией. Эти параметры управляют отправкой.
| Параметр | Описание |
|---|---|
repeats=N |
Отправить каждый сегмент N раз |
ifout=<iface> |
Интерфейс для отправки |
fwmark=N |
Firewall mark (только Linux) |
Важно: repeats применяется только к фейкам. Оригиналы используют rawsend_opts_base, в которой repeats отсутствует. Фейки используют rawsend_opts, включающую repeats. Таким образом repeats=3 означает, что каждый фейковый сегмент отправится 3 раза, а каждый реальный — 1 раз.
ifout и fwmark применяются и к фейкам, и к оригиналам.
Порядок отправки сегментов
В этом разделе показано, в каком порядке уходят сегменты и с каким seq. Реальный сегмент несёт настоящие данные запроса и доходит до сервера, фейк несёт ложное имя и должен быть отброшен сервером. Пометки opts_orig и opts_fake — два набора опций: для реальных сегментов и для фейков (#Разделение opts: оригиналы vs фейки). Seq сегмента — место его данных в потоке: seq=host значит «с того байта, где начинается имя». Настоящие части идут слева направо (кроме случая disorder_after), а второй фейк возвращается на то же место, где стоит имя.
Базовый вариант (без midhost, без disorder_after)
!wiki-anim-z2-hostfake-base.svg
С midhost (hostname делится на две части)
!wiki-anim-z2-hostfake-midhost.svg
С disorder_after (хвост в обратном порядке)
!wiki-anim-z2-hostfake-disorder.svg
Полный вариант (midhost + disorder_after + оба фейка)
Payload:
[before_host][host_p1][host_p2][after_1][after_2]
^host ^mid ^endhost ^disord
Отправка (7 сегментов):
1. [before_host] seq=0 opts_orig
2. [FAKE hostname] seq=host opts_fake (fake1)
3a. [host_p1] seq=host opts_orig
3b. [host_p2] seq=midhost opts_orig
4. [FAKE hostname] seq=host opts_fake (fake2)
5a. [after_2] seq=disorder_after opts_orig (ПЕРВЫМ)
5b. [after_1] seq=endhost opts_orig (ВТОРЫМ)
Обратите внимание на seq: у обоих фейков и у первой части настоящего имени он один и тот же (seq=host); при midhost вторая часть имени идёт со своим seq (seq=midhost).
ASCII-диаграмма: вид на уровне TCP sequence
TCP sequence (байты):
0 host midhost endhost disord end
| | | | | |
v v v v v v
[before_host][=hostname=area=][=after_host_area=]
Порядок отправки пакетов по времени (сверху вниз):
t=1 |-before-| seq=0 REAL
t=2 |====FAKE_HOST====| seq=host FAKE
t=3 |--p1--| seq=host REAL
t=4 |---p2---| seq=mid REAL
t=5 |====FAKE_HOST====| seq=host FAKE
t=6 |--after2--| seq=disord REAL
t=7 |after1--| seq=endhost REAL
На схеме время идёт сверху вниз, а по горизонтали отложено место данных в потоке. Полоса FAKE_HOST покрывает то же место, что обе части имени (p1 и p2), поэтому на месте имени оказываются два фейка и настоящее имя, разрезанное на p1 и p2.
Что видит DPI: на одном и том же TCP sequence (host) приходят разные данные — реальный hostname и фейковый. DPI должен выбрать, какой из них "правильный". Если DPI выбирает фейк — обход успешен.
Что видит сервер: фейковые пакеты отброшены благодаря fooling (инвалидный TTL, md5sig, badseq и т.д.). Реальные сегменты корректно реассемблируются TCP-стеком.
[!note] Проще говоря DPI видит на месте имени три кандидата и должен угадать настоящий; сервер фейки выбрасывает и собирает запрос из настоящих сегментов. Те же две картинки на примере
fakedsplit— в разделах Zapret2/desync/fakedsplit#Что видит DPI и Zapret2/desync/fakedsplit#Что видит сервер.
Разделение opts: оригиналы vs фейки
Перед отправкой функция собирает два набора настроек: opts_orig для настоящих сегментов («оригиналов») и opts_fake для фейков. Именно это различие позволяет испортить только фейки. Тот же приём использует Zapret2/desync/fakedsplit#Два набора опций: opts_orig vs opts_fake; у multisplit такого разделения нет, там fooling достаётся всем сегментам (Zapret2/desync/multisplit#6. Fooling применяется ко ВСЕМ сегментам).
Это ключевая особенность hostfakesplit (и аналогичных fake-функций):
opts_orig = {
rawsend = rawsend_opts_base(desync), -- ifout, fwmark. БЕЗ repeats
reconstruct = {}, -- пустой (без badsum)
ipfrag = {}, -- пустой (ipfrag не используется)
ipid = desync.arg, -- ip_id применяется
fooling = {tcp_ts_up = desync.arg.tcp_ts_up} -- ТОЛЬКО tcp_ts_up
}
opts_fake = {
rawsend = rawsend_opts(desync), -- ifout, fwmark, repeats
reconstruct = reconstruct_opts(desync), -- badsum
ipfrag = {}, -- пустой (ipfrag не используется)
ipid = desync.arg, -- ip_id применяется
fooling = desync.arg -- ВСЕ fooling-опции
}
| Что | Оригиналы | Фейки |
|---|---|---|
| fooling | Только tcp_ts_up |
Все (ip_ttl, tcp_md5, tcp_seq, ...) |
| reconstruct (badsum) | Нет | Да |
| repeats | Нет (всегда 1 раз) | Да |
| ipfrag | Нет | Нет |
| ip_id | Да | Да |
| ifout, fwmark | Да | Да |
[!note] Проще говоря Настоящие сегменты уходят почти нетронутыми: из fooling-опций им достаётся только
tcp_ts_up(аip_id,ifoutиfwmarkдействуют на оба набора). Фейки получают весь fooling, порчу контрольной суммы (badsum) и повторы (repeats). Полеipfrag(IP-фрагментация, см. Zapret2/desync/multisplit#F) Standard ipfrag) в обоих наборах пустое:ipfrag = {}.
Поведение при replay / reasm
!desync-anim-hub-reasm.svg
Многопакетный запрос nfqws2 придерживает, собирает в буфер reasm_data и перепроигрывает через desync-функции. hostfakesplit использует штатную развилку из общего скелета desync-функций (жизненный цикл desync-функции, стадия 6) без изменений: на первой части определяет по собранному буферу границы имени хоста, генерирует фейковый hostname той же длины и отправляет всю нарезку; остальные части дропает. Если отправка сорвалась, флаг «реасм уже отправлен» не ставится и оставшиеся части проходят как есть.
Специфика функции здесь важная: имя хоста ищется по всему реасму, поэтому приём работает и тогда, когда Host: или SNI физически лежали во втором пакете запроса.
[!note] Проще говоря Если запрос не влез в один пакет, движок придерживает пакеты, собирает запрос целиком (reasm — повторная сборка) и затем проигрывает придержанные части заново (replay).
hostfakesplitищет имя во всём собранном запросе, отправляет всю нарезку на первой части, а остальные части выбрасывает. Как это устроено, описано в Zapret2/жизненный цикл desync-функции#Стадия 6. Развилка перепроигрывания (replay) и в разделе Zapret2/структура desync и диссекта#Приём многопакетных пейлоадов (reasm / decrypt / replay); на примереmultisplit— в разделе Zapret2/desync/multisplit#Поведение при replay / reasm.
Место в общем скелете
Тело hostfakesplit построено по тому же шаблону, что и все остальные функции дурения: восемь стадий от отсева чужого транспорта до вердикта. Разобраны они один раз в жизненный цикл desync-функции — здесь только отклонения:
| Стадия общего скелета | Что делает hostfakesplit |
|---|---|
| 1. Отсев транспорта | только TCP; на не-TCP делает instance_cutoff (кроме связанного ICMP) |
| 2. Направление | dir=out по умолчанию, отключается от входящего |
| 3. Аргументы | обязательных нет; optional + отсутствующий blob → тихий выход |
| 4. Данные | штатная цепочка blob → reasm → payload |
| 5. Гварды | штатные #data>0, direction_check, payload_check (по умолчанию known) |
| 6. replay | штатная развилка: работа на первой части, дроп остальных |
| 7. Своя техника | позиции не задаются вручную: функция сама разрешает диапазон host,endhost-1 и строит нарезку вокруг имени хоста, подмешивая фейковые hostname — этому посвящена вся заметка |
| 8. Вердикт | VERDICT_DROP после успешной отправки, VERDICT_PASS при nodrop или сбое rawsend |
Две особенности стоит держать в голове. Первая: диапазон имени хоста разрешается в строгом режиме — если маркеры host/endhost не разрешились (payload не распознан как HTTP или TLS), функция не делает ничего. Вторая: как и у fakedsplit, опции собираются в два набора — полный fooling и repeats уходят только на фейковые hostname, реальным частям достаётся лишь tcp_ts_up и ip_id. Про ipfrag комментарий в коде (перед отправкой реальных частей имени) обещает выборочность: если IP-фрагментация задана, фрагментировать только реальные части имени хоста, а фейки и всё вне имени оставлять видимыми для DPI. Но в текущей версии кода оба набора опций передают ipfrag = {}, поэтому фактически IP-фрагментации нет ни у одной части — подробно в #3. ipfrag не задействуется.
[!note] Проще говоря Стадии скелета в таблице выше разобраны в заметке про жизненный цикл:
instance_cutoff— навсегда отключиться от потока, который не TCP (Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff); гварды — три проверки перед работой (Zapret2/жизненный цикл desync-функции#Стадия 5. Три гварда); replay — работа на первой части собранного запроса (Zapret2/жизненный цикл desync-функции#Стадия 6. Развилка перепроигрывания (replay)); вердикт — решение, что делать с оригиналом (Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация).ipfrag— IP-фрагментация, деление пакета на уровне IP (Zapret2/desync/multisplit#F) Standard ipfrag).
Псевдокод алгоритма
Тело функции целиком, включая общие для всех техник стадии — они пронумерованы так же, как в жизненный цикл desync-функции:
function hostfakesplit(ctx, desync)
-- 1. Проверка: только TCP
if not desync.dis.tcp then
if not desync.dis.icmp then instance_cutoff_shim() end
return
end
-- 2. Cutoff противоположного направления
direction_cutoff_opposite(ctx, desync)
-- 3. Проверка optional blob
if optional and blob specified and blob not exists then
DLOG("blob not found. skipped")
return
end
-- 4. Выбор данных
data = blob_or_def(blob) or reasm_data or dis.payload
-- 5. Проверки: данные не пусты, направление OK, payload OK
if #data > 0 and direction_check() and payload_check() then
if replay_first() then
-- 6. Разрешение host и endhost (strict=true)
pos = resolve_range(data, l7payload, "host,endhost-1", true)
-- pos[1] = host, pos[2] = endhost-1 (последний байт hostname)
if pos then
-- 7. Подготовка opts
opts_orig = { fooling = {tcp_ts_up only}, no repeats, no badsum }
opts_fake = { fooling = all, repeats, badsum }
-- 8. Отправка before_host (реальный)
rawsend(data[1..host-1], seq=0, opts_orig)
-- 9. Генерация фейкового hostname
fakehost = genhost(endhost - host, arg.host)
-- 10. Fake1 (если не nofake1)
if not nofake1 then
rawsend(fakehost, seq=host-1, opts_fake)
end
-- 11. Реальный hostname (с midhost или без)
if midhost and midhost внутри host+1..endhost-1 then
rawsend(data[host..midhost-1], seq=host-1, opts_orig)
rawsend(data[midhost..endhost-1], seq=midhost-1, opts_orig)
else
rawsend(data[host..endhost-1], seq=host-1, opts_orig)
end
-- 12. Fake2 (если не nofake2)
if not nofake2 then
rawsend(fakehost, seq=host-1, opts_fake)
end
-- 13. After_host (с disorder или без)
if disorder_after and disorder_after > endhost then
-- ОБРАТНЫЙ порядок:
rawsend(data[disorder_after..end], seq=disorder_after-1, opts_orig)
rawsend(data[endhost..disorder_after-1], seq=endhost-1, opts_orig)
else
rawsend(data[endhost..end], seq=endhost-1, opts_orig)
end
-- 14. Пометить как отправленное
replay_drop_set()
return nodrop and VERDICT_PASS or VERDICT_DROP
else
DLOG("host range cannot be resolved")
end
else
DLOG("not acting on further replay pieces")
end
-- 15. Drop replayed packets если ранее успешно отправлено
if replay_drop() then
return nodrop and VERDICT_PASS or VERDICT_DROP
end
end
end
[!note] Проще говоря Псевдокод читается по порядку. Если это не TCP, функция выходит. Иначе она выбирает данные, находит границы имени и отправляет: часть до имени, первый фейк, настоящее имя (одним куском или двумя, если задан
midhost), второй фейк и часть после имени (в обратном порядке, если заданdisorder_after). В конце она помечает запрос отправленным и выбрасывает оригинал (приnodrop— пропускает).
Нюансы и подводные камни
1. Работает только с http_req и tls_client_hello
hostfakesplit требует payload с hostname. Для unknown, quic_initial и любых других типов маркеры host/endhost не разрешатся, и функция ничего не сделает. Это не ошибка — просто тихий пропуск.
[!note] Проще говоря
unknown— данные, тип которых не распознан;quic_initial— первый пакет QUIC; это не TCP, а на не-TCP функция отключается (см. #Место в общем скелете). Как распознаются типы, описано в заметке про Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока).
2. Fooling обязателен
Без fooling фейковые hostname будут приняты сервером. Сервер получит два противоречивых hostname на одном TCP sequence. Результат непредсказуем: от ошибки TLS handshake до 400 Bad Request. Всегда задавайте хотя бы одну fooling-опцию (tcp_md5, ip_ttl=1, badsum, tcp_seq=-10000 и т.д.).
Каталог способов испортить фейк — в разделе «Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов».
3. ipfrag не задействуется
В отличие от multisplit и multidisorder, hostfakesplit не поддерживает IP-фрагментацию. Поля ipfrag = {} пусты и для оригиналов, и для фейков. Если вам нужна IP-фрагментация поверх hostname-разреза — придётся комбинировать с другими инструментами.
Проще говоря: IP-фрагментация — деление пакета на части уже на уровне IP, а не TCP; что это такое и какие у неё параметры, описано в разделе Zapret2/desync/multisplit#F) Standard ipfrag.
4. repeats идут только на фейки
repeats=5 означает, что каждый фейковый сегмент отправится 5 раз, а реальные — по 1 разу. Это полезно: повторение фейков увеличивает шанс, что DPI примет именно фейк, а не реальный hostname.
То же самое для fakedsplit — в нюансе Zapret2/desync/fakedsplit#6. repeats применяются ТОЛЬКО к фейкам.
5. midhost за пределами hostname молча игнорируется
Если midhost разрешается в позицию за пределами hostname (например, midhost=method для HTTP, который указывает на начало GET), разрез внутри hostname не произойдет. Функция логирует "midhost is not inside the host range" и отправляет hostname одним сегментом.
Проще говоря: маркер method указывает на начало HTTP-метода (GET), то есть далеко от имени, поэтому такой midhost разреза не даёт. Описание маркеров — в разделе «Zapret2/desync/multisplit#Относительные маркеры»; работа midhost — в разделе #midhost — разрез внутри hostname.
6. disorder_after с пустым значением = "-1"
Запись :disorder_after (без =значения) эквивалентна :disorder_after=-1. Маркер -1 разрешается в #data - 1 (предпоследний байт payload в 0-based, последний байт в 1-based). Это делает disorder хвостовой части максимально выраженным: последний байт отправляется первым, а основная часть хвоста — вторым.
Проще говоря: :disorder_after без значения означает «последний байт данных отправить первым, а всё остальное после имени — вторым».
7. Оба фейка используют один и тот же fakehost
genhost вызывается один раз, и результат (fakehost) используется и для fake1, и для fake2. Оба фейковых сегмента содержат идентичный фейковый hostname и отправляются с одинаковым TCP sequence (pos[1]-1).
Проще говоря: оба фейка одинаковые, а не два разных случайных имени. Как строится фейк, описано в разделе #Генерация фейкового hostname (genhost).
8. TCP sequence фейков = TCP sequence реального hostname
Фейки отправляются с тем же seq = pos[1] - 1, что и реальный hostname. Это означает, что DPI видит перекрывающиеся данные: на одном и том же диапазоне sequence — фейковый hostname, затем реальный, затем снова фейковый. TCP-стек сервера отбросит фейки (благодаря fooling) и примет реальный.
Проще говоря: три сегмента претендуют на одно и то же место потока — фейк, настоящее имя, фейк. Сервер оставляет настоящее. Картинка — в разделе #ASCII-диаграмма: вид на уровне TCP sequence, про seq — в разделе #Коротко и по-простому.
9. nodrop создает дублирование
С nodrop оригинальный пакет тоже будет отправлен. Сервер получит hostname дважды (из нарезанных сегментов и из оригинала). Используйте nodrop только для отладки.
Про решение «выбросить или пропустить» — Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация; то же для multisplit — Zapret2/desync/multisplit#4. nodrop создаёт дублирование.
10. Порядок инстансов важен
Если перед hostfakesplit стоит pktmod с fooling — fooling применится к диссекту, и hostfakesplit порежет модифицированный пакет. Если после hostfakesplit стоит другой инстанс — он увидит VERDICT_DROP и не получит оригинальный payload.
Уточнение из разбора вердиктов: VERDICT_DROP не обрывает цепочку. Движок проходит все инстансы профиля и складывает их вердикты (VERDICT_DROP перебивает VERDICT_PASS и VERDICT_MODIFY), так что инстансы после hostfakesplit вызываются как обычно, а дроп означает лишь то, что оригинальный пакет в итоге не уйдёт (Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация; то же — в Zapret2/desync/multisplit#7. Порядок инстансов важен и в Zapret2/desync/fakedsplit#10. Порядок инстансов важен).
Проще говоря: инстанс — одна запись --lua-desync=… в профиле, а диссект — пакет, заранее разобранный движком на поля (структура desync и диссекта). pktmod — функция, которая модифицирует диссект. Порядок инстансов задаёт порядок флагов в командной строке (последовательность аргументов); как складываются вердикты нескольких инстансов — Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация.
Отличия от других функций сегментации
Пять функций ниже режут данные на TCP-сегменты, но различаются числом разрезов, наличием фейков, порядком отправки и тем, как устроены seqovl (скрытый фейк внутри сегмента, Zapret2/desync/multisplit#seqovl — скрытый фейк внутри сегмента) и ipfrag (IP-фрагментация). Подробности о каждой — в разделах Zapret2/desync/multisplit#Отличия от других функций сегментации, Zapret2/desync/fakedsplit#Отличия от других функций сегментации и Zapret2/desync/fakeddisorder#Отличия от fakedsplit, multisplit и multidisorder.
| Аспект | hostfakesplit |
fakedsplit |
fakeddisorder |
multisplit |
multidisorder |
|---|---|---|---|---|---|
| Точки разреза | Автоматические (host/endhost) + midhost, disorder_after | Одна произвольная | Одна произвольная | Список произвольных | Список произвольных |
| Привязка к hostname | Да (только http_req, tls_client_hello) | Нет | Нет | Нет | Нет |
| Фейковые сегменты | 2 (фейковый hostname) | До 4 (паттерн) | До 4 (паттерн) | Нет | Нет |
| Содержимое фейков | Осмысленный hostname (genhost) | Паттерн (0x00 и т.п.) | Паттерн (0x00 и т.п.) | — | — |
| disorder хвоста | Опционально (disorder_after) | Нет | Весь порядок обратный | Нет | Весь порядок обратный |
| seqovl | Нет | Да (число) | Да (маркер) | Да (число) | Да (маркер) |
| ipfrag | Нет | Нет | Нет | Да | Да |
| Fooling к | Только фейкам | Только фейкам | Только фейкам | Всем сегментам | Всем сегментам |
| repeats к | Только фейкам | Только фейкам | Только фейкам | Всем сегментам | Всем сегментам |
[!note] Проще говоря
hostfakesplit— единственная из пяти функций, которая сама знает, где в запросе имя сайта, и единственная, чьи фейки — осмысленное имя, а не заливка паттерном. Обратный порядок у неё необязательный и касается только данных после имени.
Миграция с nfqws1
Этот раздел для тех, кто переносит настройки из первой версии zapret (программа nfqws) во вторую (nfqws2). В первой версии техника выбиралась ключом --dpi-desync=…, а её параметры — отдельными ключами --dpi-desync-…; во второй всё это записывается аргументами одного вызова --lua-desync=hostfakesplit:….
Соответствие параметров
| nfqws1 | nfqws2 |
|---|---|
--dpi-desync=hostfakesplit |
--lua-desync=hostfakesplit |
--dpi-desync-fooling=md5sig |
:tcp_md5 |
--dpi-desync-fooling=badseq |
:tcp_seq=-10000 |
--dpi-desync-fooling=ttl + --dpi-desync-ttl=N |
:ip_ttl=N |
--dpi-desync-fake-unknown=hex |
:host=<template> (не прямой аналог; genhost вместо hex) |
| нет аналога | :midhost=midsld (новая возможность nfqws2) |
| нет аналога | :disorder_after (новая возможность nfqws2) |
| нет аналога | :nofake1, :nofake2 (новая возможность nfqws2) |
Проще говоря: режимы fooling из nfqws1 становятся аргументами функции (md5sig превращается в :tcp_md5), а midhost, disorder_after, nofake1 и nofake2 в первой версии просто не существовали.
Пример полной миграции
# nfqws1:
nfqws --dpi-desync=hostfakesplit \
--dpi-desync-fooling=md5sig
# nfqws2 (эквивалент):
nfqws2 \
--payload=tls_client_hello,http_req \
--lua-desync=hostfakesplit:tcp_md5
# nfqws1: hostfakesplit + fake
nfqws --dpi-desync=fake,hostfakesplit \
--dpi-desync-fooling=md5sig \
--dpi-desync-fake-tls-mod=rnd,rndsni,dupsid
# nfqws2 (два инстанса):
nfqws2 \
--payload=tls_client_hello \
--lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \
--payload=http_req \
--lua-desync=fake:blob=fake_default_http:tcp_md5 \
--payload=tls_client_hello,http_req \
--lua-desync=hostfakesplit:tcp_md5
В nfqws1 фейк и разрез задавались одним ключом --dpi-desync=fake,hostfakesplit, в nfqws2 каждая техника становится отдельным инстансом --lua-desync.
# nfqws1: hostfakesplit + syndata
nfqws --dpi-desync=syndata,hostfakesplit \
--dpi-desync-fooling=md5sig --wssize 1:6
# nfqws2:
nfqws2 \
--lua-desync=wssize:wsize=1:scale=6 \
--lua-desync=syndata \
--payload=tls_client_hello,http_req \
--lua-desync=hostfakesplit:tcp_md5
Третий пример переносит связку с Zapret2/desync/syndata#Зачем нужен syndata и ключом --wssize; каждый ключ nfqws1 тоже становится своим инстансом (о wssize см. desync).
Практические примеры
Ниже шестнадцать готовых сочетаний, от минимального до боевых. В большинстве сначала стоит фильтр --payload=… («на каких данных срабатывать»), потом --lua-desync=hostfakesplit:…; fooling на фейках обязателен (Zapret2/ts-and-fooling#Что такое fooling).
1. Минимальный (TLS + md5sig fooling)
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5
Разрезает TLS ClientHello по границам SNI, вставляет 2 фейка с md5sig fooling.
2. HTTP + TTL fooling
--payload=http_req --lua-desync=hostfakesplit:ip_ttl=4
Разрезает HTTP-запрос по границам Host:, фейки с TTL=4 (не доживут до сервера).
TTL — ограничение числа промежуточных узлов, которые может пройти пакет; фейк с малым TTL «умирает» по дороге и до сервера не доходит (Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов).
3. С шаблоном фейкового hostname
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:host=google.com
Фейковый SNI будет выглядеть как r7k2q.google.com (длина = длина реального hostname).
4. С midhost (разрез hostname пополам)
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:midhost=midsld
Реальный hostname разрезается посередине SLD. Вместо 3 реальных сегментов — 4. DPI получает ещё более фрагментированную картину.
5. С disorder хвоста (пустое значение = "-1")
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:disorder_after
Хвостовая часть (после hostname) отправляется в обратном порядке: последний байт первым, основная часть — вторым.
6. С disorder_after по конкретной позиции
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:disorder_after=-20
Хвост разрезается за 20 байт до конца payload. Последние 20 байт идут первыми, остальная часть хвоста — вторым.
7. Полный набор: midhost + disorder_after + шаблон
--payload=tls_client_hello \
--lua-desync=hostfakesplit:tcp_md5:host=vk.com:midhost=midsld:disorder_after
Максимальная фрагментация: hostname разрезан пополам, хвост перевернут, фейки с правдоподобным *.vk.com.
8. Только один фейк (nofake2)
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:nofake2
Отправляется только fake1 (перед реальным hostname). Полезно если DPI реагирует именно на первый hostname в потоке.
9. Только второй фейк (nofake1)
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:nofake1
Отправляется только fake2 (после реального hostname). Для DPI, которые берут последний hostname.
10. С badsum на фейках
--payload=tls_client_hello --lua-desync=hostfakesplit:badsum
Фейковые сегменты получают невалидный TCP checksum. Сервер отбросит их, DPI (который часто не проверяет checksum) примет.
11. Многократные фейки (repeats)
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:repeats=5
Каждый фейковый сегмент отправляется 5 раз. Реальные — по 1 разу. Итого: 1 before_host + 5 fake1 + 1 hostname + 5 fake2 + 1 after_host = 13 пакетов.
12. Комбинация: fake → hostfakesplit (два инстанса)
--payload=tls_client_hello \
--lua-desync=fake:blob=fake_default_tls:tcp_md5:repeats=3:tls_mod=rnd,rndsni,dupsid \
--lua-desync=hostfakesplit:tcp_md5:midhost=midsld:host=google.com
Сначала 3 фейковых TLS ClientHello (целиком), затем реальный — нарезанный по hostname с дополнительным разрезом по midsld и фейковым hostname *.google.com.
Проще говоря: инстанс Zapret2/desync/fake#Зачем нужен fake отправляет отдельный фейковый ClientHello целиком (заготовленный blob fake_default_tls, см. Zapret2/desync/fake#Стандартные blob-ы), а tls_mod=rnd,rndsni,dupsid меняет в нём случайные поля, SNI и Session ID (Zapret2/desync/fake#Опции tls_mod).
13. Боевой пример для YouTube (TLS)
--filter-tcp=443 --hostlist=youtube.txt \
--lua-desync=fake:blob=fake_default_tls:repeats=5:tcp_md5 \
--lua-desync=hostfakesplit:tcp_md5:midhost=midsld:host=google.com
5 фейковых TLS ClientHello + реальный с hostname-фейками и разрезом по midsld.
Здесь --filter-tcp=443 и --hostlist=youtube.txt — фильтры профиля (порт и список доменов, filter и hostlist). Похожий боевой пример с multisplit — Zapret2/desync/fake#17. Боевая связка: fake + multisplit для YouTube.
14. HTTP + TLS в одном конфиге
--filter-tcp=80 --hostlist=blocked.txt \
--lua-desync=hostfakesplit:ip_ttl=3 \
--filter-tcp=443 --hostlist=blocked.txt \
--lua-desync=hostfakesplit:tcp_md5:midhost=midsld
HTTP с TTL-fooling, TLS с md5sig и разрезом hostname.
--filter-tcp=80 и --filter-tcp=443 — фильтры по порту TCP: 80 обычно занят HTTP, 443 — HTTPS (filter). Между двумя блоками в примере нет --new, которым в Zapret 2 разделяют профили (filter); как движок разберёт два --filter-tcp подряд без разделителя, статья не уточняет. Если нужны два отдельных профиля, между блоками ставят --new.
15. С blob (отправка произвольного payload)
--blob=my_ch:@custom_clienthello.bin \
--payload=tls_client_hello \
--lua-desync=hostfakesplit:tcp_md5:blob=my_ch
Вместо реального ClientHello отправляется кастомный blob (при условии, что в нём есть валидный SNI).
--blob=my_ch:@custom_clienthello.bin объявляет blob из файла; подробности — в заметке blob.
16. Защита от отсутствующего blob
--payload=tls_client_hello \
--lua-desync=hostfakesplit:tcp_md5:blob=maybe_missing:optional
Если blob не существует — тихий пропуск, без ошибок.
📚 См. также
- desync — обзор всех функций
--lua-desyncи общий контракт: кто вызывает функцию, что ей передаёт и как складываются вердикты - жизненный цикл desync-функции — восемь стадий, общих для всех техник дурения: отсев транспорта, выбор данных, replay, вердикты
- структура desync и диссекта — устройство таблицы
desyncи диссекта пакета - fakedsplit — разрез по одной произвольной позиции с фейковыми сегментами, заполненными паттерном; ближайший родственник
hostfakesplit - fakeddisorder — фейки и обратный порядок отправки
- multisplit — обычная последовательная сегментация без фейков; там же разобраны маркеры позиций и
seqovl - multidisorder — тот же разрез в обратном порядке
- tcpseg — отправка диапазона данных между двумя маркерами
- oob — сегментация с urgent-байтом
- fake — отдельный фейковый пакет, после которого чаще всего ставят
hostfakesplit - blob — как объявлять и передавать данные для аргумента
blob - payload — типы payload, от которых зависит, найдутся ли маркеры
hostиendhost - ts-and-fooling — способы испортить фейк, чтобы сервер его отбросил
- verify-strategy — как честно проверить, работает ли подобранная комбинация
- последовательность аргументов — как выстраивается цепочка инстансов в профиле
- profile — где
--lua-desyncживёт среди остальных настроек - DPI/DPI — как устроена инспекция трафика, против которой работает
hostfakesplit
Источники:
lua/zapret-antidpi.lua:695-800,lua/zapret-lib.lua:396-401(rawsend_opts/rawsend_opts_base),lua/zapret-lib.lua:1287-1302(genhost),docs/manual.md:4208-4246из репозитория zapret2.
[!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: исходник этой заметки · скачать весь репозиторий одним zip-архивом.