fakeddisorder — TCP-сегментация с фейками в обратном порядке (zapret2 / nfqws2)

Коротко и по-простому

Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке Host: запроса. ТСПУ (Технические средства противодействия угрозам — оборудование DPI, то есть глубокой инспекции трафика, которое стоит у провайдера) ищет это имя в первом пакете соединения и по нему решает, что делать с соединением дальше.

HTTP-запросы и TLS-соединения передаются по протоколу TCP, а TCP отправляет данные кусками — сегментами. У каждого байта в соединении есть порядковый номер (sequence number, коротко seq), и сервер раскладывает принятые куски в свой буфер строго по этим номерам. Поэтому ему безразлично, в каком порядке куски доехали: если конец запроса пришёл раньше начала, сервер подержит его в буфере, дождётся начала и соберёт исходный поток. Почему это гарантирует именно TCP, разобрано в нюансе «Работает только с TCP» из статьи про multisplit.

fakeddisorder использует эту особенность TCP дважды, и понять его проще, если сначала разобрать два приёма по отдельности. Первый приём — фейки. Фейк — это пакет-обманка: он несёт мусорные данные (по умолчанию нули), но у него тот же размер и тот же seq, что у настоящего куска, поэтому со стороны он похож на повторную отправку этого куска. Если сервер примет такой пакет, мусор попадёт в поток и соединение сломается (нюанс 4). Поэтому фейкам нарочно портят заголовок так, чтобы сервер их отбросил, а DPI всё равно мог прочитать; эту порчу называют fooling (Что такое fooling). В большинстве команд этой статьи фейкам сдвигают номер подтверждения (ACK, поле «что подтверждено») на 66000: tcp_ack=-66000. Из опций порчи к настоящим кускам применяется только tcp_ts_up (плюс политика нумерации ip_id): он ничего не портит, а поднимает опцию TCP timestamp в начало заголовка, и без этого Linux-сервер ненадёжно отбрасывает фейки с неверным ACK; подробнее в нюансе 5 и в разделе Применение fooling и reconstruct.

Второй приём — обратный порядок (по-английски disorder): куски отправляются не от начала к концу, а наоборот. Серверный TCP-стек рассчитан на куски вразнобой, он буферизирует их и ждёт недостающие. DPI, как правило, такой роскоши не имеет: получив конец запроса первым, он видит данные без начала и может не распознать протокол; DPI с пересборкой потока теоретически справится, но у многих ограниченный буфер или таймаут, и они не дожидаются всех частей. Подробнее — в разделе Почему disorder ломает DPI статьи про multidisorder.

Теперь шаги самого fakeddisorder. Движок Zapret 2 (nfqws2) перехватывает исходящий пакет с ClientHello или HTTP-запросом, и функция делает четыре вещи. Сначала режет данные пакета в одном месте на две части: место задаёт аргумент pos, это может быть число или маркер, привязанный к устройству запроса, например midsld — середина домена второго уровня, то есть главной части имени сайта (у www.youtube.com это youtube); по умолчанию разрез после второго байта, а список позиций, как у multisplit, здесь не принимается — только один маркер (Маркер позиции (pos)). Потом готовит по два фейка на каждую часть с тем же размером и тем же seq. Потом отправляет шесть пакетов, начиная со второй части: FAKE2, REAL2, FAKE2, FAKE1, REAL1, FAKE1, где REAL — настоящий кусок, FAKE — фейк, а цифра — номер части запроса. В конце выбрасывает перехваченный оригинал, чтобы те же данные не ушли второй раз. Подробная раскладка по пакетам — в разделе Порядок отправки сегментов (6 пакетов).

Сервер получает все шесть пакетов, четыре фейка отбрасывает из-за испорченного ACK, принимает REAL2, держит его в буфере до прихода REAL1 и по номерам seq собирает ровно тот запрос, который отправил браузер, поэтому отвечает как обычно. DPI видит другое: по три сегмента на каждую позицию (настоящий и два фейка, со стороны они похожи на повторные отправки одного и того же) и конец запроса раньше начала. Какой из трёх сегментов настоящий, DPI не знает, и он может проглотить фейки как реальные данные. Как это выглядит с обеих сторон, показано в разделах Что видит DPI и Что видит сервер статьи про fakedsplit, где схема та же, но порядок прямой. Сработает ли это на конкретном провайдере, заранее сказать нельзя: результат зависит от конкретного DPI, способ порчи фейков выбирают по месту запуска и подтверждают тестами (каталог способов), а итог проверяют на своём провайдере по чек-листу честной проверки.

Команда из быстрого старта fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up режет ClientHello посередине домена второго уровня, но первой отправляет вторую часть, и каждая часть окружена фейками того же размера и с тем же seq: FAKE2, REAL2, FAKE2, FAKE1, REAL1, FAKE1. ТСПУ получает по три сегмента на каждую позицию, да ещё конец запроса раньше начала; сервер отбрасывает фейки по испорченному номеру подтверждения (ACK сдвинут на 66000) и держит REAL2 в буфере до прихода REAL1. Как читать схему: строки — пакеты в порядке отправки, подписанные в тех же обозначениях, что и таблицы в этой статье; по горизонтали — место данных в TCP-потоке (номер последовательности, seq); в колонке справа — что сделал с пакетом сервер; внизу — буфер сервера, в который принятые куски встают по своим seq.

fakeddisorder: фейки и обратный порядок частей Карта потока для fakeddisorder с pos=midsld: сначала вторая часть ClientHello в окружении фейков (FAKE2, REAL2, FAKE2), затем первая (FAKE1, REAL1, FAKE1). Сервер отбрасывает фейки по неверному ACK и собирает поток по seq. пакет в порядке отправки место в потоке (seq) → сервер 0 midsld 26 #1 FAKE2 seq=14 len=12 XXXXXXXXXXXX отброшен: ack #2 REAL2 seq=14 len=12 tube.com···· принят #3 FAKE2 seq=14 len=12 XXXXXXXXXXXX отброшен: ack #4 FAKE1 seq=0 len=14 XXXXXXXXXXXXXX отброшен: ack #5 REAL1 seq=0 len=14 ···········you принят #6 FAKE1 seq=0 len=14 XXXXXXXXXXXXXX отброшен: ack буфер сервера tube.com···· ···········you поток собран DPI по три копии каждой части, и конец запроса приходит раньше начала сервер фейки отбрасывает по неверному ACK (tcp_ack=-66000), REAL2 держит в буфере до прихода REAL1

Самая короткая команда включает fakeddisorder со значениями по умолчанию: один разрез после второго байта (pos=2), обработка только распознанных протоколов (payload=known) и только исходящих пакетов (dir=out), а фейкам портится ACK. Строку --lua-desync=… пишут в профиль — набор правил Zapret 2 для определённого трафика:

--lua-desync=fakeddisorder:tcp_ack=-66000

Статья советует добавлять к tcp_ack ещё и tcp_ts_up (см. нюанс 5). Для HTTPS обычно берут вариант из анимации: --payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up; здесь --payload=tls_client_hello значит «срабатывать только на TLS ClientHello». Остальные готовые команды собраны в разделах Быстрый старт и Практические примеры.

К разрезу можно добавить приём seqovl. Тогда перед второй частью, которая уходит первой из настоящих, дописывается несколько байт заполнителя (seqovl_pattern, по умолчанию нули), а номер seq этой части сдвигается назад на ту же длину. Сервер эти байты отбрасывает как вышедшие за левую границу окна приёма, а DPI видит цельный сегмент вместе с заполнителем и может не распознать в нём имя сайта; общий принцип с раскладкой по байтам разобран в разделе seqovl — скрытый фейк внутри сегмента статьи про multisplit. У fakeddisorder два отличия от multisplit и fakedsplit. Во-первых, seqovl здесь применяется ко второй части, а не к первой. Во-вторых, его значение не число, а маркер того же вида, что pos (seqovl=host, seqovl=midsld-2), как у multidisorder; результат (после вычитания единицы) должен быть строго меньше pos-1, иначе seqovl отменяется. Подробно — в разделах seqovl — скрытый фейк внутри реального сегмента и seqovl — маркер, а не число.

Ближайшие соседи fakeddisorder — три техники с одним из его приёмов. multisplit только режет и шлёт по порядку, без фейков. multidisorder режет и шлёт в обратном порядке, тоже без фейков. fakedsplit окружает части такими же фейками, но шлёт в прямом порядке. Сводная таблица по ним — в разделе Отличия от fakedsplit, multisplit и multidisorder, а подсказка «когда что брать» — в подразделе Когда что использовать: fakeddisorder рассчитан на DPI, который проверяет и порядок, и содержимое. Ограничения такие: pos — один маркер, а не список (нюанс 2); IP-фрагментации функция не поддерживает, для неё нужен multisplit или multidisorder (нюанс 6); работает только с TCP (нюанс 1). Флаги nofake1–nofake4 отключают отдельные фейки, а если отключить все четыре, останется multidisorder с одной позицией (пример 11).


