hostfakesplit — TCP-сегментация с фейковым hostname (zapret2 / nfqws2)
Коротко и по-простому
Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке Host: запроса. ТСПУ (Технические средства противодействия угрозам — оборудование 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; общий список маркеров — в разделе Относительные маркеры статьи про multisplit, а как hostfakesplit их использует — в разделе Основные точки разреза.
HTTP-запросы и TLS-соединения передаются по протоколу TCP, а TCP отправляет данные кусками — сегментами. У каждого байта в соединении есть порядковый номер (sequence number, коротко seq), и сервер раскладывает принятые куски в свой буфер строго по этим номерам. Поэтому ему безразлично, пришли данные одним пакетом или несколькими и в каком порядке куски доехали: исходный поток он всё равно соберёт. Почему это гарантирует именно TCP, разобрано в нюансе «Работает только с TCP» статьи про multisplit.
Ещё одно понятие: фейк. Это сегмент с ложными данными, здесь — с подложным именем сайта. Чтобы сервер не принял фейк за настоящие данные и не сломал соединение, фейк «портят» так, чтобы сервер его отбросил, а DPI мог прочитать: такая порча называется fooling (Что такое fooling). В команде из быстрого старта это tcp_md5: к сегменту добавляется TCP-опция с подписью MD5 (16 байт, по умолчанию случайные), и Linux-сервер обычно такой сегмент отбрасывает. Способ не универсальный, другие собраны в каталоге способов. Без 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.
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5Строка --payload=tls_client_hello значит «срабатывать только на TLS ClientHello», а --lua-desync=hostfakesplit:tcp_md5 включает функцию с fooling tcp_md5. Обе строки пишут в профиль — набор правил Zapret 2 для определённого трафика. Остальные готовые команды собраны в разделах Быстрый старт и Практические примеры. К этой схеме можно добавить несколько опций. Аргумент midhost= режет настоящее имя ещё на две части, например посередине домена второго уровня (midhost=midsld; у www.youtube.com домен второго уровня — youtube): вместо одного настоящего сегмента с именем уйдут два (подробнее). Аргумент disorder_after режет данные после имени ещё на две части и отправляет их в обратном порядке (подробнее). Флаги nofake1 и nofake2 отключают первый или второй фейк, а repeats=N отправляет каждый фейк N раз (настоящие сегменты — по одному разу). Все аргументы разобраны в разделе Полный список аргументов.
Чем hostfakesplit отличается от соседей. multisplit только режет настоящие данные и фейков не шлёт; места разреза там задаёт аргумент pos, а у hostfakesplit pos нет: границы всегда стоят по имени. fakedsplit режет по одной произвольной позиции и заливает фейки паттерном (нулями и т.п.), а hostfakesplit знает, где стоит имя, и подмешивает осмысленное фейковое имя той же длины. fakeddisorder и multidisorder отправляют куски в обратном порядке; у hostfakesplit обратный порядок необязательный и касается только данных после имени. Все отличия сведены в таблицу в разделе Отличия от других функций сегментации.
Когда техника не сработает. Функция понимает только HTTP-запрос и TLS ClientHello: для QUIC (это не TCP) и других протоколов границ имени не найти, и она тихо ничего не делает. Приёма seqovl (скрытого фейка внутри сегмента, разбор) у неё нет. Обход удаётся, только если DPI выберет из трёх кандидатов фейк, а не настоящее имя, поэтому рабочую комбинацию для своего провайдера подбирают опытным путём и проверяют по чек-листу честной проверки. В боевых примерах hostfakesplit ставят после fake — отдельного пакета-обманки с ложным ClientHello; см. примеры 12 и 13.
Оглавление
- Коротко и по-простому
- Справка: где функция в коде и родственники
- Зачем нужен 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, чтобы оригинальный пакет не ушёл.
Проще говоря
Payload — полезные данные пакета без сетевых заголовков: сам HTTP-запрос или сам ClientHello.
http_reqиtls_client_hello— типы payload, то есть названия распознанных протоколов. Hostname — имя сайта, то самое, которое ищет DPI.genhost— функция, которая генерирует фейковое имя нужной длины (подробно).VERDICT_DROP— решение «перехваченный пакет не отправлять»; как движок складывает такие решения от разных функций, описано в стадии 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-механизм.
Проще говоря
DPI ищет имя сайта в пакетах. Самый простой DPI разбирает каждый пакет отдельно и не склеивает их обратно в поток (склейка называется реассемблированием, от английского reassembly), поэтому его сбивает обычный разрез. Продвинутый DPI поток склеивает и может собрать имя из нескольких сегментов. Против него
hostfakesplitподмешивает к настоящим сегментам фейковые с ложным именем: на одном месте потока лежат разные имена, и DPI приходится выбирать. Fooling здесь — способ испортить фейки так, чтобы сервер их выбросил; подробно — Что такое fooling, короткие описания способов — каталог способов.
Ключевое отличие от fakedsplit: hostfakesplit автоматически знает где hostname, генерирует осмысленный фейковый hostname той же длины и имеет опцию midhost для дополнительного разреза внутри hostname. fakedsplit работает по произвольной позиции и заливает фейк паттерном (0x00 и т.п.).
Как отправляет пакеты fakedsplit и что при этом видит DPI, показано в разделе Последовательность отправки (6 пакетов). Про обычный разрез без фейков — Зачем нужен multisplit. Сводная таблица отличий — в разделе Отличия от других функций сегментации.
Быстрый старт
Ниже четыре готовые строки для профиля Zapret 2, от самой короткой к более сложным. Флаг --payload=… работает как фильтр для всех вызовов --lua-desync, которые идут после него: они срабатывают только на данных указанного типа. Поэтому --payload пишут перед --lua-desync (подробно — в последовательность аргументов). Опция tcp_md5 в каждой строке — fooling для фейков (Что такое fooling); без него функция вредна, см. нюанс «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).
С дополнительным разрезом посередине hostname:
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:midhost=midsldНастоящее имя режется посередине домена второго уровня и уходит двумя сегментами (midhost).
С 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 может распознать.
Проще говоря
Функция сначала спрашивает: «мне явно подсунули blob (заготовленные данные вместо настоящих, см. blob)? — беру его». Если нет: «движок собрал запрос из нескольких пакетов (reasm)? — беру собранное». Если и этого нет: «беру тело текущего пакета». Маркеры
hostиendhostищут имя в тех данных, которые выиграли этот выбор. Та же цепочка общая для большинства функций дурения: стадия 4 жизненного цикла; на примере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 — это арифметика маркеров: «на один байт левее endhost». Какие типы бывают у payload и почему unknown или quic_initial не подходят, разобрано в заметке про тип payload; все маркеры — в разделе «относительные маркеры».
Проще говоря
Функция сама находит начало и конец имени сайта в запросе. Если запрос не похож ни на 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 — случайная строка без точек
Проще говоря
Фейк должен занимать ровно столько же байт, сколько настоящее имя, поэтому его собирает
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)
Проще говоря
Обычно настоящее имя уходит одним сегментом. С
midhostоно уходит двумя, и в каждом лежит только часть имени. Значенияmidsld,sld,endsldиhost+5— те же маркеры, что описаны в разделе «относительные маркеры», а+5— арифметика маркеров.resolve_pos— вызов, который превращает маркер в номер байта. Если позиция выходит за пределы имени, разреза не будет, см. нюанс 5.
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] (средняя часть, отправлена ВТОРОЙ)
Проще говоря
«Хвост» — всё, что стоит в запросе после имени.
disorder_afterрежет его ещё на две части и отправляет вторую раньше первой. Такой обратный порядок (Обратный порядок отправки — суть disorder) может помешать DPI, который ждёт данные по порядку. Отрицательные маркеры вроде-1и-20отсчитываются от конца данных (Типы маркеров). Про пустое значение см. нюанс 6.
DPI, ожидающий данные по порядку, может не собрать хвост корректно.
Полный список аргументов
Формат вызова:
--lua-desync=hostfakesplit[:arg1[=val1][:arg2[=val2]]...]
Все val приходят в Lua как строки. Если =val не указан, значение = пустая строка "" (в Lua это truthy), поэтому флаги пишутся просто как :optional, :nodrop, :nofake1.
Проще говоря
Аргументы делятся на собственные (раздел 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, но для второго фейка - Подробнее: такие же флаги есть у fakedsplit (там фейков до четырёх)
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-ов — в разделе Стандартные 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; то же для
multisplit— нюанс 4;VERDICT_PASSиVERDICT_DROP— стадия 8 жизненного цикла
B) Standard direction
| Параметр | Значения | По умолчанию |
|---|---|---|
dir | in, out, any | out |
Фильтр по направлению пакета. hostfakesplit по умолчанию работает только с исходящими (out).
Направление говорит, в какую сторону идёт пакет: от вашего компьютера к серверу (исходящий) или обратно (входящий). При первом вызове функция отсекает себя от противоположного направления: стадия 2 жизненного цикла.
dir=out— только исходящие (от клиента к серверу)dir=in— только входящие (от сервера к клиенту)dir=any— оба направления
C) Standard payload
| Параметр | Значения | По умолчанию |
|---|---|---|
payload | список типов через запятую | known |
Фильтр по типу payload на уровне Lua. Это дополнительный фильтр к --payload=... на уровне профиля.
Проще говоря: тип payload — название распознанного протокола (тип payload). Основной отбор лучше делать флагом --payload=… на уровне профиля: он работает в быстром ядре программы, а аргумент payload= проверяется уже в Lua (стадия 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 его прочитал (Что такое fooling). L3/L4 — это заголовки IP (сетевой уровень) и TCP (транспортный). Таблица ниже — полный набор опций; как убивают фейк и где ломаются основные из них, описано в разделе «каталог способов».
| Параметр | Описание | Пример |
|---|---|---|
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.
Проще говоря
Фейки — это ложные данные. Если сервер их не отбросит, он получит два противоречивых имени на одном месте потока. Fooling делает так, что сервер выбрасывает именно фейки, а настоящие сегменты принимает: настоящим fooling не достаётся, кроме
tcp_ts_up.
Заметка про tcp_ts_up: это единственная fooling-опция, которая применяется и к оригинальным сегментам. Она перемещает TCP timestamp option в начало заголовка, что улучшает совместимость с badseq-fooling на серверах с Linux TCP-стеком.
Подробнее про эту опцию — в заметке ts и 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). Seq сегмента — место его данных в потоке: seq=host значит «с того байта, где начинается имя». Настоящие части идут слева направо (кроме случая disorder_after), а второй фейк возвращается на то же место, где стоит имя.
Базовый вариант (без midhost, без disorder_after)
Payload: [....before_host....][..hostname..][....after_host....]
^host ^endhost
Отправка (5 сегментов, 3 реальных + 2 фейка):
1. [before_host] — реальный, opts_orig
2. [FAKE hostname] — фейк (fake1), opts_fake
3. [hostname] — реальный, opts_orig
4. [FAKE hostname] — фейк (fake2), opts_fake
5. [after_host] — реальный, opts_orig
С midhost (hostname делится на две части)
Payload: [....before_host....][..host_part1..][..host_part2..][....after_host....]
^host ^midhost ^endhost
Отправка (6 сегментов, 4 реальных + 2 фейка):
1. [before_host] — реальный, opts_orig
2. [FAKE hostname] — фейк (fake1), opts_fake
3a.[host_part1] — реальный (host → midhost-1), opts_orig
3b.[host_part2] — реальный (midhost → endhost-1), opts_orig
4. [FAKE hostname] — фейк (fake2), opts_fake
5. [after_host] — реальный, opts_orig
С disorder_after (хвост в обратном порядке)
Payload: [....before_host....][..hostname..][..after_1..][..after_2..]
^host ^endhost ^disorder_after
Отправка (6 сегментов, 4 реальных + 2 фейка):
1. [before_host] — реальный, opts_orig
2. [FAKE hostname] — фейк (fake1), opts_fake
3. [hostname] — реальный, opts_orig
4. [FAKE hostname] — фейк (fake2), opts_fake
5a.[after_2] — реальный (disorder_after → конец), ПЕРВЫМ
5b.[after_1] — реальный (endhost → disorder_after-1), ВТОРЫМ
Полный вариант (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-стеком.
Проще говоря
DPI видит на месте имени три кандидата и должен угадать настоящий; сервер фейки выбрасывает и собирает запрос из настоящих сегментов. Те же две картинки на примере
fakedsplit— в разделах Что видит DPI и Что видит сервер.
Разделение opts: оригиналы vs фейки
Перед отправкой функция собирает два набора настроек: opts_orig для настоящих сегментов («оригиналов») и opts_fake для фейков. Именно это различие позволяет испортить только фейки. Тот же приём использует fakedsplit; у multisplit такого разделения нет, там fooling достаётся всем сегментам (нюанс 6).
Это ключевая особенность 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 | Да | Да |
Проще говоря
Настоящие сегменты уходят почти нетронутыми: из fooling-опций им достаётся только
tcp_ts_up(аip_id,ifoutиfwmarkдействуют на оба набора). Фейки получают весь fooling, порчу контрольной суммы (badsum) и повторы (repeats). Полеipfrag(IP-фрагментация, см. F) Standard ipfrag) в обоих наборах пустое:ipfrag = {}.
Поведение при replay / reasm
Многопакетный запрос nfqws2 придерживает, собирает в буфер reasm_data и перепроигрывает через desync-функции. hostfakesplit использует штатную развилку из общего скелета desync-функций (жизненный цикл desync-функции, стадия 6) без изменений: на первой части определяет по собранному буферу границы имени хоста, генерирует фейковый hostname той же длины и отправляет всю нарезку; остальные части дропает. Если отправка сорвалась, флаг «реасм уже отправлен» не ставится и оставшиеся части проходят как есть.
Специфика функции здесь важная: имя хоста ищется по всему реасму, поэтому приём работает и тогда, когда Host: или SNI физически лежали во втором пакете запроса.
Проще говоря
Если запрос не влез в один пакет, движок придерживает пакеты, собирает запрос целиком (reasm — повторная сборка) и затем проигрывает придержанные части заново (replay).
hostfakesplitищет имя во всём собранном запросе, отправляет всю нарезку на первой части, а остальные части выбрасывает. Как это устроено, описано в стадии 6 жизненного цикла и в разделе Приём многопакетных пейлоадов; на примереmultisplit— в разделе 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.
Проще говоря
Стадии скелета в таблице выше разобраны в заметке про жизненный цикл:
instance_cutoff— навсегда отключиться от потока, который не TCP (стадия 1); гварды — три проверки перед работой (стадия 5); replay — работа на первой части собранного запроса (стадия 6); вердикт — решение, что делать с оригиналом (стадия 8).ipfrag— IP-фрагментация, деление пакета на уровне IP (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Проще говоря
Псевдокод читается по порядку. Если это не TCP, функция выходит. Иначе она выбирает данные, находит границы имени и отправляет: часть до имени, первый фейк, настоящее имя (одним куском или двумя, если задан
midhost), второй фейк и часть после имени (в обратном порядке, если заданdisorder_after). В конце она помечает запрос отправленным и выбрасывает оригинал (приnodrop— пропускает).
Нюансы и подводные камни
1. Работает только с http_req и tls_client_hello
hostfakesplit требует payload с hostname. Для unknown, quic_initial и любых других типов маркеры host/endhost не разрешатся, и функция ничего не сделает. Это не ошибка — просто тихий пропуск.
Проще говоря
unknown— данные, тип которых не распознан;quic_initial— первый пакет QUIC; это не TCP, а на не-TCP функция отключается (см. Место в общем скелете). Как распознаются типы, описано в заметке про тип payload.
2. Fooling обязателен
Без fooling фейковые hostname будут приняты сервером. Сервер получит два противоречивых hostname на одном TCP sequence. Результат непредсказуем: от ошибки TLS handshake до 400 Bad Request. Всегда задавайте хотя бы одну fooling-опцию (tcp_md5, ip_ttl=1, badsum, tcp_seq=-10000 и т.д.).
Каталог способов испортить фейк — в разделе «каталог способов».
3. ipfrag не задействуется
В отличие от multisplit и multidisorder, hostfakesplit не поддерживает IP-фрагментацию. Поля ipfrag = {} пусты и для оригиналов, и для фейков. Если вам нужна IP-фрагментация поверх hostname-разреза — придётся комбинировать с другими инструментами.
Проще говоря: IP-фрагментация — деление пакета на части уже на уровне IP, а не TCP; что это такое и какие у неё параметры, описано в разделе F) Standard ipfrag.
4. repeats идут только на фейки
repeats=5 означает, что каждый фейковый сегмент отправится 5 раз, а реальные — по 1 разу. Это полезно: повторение фейков увеличивает шанс, что DPI примет именно фейк, а не реальный hostname.
То же самое для fakedsplit — в нюансе repeats применяются ТОЛЬКО к фейкам.
5. midhost за пределами hostname молча игнорируется
Если midhost разрешается в позицию за пределами hostname (например, midhost=method для HTTP, который указывает на начало GET), разрез внутри hostname не произойдет. Функция логирует “midhost is not inside the host range” и отправляет hostname одним сегментом.
Проще говоря: маркер method указывает на начало HTTP-метода (GET), то есть далеко от имени, поэтому такой midhost разреза не даёт. Описание маркеров — в разделе «относительные маркеры»; работа midhost — в разделе midhost.
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.
8. TCP sequence фейков = TCP sequence реального hostname
Фейки отправляются с тем же seq = pos[1] - 1, что и реальный hostname. Это означает, что DPI видит перекрывающиеся данные: на одном и том же диапазоне sequence — фейковый hostname, затем реальный, затем снова фейковый. TCP-стек сервера отбросит фейки (благодаря fooling) и примет реальный.
Проще говоря: три сегмента претендуют на одно и то же место потока — фейк, настоящее имя, фейк. Сервер оставляет настоящее. Картинка — в разделе ASCII-диаграмма, про seq — в разделе Коротко и по-простому.
9. nodrop создает дублирование
С nodrop оригинальный пакет тоже будет отправлен. Сервер получит hostname дважды (из нарезанных сегментов и из оригинала). Используйте nodrop только для отладки.
Про решение «выбросить или пропустить» — стадия 8 жизненного цикла; то же для multisplit — нюанс 4.
10. Порядок инстансов важен
Если перед hostfakesplit стоит pktmod с fooling — fooling применится к диссекту, и hostfakesplit порежет модифицированный пакет. Если после hostfakesplit стоит другой инстанс — он увидит VERDICT_DROP и не получит оригинальный payload.
Уточнение из разбора вердиктов: VERDICT_DROP не обрывает цепочку. Движок проходит все инстансы профиля и складывает их вердикты (VERDICT_DROP перебивает VERDICT_PASS и VERDICT_MODIFY), так что инстансы после hostfakesplit вызываются как обычно, а дроп означает лишь то, что оригинальный пакет в итоге не уйдёт (стадия 8 жизненного цикла; то же — в нюансе о порядке инстансов в multisplit и в fakedsplit).
Проще говоря: инстанс — одна запись --lua-desync=… в профиле, а диссект — пакет, заранее разобранный движком на поля (структура desync и диссекта). pktmod — функция, которая модифицирует диссект. Порядок инстансов задаёт порядок флагов в командной строке (последовательность аргументов); как складываются вердикты нескольких инстансов — стадия 8 жизненного цикла.
Отличия от других функций сегментации
Пять функций ниже режут данные на TCP-сегменты, но различаются числом разрезов, наличием фейков, порядком отправки и тем, как устроены seqovl (скрытый фейк внутри сегмента, разбор) и ipfrag (IP-фрагментация). Подробности о каждой — в разделах multisplit, fakedsplit и fakeddisorder.
| Аспект | 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 к | Только фейкам | Только фейкам | Только фейкам | Всем сегментам | Всем сегментам |
Проще говоря
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Третий пример переносит связку с syndata и ключом --wssize; каждый ключ nfqws1 тоже становится своим инстансом (о wssize см. desync).
Практические примеры
Ниже шестнадцать готовых сочетаний, от минимального до боевых. В большинстве сначала стоит фильтр --payload=… («на каких данных срабатывать»), потом --lua-desync=hostfakesplit:…; 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 «умирает» по дороге и до сервера не доходит (каталог способов).
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.
Проще говоря: инстанс fake отправляет отдельный фейковый ClientHello целиком (заготовленный blob fake_default_tls, см. Стандартные blob-ы), а tls_mod=rnd,rndsni,dupsid меняет в нём случайные поля, SNI и Session ID (Опции 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.com5 фейковых TLS ClientHello + реальный с hostname-фейками и разрезом по midsld.
Здесь --filter-tcp=443 и --hostlist=youtube.txt — фильтры профиля (порт и список доменов, filter и hostlist). Похожий боевой пример с multisplit — в статье про fake.
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=midsldHTTP с 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 и ТСПУ — как устроена инспекция трафика, против которой работает
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.
🤖 Эти статьи открыты — можно обучать на них ИИ
При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: исходник этой заметки · скачать весь репозиторий одним zip-архивом.