Оглавление


Справка: где функция в коде и родственники

Паспорт функции для тех, кто сверяется с исходниками zapret2: в каком файле и на какой строке лежит код, какой ключ первой версии zapret она заменяет и как объявлена.

Файл: lua/zapret-antidpi.lua:931 nfqws1 эквивалент: --dpi-desync=fakeddisorder Сигнатура: function fakeddisorder(ctx, desync)

fakeddisorder — функция TCP-десинхронизации с двойным запутыванием DPI: по содержимому (реальные vs фейковые сегменты) и по порядку (обратная последовательность — disorder). Payload разрезается по одной позиции на 2 части, и каждая часть обрамляется фейковыми сегментами. Отправка идёт в обратном порядке: сначала вторая часть (с фейками), затем первая (с фейками). Итого — 6 пакетов. После успешной отправки выносит VERDICT_DROP, чтобы оригинальный пакет не ушёл.

Проще говоря

Payload — полезные данные пакета без сетевых заголовков: сам HTTP-запрос или сам ClientHello. Reasm — те же данные, собранные движком из нескольких пакетов, если запрос в один пакет не влез. Blob — заранее заготовленные данные, которые можно подсунуть вместо настоящих. Какой из трёх источников выбирается, разобрано в разделе Откуда берутся данные для нарезки. VERDICT_DROP — решение «перехваченный пакет не отправлять»; как движок складывает такие решения от разных функций, описано в стадии 8 жизненного цикла. «Двойное запутывание» — это два приёма сразу: фейки (запутывание по содержимому) и обратный порядок отправки (запутывание по порядку); оба разобраны в разделе Зачем нужен fakeddisorder.

Родственные функции: multisplit (прямой порядок, без фейков), multidisorder (обратный порядок, без фейков), fakedsplit (с фейками, прямой порядок), hostfakesplit (по hostname), tcpseg (диапазон), oob (urgent byte).

Общий для всех техник дурения порядок работы — восемь стадий от отсева чужого транспорта до вердикта — разобран в жизненный цикл desync-функции; здесь описано только то, чем fakeddisorder от этого скелета отличается.


Зачем нужен fakeddisorder

DPI анализирует TCP-поток, пытаясь собрать полный payload и найти в нём сигнатуры (hostname в HTTP, SNI в TLS). fakeddisorder атакует DPI двумя способами одновременно:

  1. Запутывание по содержимому (fake). Между реальными сегментами посылаются фейковые, содержащие мусорные данные. DPI должен отличить настоящие сегменты от поддельных. Фейки отбрасываются сервером благодаря fooling (badseq, badsum, TTL и т.д.), но DPI может их проглотить.

  2. Запутывание по порядку (disorder). Сегменты отправляются в обратном порядке: сначала вторая часть payload, затем первая. DPI, ожидающий последовательного потока, может быть сбит с толку.

Проще говоря

Сигнатура — заранее известная строка, по которой DPI узнаёт сайт. Фейк — пакет-обманка с мусором вместо данных, у которого тот же размер и тот же seq (порядковый номер байта в потоке), что у настоящего куска: сервер отбрасывает его благодаря порче заголовка, а DPI может принять за настоящие данные. Disorder — отправка кусков не по порядку: сервер ждёт недостающие и склеивает всё по seq, а DPI, которому нужен последовательный поток, может запутаться. Фейки подробнее разобраны в разделах Зачем нужен fake и Зачем нужен fakedsplit, обратный порядок — в разделе Обратный порядок отправки — суть disorder.

Двойное запутывание (содержимое + порядок) эффективнее, чем каждый приём по отдельности:

multisplit:       [часть 1] → [часть 2]                    — только разрез
multidisorder:    [часть 2] → [часть 1]                    — разрез + обратный порядок
fakedsplit:       [F1][часть 1][F2] → [F3][часть 2][F4]    — разрез + фейки
fakeddisorder:    [F1][часть 2][F2] → [F3][часть 1][F4]    — разрез + фейки + обратный порядок

Как читать схему: четыре строки — четыре функции, стрелка показывает порядок отправки, каждые квадратные скобки — один TCP-сегмент. F1–F4 — фейковые сегменты, «часть 1» и «часть 2» — настоящие куски payload. В нижней строке фейки стоят по обе стороны от каждой настоящей части, а часть 2 уходит раньше части 1. Раскладка на шесть пакетов подробно показана в разделе Порядок отправки сегментов (6 пакетов).

Сервер при этом корректно собирает поток — TCP-стек гарантирует это через sequence numbers, а фейки отбрасываются благодаря fooling.

Проще говоря

Для сервера порядок прибытия не важен: TCP-стек сам расставит куски по seq и отбросит фейки. Для DPI важны и порядок, и то, какие из сегментов настоящие, поэтому обман сразу по двум признакам может сработать там, где одного было бы мало. Какую функцию брать под какой тип DPI, подсказывает таблица в подразделе Когда что использовать.


Быстрый старт

Ниже готовые строки для профиля Zapret 2, от самой короткой к более сложным. Флаг --payload=… работает как фильтр для всех вызовов --lua-desync, которые идут после него (до следующего --payload или до конца профиля): они срабатывают только на данных указанного типа. Поэтому --payload пишут перед --lua-desync, а не после (подробно — в последовательность аргументов).

Минимально (разрез по позиции 2, payload=known, dir=out):

--lua-desync=fakeddisorder:tcp_ack=-66000

Здесь payload=known означает «только распознанные протоколы», а dir=out — «только исходящие пакеты»; оба аргумента разобраны в разделах B) Standard direction и C) Standard payload. Фейкам портится ACK (tcp_ack=-66000), а tcp_ts_up в этой минимальной строке нет: статья советует ставить его всегда вместе с tcp_ack (нюанс 5).

Типовой TLS-разрез посередине SNI:

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

Функция срабатывает только на TLS ClientHello и режет его посередине домена второго уровня; уходят шесть пакетов, вторая часть первой.

TLS с seqovl для тройного запутывания:

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:seqovl=5:seqovl_pattern=0x1603030000:tcp_ack=-66000:tcp_ts_up

К настоящей второй части слева приклеивается заполнитель 0x1603030000, похожий на начало TLS record — заголовка записи TLS (для seqovl=5 это 4 байта, см. пример 3 в разделе Практические примеры). Как это работает, разобрано в разделе seqovl — скрытый фейк внутри реального сегмента.

HTTP с кастомным паттерном фейков:

--payload=http_req --lua-desync=fakeddisorder:pos=host:pattern=0x474554202F:tcp_ack=-66000

Здесь pos=host режет HTTP-запрос в начале имени хоста, а pattern=0x474554202F заполняет фейки не нулями, а образцом, похожим на начало HTTP GET; подробнее — в разделе Фейковые сегменты и pattern.


Откуда берутся данные для нарезки

Внутри fakeddisorder данные (data) выбираются в следующем порядке приоритетов:

1. blob_or_def(desync, desync.arg.blob)    — если задан blob= и он существует
2. desync.reasm_data                        — если есть реассемблированные данные (multi-packet payload)
3. desync.dis.payload                       — текущий пакет (fallback)

Следствие: все маркеры pos, seqovl и прочие аргументы применяются именно к тем данным, которые реально выбраны. Если вы задали blob=myblob, маркеры вроде midsld будут работать только если myblob содержит валидный TLS/HTTP payload, который zapret может распознать.

Проще говоря

Payload — полезные данные текущего пакета без сетевых заголовков, reasm — те же данные, собранные движком из нескольких пакетов, blob — заранее заготовленные данные, которые подставляются вместо настоящих (как их объявлять — в заметке blob). Функция берёт первый из трёх источников, который есть: сначала blob, если он задан, потом собранный reasm, в последнюю очередь текущий пакет. Тот же выбор описан как стадия 4 общего скелета в заметке жизненный цикл desync-функции. Какие payload распознаёт nfqws2 и почему от этого зависят относительные маркеры вроде midsld, разобрано в заметке о типах payload.


Маркер позиции (pos)

pos — главный аргумент fakeddisorder. Определяет где внутри payload будет произведён разрез. В отличие от multisplit и multidisorder, pos — это один маркер, а не список. Payload всегда разрезается на ровно 2 части.

Проще говоря

Маркер — способ указать место разреза либо числом байт, либо словом («середина домена второго уровня»), которое nfqws2 сам переводит в число по устройству запроса. В fakeddisorder маркер один, поэтому запрос всегда делится ровно на две части. Как задают список маркеров, разобрано в разделах Маркеры позиций (pos) статьи про multisplit и Маркеры позиций (pos) статьи про multidisorder.

Типы маркеров

ТипОписаниеПример
Абсолютный положительныйСмещение от начала payload, считая с 0. pos=N → первые N байт становятся отдельным сегментом2, 5, 100
Абсолютный отрицательныйСмещение от конца payload. -1 = последний байт-1, -10, -50
ОтносительныйЛогическая позиция внутри распознанного payload. Привязана к структуре протоколаmidsld, host, sniext

Абсолютные маркеры не зависят от содержимого. Относительные привязаны к устройству запроса и работают только тогда, когда nfqws2 распознал протокол: в столбце «Для каких payload» таблицы ниже указано, для каких типов данных маркер имеет смысл (про типы payload — Что такое тип payload).

Относительные маркеры

МаркерОписаниеДля каких payload
methodНачало HTTP-метода (GET, POST, HEAD, PUT и т.д.). Обычно позиция 0, но может стать 1-2 при использовании http_methodeolhttp_req
hostПервый байт имени хоста (Host: в HTTP, SNI в TLS)http_req, tls_client_hello
endhostБайт, следующий за последним байтом имени хоста. Т.е. host..endhost-1 = полный hostnamehttp_req, tls_client_hello
sldПервый байт домена второго уровня (SLD). Для www.example.com — это e в examplehttp_req, tls_client_hello
endsldБайт, следующий за последним байтом SLD. Для example.com — это . после examplehttp_req, tls_client_hello
midsldСередина SLD (самый популярный маркер). Для example (7 символов) — позиция 3-го или 4-го символаhttp_req, tls_client_hello
sniextНачало поля данных SNI extension в TLS ClientHello. Extension состоит из type (2 байта) + length (2 байта) + данные — sniext указывает на начало данныхtls_client_hello
extlenПоле длины всех TLS extensionstls_client_hello

Арифметика маркеров

К любому маркеру можно прибавить (+) или вычесть (-) целое число:

midsld+1      — один байт ПОСЛЕ середины SLD
midsld-1      — один байт ДО середины SLD
endhost-2     — два байта до конца hostname
method+2      — два байта после начала метода
sniext+1      — один байт после начала SNI extension data
host+3        — три байта после начала hostname
-1            — последний байт payload (абсолютный, не относительный)

Проще говоря, midsld+1 значит «на один байт правее середины домена второго уровня», а -1 — последний байт всего payload, потому что абсолютное число не привязано к структуре протокола.

Как маркер разрешается в коде

В отличие от multisplit (который использует resolve_multi_pos для списка маркеров), fakeddisorder использует resolve_pos — функцию для одного маркера:

local spos = desync.arg.pos or "2"
local pos = resolve_pos(data, desync.l7payload, spos)

resolve_pos возвращает одно число (абсолютную Lua-позицию, 1-based) или nil, если маркер не может быть разрешён.

Проще говоря

resolve_pos — функция, которая переводит слово-маркер в номер байта. Lua нумерует позиции с единицы, а смещения в командной строке считаются с нуля, поэтому маркеру 0 соответствует Lua-позиция 1; именно её функция отвергает (см. следующий раздел).

Важные нюансы pos

  • Только ОДИН маркер. pos=midsld,endhost — невалидно (в отличие от multisplit/multidisorder, которые принимают списки). Если вам нужно несколько позиций разреза с фейками — используйте несколько инстансов fakeddisorder в цепочке.
  • Разрез в самом начале данных запрещён. Если маркер разрешается в Lua-позицию 1 — это маркер 0, поскольку смещения в командной строке считаются с нуля, — fakeddisorder логирует "split pos resolved to 0. cannot split." и ничего не делает. Не работает именно pos=0; pos=1 (первый байт отдельным сегментом) корректен. Частая жертва правила — pos=method для HTTP, где метод лежит на смещении 0.
  • Неразрешимый маркер = ничего не делает. Если маркер не может быть разрешён (например, midsld для unknown payload), fakeddisorder логирует "cannot resolve pos" и пропускает операцию.
  • По умолчанию pos=“2”. Если pos не задан, разрез по позиции 2 — payload делится на первые 2 байта и весь остаток (pos=N даёт первые N байт отдельным сегментом, см. таблицу типов маркеров выше; описание «1-й байт и остальное» относится к pos=1).

Проще говоря

Если разрез не получился (позиция 0 или неразрешимый маркер), функция ничего не отправляет и только записывает причину в лог. Такие случаи разобраны и в нюансах 2 и 3 ниже.


seqovl — скрытый фейк внутри реального сегмента

seqovl (Sequence Overlap) — техника скрытого замешивания фейковых данных в реальный TCP-сегмент через манипуляцию TCP sequence number. В fakeddisorder seqovl применяется только ко второму реальному сегменту (real part 2 — первый реальный пакет в порядке отправки).

Sequence Overlap — «перекрытие по номерам последовательности». В multisplit и fakedsplit эта техника применяется к первому настоящему сегменту, а в fakeddisorder — ко второму, который уходит первым из настоящих: сравнение — в нюансе 8. У multidisorder seqovl тоже маркер, и его механика разобрана в отдельном разделе той статьи.

Принцип работы seqovl в fakeddisorder

Без seqovl:
  Пакет 2 (real part 2):
    TCP seq: pos-1
    Данные:  [РЕАЛЬНАЯ_ЧАСТЬ_2]
    Сервер:  принимает [РЕАЛЬНАЯ_ЧАСТЬ_2] целиком

С seqovl=10:
  Пакет 2 (real part 2):
    TCP seq: pos-1-10           (уменьшен на 10)
    Данные:  [PATTERN_10_БАЙТ][РЕАЛЬНАЯ_ЧАСТЬ_2]

  Что видит DPI:
    Единый TCP-сегмент начиная с seq pos-1-10.
    DPI анализирует весь блок, включая PATTERN.
    PATTERN содержит мусор → DPI может не распознать SNI.

  Что видит сервер (TCP-стек):
    Байты до pos-1 выходят за левую границу window → отбрасываются.
    Байты с pos-1 (РЕАЛЬНАЯ_ЧАСТЬ_2) → принимаются.

Визуализация:

           TCP window boundary (= pos-1)
                 ↓
  |  ОТБРОСИТЬ  | ПРИНЯТЬ              |
  | PATTERN(10) | РЕАЛЬНАЯ_ЧАСТЬ_2     |
  ^seq=pos-11    ^seq=pos-1

Проще говоря

Перед настоящей второй частью приклеено 10 байт заполнителя (PATTERN), а seq сдвинут назад на 10. Границей приёма служит pos-1: всё левее сервер выбрасывает, всё от границы принимает. DPI видит цельный сегмент, начинающийся с pos-11, и может не распознать в нём SNI. Общее объяснение окна приёма и разбор того, что видят DPI и сервер, есть в разделе Принцип работы seqovl статьи про multisplit.

seqovl — маркер, а не число

Критическое отличие от multisplit и fakedsplit: в fakeddisorder (как и в multidisorder) seqovl — это маркер, а не просто число. Он разрешается через resolve_pos:

seqovl = resolve_pos(data, desync.l7payload, desync.arg.seqovl)
seqovl = seqovl - 1  -- Lua→0-based

Это значит, что можно использовать:

  • seqovl=5 — абсолютное число (разрешится в 5, станет 4 после -1)
  • seqovl=host — относительный маркер (разрешится в позицию начала hostname, -1)
  • seqovl=midsld-2 — маркер с арифметикой

Проще говоря

seqovl задают так же, как место разреза: числом или словом-маркером. Внутри функции позиция сначала переводится в номер байта, а потом от неё отнимается 1 (Lua→0-based, то есть переход от счёта с единицы к счёту с нуля). Как то же устроено у другой функции — в разделе seqovl как маркер статьи про multidisorder.

Ограничение: результат seqovl (после -1) должен быть строго меньше pos-1. Иначе seqovl отменяется с логом:

fakeddisorder: seqovl cancelled because seqovl N is not less than the split pos M

Если маркер не разрешается, seqovl тоже отменяется:

fakeddisorder: seqovl cancelled because could not resolve marker 'xxx'

Проще говоря

Отмена seqovl не отменяет всю функцию: по псевдокоду ниже в этом случае seqovl обнуляется, а разрез и отправка шести пакетов идут дальше.

Зачем seqovl лучше обычного fooling

Обычный способ спрятать фейк от сервера — fooling: пакету-обманке портят заголовки (TTL, номера seq или ack, опцию md5sig), и сервер отбрасывает такой пакет. seqovl добивается похожего результата другим путём, и таблица ниже сравнивает оба подхода.

КритерийОбычный fooling (TTL, badseq, md5sig)seqovl
ЗаголовкиМодифицируются (TTL, seq, ack, md5)Не модифицируются — пакет выглядит полностью легитимным
ОбнаружениеDPI может детектировать подозрительные заголовкиDPI видит “честный” сегмент с правильными заголовками
Механизм отбрасыванияСервер отбрасывает весь пакет из-за невалидных заголовковСервер отбрасывает только часть, выходящую за TCP window
НадёжностьЗависит от поведения конкретного стекаОснован на фундаментальном свойстве TCP

Вывод: seqovl добавляет третий уровень запутывания (к фейкам и disorder), причём реальный сегмент с seqovl выглядит абсолютно легитимно для DPI.

seqovl_pattern

Паттерн, которым заполняется seqovl-область. По умолчанию — 0x00 (нули).

В fakeddisorder seqovl_pattern — это имя blob. Паттерн повторяется до нужной длины seqovl.

Blob можно записать прямо в команде шестнадцатеричной строкой (inline hex, 0x…) или заранее объявить флагом --blob и сослаться на него по имени; подробно — в заметке blob.

# Inline hex blob (маскировка под начало TLS record)
--lua-desync=fakeddisorder:pos=midsld:seqovl=5:seqovl_pattern=0x1603030000:tcp_ack=-66000
 
# Предзагруженный blob
--blob=tlspat:0x1603030100 \
--lua-desync=fakeddisorder:pos=midsld:seqovl=8:seqovl_pattern=tlspat:tcp_ack=-66000

Если optional задан и blob seqovl_pattern отсутствует — используется нулевой паттерн (seqovl не отменяется).


Фейковые сегменты и pattern

Фейковые сегменты генерируются функцией pattern():

fakepat = desync.arg.pattern and blob(desync, desync.arg.pattern) or "\x00"
fake_part2 = pattern(fakepat, pos, #data - pos + 1)   -- фейк для части 2
fake_part1 = pattern(fakepat, 1, pos - 1)             -- фейк для части 1

Ключевые свойства:

  • Фейк совпадает по размеру с реальной частью, которую он имитирует
  • pattern() принимает смещение (pos или 1), что означает, что фейк заполняется так, как будто он начинается с той же позиции в payload. Это делает его TCP sequence корректным для DPI
  • По умолчанию pattern = \x00 (нули). Можно задать свой blob через pattern=<blob>
  • Blob повторяется до нужной длины

Важно: фейки отправляются с полным fooling (badseq, TTL и т.д.), чтобы сервер их отбросил. Без fooling сервер примет фейки как реальные данные и поток будет повреждён.

Проще говоря

pattern() — функция, которая повторяет образец (по умолчанию один нулевой байт) до нужной длины. Фейк для части 2 по размеру равен части 2, фейк для части 1 — части 1, а seq у них такие же, как у настоящих частей. Образец задаёт аргумент pattern, его значение — имя blob (см. blob). Как именно фейкам портят заголовки, разобрано в разделе Применение fooling и reconstruct.


Полный список аргументов

Аргументы делятся на собственные аргументы fakeddisorder (раздел A) и стандартные (разделы B–G): их понимают и другие функции дурения, а общий обзор есть в desync.

Формат вызова:

--lua-desync=fakeddisorder[:arg1[=val1][:arg2[=val2]]...]

Все val приходят в Lua как строки. Если =val не указан, значение = пустая строка "" (в Lua это truthy), поэтому флаги пишутся просто как :optional, :nodrop, :tcp_ts_up.

A) Собственные аргументы fakeddisorder

pos

  • Формат: pos=<marker>
  • Тип: строка с одним маркером (НЕ список!)
  • По умолчанию: "2"
  • Описание: Точка разреза. Payload делится на 2 части: data[1..pos-1] и data[pos..#data]
  • Примеры:
    • pos=2 — разрез после 2-го байта (дефолт)
    • pos=1 — первый байт уходит отдельным сегментом
    • pos=midsld — разрез посередине SLD
    • pos=host — разрез в начале hostname
    • pos=sniext+1 — один байт после начала SNI extension data
    • pos=endhost-2 — два байта до конца hostname

seqovl

  • Формат: seqovl=<marker>
  • Тип: маркер (не только число — в отличие от multisplit/fakedsplit!)
  • По умолчанию: не задан (нет seqovl)
  • Описание: Применяется ко второму реальному сегменту (real part 2). К данным слева добавляется seqovl байт seqovl_pattern, а TCP th_seq уменьшается на seqovl. Маркер разрешается через resolve_pos, затем результат уменьшается на 1 (Lua→0-based). Результат должен быть строго меньше pos-1
  • Примеры:
    • seqovl=5 — 4 байта фейка слева (5→resolve→5, 5-1=4)
    • seqovl=host — seqovl равен позиции начала hostname (минус 1)
    • seqovl=sld-2 — маркер с арифметикой

seqovl_pattern

  • Формат: seqovl_pattern=<blobName>
  • Тип: имя blob-переменной
  • По умолчанию: один байт 0x00, повторяемый до длины seqovl
  • Описание: Данные для заполнения seqovl-области. Blob повторяется функцией pattern() до нужного размера
  • Поведение с optional: если optional задан и blob отсутствует — используется нулевой паттерн, seqovl не отменяется
  • Примеры:
    • seqovl_pattern=0x1603030000 — inline hex (маскировка под TLS)
    • seqovl_pattern=my_pattern_blob — предзагруженный blob

pattern

  • Формат: pattern=<blobName>
  • Тип: имя blob-переменной
  • По умолчанию: один байт 0x00
  • Описание: Данные для заполнения фейковых сегментов. Blob повторяется до размера соответствующей реальной части
  • Примеры:
    • pattern=0x474554202F — inline hex (маскировка под начало HTTP GET)
    • pattern=fake_tls_pat — предзагруженный blob

blob

  • Формат: blob=<blobName>
  • Тип: имя blob-переменной
  • По умолчанию: не задан
  • Описание: Заменить текущий payload/reasm на указанный blob и резать/слать его. Используется для отправки произвольных данных (фейковых payload, модифицированных ClientHello и т.д.)
  • Примеры:
    • blob=fake_default_tls — стандартный TLS-фейк
    • blob=0xDEADBEEF — inline hex
    • blob=my_custom_ch — предзагруженный blob

nofake1

  • Формат: nofake1 (флаг, без значения)
  • Описание: Не отправлять первый фейковый пакет (fake part 2, первый по порядку отправки)

nofake2

  • Формат: nofake2 (флаг, без значения)
  • Описание: Не отправлять третий пакет (fake part 2 повторно, после реального part 2)

nofake3

  • Формат: nofake3 (флаг, без значения)
  • Описание: Не отправлять четвёртый пакет (fake part 1, перед реальным part 1)

nofake4

  • Формат: nofake4 (флаг, без значения)
  • Описание: Не отправлять шестой пакет (fake part 1 повторно, после реального part 1)

Проще говоря

Четыре флага соответствуют четырём фейкам по порядку отправки: nofake1 — пакет 1, nofake2 — пакет 3, nofake3 — пакет 4, nofake4 — пакет 6. Флага для отключения настоящих пакетов 2 и 5 среди аргументов нет. Примеры — в разделе Пример с nofake-флагами.

optional

  • Формат: optional (флаг, без значения)
  • Описание: Мягкий режим:
    • Если задан blob=... и blob отсутствует — fakeddisorder ничего не делает (тихий skip, без ошибок)
    • Если задан seqovl_pattern=... и blob отсутствует — используется нулевой паттерн (seqovl не отменяется)

Проще говоря

optional смягчает две ситуации: если не найден blob для данных, функция тихо ничего не делает, а если не найден blob-образец для seqovl, заполнитель будет нулевым.

nodrop

  • Формат: nodrop (флаг, без значения)
  • Описание: После успешной отправки не выносить VERDICT_DROP (вместо этого вернуть VERDICT_PASS). Оригинальный пакет тоже будет отправлен (наряду с 6 нарезанными пакетами)
  • Предупреждение: в боевых профилях nodrop обычно нежелателен — оригинал ещё раз уйдёт, что создаст дублирование

B) Standard direction

ПараметрЗначенияПо умолчанию
dirin, out, anyout

Фильтр по направлению пакета. fakeddisorder по умолчанию работает только с исходящими (out).

  • dir=out — только исходящие (от клиента к серверу)
  • dir=in — только входящие (от сервера к клиенту)
  • dir=any — оба направления

При первом вызове с указанным dir функция делает direction_cutoff_opposite — отсекает себя от противоположного направления.

Проще говоря

Cutoff (отсечение) — команда «эта функция больше не вызывается для такого направления или потока». Что происходит на стадии отсечения направления, описано в заметке стадия 2 жизненного цикла.


C) Standard payload

ПараметрЗначенияПо умолчанию
payloadсписок типов через запятуюknown

Фильтр по типу payload на уровне Lua. Это дополнительный фильтр к --payload=... на уровне профиля.

  • payload=known — только распознанные протоколы (http_req, tls_client_hello, quic_initial и т.д.)
  • payload=all — любой payload, включая unknown
  • payload=tls_client_hello,http_req — конкретные типы
  • payload=~unknown — инверсия: всё кроме unknown

Важно: лучше ставить --payload=... на уровне профиля (C-код, быстрее), а не полагаться только на Lua-фильтр.

Проще говоря

Тип payload — то, что движок распознал в данных пакета (tls_client_hello, http_req и другие). Подробнее — в заметке Что такое тип payload; порядок --payload и --lua-desync в профиле разобран в последовательность аргументов.


D) Standard fooling

Модификации L3/L4 заголовков. В fakeddisorder fooling применяется только к фейковым сегментам. К реальным применяется только tcp_ts_up.

Каждая строка таблицы — один способ порчи заголовков IP или TCP у фейка, чтобы сервер его отбросил. Разбор способов по слоям и того, где какой ломается, есть в разделе Как заставить fake умереть: каталог способов.

ПараметрОписаниеПример
ip_ttl=NУстановить IPv4 TTLip_ttl=6
ip6_ttl=NУстановить IPv6 Hop Limitip6_ttl=6
ip_autottl=delta,min-maxАвтоматический TTL (delta от серверного TTL)ip_autottl=-2,40-64
ip6_autottl=delta,min-maxАналогично для IPv6ip6_autottl=-2,40-64
ip6_hopbyhop[=HEX]Вставить extension header hop-by-hop (по умолчанию 6 нулей)ip6_hopbyhop
ip6_hopbyhop2[=HEX]Второй hop-by-hop headerip6_hopbyhop2
ip6_destopt[=HEX]Destination options headerip6_destopt
ip6_destopt2[=HEX]Второй destination optionsip6_destopt2
ip6_routing[=HEX]Routing headerip6_routing
ip6_ah[=HEX]Authentication headerip6_ah
tcp_seq=NСместить TCP sequence (+ или -)tcp_seq=-10000
tcp_ack=NСместить TCP ack (+ или -)tcp_ack=-66000
tcp_ts=NСместить TCP timestamptcp_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-функция foolingfool=my_fooler

Ключевое: tcp_ts_up — единственный параметр fooling, который применяется и к реальным, и к фейковым сегментам. Все остальные fooling-параметры применяются только к фейкам.

Заметка про tcp_ts_up: На Linux-серверах пакеты с инвалидным ACK стабильно отбрасываются только если TCP timestamp option идёт первой в заголовке. tcp_ts_up перемещает её в начало, обеспечивая корректную работу badseq-fooling. Поэтому tcp_ts_up рекомендуется включать всегда вместе с tcp_ack.

Проще говоря

TCP timestamp — необязательная опция в конце TCP-заголовка (устройство опций разобрано в разделе Что такое TCP options). tcp_ts_up только переставляет её в начало и ничего не портит, поэтому его безопасно применять и к настоящим сегментам.


E) Standard ipid

ПараметрОписаниеПо умолчанию
ip_id=seqПоследовательные IP IDseq
ip_id=rndСлучайные IP ID—
ip_id=zeroНулевые IP ID—
ip_id=noneНе менять IP ID—
ip_id_connСквозная нумерация IP ID в рамках соединения (требует tracking)—

ip_id применяется и к фейкам, и к оригиналам.

Проще говоря

ip_id задаёт, как нумеруются IP ID пакетов: seq — по порядку, rnd — случайно, zero — нулями, none — не менять. К фейкам и к настоящим сегментам он применяется одинаково (таблица в разделе Применение fooling и reconstruct).


F) Standard reconstruct

ПараметрОписание
badsumИспортить L4 (TCP) checksum при реконструкции raw-пакета

reconstruct применяется только к фейкам. К оригиналам reconstruct не применяется.

Проще говоря

Reconstruct — сборка готового сырого пакета из полей и данных; badsum при такой сборке нарочно портит контрольную сумму TCP. Настоящие сегменты собираются без этого.


G) Standard rawsend

ПараметрОписание
repeats=NОтправить каждый пакет N раз (применяется только к фейкам)
ifout=<iface>Интерфейс для отправки (по умолчанию определяется автоматически)
fwmark=NFirewall mark (только Linux, nftables/iptables)

Важно: repeats в fakeddisorder применяется только к фейковым сегментам (через rawsend_opts), а не ко всем. Реальные сегменты отправляются через rawsend_opts_base, где repeats не задействуется.

Важно: ipfrag не задействуется в fakeddisorder — ни для фейков, ни для оригиналов. Объект ipfrag установлен в пустую таблицу {} для обоих типов пакетов.

Проще говоря

Rawsend — отправка собранных пакетов в сеть; его параметры задают число повторов, интерфейс и метку firewall. IP-фрагментация — деление пакета на части на уровне IP: multisplit её поддерживает, а fakeddisorder нет.


Порядок отправки сегментов (6 пакетов)

fakeddisorder всегда отправляет 6 пакетов (если все nofake-флаги не установлены). Порядок обратный по отношению к fakedsplit: сначала идёт вторая часть (с фейками), затем первая (с фейками).

Проще говоря

Пакеты 1–3 — вторая часть запроса в окружении двух фейков, пакеты 4–6 — первая часть в таком же окружении. Смещение (seq offset) считается от начала payload и у фейка равно смещению настоящей части, которую он копирует. Столбец nofake показывает, каким флагом отключается пакет.

ASCII-диаграмма полной последовательности

Payload (100 байт), pos=midsld (разрешился в позицию 40):

  Часть 1: data[1..39]     (39 байт, seq offset = 0)
  Часть 2: data[40..100]   (61 байт, seq offset = 39)

Порядок отправки (хронологический):

  №  Пакет              Тип      seq offset  Размер  Opts         nofake
  ─────────────────────────────────────────────────────────────────────────
  1  fake  part 2       ФЕЙК     39          61      opts_fake    nofake1
  2  real  part 2       РЕАЛ     39 (-seqovl) 61(+ovl) opts_orig    —
  3  fake  part 2       ФЕЙК     39          61      opts_fake    nofake2
  4  fake  part 1       ФЕЙК     0           39      opts_fake    nofake3
  5  real  part 1       РЕАЛ     0           39      opts_orig    —
  6  fake  part 1       ФЕЙК     0           39      opts_fake    nofake4
  ─────────────────────────────────────────────────────────────────────────

opts_orig: только tcp_ts_up из fooling, пустой reconstruct, пустой ipfrag
opts_fake: полный fooling + полный reconstruct, пустой ipfrag

Ниже те же шесть пакетов вдоль оси времени: слева самый ранний, справа самый поздний; под каждым подписано, что с ним делает сервер.

Визуализация на временной оси:

Время ──────────────────────────────────────────────────────────────────→

  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
  │ FAKE P2  │  │ REAL P2  │  │ FAKE P2  │  │ FAKE P1  │  │ REAL P1  │  │ FAKE P1  │
  │ nofake1  │  │ (+seqovl)│  │ nofake2  │  │ nofake3  │  │          │  │ nofake4  │
  │ seq=39   │  │ seq=39-N │  │ seq=39   │  │ seq=0    │  │ seq=0    │  │ seq=0    │
  │ [МУСОР]  │  │[PAT][DAT]│  │ [МУСОР]  │  │ [МУСОР]  │  │ [ДАННЫЕ] │  │ [МУСОР]  │
  └──────────┘  └──────────┘  └──────────┘  └──────────┘  └──────────┘  └──────────┘
  ^^^^ФЕЙК^^^^  ^^РЕАЛЬНЫЙ^^  ^^^^ФЕЙК^^^^  ^^^^ФЕЙК^^^^  ^^РЕАЛЬНЫЙ^^  ^^^^ФЕЙК^^^^
       ↓              ↓             ↓             ↓              ↓             ↓
   сервер            сервер      сервер       сервер          сервер       сервер
   ОТБРОСИТ          ПРИМЕТ      ОТБРОСИТ     ОТБРОСИТ        ПРИМЕТ       ОТБРОСИТ
  (fooling)                     (fooling)    (fooling)                    (fooling)

Сравнение с fakedsplit (прямой порядок):

fakedsplit:      [F1_P1] [REAL_P1] [F2_P1] → [F3_P2] [REAL_P2] [F4_P2]
fakeddisorder:   [F1_P2] [REAL_P2] [F2_P2] → [F3_P1] [REAL_P1] [F4_P1]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                 Сначала ВТОРАЯ часть            Потом ПЕРВАЯ часть

Проще говоря

Это та же схема, что у fakedsplit, только две половины поменялись местами: у fakedsplit сначала уходит первая часть, у fakeddisorder вторая. Схема fakedsplit для сравнения — в разделе Последовательность отправки (6 пакетов).

Пример с seqovl

Payload (100 байт), pos=midsld (40), seqovl=10:

  №  Пакет              seq offset   Данные
  ──────────────────────────────────────────────────────────────────────────
  1  fake  part 2       39           [PATTERN 61 байт]
  2  real  part 2       29           [SEQOVL_PAT 10 байт][data[40..100] 61 байт]
  3  fake  part 2       39           [PATTERN 61 байт]
  4  fake  part 1       0            [PATTERN 39 байт]
  5  real  part 1       0            [data[1..39] 39 байт]
  6  fake  part 1       0            [PATTERN 39 байт]

  Сервер для пакета 2:
    TCP window начинается с seq=39
    Байты 29-38 (SEQOVL_PAT) → отбрасываются
    Байты 39-99 (data[40..100]) → принимаются

Здесь у пакета 2 смещение 29, то есть 39 минус 10 байт заполнителя. По схеме выше окно приёма сервера для этого пакета начинается с seq=39, поэтому байты 29–38 он отбрасывает, а с 39-го принимает настоящую часть 2.

Пример с nofake-флагами

fakeddisorder:pos=midsld:tcp_ack=-66000:nofake2:nofake4

Отправляется 4 пакета вместо 6:

  №  Пакет              Тип
  ────────────────────────────
  1  fake  part 2       ФЕЙК
  2  real  part 2       РЕАЛ
  —  (пропущен nofake2)
  3  fake  part 1       ФЕЙК
  4  real  part 1       РЕАЛ
  —  (пропущен nofake4)

Проще говоря

nofake2 и nofake4 убирают повторные фейки, которые идут после настоящих частей; остаётся по одному фейку перед каждой настоящей частью, всего четыре пакета.

Можно отключить все фейки кроме одного, или вообще все:

# Только 2 реальных пакета (все фейки отключены) = multidisorder с одной позицией
fakeddisorder:pos=midsld:nofake1:nofake2:nofake3:nofake4

Применение fooling и reconstruct

Это ключевое отличие от multisplit и multidisorder, где fooling применяется ко всем сегментам.

Проще говоря

У пакетов два набора опций: opts_orig для настоящих сегментов и opts_fake для фейков. Общий разбор наборов опций — в разделе Наборы опций: кто их применяет и к чему заметки про жизненный цикл, а у fakedsplit — в разделе Два набора опций: opts_orig vs opts_fake.

                          opts_orig                      opts_fake
                    (реальные сегменты)            (фейковые сегменты)
  ───────────────────────────────────────────────────────────────────
  fooling           ТОЛЬКО tcp_ts_up               ВСЁ (tcp_ack, tcp_seq,
                                                    ip_ttl, tcp_md5, ...)
  reconstruct       пусто ({})                     ВСЁ (badsum и т.д.)
  ipfrag            пусто ({})                     пусто ({})
  ipid              desync.arg (полный)            desync.arg (полный)
  rawsend           rawsend_opts_base              rawsend_opts (с repeats)
  ───────────────────────────────────────────────────────────────────

Почему так: реальные сегменты должны дойти до сервера в неповреждённом виде. Fooling вроде tcp_ack=-66000 сделал бы их невалидными для сервера. Поэтому fooling идёт только на фейки — чтобы сервер их отбросил, а DPI — запутался.

Исключение — tcp_ts_up. Он не портит пакет, а лишь перемещает TCP timestamp в начало заголовка. Это полезно для обеспечения корректной работы badseq/badack на принимающей стороне и применяется ко всем сегментам.


Поведение при replay / reasm

Многопакетный запрос nfqws2 придерживает, собирает в буфер reasm_data и перепроигрывает через desync-функции. fakeddisorder использует штатную развилку из общего скелета desync-функций (жизненный цикл desync-функции, стадия 6) без изменений: на первой части отправляет всю шестёрку пакетов (2 реальных + 4 фейка) по собранному буферу, остальные части дропает. Если отправка сорвалась, флаг «реасм уже отправлен» не ставится и оставшиеся части проходят как есть.

Проще говоря

Если ClientHello не влез в один пакет, движок придерживает пакеты, собирает запрос целиком в буфер reasm_data (reasm — от reassembly, «пересборка») и «проигрывает» (replay) его по кускам через функции дурения. Функция должна сработать один раз, на первом куске, а остальные куски выбросить. Подробно — в разделе Стадия 6. Развилка перепроигрывания (replay).

Специфика функции: раз работа идёт по всему реасму, обратный порядок и размеры фейков определяются целым запросом, а не отдельным пакетом.


Автосегментация по MSS

О размерах TCP-сегментов думать не нужно. Функция rawsend_payload_segmented из zapret-lib.lua автоматически:

  1. Отслеживает MSS для каждого TCP-соединения
  2. Если часть payload превышает MSS — дополнительно режет по MSS
  3. Каждый под-сегмент отправляется с корректным TCP sequence

Проще говоря

MSS (Maximum Segment Size) — максимальный размер TCP-сегмента: если часть запроса больше, она уходит несколькими сегментами, и у каждого seq правильный. Указывать это в аргументах не нужно.


Место в общем скелете

Тело fakeddisorder построено по тому же шаблону, что и все остальные функции дурения: восемь стадий от отсева чужого транспорта до вердикта. Разобраны они один раз в жизненный цикл desync-функции — здесь только отклонения:

Стадия общего скелетаЧто делает fakeddisorder
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. Своя техникаодна позиция разреза, два реальных сегмента и до четырёх фейков, отправка в обратном порядке, seqovl как маркер — этому посвящена вся заметка
8. ВердиктVERDICT_DROP после успешной отправки, VERDICT_PASS при nodrop или сбое rawsend

Как и fakedsplit, функция собирает два разных набора опций: фейкам достаются fooling, reconstruct и repeats в полном объёме, реальным частям — только tcp_ts_up и политика ip_id. Смысл в том, что фейк сервер обязан отвергнуть, а реальные данные — принять. Общий разбор наборов опций — в жизненный цикл desync-функции.

Проще говоря

Cutoff (отсечение) — «эта функция больше не вызывается»; вердикт — решение, что делать с перехваченным пакетом: VERDICT_DROP выбросить его, VERDICT_PASS пропустить. Как вердикты складываются между функциями, описано в заметке Стадия 8. Вердикт и его агрегация.


Псевдокод алгоритма

Тело функции целиком, включая общие для всех техник стадии — они пронумерованы так же, как в жизненный цикл desync-функции:

Читать код удобнее вместе с таблицей из раздела Место в общем скелете и схемой из раздела Порядок отправки сегментов (6 пакетов): комментарии в коде отмечают, где кончается общая часть и начинаются шесть отправок.

function fakeddisorder(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
 
        -- 6. Только первый replay
        if replay_first() then
            -- 7. Разрешение маркера (ОДИН, не список!)
            spos = desync.arg.pos or "2"
            pos = resolve_pos(data, l7payload, spos)
 
            if pos == nil then
                DLOG("cannot resolve pos")
            elseif pos == 1 then
                DLOG("split pos resolved to 0. cannot split.")
            else
                -- 8. Подготовка opts
                opts_orig = {
                    fooling = {tcp_ts_up = desync.arg.tcp_ts_up},  -- ТОЛЬКО tcp_ts_up
                    reconstruct = {},                                -- пусто
                    ipfrag = {},                                     -- пусто
                    ipid = desync.arg,
                    rawsend = rawsend_opts_base(desync)
                }
                opts_fake = {
                    fooling = desync.arg,           -- ПОЛНЫЙ fooling
                    reconstruct = reconstruct_opts(desync),  -- ПОЛНЫЙ reconstruct
                    ipfrag = {},                     -- пусто
                    ipid = desync.arg,
                    rawsend = rawsend_opts(desync)   -- с repeats
                }
 
                fakepat = pattern_blob or "\x00"
 
                -- ============ ЧАСТЬ 2 (ОБРАТНЫЙ ПОРЯДОК!) ============
 
                -- 9. Fake part 2 (пакет 1)
                fake2 = pattern(fakepat, pos, #data - pos + 1)
                if not nofake1 then
                    rawsend_payload_segmented(fake2, pos-1, opts_fake)
                end
 
                -- 10. Real part 2 + seqovl (пакет 2)
                part2 = data:sub(pos)
                seqovl = 0
                if desync.arg.seqovl then
                    seqovl = resolve_pos(data, l7payload, desync.arg.seqovl)
                    if seqovl then
                        seqovl = seqovl - 1    -- Lua→0-based
                        if seqovl >= (pos-1) then
                            DLOG("seqovl cancelled: not less than split pos")
                            seqovl = 0
                        else
                            pat = seqovl_pattern_blob or "\x00"
                            part2 = pattern(pat, 1, seqovl) .. part2
                        end
                    else
                        DLOG("seqovl cancelled: cannot resolve marker")
                        seqovl = 0
                    end
                end
                rawsend_payload_segmented(part2, pos-1-seqovl, opts_orig)
 
                -- 11. Fake part 2 again (пакет 3)
                if not nofake2 then
                    rawsend_payload_segmented(fake2, pos-1, opts_fake)
                end
 
                -- ============ ЧАСТЬ 1 ============
 
                -- 12. Fake part 1 (пакет 4)
                fake1 = pattern(fakepat, 1, pos-1)
                if not nofake3 then
                    rawsend_payload_segmented(fake1, 0, opts_fake)
                end
 
                -- 13. Real part 1 (пакет 5)
                part1 = data:sub(1, pos-1)
                rawsend_payload_segmented(part1, 0, opts_orig)
 
                -- 14. Fake part 1 again (пакет 6)
                if not nofake4 then
                    rawsend_payload_segmented(fake1, 0, opts_fake)
                end
 
                -- 15. Пометить как отправленное
                replay_drop_set()
                return nodrop and VERDICT_PASS or VERDICT_DROP
            end
        else
            -- 16. Не первый replay
            DLOG("not acting on further replay pieces")
        end
        -- 17. Дропнуть если ранее успешно отправлено
        if replay_drop() then
            return nodrop and VERDICT_PASS or VERDICT_DROP
        end
    end
end

Нюансы и подводные камни

1. Работает только с TCP

Если текущий пакет не TCP (UDP, ICMP и т.д.), fakeddisorder делает instance_cutoff_shim — отключает себя для этого потока навсегда. Исключение — ICMP-пакеты (related), для которых cutoff не выполняется (просто return).

Проще говоря

Instance cutoff — отключение функции для этого потока до его конца. Почему техника работает только с TCP, разобрано в нюансе «Работает только с TCP — и почему» статьи про multisplit; о самой стадии отсева — в разделе Стадия 1.

2. Только ОДИН маркер в pos

В отличие от multisplit и multidisorder, fakeddisorder принимает один маркер, а не список. pos=midsld,endhost не будет работать как два разреза. Если нужно несколько разрезов с фейками, используйте цепочку инстансов (хотя это нетривиально из-за VERDICT_DROP).

Проще говоря

Цепочка инстансов нетривиальна из-за VERDICT_DROP: по нюансу 11 инстанс, стоящий после fakeddisorder, увидит VERDICT_DROP и не получит оригинальный payload (нюанс 11). Про списки маркеров у соседей — Маркеры позиций (pos).

3. Позиция 1 не работает

Если маркер разрешается в Lua-позицию 1 (то есть задан маркер 0), fakeddisorder логирует "split pos resolved to 0. cannot split." и ничего не делает. Минимальный работающий разрез — pos=1, дефолт — pos=2.

4. Fooling ОБЯЗАТЕЛЕН для фейков

Без fooling-параметров (tcp_ack, tcp_seq, ip_ttl, tcp_md5, badsum и т.д.) сервер примет фейковые сегменты как реальные данные. Это повредит TCP-поток. Всегда указывайте хотя бы один fooling-параметр:

# ПЛОХО: фейки без fooling — сервер их примет!
--lua-desync=fakeddisorder:pos=midsld
 
# ХОРОШО: фейки с badseq
--lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

Проще говоря

Фейк без порчи заголовка сервер не отличит от настоящих данных и примет. Как фейкам портят заголовки, разобрано в разделе Что такое fooling.

5. tcp_ts_up рекомендуется с tcp_ack

На Linux-серверах пакеты с инвалидным ACK стабильно отбрасываются только если TCP timestamp option идёт первой в заголовке. Без tcp_ts_up есть риск, что сервер примет фейк с tcp_ack=-66000.

Проще говоря

Инвалидный ACK — номер подтверждения, который не подходит серверу; именно его портит tcp_ack=-66000. Опция timestamp и её порядок в заголовке описаны в разделе Что такое TCP options.

6. ipfrag НЕ задействуется

В отличие от multisplit и multidisorder, fakeddisorder не поддерживает IP-фрагментацию. Объект ipfrag установлен в {} и для фейков, и для оригиналов. Если нужна IP-фрагментация — используйте multidisorder (без фейков, но с ipfrag).

Проще говоря

IP-фрагментация — деление пакета на части на уровне IP, ниже TCP. Здесь её нет ни для фейков, ни для настоящих сегментов; функцию, которая её поддерживает, описывает раздел F) Standard ipfrag статьи про multisplit.

7. seqovl — маркер, а не число

В multisplit и fakedsplit seqovl — просто tonumber(desync.arg.seqovl). В fakeddisorder (как и в multidisorder) seqovl разрешается через resolve_pos. Это значит:

  • seqovl=5 работает (разрешится в абсолютную позицию 5, затем -1 = 4)
  • seqovl=host тоже работает
  • Но результат должен быть < pos-1

8. seqovl применяется только к real part 2

В отличие от fakedsplit (где seqovl к real part 1 — первому реальному), в fakeddisorder seqovl применяется к real part 2 — первому реальному по порядку отправки (но второму по порядку в payload). Это логично: в disorder-порядке сначала отправляется вторая часть, и именно она получает seqovl.

Проще говоря

Real part 2 — вторая часть запроса, которая при обратном порядке уходит первой из настоящих; поэтому seqovl достаётся именно ей.

9. nodrop создаёт дублирование

С nodrop fakeddisorder отправляет 6 нарезанных пакетов И пропускает оригинальный. Сервер получит данные дважды. Используйте nodrop только для отладки.

Проще говоря

Без nodrop оригинальный пакет выбрасывается, и сервер получает данные один раз; с nodrop уходят и оригинал, и все шесть пакетов, поэтому флаг годится только для отладки.

10. Ошибка rawsend прерывает цепочку

Если любой из 6 вызовов rawsend_payload_segmented возвращает false, fakeddisorder немедленно возвращает VERDICT_PASS (оригинальный пакет пропускается). Остальные пакеты из 6 не отправляются, replay_drop_set не вызывается.

Проще говоря

Если хотя бы одна отправка не удалась, остальные не выполняются, а оригинальный пакет пропускается как есть.

11. Порядок инстансов важен

Если перед fakeddisorder стоит fake — он отправит свой фейк до того, как fakeddisorder начнёт свою последовательность. Если после fakeddisorder стоит ещё один инстанс — он увидит VERDICT_DROP и не получит оригинальный payload.

Проще говоря

Как показывает пример с fake перед fakeddisorder, инстансы отрабатывают по очереди, в порядке записи в профиле. Про такой же нюанс у самого fake — 8. Порядок инстансов: fake всегда первый.


Отличия от fakedsplit, multisplit и multidisorder

Таблица сравнивает четыре функции по строкам; жирным выделено то, что относится к fakeddisorder. Отличия от других функций сегментации со стороны соседей разобраны в разделах multisplit, multidisorder и fakedsplit.

Аспектmultisplitmultidisorderfakedsplitfakeddisorder
Количество позицийСписок (любое кол-во)Список (любое кол-во)ОднаОдна
Порядок отправкиПрямой (1-2-3)Обратный (3-2-1)Прямой (1-2)Обратный (2-1)
Фейковые сегментыНетНетДа (до 4 шт.)Да (до 4 шт.)
Всего пакетов (max)N+1 (по кол-ву позиций)N+166
seqovl типТолько числоМаркерТолько числоМаркер
seqovl к какому сегменту1-й (прямой)2-й (предпоследний)1-й реальный (part 1)2-й реальный (part 2)
Fooling кВсем сегментамВсем сегментамТолько к фейкамТолько к фейкам
reconstruct кВсемВсемТолько к фейкамТолько к фейкам
repeats кВсемВсемТолько к фейкамТолько к фейкам
ipfragДаДаНетНет
Уровень запутыванияОдин (разрез)Два (разрез + порядок)Два (разрез + фейки)Три (разрез + фейки + порядок)

Когда что использовать

СценарийРекомендация
DPI не реассемблирует TCP, достаточно разрезатьmultisplit
DPI реассемблирует, но не проверяет порядокmultidisorder
DPI проверяет порядок, но не отличает фейкиfakedsplit
DPI проверяет и порядок, и содержимоеfakeddisorder
Нужна IP-фрагментация поверх TCPmultisplit или multidisorder
Нужно несколько позиций разреза с фейкамиЦепочка fakedsplit / fakeddisorder

Таблица выше отвечает на вопрос, какую функцию брать под какой тип DPI; для конкретного провайдера выбор всё равно проверяют на деле по чек-листу честной проверки. Для нескольких позиций разреза с фейками остаётся цепочка инстансов (нюанс 2).


Миграция с nfqws1

nfqws1 — движок первой версии zapret: там техники включаются флагами --dpi-desync-*, а во второй версии --lua-desync. Таблица ниже переводит ключи первой версии в аргументы fakeddisorder, общая таблица соответствий флагов есть в заметке desync.

Соответствие параметров

nfqws1nfqws2
--dpi-desync=fakeddisorder--lua-desync=fakeddisorder
--dpi-desync-split-pos=midsld:pos=midsld
--dpi-desync-fooling=badseq:tcp_ack=-66000:tcp_ts_up
--dpi-desync-fooling=badack:tcp_ack=-66000:tcp_ts_up
--dpi-desync-fooling=md5sig:tcp_md5
--dpi-desync-fooling=datanoack:tcp_flags_unset=ACK
--dpi-desync-fooling=hopbyhop:ip6_hopbyhop
--dpi-desync-fooling=hopbyhop2:ip6_hopbyhop2
--dpi-desync-split-seqovl=N:seqovl=N
--dpi-desync-split-seqovl-pattern=HEX:seqovl_pattern=HEX
--dpi-desync-fake-tls=FILE:pattern=<blobname> (предзагрузить через --blob=)
--dpi-desync-any-protocolНе нужно; или payload=all в инстансе

Пример полной миграции

# nfqws1:
nfqws --dpi-desync=fakeddisorder \
  --dpi-desync-fooling=badseq \
  --dpi-desync-split-pos=midsld
 
# nfqws2 (эквивалент):
nfqws2 \
  --payload=tls_client_hello \
    --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up
# nfqws1 (комплексный):
nfqws --dpi-desync=fakeddisorder \
  --dpi-desync-fooling=md5sig \
  --dpi-desync-split-pos=midsld \
  --dpi-desync-split-seqovl=5 \
  --dpi-desync-split-seqovl-pattern=0x1603030000
 
# nfqws2 (эквивалент):
nfqws2 \
  --payload=tls_client_hello \
    --lua-desync=fakeddisorder:pos=midsld:tcp_md5:seqovl=5:seqovl_pattern=0x1603030000
# nfqws1 (фулл):
nfqws --dpi-desync=fake,fakeddisorder \
  --dpi-desync-fooling=badseq \
  --dpi-desync-split-pos=midsld \
  --dpi-desync-fake-tls-mod=rnd,rndsni,dupsid
 
# nfqws2 (эквивалент — fake отдельным инстансом):
nfqws2 \
  --payload=tls_client_hello \
    --lua-desync=fake:blob=fake_default_tls:tcp_ack=-66000:tcp_ts_up:tls_mod=rnd,rndsni,dupsid \
    --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

Практические примеры

Ниже 18 примеров: от минимального до боевых. Каждая команда — строка для профиля; подробности про аргументы pos, seqovl, pattern, nofake1–nofake4 — в разделах Маркер позиции (pos), seqovl и Полный список аргументов.

1. Минимальный (с fooling)

--lua-desync=fakeddisorder:tcp_ack=-66000:tcp_ts_up

Разрезает payload после 2-го байта (pos=2 по умолчанию) с badseq fooling для фейков. 6 пакетов.

2. TLS: разрез посередине SNI

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

SNI разрезан пополам. DPI видит 6 пакетов с перемешанными фейковыми и реальными сегментами в обратном порядке — двойное запутывание.

3. TLS: разрез + seqovl (тройное запутывание)

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:seqovl=5:seqovl_pattern=0x1603030000:tcp_ack=-66000:tcp_ts_up

Три уровня: фейки + disorder + seqovl. Real part 2 имеет 4-байтовый TLS-фейк слева (5→resolve→5, 5-1=4), который DPI может принять за начало TLS record.

4. TLS: seqovl как маркер

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:seqovl=sld:tcp_ack=-66000:tcp_ts_up

seqovl разрешается в позицию начала SLD. Фейковая часть слева от real part 2 — от начала SLD до позиции разреза.

5. HTTP: разрез в начале hostname

--payload=http_req --lua-desync=fakeddisorder:pos=host:tcp_ack=-66000:tcp_ts_up

Для GET / HTTP/1.1\r\nHost: example.com\r\n... разрежет так, что часть 1 содержит заголовки до hostname, а часть 2 — hostname и далее.

6. HTTP: кастомный паттерн для фейков

--payload=http_req --lua-desync=fakeddisorder:pos=host:pattern=0x474554202F20485454502F312E310D0A:tcp_ack=-66000:tcp_ts_up

Фейки заполняются паттерном, похожим на начало HTTP-запроса (GET / HTTP/1.1\r\n). DPI может принять фейк за реальный запрос к другому хосту.

7. С tcp_md5 fooling

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_md5:tcp_ts_up

Вместо badseq используется TCP MD5 signature option. Сервер отбрасывает пакеты с неверной MD5 подписью.

8. С badsum reconstruct

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:badsum

Фейки отправляются с испорченным TCP checksum. Двойная защита: и badseq, и badsum.

9. Без первого и последнего фейка (4 пакета)

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:nofake1:nofake4

Отключены фейки “снаружи” (первый и последний). Остаётся: real part 2, fake part 2 (после), fake part 1 (перед), real part 1. 4 пакета.

10. Только внутренние фейки (4 пакета)

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:nofake1:nofake2

Вся часть 2 — только реальная (без обрамления фейками). Часть 1 — с фейками. Полезно, когда DPI анализирует только начало потока.

11. Без всех фейков (эквивалент disorder)

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:nofake1:nofake2:nofake3:nofake4

Отключены все 4 фейка. Остаются только 2 реальных пакета в обратном порядке. Фактически — multidisorder с одной позицией. Fooling не нужен (применяется только к фейкам, которых нет).

12. Цепочка: fake + fakeddisorder

--payload=tls_client_hello \
  --lua-desync=fake:blob=fake_default_tls:repeats=5:tcp_md5 \
  --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

Сначала 5 фейковых TLS ClientHello (с md5sig fooling), затем fakeddisorder с 6 пакетами. Итого 11 пакетов. Массированная атака на DPI.

13. С произвольным blob вместо payload

--blob=mydata:@custom_payload.bin \
--lua-desync=fakeddisorder:blob=mydata:pos=50:tcp_ack=-66000:tcp_ts_up

Режет и отправляет произвольные данные из файла вместо реального payload.

14. С optional для необязательного blob

--lua-desync=fakeddisorder:blob=maybe_missing:optional:pos=midsld:tcp_ack=-66000:tcp_ts_up

Если blob maybe_missing не существует — тихий пропуск, без ошибок.

15. Боевой пример для YouTube

--filter-tcp=443 --hostlist=youtube.txt \
  --payload=tls_client_hello \
    --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

Разрезает TLS ClientHello посередине SNI домена YouTube с фейками в обратном порядке.

16. Комбинация fooling + repeats для фейков

--payload=tls_client_hello \
  --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:repeats=3

Каждый из 4 фейковых пакетов отправляется 3 раза. Реальные — по 1 разу. Итого 14 пакетов (4*3 + 2).

17. IPv6: hop-by-hop fooling

--payload=tls_client_hello \
  --lua-desync=fakeddisorder:pos=midsld:ip6_hopbyhop:tcp_ts_up

Для IPv6: фейки отправляются с extension header hop-by-hop, который сбрасывается на первом маршрутизаторе. Реальные сегменты — без hop-by-hop.

18. Отладка: не блокировать оригинал

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:nodrop

Отправляет 6 нарезанных пакетов И пропускает оригинальный (для экспериментов/отладки).


📚 См. также

  • desync — обзор всех функций --lua-desync и общий контракт: кто вызывает функцию, что ей передаёт и как складываются вердикты
  • жизненный цикл desync-функции — восемь стадий, общих для всех техник дурения: отсев транспорта, выбор данных, replay, вердикты
  • multisplit — тот же разрез, но без фейков и без обратного порядка; там же подробно разобран принцип seqovl
  • multidisorder — тот же обратный порядок, но без фейков; там seqovl тоже маркер, разобранный в разделе seqovl как маркер
  • fakedsplit — те же фейки вокруг каждой части, но части уходят в прямом порядке
  • hostfakesplit — разрез по границам имени хоста с подмешиванием фейкового имени
  • tcpseg — отправка произвольного диапазона данных, ограниченного двумя маркерами
  • oob — сегментация с urgent-байтом вместо разреза
  • fake — отдельный фейковый пакет, с которым fakeddisorder комбинируют в цепочке
  • blob — как объявлять и передавать данные для аргументов blob, pattern и seqovl_pattern
  • payload — распознавание типов протоколов, от которого зависит работа относительных маркеров
  • ts-and-fooling — что такое fooling, как фейк заставляют «умереть» до сервера и зачем нужен tcp_ts_up
  • verify-strategy — как честно проверить, работает ли подобранная комбинация
  • последовательность аргументов — как выстраивается цепочка инстансов в профиле
  • profile — где --lua-desync живёт среди остальных настроек
  • DPI и ТСПУ — как устроена инспекция трафика, против которой работает fakeddisorder

Источники: lua/zapret-antidpi.lua:908-1021, docs/manual.md:4165-4206, docs/manual.en.md:3984-4025 из репозитория zapret2.


🤖 Эти статьи открыты — можно обучать на них ИИ

При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: исходник этой заметки · скачать весь репозиторий одним zip-архивом.