multidisorder — TCP-сегментация в обратном порядке (zapret2 / nfqws2)
Коротко и по-простому
Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке Host: запроса. ТСПУ (Технические средства противодействия угрозам — оборудование DPI, то есть глубокой инспекции трафика, которое стоит у провайдера) ищет это имя в первом пакете соединения и по нему решает, что делать с соединением дальше.
HTTP-запросы и TLS-соединения передаются по протоколу TCP, а TCP отправляет данные кусками — сегментами. У каждого байта в соединении есть порядковый номер (sequence number, коротко seq), и сервер раскладывает принятые куски в свой буфер по этим номерам. Если пришёл кусок из середины или из конца запроса, сервер держит его в буфере и ждёт недостающие: приложению данные отдаются только тогда, когда набралась непрерывная последовательность байтов. Поэтому порядок, в котором куски доехали, серверу безразличен — исходный поток он соберёт всё равно. Почему такую сборку гарантирует именно TCP, а у UDP её нет, объяснено в нюансе «Работает только с TCP — и почему» статьи про multisplit.
На этой особенности TCP и построен multidisorder (по-английски disorder — «беспорядок»). Движок Zapret 2 (nfqws2) перехватывает исходящий пакет с ClientHello или HTTP-запросом, а multidisorder делает три шага. Сначала режет данные пакета в заданных местах — так же, как multisplit, например посередине домена второго уровня, то есть главной части имени сайта (у www.youtube.com это youtube). Места разреза задаёт аргумент pos: число («первые N байт уходят отдельным сегментом») или маркер, привязанный к устройству запроса, например midsld — середина домена второго уровня; маркеров можно перечислить несколько через запятую, все они разобраны в разделе Маркеры позиций (pos). Потом отправляет получившиеся куски отдельными TCP-сегментами в обратном порядке — от последнего к первому, в порядке убывания seq; каждый сегмент несёт свой номер seq, соответствующий месту куска в исходном запросе. В конце выбрасывает перехваченный оригинал, чтобы те же данные не ушли второй раз. Подробно про обратный порядок — в разделе Обратный порядок отправки — суть disorder.
Сервер получает все куски, кладёт их в буфер по номерам seq и, когда приходит самый первый кусок, получает непрерывный поток — ровно тот запрос, который отправил браузер, — и отвечает как обычно. DPI видит другое: сначала конец запроса без начала, а имя сайта разорвано между пакетами. Потоковый DPI (тот, что разбирает данные по мере поступления) при таком порядке может потерять контекст, попакетный (разбирающий каждый пакет отдельно) не находит сигнатуру — заранее известную строку — целиком ни в одном сегменте, а DPI, который умеет пересобирать поток, теоретически справится, но у многих из них на практике ограничены буфер или время ожидания, и они не дожидаются всех частей. Гарантий здесь нет, всё зависит от конкретного DPI; подробнее — в разделе Почему disorder ломает DPI.
Команда из быстрого старта multidisorder:pos=midsld режет ClientHello посередине домена второго уровня и первой отправляет вторую часть. ТСПУ видит конец запроса раньше начала; если DPI не пересобирает поток, пришедший не по порядку, имя целиком он не видит. Сервер держит пришедшую вперёд часть в буфере, пока не придёт первая. Как читать схему: строки — пакеты в порядке отправки, подписанные в тех же обозначениях, что и таблицы в этой статье; по горизонтали — место данных в TCP-потоке (номер последовательности, seq); в колонке справа — что сделал с пакетом сервер; внизу — буфер сервера, в который принятые куски встают по своим seq.
Самая короткая команда включает multidisorder со значениями по умолчанию: один разрез после второго байта (pos=2), обработка только распознанных протоколов (payload=known) и только исходящих пакетов (dir=out). Строку --lua-desync=… пишут в профиль — набор правил Zapret 2 для определённого трафика:
--lua-desync=multidisorderДля HTTPS обычно берут типовой вариант --payload=tls_client_hello --lua-desync=multidisorder:pos=midsld; здесь --payload=tls_client_hello значит «срабатывать только на TLS ClientHello». Остальные готовые команды собраны в разделах Быстрый старт и Практические примеры.
Кроме обратного порядка отправки, multidisorder отличается от multisplit приёмом seqovl (Sequence Overlap, «перекрытие по номерам последовательности»). В multisplit он выводит лишние байты фальшивки за левую границу окна приёма (окно — диапазон номеров, которые сервер готов принять), и сервер их выбрасывает сразу; так устроен приём в multisplit. В multidisorder всё иначе. К куску запроса, который в оригинале стоит вторым, слева приписывают несколько байт паттерна (фейка) и сдвигают его seq назад на ту же длину, так что паттерн занимает номера, принадлежащие первому куску. Этот сегмент уходит предпоследним и попадает в буфер сокета — место, где стек сервера держит принятые байты, пока приложение их не забрало. Последним уходит первый кусок запроса: он приходит с теми же номерами и на Linux, BSD и macOS перезаписывает паттерн настоящими байтами, так что приложение получает верный запрос. Фейк здесь пропадает потому, что его перезаписывает следующий сегмент. Поэтому на Windows-сервере техника с seqovl ломается: он сохраняет первые полученные данные и не перезаписывает их перекрывающимся сегментом, поэтому вместо запроса получит мусор из паттерна. Без seqovl multidisorder работает на всех системах — простое разупорядочивание от поведения при перекрытии не зависит. Раскладка по байтам — в разделе seqovl — перезапись буфера сокета через перекрытие, сравнение с multisplit — в таблице Отличие seqovl от multisplit, про Windows — в разделе Ограничение: Windows-серверы. Если паттерн сделать похожим на начало TLS record (заголовка записи TLS), DPI может принять такие байты за легитимный TLS-трафик (пример 4).
Когда multidisorder помогает, а когда нет. Он рассчитан на DPI, который не справляется со сборкой сегментов, пришедших вразнобой; против DPI, умеющего пересобирать поток, одного обратного порядка может не хватить, и его сочетают с fake — отдельным пакетом-обманкой с ложными данными (примеры 12 и 13 в разделе Практические примеры). Сработает ли комбинация у вашего провайдера, проверяйте по чек-листу честной проверки. Как и multisplit, multidisorder работает только с TCP: UDP-трафик, включая QUIC, так не режут (нюанс «Работает только с TCP»). Есть и ловушка с fooling — так называют намеренную порчу заголовков пакета (TTL, номера seq или ack, TCP-опцию MD5 — в nfqws2 это tcp_md5), обычно чтобы сервер отбросил такой пакет, а DPI его прочитал; подробно — Что такое fooling. В multidisorder fooling применяется ко всем отправляемым сегментам, а не только к фейкам, как у fakeddisorder. Если задать, например, tcp_ack=-66000, настоящие данные тоже получат неверный ack, и Linux-сервер их отбросит: на стенде (сентябрь 2026) соединение устанавливалось только с задержкой, после того как клиент повторно передал ClientHello целиком, без разреза. Разбор — в нюансе 9. Fooling применяется ко ВСЕМ сегментам.
У multidisorder есть близкие родственники. multisplit режет так же, но отправляет куски по порядку, принимает в seqovl только число, а сам seqovl работает и на Windows-серверах. fakedsplit и fakeddisorder делают один разрез и подмешивают к настоящим кускам фейковые сегменты, причём fakeddisorder, как и multidisorder, отправляет в обратном порядке. hostfakesplit режет запрос по границам имени хоста и подмешивает фейковое имя, tcpseg отправляет диапазон данных между двумя маркерами, а oob вместо разреза вставляет «срочный» (urgent) байт. multidisorder_legacy — вариант, совместимый с алгоритмом nfqws1: при многопакетных запросах порядок сегментов у него другой (см. Ключевое отличие: попакетная обработка vs целый reasm). Все отличия сведены в таблицу в разделе Отличия от других функций сегментации.
Оглавление
- Коротко и по-простому
- Справка: где функция в коде и родственники
- Зачем нужен multidisorder
- Быстрый старт
- Откуда берутся данные для нарезки
- Маркеры позиций (pos)
- Обратный порядок отправки — суть disorder
- seqovl — перезапись буфера сокета через перекрытие
- Полный список аргументов
- Порядок отправки сегментов
- Поведение при replay / reasm
- Автосегментация по MSS
- Место в общем скелете
- Псевдокод алгоритма
- Нюансы и подводные камни
- 1. Работает только с TCP
- 2. Разрез в самом начале данных отбрасывается
- 3. Все маркеры могут не разрешиться
- 4. seqovl как маркер тоже может не разрешиться
- 5. seqovl НЕ работает на Windows-серверах
- 6. ovl = seqovl - 1: размер перекрытия на 1 меньше
- 7. seqovl >= pos[1] → отмена seqovl
- 8. nodrop создаёт дублирование
- 9. Fooling применяется ко ВСЕМ сегментам
- 10. Алгоритм отличается от nfqws1
- 11. Порядок инстансов важен
- Отличия от других функций сегментации
- Миграция с nfqws1
- Практические примеры
- 📚 См. также
Справка: где функция в коде и родственники
Паспорт функции для тех, кто сверяется с исходниками zapret2: в каком файле и на какой строке лежит код, какой ключ первой версии zapret она заменяет и как объявлена.
Файл: lua/zapret-antidpi.lua:597
nfqws1 эквивалент: --dpi-desync=multidisorder
Сигнатура: function multidisorder(ctx, desync)
multidisorder — функция TCP-сегментации в zapret2, которая берёт текущий payload (или reasm, или blob), разрезает его на несколько TCP-сегментов по заданным позициям и отправляет их в обратном порядке (от последнего к первому). Обратный порядок (disorder) эксплуатирует поведение TCP-стека: при получении внеочередных сегментов стек буферизирует их и собирает поток, а DPI зачастую не справляется с реассемблированием разупорядоченных данных. После успешной отправки выносит VERDICT_DROP, чтобы оригинальный пакет не ушёл.
Проще говоря
Payload — полезные данные пакета без сетевых заголовков: сам HTTP-запрос или сам ClientHello. Reasm — те же данные, собранные движком из нескольких пакетов, если запрос в один пакет не влез. Blob — заранее заготовленные данные, которые можно подсунуть вместо настоящих. Какой из трёх источников выбирается, разобрано в разделе Откуда берутся данные для нарезки. Реассемблирование — склейка сегментов обратно в один поток; «DPI не справляется с реассемблированием разупорядоченных данных» значит, что он не умеет правильно склеить сегменты, пришедшие не по порядку.
VERDICT_DROP— решение «перехваченный пакет не отправлять»; как движок складывает такие решения от разных функций, описано в стадии 8 жизненного цикла.
Родственные функции: multisplit (прямой порядок), fakedsplit (с фейками), fakeddisorder (фейки + обратный порядок), hostfakesplit (по hostname), tcpseg (диапазон), oob (urgent byte), multidisorder_legacy (совместимый с nfqws1 алгоритм).
Общий для всех техник дурения порядок работы — восемь стадий от отсева чужого транспорта до вердикта — разобран в жизненный цикл desync-функции; здесь описано только то, чем multidisorder от этого скелета отличается.
Зачем нужен multidisorder
DPI анализирует TCP-поток, пытаясь собрать полный payload и найти в нём сигнатуры (hostname в HTTP, SNI в TLS). Разрезание payload на сегменты уже усложняет работу DPI (multisplit), но обратный порядок отправки создаёт дополнительную проблему:
- DPI не реассемблирует out-of-order сегменты: многие DPI работают потоково (stream-based) и ожидают сегменты в порядке sequence number. Получив сначала хвост потока, DPI может потерять контекст или не дождаться головы
- DPI не может сопоставить hostname: если разрез проходит через SNI/Host, а сегменты приходят задом наперёд, ни попакетный, ни потоковый DPI не найдёт сигнатуру
- seqovl в режиме disorder перезаписывает буфер: в отличие от multisplit, где seqovl работает через выход за TCP window, в disorder фейковые данные сначала попадают в буфер сокета, а затем перезаписываются реальными данными из последнего сегмента
Сервер при этом корректно собирает поток — TCP-стек буферизирует внеочередные сегменты и отдаёт данные приложению только после сборки непрерывной последовательности.
Термины из списка выше по-русски. Sequence number (seq) — порядковый номер байта в TCP-потоке. Out-of-order («внеочередной») сегмент — тот, что пришёл раньше сегментов, стоящих перед ним по номерам. Реассемблирование — склейка сегментов обратно в один поток по этим номерам. Потоковый (stream-based) DPI разбирает данные по мере поступления, попакетный — каждый пакет отдельно, не склеивая сегменты; чем они отличаются в работе, разобрано в разделе Почему disorder ломает DPI. Про seqovl в multisplit и разницу с multidisorder — в разделе seqovl — перезапись буфера сокета через перекрытие.
Проще говоря
Обратный порядок — это те же куски запроса, отправленные от конца к началу. Сервер по правилам TCP дождётся недостающего начала и соберёт запрос. DPI, которому нужны куски по порядку, может потерять нить. Отдельный трюк
seqovlв этой технике работает иначе, чем в multisplit: он опирается на перезапись данных в буфере сервера.
multidisorder vs multisplit: disorder работает за счёт разупорядочивания, а не за счёт линейного дробления. Для замешивания фейковых сегментов (отдельные пакеты с fooling) есть fakeddisorder.
Сводное сравнение с multisplit, fakedsplit и fakeddisorder по всем признакам — в таблице раздела Отличия от других функций сегментации. Про fooling (порчу заголовков, из-за которой сервер отбрасывает пакет) — в разделе D) Standard fooling.
Быстрый старт
Ниже четыре готовые строки для профиля Zapret 2, от самой короткой к более сложным. Флаг --payload=… работает как фильтр для всех вызовов --lua-desync, которые идут после него (до следующего --payload или до конца профиля): они срабатывают только на данных указанного типа. Поэтому --payload пишут перед --lua-desync, а не после (подробно — в последовательность аргументов).
Минимально (разрез по позиции 2, payload=known, dir=out):
--lua-desync=multidisorderЗдесь payload=known означает «только распознанные протоколы», а dir=out — «только исходящие пакеты»; оба аргумента разобраны в разделах B) Standard direction и C) Standard payload. Без pos действует дефолт pos=2: первые 2 байта уходят отдельным сегментом, остаток — вторым, и сначала отправляется остаток.
Типовой TLS-разрез:
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsldФункция срабатывает только на TLS ClientHello, режет его посередине домена второго уровня на два сегмента, и вторая половина уходит раньше первой.
TLS с seqovl (маркер):
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld:seqovl=midsld-1К сегменту с хвостом ClientHello слева приклеивается паттерн-фейк (seqovl=midsld-1 — маркер с арифметикой, см. seqovl как маркер), а последним уходит начало запроса и перезаписывает этот паттерн в буфере сервера. Работает такое не на всех серверах: Ограничение: Windows-серверы.
HTTP с разрезом по hostname:
--payload=http_req --lua-desync=multidisorder:pos=host,midsld,endhostТри разреза — в начале имени хоста, посередине домена второго уровня и сразу после имени — дают 4 сегмента, которые уходят от последнего к первому.
Откуда берутся данные для нарезки
Внутри multidisorder данные (data) выбираются в следующем порядке приоритетов:
1. blob_or_def(desync, desync.arg.blob) — если задан blob= и он существует
2. desync.reasm_data — если есть реассемблированные данные (multi-packet payload)
3. desync.dis.payload — текущий пакет (fallback)
Первая строка — самый высокий приоритет. blob_or_def достаёт содержимое blob — заранее заготовленных данных (как их объявлять, описано в заметке blob). reasm_data — реассемблированные данные, то есть запрос, склеенный движком из нескольких TCP-пакетов, если в один пакет он не влез. dis.payload — тело текущего перехваченного пакета (dis — диссект, то есть пакет, разобранный движком на поля; его устройство описано в структура desync и диссекта). Та же цепочка общая для большинства функций дурения и описана в стадии 4 жизненного цикла; подробный разбор трёх источников есть в статье про multisplit: Откуда берутся данные для нарезки.
Следствие: все маркеры pos, seqovl и прочие аргументы применяются именно к тем данным, которые реально выбраны. Если вы задали blob=myblob, маркеры вроде midsld будут работать только если myblob содержит валидный TLS/HTTP payload, который zapret может распознать.
Поле desync.l7payload хранит тип payload — название распознанного протокола вроде tls_client_hello или http_req; по нему функция решает, найдётся ли в данных место для относительного маркера.
Проще говоря
Функция сначала спрашивает: «мне явно подсунули blob? — режу его». Если нет: «есть собранный из кусков payload? — режу его». Если и его нет: «режу тело текущего пакета». Маркеры и seqovl всегда работают по тем данным, которые выиграли этот выбор.
Маркеры позиций (pos)
pos — главный аргумент multidisorder. Определяет где внутри payload будет произведён разрез. Задаётся как строка со списком маркеров через запятую.
Маркер — это смещение от начала данных, считая с нуля: разрез происходит перед байтом с таким номером. Отсюда простое правило: pos=N означает «первые N байт становятся отдельным сегментом». pos=1 → первый сегмент длиной 1 байт, pos=5 → 5 байт, дефолтный pos=2 → 2 байта. Внутри Lua те же позиции хранятся как 1-based индексы строки, то есть с нумерацией от единицы (resolve_multi_pos прибавляет единицу), но в командной строке считать нужно от нуля. Набор маркеров и арифметика здесь такие же, как у multisplit (список маркеров в таблицах совпадает); главное отличие — готовые куски потом уходят в обратном порядке.
Проще говоря
В командной строке пишите, сколько байт должно уйти в кусок, который в запросе стоит первым; отправлен этот кусок будет последним. Сдвиг на единицу внутри Lua пригодится, только когда читаете код функции (раздел Псевдокод алгоритма) или разбираетесь, почему
pos=0не срабатывает.
Типы маркеров
Маркеры бывают трёх видов. Абсолютные отсчитывают байты от начала или от конца данных и не зависят от их содержимого. Относительные ищут место по смыслу — начало имени хоста, середину домена — и поэтому работают, только если движок распознал протокол.
| Тип | Описание | Пример |
|---|---|---|
| Абсолютный положительный | Смещение от начала payload, считая с 0. pos=N → первые N байт становятся отдельным сегментом | 1, 5, 100 |
| Абсолютный отрицательный | Смещение от конца payload. -1 = последний байт | -1, -10, -50 |
| Относительный | Логическая позиция внутри распознанного payload. Привязана к структуре протокола | midsld, host, sniext |
Относительные маркеры
В правой колонке указано, в каких типах payload маркер имеет смысл: http_req — HTTP-запрос, tls_client_hello — TLS ClientHello. Как движок определяет тип, описано в заметке о типах payload. SLD (second-level domain) — домен второго уровня: в www.example.com это example. SNI extension — расширение ClientHello, в котором браузер передаёт имя сайта.
| Маркер | Описание | Для каких payload |
|---|---|---|
method | Начало HTTP-метода (GET, POST, HEAD, PUT и т.д.). Обычно позиция 0, но может стать 1-2 при использовании http_methodeol | http_req |
host | Первый байт имени хоста (Host: в HTTP, SNI в TLS) | http_req, tls_client_hello |
endhost | Байт, следующий за последним байтом имени хоста. Т.е. host..endhost-1 = полный hostname | http_req, tls_client_hello |
sld | Первый байт домена второго уровня (SLD). Для www.example.com — это e в example | http_req, tls_client_hello |
endsld | Байт, следующий за последним байтом SLD. Для example.com — это . после example | http_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 extensions | tls_client_hello |
Арифметика маркеров
Маркер можно сдвинуть на несколько байт вперёд или назад, если разрез нужен рядом с ним, а не точно в нём.
К любому маркеру можно прибавить (+) или вычесть (-) целое число:
midsld+1 — один байт ПОСЛЕ середины SLD
midsld-1 — один байт ДО середины SLD
endhost-2 — два байта до конца hostname
method+2 — два байта после начала метода (разрежет "GET " после "GE")
sniext+1 — один байт после начала SNI extension data
host+3 — три байта после начала hostname
-1 — последний байт payload (абсолютный, не относительный)
Арифметика работает и с абсолютными маркерами, хотя это избыточно (5+3 = 8).
Пример списка маркеров
pos=100,midsld,sniext+1,endhost-2,-10
Здесь 5 маркеров → payload разрежется максимум на 6 частей (если все маркеры успешно разрешатся и дадут различные позиции).
В списке есть абсолютный маркер от начала (100), три относительных (midsld, sniext+1, endhost-2) и абсолютный от конца (-10). Сколько бы частей ни получилось, отправляются они в обратном порядке — от хвоста к началу.
Как маркеры разрешаются в коде
Разрешить маркер — значит превратить его название в конкретный номер байта в выбранных данных.
Внутри multidisorder вызывается:
local pos = resolve_multi_pos(data, desync.l7payload, spos)Функция resolve_multi_pos:
- Разбивает строку
sposпо запятым - Для каждого маркера вызывает
resolve_pos(blob, l7payload_type, marker) - Если маркер не может быть разрешён (например,
midsldдляunknownpayload) — он молча пропускается - Результаты дедуплицируются и сортируются
- Возвращается массив уникальных позиций, уже переведённых в 1-based индексы Lua (маркер
1превращается в позицию2, маркер0— в позицию1)
Затем вызывается:
delete_pos_1(pos) -- удалить Lua-позицию 1, то есть маркер 0Дедупликация — это удаление повторов. Зачем нужен delete_pos_1, объяснено ниже в списке нюансов и в нюансе 2. Разрез в самом начале данных отбрасывается.
Важные нюансы pos
- Разрез в самом начале данных отбрасывается.
delete_pos_1удаляет Lua-позицию 1, которая соответствует маркеру0: первый сегмент вышел бы пустым. Считать маркеры нужно с нуля, поэтому под удаление попадаетpos=0, аpos=1(первый байт отдельным сегментом) работает и применяется часто. Частая жертва этого правила —pos=methodдля HTTP: метод обычно лежит на смещении 0, разреза не произойдёт; нужен сдвиг вродеpos=method+2 - Дублирующиеся позиции объединяются.
pos=5,5,5=pos=5 - Неразрешимые маркеры пропускаются. Если
midsldне разрешается (payload = unknown), он просто исчезает из списка. Если все маркеры не разрешились — multidisorder ничего не делает (логирует “no valid split positions”) - Позиции сортируются. Независимо от порядка записи,
pos=100,5,50будет обработано как5,50,100 - По умолчанию
pos=2. Еслиposне задан, payload делится на 2 части: первые 2 байта отдельным сегментом, весь остаток — вторым
Проще говоря
Обратный порядок отправки — суть disorder
Ключевое отличие multidisorder от multisplit: сегменты отправляются от последнего к первому (в порядке убывания sequence number). Цикл в multidisorder_send:
for i=#pos,0,-1 domultisplit: отправка 1 -> 2 -> 3 -> 4 (прямой порядок)
multidisorder: отправка 4 -> 3 -> 2 -> 1 (обратный порядок)
Цикл идёт от #pos (число позиций разреза, оно же номер последнего куска) вниз до 0. Кусков на один больше, чем разрезов: они нумеруются с 0 (начало данных) до #pos (хвост). Поэтому отправка начинается с хвоста и заканчивается началом. У multisplit порядок прямой.
Проще говоря
Куски запроса нумеруются 0, 1, 2, …;
multisplitотправляет 0 → 1 → 2, аmultidisorder— 2 → 1 → 0. У каждого куска в заголовке свой seq, по которому сервер потом ставит его на место.
Почему disorder ломает DPI
TCP-стек сервера спроектирован для работы с внеочередными сегментами — он буферизирует их и ждёт недостающие части. DPI, как правило, не имеет такой роскоши:
- Потоковые DPI обрабатывают данные по мере поступления. Получив последний сегмент первым, DPI видит данные без начала (без заголовков HTTP/TLS) и не может распознать протокол
- Попакетные DPI анализируют каждый пакет отдельно. В каждом отдельном сегменте нет полной сигнатуры
- DPI с реассемблированием теоретически могут собрать поток, но на практике многие имеют ограниченный буфер или timeout и не дожидаются всех частей
DPI с реассемблированием может собрать поток, поэтому против него обратный порядок может сработать лишь там, где у DPI не хватает буфера или времени ждать все части. Результат проверяют на своём провайдере по чек-листу честной проверки.
Проще говоря
Потоковый DPI ждёт данные по порядку и теряет нить, попакетный ищет имя в одном пакете и не находит его целиком, а DPI с пересборкой потока может справиться, если у него хватает буфера и терпения.
Ограничение: Windows-серверы
Перекрытие номеров — ситуация, когда два принятых сегмента претендуют на одни и те же байты потока; что сервер делает в этом случае, зависит от его операционной системы.
Техника seqovl в режиме disorder не работает с Windows-серверами. При перекрытии sequence numbers:
- Linux/BSD/macOS: более поздний сегмент перезаписывает ранее буферизированные данные
- Windows: сохраняет первые полученные данные, игнорируя перекрывающиеся
Это означает, что seqovl_pattern, записанный в буфер предпоследним сегментом, на Windows не будет перезаписан реальными данными из последнего сегмента — сервер получит мусор.
Без seqovl multidisorder работает нормально на всех системах — простое разупорядочивание не зависит от поведения при перекрытии.
Проще говоря
На Windows-сервере фейк не затирается настоящими данными: он остаётся в буфере, и запрос приходит испорченным. Если целевой сервер работает под Windows,
seqovlвmultidisorderне нужен, а без него техника работает везде. Вmultisplitseqovlустроен иначе и на Windows работает: см. таблицу в разделе Отличие seqovl от multisplit и нюанс 5.
seqovl — перезапись буфера сокета через перекрытие
В multidisorder техника seqovl работает принципиально иначе, чем в multisplit. Здесь seqovl эксплуатирует переписывание данных в буфере сокета при получении перекрывающихся сегментов.
seqovl (Sequence Overlap, «перекрытие по номерам последовательности») — приём, при котором к настоящему сегменту слева дописываются лишние байты-фейк, а его sequence number сдвигается назад на их длину; как это устроено в multisplit, разобрано в разделе Принцип работы seqovl. Буфер сокета — место, где стек сервера держит принятые байты, пока приложение их не забрало; байты, пришедшие раньше времени, лежат там, пока не придут недостающие. В multisplit фейк за левой границей окна приёма сервер выбрасывает сразу. В multidisorder фейк остаётся в буфере и затирается позднее следующим сегментом.
Принцип работы seqovl в disorder
Рассмотрим payload из 2 частей: pos=100 (одна точка разреза).
На схемах ниже ЧАСТЬ_1 и ЧАСТЬ_2 — два куска запроса, PATTERN — паттерн-фейк (по умолчанию нули), i — номер куска в оригинальном порядке (0 — начало данных), seq — номер первого байта сегмента, len — длина сегмента в байтах, число в скобках — размер блока в байтах. Значение seqovl в примере — 50, но паттерна приписывается на один байт меньше, то есть ovl = 49: причина разобрана в разделе Нюанс реализации: ovl = seqovl - 1.
Payload (300 байт): [ЧАСТЬ_1: 100 байт][ЧАСТЬ_2: 200 байт]
^pos=100
seqovl=midsld (разрешился в 50), ovl = 50 - 1 = 49
Порядок отправки (обратный, i от #pos до 0):
Шаг 1 (i=1): ЧАСТЬ_2 с seqovl!
К ЧАСТЬ_2 слева приписывается 49 байт PATTERN
part = [PATTERN(49)][ЧАСТЬ_2(200)]
seq = pos_start - 1 - ovl = 100 - 1 - 49 = 50
len = 249
Отправляется первым
Шаг 2 (i=0): ЧАСТЬ_1 без seqovl
part = [ЧАСТЬ_1(100)]
seq = 0
len = 100
Отправляется последним
Сначала уходит ЧАСТЬ_2 с приклеенным слева паттерном, потом ЧАСТЬ_1 — без него. Схема ниже показывает, что при этом лежит в буфере сервера.
Что происходит на сервере (Linux/BSD):
t1: приходит [PATTERN(49)][ЧАСТЬ_2(200)] seq=50 len=249
Буфер сокета:
позиции: 0 50 100 300
[ пусто ][PATTERN(49)][ЧАСТЬ_2(200)........]
^--- PATTERN занимает позиции 50..98
^--- ЧАСТЬ_2 начинается с позиции 99
Данные НЕ выдаются приложению — нет непрерывной последовательности от 0
t2: приходит [ЧАСТЬ_1(100)] seq=0 len=100
Буфер сокета (Linux/BSD — позднее ПЕРЕЗАПИСЫВАЕТ):
позиции: 0 100 300
[ЧАСТЬ_1(100)....................][ЧАСТЬ_2(200)........]
^--- ЧАСТЬ_1 занимает позиции 0..99
PATTERN ПЕРЕЗАПИСАН реальными данными!
Непрерывная последовательность 0..299 -> выдаётся приложению корректно
Пока ЧАСТИ_1 нет, сервер данные приложению не отдаёт: непрерывной последовательности от нуля нет. Когда ЧАСТЬ_1 приходит, она занимает те же номера, что и PATTERN, и на Linux/BSD затирает его: приложение получает непрерывные байты 0..299, и PATTERN в них уже нет.
На Windows-сервере:
t2: приходит [ЧАСТЬ_1(100)] seq=0 len=100
Буфер сокета (Windows — СОХРАНЯЕТ старое):
позиции: 0 50 100 300
[ЧАСТЬ_1(50)][PATTERN(49)][ЧАСТЬ_2(200)........]
^--- только байты 0..49 из ЧАСТЬ_1 записались
^--- байты 50..98 остались от PATTERN (мусор!)
Приложение получает повреждённые данные!
Проще говоря
Сервер сначала получил фейк, потом настоящие данные поверх. Linux, BSD и macOS принимают более позднее и затирают фейк. Windows оставляет первое: байты 50..98 остаются паттерном, и приложение получает испорченный запрос.
Отличие seqovl от multisplit
Таблица ниже сравнивает приём с одним названием в двух функциях по пяти признакам: как устроен механизм, к какому сегменту он применяется, что принимает в качестве значения, кто отбрасывает фейк и работает ли он на Windows. Как это устроено в multisplit, — в разделе seqovl — скрытый фейк внутри сегмента.
| Аспект | seqovl в multisplit | seqovl в multidisorder |
|---|---|---|
| Механизм | Выход за левую границу TCP window | Перезапись буфера перекрывающимся сегментом |
| К какому сегменту | 1-й отправляемый (1-й в оригинале, i==0) | Предпоследний отправляемый (2-й в оригинале, i==1) |
| Тип значения | Только число | Маркер (число, имя маркера, маркер+арифметика) |
| Кто отбрасывает фейк | TCP-стек (данные за пределами window) | Перезапись следующим сегментом (последним отправляемым) |
| Работает на Windows | Да (TCP window — универсальный механизм) | Нет (Windows не перезаписывает буфер) |
Проще говоря: в multisplit фейк выкидывается сразу, как байты за окном приёма, поэтому Windows не мешает. В multidisorder фейк сначала принимается в буфер, и всё зависит от того, перезапишет ли операционная система его настоящими данными.
seqovl как маркер
Маркер, напомним, — название места в данных (число, midsld, host+2), которое функция превращает в номер байта; как это делается, разобрано в разделе Как маркеры разрешаются в коде.
В отличие от multisplit, где seqovl принимает только число, в multidisorder seqovl разрешается через resolve_pos() — полноценный маркер:
seqovl = resolve_pos(data, desync.l7payload, desync.arg.seqovl)Это означает, что поддерживаются:
seqovl=5 — число (абсолютная позиция, как в multisplit)
seqovl=midsld-1 — маркер с арифметикой (типичное использование)
seqovl=host+2 — маркер с арифметикой
seqovl=sld — маркер без арифметики
seqovl=-10 — отрицательная позиция (от конца данных)
Типичный паттерн: pos=midsld + seqovl=midsld-1. Разрез по середине SLD, а seqovl перекрывает почти всю первую часть.
Проще говоря: длина фейка задаётся не числом байт, а местом в запросе: midsld-1 — «на один байт левее середины домена второго уровня».
Если маркер не резолвится (например, seqovl=midsld-1 для unknown payload) — seqovl отменяется, но сегментация в обратном порядке всё равно происходит. В лог записывается “seqovl cancelled because could not resolve marker”.
Валидация seqovl: должен быть меньше pos[1]
seqovl ограничен сверху: перекрытие не должно вылезать за начало данных первой части запроса.
Из multidisorder_send:
if seqovl>=pos[1] then
DLOG("multidisorder: seqovl cancelled because seqovl "..
(seqovl-1).." is not less than the first split pos "..(pos[1]-1))seqovl (разрешённое значение маркера) обязательно должен быть строго меньше pos[1] (первой позиции разреза). Если это условие не выполнено — seqovl отменяется (но сегментация всё равно происходит, просто без перекрытия).
Почему: seqovl приписывается к сегменту i==1 (ЧАСТЬ_2, от pos[1] до конца или до pos[2]-1). Перекрытие заползает назад на ovl байт. Если seqovl >= pos[1], перекрытие вылезло бы за начало данных первого сегмента, что не имеет смысла.
Пример:
pos=10,50 -> pos[1]=10
seqovl=midsld -> разрешился в 8 -> OK (8 < 10)
seqovl=midsld -> разрешился в 10 -> CANCELLED (10 >= 10, не строго меньше)
seqovl=midsld -> разрешился в 15 -> CANCELLED (15 >= 10)
Проще говоря: если разрешённое значение seqovl оказалось не меньше первой позиции разреза, seqovl отключается, а сегментация в обратном порядке остаётся. В логе тогда будет строка про “seqovl cancelled”. Подробнее — нюанс нюанс 7.
Нюанс реализации: ovl = seqovl - 1
В коде multidisorder_send есть важная деталь:
ovl = seqovl - 1Реальный размер перекрытия на 1 байт меньше разрешённого значения маркера. Это связано с тем, что seqovl возвращается как позиция (1-based в Lua), а ovl используется как размер смещения (0-based). Для пользователя это означает:
seqovl=midsld (разрешился, скажем, в 50)
-> ovl = 49
-> К сегменту приписывается 49 байт pattern слева
-> TCP seq уменьшается на 49
Следствие: если seqovl разрешится в 1, то ovl = 0 — seqovl фактически не применяется. Минимальный эффективный seqovl — значение 2 (ovl = 1, 1 байт перекрытия).
Проще говоря: seqovl — это номер позиции, а не число байт; байт фейка всегда на один меньше. Поэтому в описании аргумента seqovl=5 дают 4 байта фейка слева (раздел A) Собственные аргументы multidisorder).
seqovl_pattern
Паттерн, которым заполняется seqovl-область (ovl байт слева от реальных данных). По умолчанию — 0x00 (нули).
В multidisorder seqovl_pattern — это имя blob. Паттерн повторяется функцией pattern() до нужной длины ovl (= seqovl - 1).
Blob можно записать прямо в команде шестнадцатеричной строкой (inline hex, 0x…) или заранее объявить флагом --blob и сослаться на него по имени; подробно — в заметке blob. Пример 0x1603030000 — байты, похожие на начало TLS record, заголовка записи TLS.
# Inline hex blob (маскировка под начало TLS record)
--lua-desync=multidisorder:pos=midsld:seqovl=midsld-1:seqovl_pattern=0x1603030000
# Предзагруженный blob
--blob=tlspat:0x1603030100 \
--lua-desync=multidisorder:pos=midsld:seqovl=midsld-1:seqovl_pattern=tlspatЕсли optional задан и blob seqovl_pattern отсутствует — используется нулевой паттерн (операция не отменяется).
Полный список аргументов
Аргументы делятся на собственные аргументы multidisorder (раздел A) и стандартные (разделы B–H): их понимают и другие функции дурения, а общий обзор есть в desync.
Формат вызова:
--lua-desync=multidisorder[:arg1[=val1][:arg2[=val2]]...]
Все val приходят в Lua как строки. Если =val не указан, значение = пустая строка "" (в Lua это truthy), поэтому флаги пишутся просто как :optional, :nodrop, :tcp_ts_up.
Truthy в Lua — значение, которое условие считает истинным; поэтому флагу достаточно просто появиться в строке.
A) Собственные аргументы multidisorder
pos
- Формат:
pos=<marker[,marker2,...]> - Тип: строка со списком маркеров через запятую
- По умолчанию:
"2" - Описание: Точки разреза. Каждый маркер определяет позицию, по которой payload будет разрезан. N маркеров → до N+1 сегментов. Сегменты отправляются в обратном порядке. Виды маркеров — в разделе Маркеры позиций (pos)
- Примеры:
pos=2— разрез после 2-го байта (дефолт)pos=1— первый байт уходит отдельным сегментомpos=midsld— разрез посередине SLDpos=1,midsld— два разреза: после 1-го байта и посередине SLD → 3 сегментаpos=host,midsld,endhost-2,-10— четыре разреза → до 5 сегментовpos=method+2— после первых 2 символов HTTP-метода
seqovl
- Формат:
seqovl=<marker>(маркер, число, или маркер+арифметика) - Тип: маркер (в отличие от multisplit, где только число). Разрешается через
resolve_pos() - По умолчанию: не задан (нет seqovl)
- Описание: Применяется к сегменту
i==1(2-й в оригинальном порядке, предпоследний отправляемый). К данным этого сегмента слева добавляетсяseqovl-1байтseqovl_pattern, а TCPth_seqуменьшается наseqovl-1. Последний отправляемый сегмент (1-й в оригинале) перезаписывает фейковые данные в буфере сервера. Здесьth_seq— поле номера последовательности в TCP-заголовке. Механика — в разделе seqovl — перезапись буфера сокета через перекрытие - Ограничение: разрешённое значение seqovl должно быть строго меньше pos[1], иначе seqovl отменяется. Подробнее — Валидация seqovl
- Примеры:
seqovl=5— 4 байта фейка слева (ovl = 5-1)seqovl=midsld-1— типичное использование сpos=midsldseqovl=host+2— маркер с арифметикойseqovl=sld— маркер без арифметики
seqovl_pattern
- Формат:
seqovl_pattern=<blobName> - Тип: имя blob-переменной
- По умолчанию: один байт
0x00, повторяемый до длиныovl(= seqovl - 1) - Описание: Данные для заполнения seqovl-области. Blob повторяется функцией
pattern()до нужного размера - Поведение с
optional: еслиoptionalзадан и blob отсутствует — используется нулевой паттерн, seqovl не отменяется - Примеры:
seqovl_pattern=0x1603030000— inline hex (маскировка под TLS)seqovl_pattern=my_pattern_blob— предзагруженный blob
blob
- Формат:
blob=<blobName> - Тип: имя blob-переменной
- По умолчанию: не задан
- Описание: Заменить текущий payload/reasm на указанный blob и резать/слать его. Используется для отправки произвольных данных (фейковых payload, модифицированных ClientHello и т.д.). Порядок выбора данных — в разделе Откуда берутся данные для нарезки, про blob — в заметке blob
- Примеры:
blob=fake_default_tls— стандартный TLS-фейк (что внутри, см. стандартные blob-ы)blob=0xDEADBEEF— inline hexblob=my_custom_ch— предзагруженный blob
optional
- Формат:
optional(флаг, без значения) - Описание: Мягкий режим:
- Если задан
blob=...и blob отсутствует → multidisorder ничего не делает (тихий skip, без ошибок) - Если задан
seqovl_pattern=...и blob отсутствует → используется нулевой паттерн (seqovl не отменяется)
- Если задан
- Использование: защита от ошибок при использовании blob, которые могут отсутствовать (например, если blob генерируется другой функцией)
nodrop
- Формат:
nodrop(флаг, без значения) - Описание: После успешной отправки сегментов не выносить
VERDICT_DROP(вместо этого вернутьVERDICT_PASS— «пропустить пакет»). Это означает, что оригинальный пакет тоже будет отправлен (наряду с нарезанными сегментами) - Использование: для отладки, для отправки произвольных данных без блокировки оригинала
- Предупреждение: в боевых профилях
nodropобычно нежелателен — оригинал ещё раз уйдёт, что создаст дублирование и может ухудшить обход (см. нюанс 8)
B) Standard direction
Направление говорит, в какую сторону идёт пакет: от вашего компьютера к серверу (исходящий) или обратно (входящий).
| Параметр | Значения | По умолчанию |
|---|---|---|
dir | in, out, any | out |
Фильтр по направлению пакета. multidisorder по умолчанию работает только с исходящими (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, включаяunknownpayload=tls_client_hello,http_req— конкретные типыpayload=~unknown— инверсия: всё кроме unknown
Важно: лучше ставить --payload=... на уровне профиля (C-код, быстрее), а не полагаться только на Lua-фильтр.
Проще говоря: фильтр --payload= на уровне профиля работает в быстром ядре программы, написанном на C, а аргумент payload= проверяется уже в Lua, то есть медленнее. Поэтому основной отбор лучше делать на уровне профиля.
D) Standard fooling
Модификации L3/L4 заголовков. В multidisorder применяются ко всем отправляемым сегментам (в отличие от fakeddisorder, где fooling идёт только на фейки). Fooling — намеренная порча заголовков пакета, обычно для того, чтобы сервер отбросил фейк, а DPI его прочитал (подробно — Что такое fooling); L3/L4 — заголовки IP (сетевой уровень) и TCP (транспортный уровень). Многие опции из таблицы портят и настоящие данные: почему — в нюансе 9. Fooling применяется ко ВСЕМ сегментам.
| Параметр | Описание | Пример |
|---|---|---|
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 (по умолчанию 6 нулей) | 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 |
Заметка про tcp_ts_up: На Linux-серверах пакеты с инвалидным ACK стабильно отбрасываются только если TCP timestamp option идёт первой в заголовке. tcp_ts_up перемещает её в начало, обеспечивая корректную работу badseq-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 в рамках соединения (требует tracking — отслеживания соединения) | — |
ip_id применяется к каждому отправляемому сегменту (включая под-сегменты при MSS-сегментации). О под-сегментах — в разделе Автосегментация по MSS.
F) Standard ipfrag
IP-фрагментация поверх TCP-сегментации. Каждый TCP-сегмент дополнительно фрагментируется на уровне IP.
Проще говоря: TCP-сегмент, уже отрезанный multidisorder, при отправке ещё раз делится на части, но уже средствами протокола IP, а собирает их обратно получатель на уровне IP. Вместе с ipfrag_disorder получается двойной disorder, см. пример 11.
| Параметр | Описание | По умолчанию |
|---|---|---|
ipfrag[=func] | Включить IP-фрагментацию. Если без значения → ipfrag2 | — |
ipfrag_disorder | Отправить IP-фрагменты в обратном порядке | — |
ipfrag_pos_tcp=N | Позиция фрагментации TCP (кратно 8) | 32 |
ipfrag_pos_udp=N | Позиция фрагментации UDP (кратно 8). Для multidisorder бесполезно — он только TCP | 8 |
ipfrag_next=N | IPv6: next protocol во 2-м фрагменте (penetration атака на фаерволы) | — |
G) Standard reconstruct
Реконструкция — сборка готового raw-пакета («сырого», со всеми заголовками) перед отправкой.
| Параметр | Описание |
|---|---|
badsum | Испортить L4 (TCP) checksum при реконструкции raw-пакета. Сервер отбросит такой пакет |
H) Standard rawsend
Rawsend — отправка готовых сегментов в сеть самой функцией; в multidisorder её выполняет rawsend_payload_segmented (см. Автосегментация по MSS). Эти параметры управляют отправкой.
| Параметр | Описание |
|---|---|
repeats=N | Отправить каждый сегмент N раз (идентичные повторы) |
ifout=<iface> | Интерфейс для отправки (по умолчанию определяется автоматически) |
fwmark=N | Firewall mark (только Linux, nftables/iptables) — метка пакета для правил фаервола |
Порядок отправки сегментов
multidisorder всегда отправляет сегменты в обратном порядке — от последнего к первому (в порядке убывания TCP sequence). Цикл в multidisorder_send: for i=#pos,0,-1. Sequence каждого сегмента равен смещению его первого байта в исходном payload (без seqovl), поэтому сервер собирает поток без пропусков, хотя куски приходят в обратном порядке. Для сравнения — Порядок отправки сегментов в multisplit.
Пример с 3 позициями разреза (без seqovl)
Возьмём payload длиной 600 байт и три позиции разреза: pos=100,300,450. Они делят данные на четыре куска (100, 200, 150 и 150 байт); на схеме куски обозначены AAA, BBB, CCC и DDD, а i — номер куска в оригинальном порядке.
Payload (600 байт):
[AAA...100 байт...AAA][BBB...200 байт...BBB][CCC...150 байт...CCC][DDD...150 байт...DDD]
^pos=100 ^pos=300 ^pos=450
Оригинальный порядок Порядок отправки
------------------- ----------------
Сегмент 0 (i=0): [AAA...100] seq=0 4-й (последний)
Сегмент 1 (i=1): [BBB...200] seq=100 3-й
Сегмент 2 (i=2): [CCC...150] seq=300 2-й
Сегмент 3 (i=3): [DDD...150] seq=450 1-й (первым отправляется)
Реальная последовательность отправки:
1. [DDD...150] seq=450 len=150 (i=3, последний сегмент данных)
2. [CCC...150] seq=300 len=150 (i=2)
3. [BBB...200] seq=100 len=200 (i=1)
4. [AAA...100] seq=0 len=100 (i=0, первый сегмент данных)
Кусок с наибольшим seq (DDD) уходит первым, кусок с seq=0 (AAA) — последним; сервер поставит их по номерам и получит исходный запрос.
Пример с seqovl (2 сегмента)
Тот же принцип, но с seqovl: два сегмента (pos=100), seqovl разрешился в 50, а значит ovl = 49. Откуда берётся «минус 1», разобрано в разделе Нюанс реализации: ovl = seqovl - 1.
Payload (300 байт), pos=100, seqovl=midsld (разрешился в 50):
ovl = 50 - 1 = 49
Оригинальный порядок Порядок отправки
------------------- ----------------
Сегмент 0 (i=0): [ЧАСТЬ_1: 100 байт] seq=0 2-й (последний)
Сегмент 1 (i=1): [ЧАСТЬ_2: 200 байт] seq=100 1-й (первым)
Реальная последовательность отправки:
1. i=1: [PATTERN(49)][ЧАСТЬ_2(200)] seq=100-1-49=50 len=249 (seqovl!)
2. i=0: [ЧАСТЬ_1(100)] seq=0 len=100 (перезаписывает PATTERN)
Первым уходит сегмент с паттерном и ЧАСТЬЮ_2, и его seq (50) меньше, чем у настоящего начала ЧАСТИ_2 (100); последним уходит ЧАСТЬ_1 и перезаписывает паттерн.
ASCII-диаграмма: хронология буфера сокета на сервере
Та же хронология, что в разделе Принцип работы seqovl в disorder, собранная в одну схему: t1 — приходит первый сегмент, t2 — второй; ниже показаны Linux/BSD и Windows. PATRN на схеме — то же, что PATTERN, сокращённо.
Время -->
t1: приходит [PATTERN(49)][ЧАСТЬ_2(200)] seq=50 len=249
Буфер сокета:
позиция: 0 50 99 100 299
[ пусто ][PATRN(49)][?? ][ЧАСТЬ_2(200)..............]
^^^^^^^^^^^^--- PATTERN записан в позиции 50-98
^^^--- ЧАСТЬ_2 в позициях 99-298
Данные НЕ выдаются приложению (нет непрерывности от 0)
t2: приходит [ЧАСТЬ_1(100)] seq=0 len=100
Linux/BSD (поздний ПЕРЕЗАПИСЫВАЕТ):
позиция: 0 100 299
[ЧАСТЬ_1(100)............][ЧАСТЬ_2(200)..............]
^--- ЧАСТЬ_1 перезаписала позиции 0-99
PATTERN уничтожен, данные корректны!
Windows (СОХРАНЯЕТ раннее):
позиция: 0 50 99 100 299
[ЧАСТЬ_1(50)][PATRN(49)][?? ][ЧАСТЬ_2(200)..............]
^--- только позиции 0-49 обновились
позиции 50-98 остались от PATTERN = МУСОР
Пример с 3 позициями + seqovl
Тот же приём при трёх разрезах: seqovl всё равно достаётся только одному сегменту.
Payload (600 байт), pos=100,300,450, seqovl=host (разрешился в 80):
ovl = 80 - 1 = 79
Порядок отправки:
1. i=3: [DDD...150] seq=450 len=150 (без seqovl)
2. i=2: [CCC...150] seq=300 len=150 (без seqovl)
3. i=1: [PATTERN(79)][BBB...200] seq=100-1-79=20 len=279 (seqovl! только i==1)
4. i=0: [AAA...100] seq=0 len=100 (перезаписывает PATTERN)
Обратите внимание: seqovl применяется только к i==1 (2-й сегмент в оригинальном порядке, предпоследний в порядке отправки), независимо от количества позиций разреза.
Поведение при replay / reasm
Многопакетный запрос (большой TLS ClientHello с post-quantum ключами и т. п.) nfqws2 придерживает, собирает в буфер reasm_data и перепроигрывает через desync-функции. multidisorder использует штатную развилку перепроигрывания из общего скелета desync-функций (жизненный цикл desync-функции, стадия 6) без изменений: нарезает и отправляет весь собранный буфер на первой части, а остальные части дропает, потому что их содержимое уже ушло в составе нарезки. Если отправка сорвалась, флаг «реасм уже отправлен» не ставится и оставшиеся части проходят как есть. Реассемблирование (reasm) — склейка запроса из нескольких TCP-пакетов; перепроигрывание (replay) — вызов функций по очереди на каждой придержанной части, уже с собранным целым на руках. Стадия 6 общего скелета разобрана в Стадия 6. Развилка перепроигрывания (replay), та же тема для multisplit — в разделе reasm.
Специфика multidisorder — в следствии этого поведения. Раз режется весь реасм целиком, обратный порядок распространяется на весь запрос, а не на отдельные пакеты: последний сегмент всего ClientHello уйдёт первым, даже если исходно он приехал третьим пакетом.
Отличие от multidisorder_legacy: legacy-версия развилку не использует вовсе — она обрабатывает каждую часть replay отдельно, нормализуя позиции разреза под границы конкретного пакета, как это делал nfqws1. Из-за этого порядок сегментов при многопакетных запросах у двух функций разный. Для полной совместимости с nfqws1 нужен именно legacy-вариант. Разбор с диаграммами — Ключевое отличие: попакетная обработка vs целый reasm.
Автосегментация по MSS
MSS (Maximum Segment Size) — наибольший объём данных, который помещается в один TCP-сегмент данного соединения.
О размерах TCP-сегментов думать не нужно. Отправкой занимается rawsend_payload_segmented из zapret-lib.lua, и она сама доводит куски до допустимого размера:
- MSS берётся из
desync.tcp_mss— движок отслеживает его для каждого TCP-соединения - Если кусок вместе с заголовками превышает MSS — он дополнительно режется по MSS
- Каждый под-сегмент отправляется с корректным TCP sequence
Пример: если seqovl разрешился в большое значение (скажем, 5000), это не вызовет ошибку. rawsend_payload_segmented отправит несколько TCP-сегментов общим размером, равным seqovl_pattern + данные сегмента. Проще говоря: слишком большой фейк — не ошибка, а просто несколько подряд идущих сегментов. Та же тема для multisplit — Автосегментация по MSS.
Место в общем скелете
Тело multidisorder построено по тому же шаблону, что и все остальные функции дурения: восемь стадий от отсева чужого транспорта до вердикта. Разобраны они один раз в жизненный цикл desync-функции — здесь только отклонения:
| Стадия общего скелета | Что делает multidisorder |
|---|---|
| 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 |
Гварды в пятой строке — проверки-предохранители: данные не пусты, направление и тип payload подходят (стадия 5). instance_cutoff отключает функцию для этого потока навсегда (стадия 1). VERDICT_DROP — решение «перехваченный оригинал не отправлять», VERDICT_PASS — «пропустить»; см. стадию 8.
Отличие от multisplit сосредоточено ровно в одной стадии — седьмой. Всё остальное у двух функций совпадает буквально построчно, вплоть до общего хелпера multidisorder_send, который и разворачивает порядок отправки.
Псевдокод алгоритма
Тело функции целиком, включая общие для всех техник стадии — они пронумерованы так же, как в жизненный цикл desync-функции:
function multidisorder(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. Разрешение маркеров позиций
pos = resolve_multi_pos(data, l7payload, pos_arg or "2")
delete_pos_1(pos) -- удалить Lua-позицию 1 (маркер 0)
if #pos > 0 then
-- 8. Разрешение seqovl (МАРКЕР, не число!)
seqovl = nil
if arg.seqovl then
seqovl = resolve_pos(data, l7payload, arg.seqovl)
if not seqovl then
DLOG("seqovl cancelled: marker not resolved")
end
end
-- 9. multidisorder_send: цикл ОБРАТНЫЙ
for i = #pos, 0, -1 do -- <--- от последнего к первому!
pos_start = pos[i] or 1
pos_end = (i < #pos) and pos[i+1]-1 or #data
part = data:sub(pos_start, pos_end)
-- 10. seqovl только для i==1 (предпоследний отправляемый)
ovl = 0
if i == 1 and seqovl and seqovl > 0 then
if seqovl >= pos[1] then
DLOG("seqovl cancelled: not less than first split pos")
else
ovl = seqovl - 1 -- ВАЖНО: минус 1!
pat = seqovl_pattern_blob or "\x00"
part = pattern(pat, 1, ovl) .. part
end
end
-- 11. Отправка с автосегментацией
rawsend_payload_segmented(part, pos_start - 1 - ovl)
end
-- 12. Пометить как отправленное
replay_drop_set()
return nodrop and VERDICT_PASS or VERDICT_DROP
else
DLOG("no valid split positions")
end
else
-- 13. Не первый replay
DLOG("not acting on further replay pieces")
end
-- 14. Дропнуть если ранее успешно отправлено
if replay_drop() then
return nodrop and VERDICT_PASS or VERDICT_DROP
end
end
endСобственно техника — шаги 7–11 в комментариях: разрешение маркеров, разрешение seqovl как маркера, обратный цикл, приписывание паттерна только при i == 1 и отправка. Остальное — общий скелет, разобранный в Восемь стадий целиком.
Проще говоря
Функция проверяет, что пакет TCP и направление подходит, выбирает данные, на первой части перепроигрывания режет их на куски, отправляет куски от последнего к первому (паттерн-фейк приписывается только второму куску запроса) и выносит вердикт «оригинал не отправлять».
Нюансы и подводные камни
1. Работает только с TCP
Если текущий пакет не TCP (UDP, ICMP и т.д.), multidisorder делает instance_cutoff_shim — отключает себя для этого потока навсегда (кроме ICMP-ответов, для которых cutoff не вызывается). Механика описана в стадии 1 жизненного цикла; почему резать в TCP-стиле нельзя в UDP — в нюансе про TCP в multisplit.
2. Разрез в самом начале данных отбрасывается
delete_pos_1(pos) убирает из списка Lua-позицию 1 — она соответствует маркеру 0, то есть разрезу перед первым байтом, от которого первый сегмент вышел бы пустым. Если после этой чистки не осталось ни одной позиции, multidisorder не делает ничего.
Путаться тут легко из-за разной нумерации: в командной строке маркеры считаются от нуля, внутри Lua — от единицы. Поэтому под удаление попадает не pos=1, а pos=0. Самая частая реальная жертва — pos=method для HTTP: метод обычно лежит на смещении 0, маркер разрешается в удаляемую позицию, и разреза не происходит. Рабочий вариант — pos=method+2. Тот же нюанс есть в списке Важные нюансы pos.
3. Все маркеры могут не разрешиться
Если вы указали pos=midsld,sniext для HTTP-payload, оба маркера (специфичные для TLS) не разрешатся. Multidisorder напишет в лог “no valid split positions” и ничего не сделает. Проще говоря: если ни один маркер из списка не нашёлся в данных этого типа payload, функция ничего не делает. Какие маркеры к каким типам подходят, показано в таблице Относительные маркеры.
4. seqovl как маркер тоже может не разрешиться
В отличие от multisplit (где seqovl — число и всегда валидно), в multidisorder seqovl=midsld-1 может не разрешиться (например, для unknown payload). В этом случае seqovl отменяется (логируется), но сегментация в обратном порядке всё равно происходит. См. seqovl как маркер.
5. seqovl НЕ работает на Windows-серверах
Если целевой сервер работает под Windows, seqovl бесполезен: Windows сохраняет первые полученные данные и не перезаписывает буфер при получении перекрывающихся сегментов. Результат — сервер получит мусор (seqovl_pattern) вместо реальных данных. Без seqovl multidisorder работает на всех ОС нормально. Подробнее — Ограничение: Windows-серверы.
6. ovl = seqovl - 1: размер перекрытия на 1 меньше
Из-за строки ovl = seqovl - 1 в multidisorder_send, если seqovl разрешился в 1, то ovl будет 0 — фактически seqovl не применяется. Минимальный эффективный seqovl — значение 2 (ovl = 1, 1 байт перекрытия). Разбор — в разделе Нюанс реализации: ovl = seqovl - 1.
7. seqovl >= pos[1] → отмена seqovl
Если разрешённое значение seqovl больше или равно первой позиции разреза, seqovl отменяется. Это частая ошибка при использовании маркеров, которые разрешаются в большие значения. Логируется как “seqovl cancelled because seqovl N is not less than the first split pos M”. Подробнее — Валидация seqovl.
8. nodrop создаёт дублирование
С nodrop multidisorder отправляет нарезанные сегменты И пропускает оригинальный пакет. Сервер получит данные дважды. Используйте nodrop только для отладки или когда это осознанно нужно. Сам флаг описан в разделе A) Собственные аргументы multidisorder.
9. Fooling применяется ко ВСЕМ сегментам
Fooling — намеренная порча заголовков пакета, обычно для того, чтобы сервер отбросил пакет, а DPI его прочитал; полный разбор — в разделе Что такое fooling, список опций — в разделе D) Standard fooling. Своих фейковых сегментов у multidisorder нет, поэтому порче подвергаются настоящие данные; как это устроено у fakeddisorder, описано в разделе Применение fooling и reconstruct: там fooling в полном объёме идёт только на фейки, а на настоящие сегменты — единственная опция tcp_ts_up.
В отличие от fakeddisorder, где fooling идёт только на фейки, в multidisorder все сегменты получают fooling. Если задать tcp_ack=-66000, все настоящие сегменты получат неверный номер подтверждения, и Linux-сервер их отбросит.
Что при этом происходит с соединением, проверено на стенде (сентябрь 2026: nfqws2 из репозитория zapret2 на клиенте, TLS-сервер на Linux 7.0, варианты с TCP timestamps и без, с tcp_ts_up и без). Во всех вариантах сервер отвечал на нарезанные сегменты только повторным ACK на начало потока, то есть не принял ни байта. Соединение всё же устанавливалось, но с задержкой 0,2–1 с: операционная система клиента, не дождавшись подтверждения, повторно передавала ClientHello, и в итоге до сервера доходила повторная передача без разреза. Для реального DPI это значит, что нарезка не работает, а соединение держится на целом ClientHello — том самом, который DPI и блокирует. Если повторные передачи тоже попадают под --out-range, они снова режутся и отбрасываются, и задержка растёт. Формат --out-range описан в заметке out-range.
Проще говоря: multidisorder с tcp_ack превращает настоящие данные в фейки, и сервер получает их только тогда, когда клиент пришлёт их заново уже без обработки.
Исключение — когда настоящие данные до сервера доставляет другой инстанс, стоящий раньше, например --lua-desync=send:repeats=2. send отправляет неизменённые копии пакета, сервер принимает ClientHello из них, а «мёртвые» сегменты multidisorder становятся для DPI ещё одной порцией фейков. Так устроены профили с tcp_ack в примерах заметки preset; на том же стенде такая связка соединялась без задержки.
Без такой подстраховки fooling в multidisorder имеет смысл только для опций, которые не мешают приёму: tcp_ts_up, ip_id, IPv6 extension headers. Поведение Windows-серверов на стенде не проверялось.
10. Алгоритм отличается от nfqws1
Из комментария в коде: “algorithm is not 100% the same as in nfqws1. multi-segment queries can produce different segment ordering.” nfqws2 работает с целым reasm, а nfqws1 — по отдельным частям replay. Для полной совместимости с nfqws1 используйте multidisorder_legacy. Разбор различий с диаграммами — Ключевое отличие: попакетная обработка vs целый reasm; когда какой вариант брать — Когда использовать multidisorder_legacy.
11. Порядок инстансов важен
Если перед multidisorder стоит fake, он отправит фейк первым. Если после multidisorder стоит ещё один инстанс — он увидит VERDICT_DROP и не получит оригинальный payload. Инстанс — одна запись --lua-desync=… в профиле; движок вызывает их по очереди. Как аргументы и инстансы выстраиваются в цепочку, описано в последовательность аргументов и desync.
Уточнение из разбора вердиктов: VERDICT_DROP не обрывает цепочку. Движок проходит все инстансы профиля и складывает их вердикты (VERDICT_DROP перебивает VERDICT_PASS и VERDICT_MODIFY), так что инстансы после multidisorder вызываются как обычно, а дроп означает лишь то, что оригинальный пакет в итоге не уйдёт (стадия 8 жизненного цикла; то же — в нюансе о порядке инстансов в multisplit).
Отличия от других функций сегментации
Четыре функции ниже режут данные на TCP-сегменты, но различаются числом разрезов, порядком отправки, наличием фейков и тем, как устроен seqovl. Подробности о каждой — в разделах multisplit, fakedsplit и fakeddisorder; сравнение с вариантом для nfqws1 — в таблице multidisorder_legacy.
| Аспект | multisplit | multidisorder | fakedsplit | fakeddisorder |
|---|---|---|---|---|
| Количество позиций | Список (любое кол-во) | Список (любое кол-во) | Одна | Одна |
| Порядок отправки | Прямой (1→2→3) | Обратный (3→2→1) | Прямой | Обратный |
| Фейковые сегменты | Нет | Нет | Да (до 4 шт.) | Да (до 4 шт.) |
| seqovl тип | Только число | Маркер | Только число | Маркер |
| seqovl к какому сегменту | 1-й (первый отправляемый, i==0) | 2-й в оригинале (предпоследний отправляемый, i==1) | 1-й реальный | 2-й реальный |
| seqovl механизм | Выход за TCP window | Перезапись буфера | Выход за TCP window | Перезапись буфера |
| Windows-серверы (seqovl) | Работает | Не работает | Работает | Не работает |
| Fooling к | Всем сегментам | Всем сегментам | Только к фейкам | Только к фейкам |
| ipfrag | Да | Да | Нет | Нет |
| blob аргумент | Да | Да | Нет | Нет |
| reasm | Целый reasm на replay_first | Целый reasm на replay_first | По частям | По частям |
Проще говоря: multisplit и multidisorder только режут настоящие данные (по порядку или задом наперёд), а fakedsplit и fakeddisorder делают один разрез и прячут настоящие куски среди фейков. Общее у multidisorder и fakeddisorder — обратный порядок и seqovl через перезапись буфера (маркером; на Windows-серверах не работает).
Миграция с nfqws1
Этот раздел для тех, кто переносит настройки из первой версии zapret (программа nfqws) во вторую (nfqws2). В первой версии техника выбиралась ключом --dpi-desync=…, а её параметры — отдельными ключами --dpi-desync-…; во второй всё это записывается аргументами одного вызова --lua-desync=multidisorder:….
Соответствие параметров
| nfqws1 | nfqws2 |
|---|---|
--dpi-desync=multidisorder | --lua-desync=multidisorder |
--dpi-desync-split-pos=midsld | :pos=midsld |
--dpi-desync-split-pos=1,midsld | :pos=1,midsld |
--dpi-desync-split-seqovl=5 | :seqovl=5 |
--dpi-desync-split-seqovl-pattern=0x1603030000 | :seqovl_pattern=0x1603030000 |
--dpi-desync-any-protocol | Не нужно; или payload=all в инстансе |
Простая миграция
# nfqws1:
nfqws --dpi-desync=multidisorder --dpi-desync-split-pos=midsld
# nfqws2:
nfqws2 --lua-desync=multidisorder:pos=midsldМиграция с fake + multidisorder
В nfqws1 фейк и разрез задавались одним ключом --dpi-desync=fake,multidisorder. В nfqws2 каждая техника становится отдельным инстансом --lua-desync, а md5sig из fooling превращается в tcp_md5 на инстансе fake; о самом fake — в его статье.
# nfqws1:
nfqws --dpi-desync=fake,multidisorder \
--dpi-desync-split-pos=1,midsld \
--dpi-desync-repeats=11 \
--dpi-desync-fooling=md5sig
# nfqws2:
nfqws2 \
--lua-desync=fake:blob=fake_default_tls:repeats=11:tcp_md5 \
--lua-desync=multidisorder:pos=1,midsldМиграция с seqovl
Ключи --dpi-desync-split-seqovl и --dpi-desync-split-seqovl-pattern становятся аргументами seqovl и seqovl_pattern; в nfqws2 seqovl может быть маркером (см. seqovl как маркер).
# nfqws1:
nfqws --dpi-desync=multidisorder \
--dpi-desync-split-pos=midsld \
--dpi-desync-split-seqovl=5 \
--dpi-desync-split-seqovl-pattern=0x1603030000
# nfqws2 (seqovl теперь может быть маркером!):
nfqws2 --lua-desync=multidisorder:pos=midsld:seqovl=5:seqovl_pattern=0x1603030000
# или с маркером (новая возможность nfqws2):
nfqws2 --lua-desync=multidisorder:pos=midsld:seqovl=midsld-1:seqovl_pattern=0x1603030000Полная миграция: fake + disorder + seqovl + fooling
Здесь фейки и разрез тоже становятся отдельными инстансами: фейки для TLS и HTTP — разными инстансами под своими --payload, потому что им нужны разные стандартные blob-ы (в коде ниже fake_default_tls и fake_default_http).
# nfqws1:
nfqws --dpi-desync=fake,multidisorder \
--dpi-desync-fooling=md5sig \
--dpi-desync-split-pos=1,midsld \
--dpi-desync-split-seqovl=5 \
--dpi-desync-split-seqovl-pattern=0x1603030000 \
--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=multidisorder:pos=1,midsld:seqovl=5:seqovl_pattern=0x1603030000Замечание о совместимости: алгоритм multidisorder в nfqws2 не на 100% совпадает с nfqws1 при многопакетных запросах (multi-segment queries). nfqws2 работает с целым reasm, а nfqws1 — по частям. Для полной совместимости используйте multidisorder_legacy. Как перейти с нового multidisorder на legacy, описано в разделе Миграция с нового multidisorder на legacy.
Практические примеры
Каждый пример — готовая строка для профиля; под ней объяснено, что получится на выходе.
1. Минимальный (дефолт: pos=2, dir=out, payload=known)
--lua-desync=multidisorderРазрезает payload после 2-го байта → 2 сегмента (первые 2 байта и остаток), отправляемых в обратном порядке.
2. TLS: разрез посередине SNI
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsldSNI разрезан пополам, хвост отправлен первым — DPI не может собрать домен.
3. TLS: разрез + seqovl маркером (классическая комбинация)
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld:seqovl=midsld-1Разрез по midsld, seqovl перекрывает почти всю первую часть фейковыми данными. Типичный паттерн для disorder. Механика — в разделе seqovl — перезапись буфера сокета через перекрытие.
4. TLS: seqovl + кастомный паттерн
--payload=tls_client_hello \
--lua-desync=multidisorder:pos=midsld:seqovl=midsld-1:seqovl_pattern=0x1603030000seqovl-область заполнена данными, похожими на начало TLS record — DPI может принять за легитимный TLS-трафик.
5. HTTP: разрез после метода (disorder)
--payload=http_req --lua-desync=multidisorder:pos=method+2Для GET /path... разрежет после GE → хвост уйдёт первым, голова — вторым. Сдвиг +2 нужен потому, что голый pos=method указывает на самое начало данных и отбрасывается (см. нюанс 2).
6. HTTP: несколько разрезов вокруг hostname
--payload=http_req --lua-desync=multidisorder:pos=host,midsld,endhostРазрезает: до hostname | первая половина | вторая половина | после hostname → 4 сегмента в обратном порядке.
7. Два разреза с seqovl
--payload=tls_client_hello --lua-desync=multidisorder:pos=2,midsld:seqovl=sld3 сегмента, seqovl применяется к сегменту i=1 (второй в оригинале). Маркер sld определяет размер перекрытия. Ограничение на значение — Валидация seqovl.
8. Произвольный blob вместо payload
--blob=mydata:@custom_payload.bin \
--lua-desync=multidisorder:blob=mydata:pos=10,100,-20Режет и отправляет произвольные данные из файла вместо реального payload, в обратном порядке. Как объявлять такие данные, описано в заметке blob.
9. Защита от отсутствующего blob
--lua-desync=multidisorder:blob=maybe_missing:optional:pos=midsldЕсли blob не существует — тихий пропуск, без ошибок и без VERDICT_DROP. Флаг optional описан в разделе A) Собственные аргументы multidisorder.
10. Отладка: не блокировать оригинал
--payload=http_req --lua-desync=multidisorder:pos=host,midsld:nodropОтправляет нарезанные сегменты в обратном порядке И пропускает оригинальный пакет (для экспериментов). В рабочей настройке так не делают: сервер получит данные дважды (см. нюанс 8).
11. IP-фрагментация поверх TCP-сегментации (двойной disorder)
--payload=tls_client_hello \
--lua-desync=multidisorder:pos=1,midsld:ipfrag:ipfrag_disorder:ipfrag_pos_tcp=32Каждый TCP-сегмент дополнительно фрагментируется на IP-уровне в обратном порядке. Двойной disorder: TCP-сегменты в обратном порядке + IP-фрагменты каждого сегмента тоже в обратном порядке. Параметры фрагментации описаны в разделе F) Standard ipfrag.
12. Комбинация: fake → multidisorder (типичный боевой профиль)
--payload=tls_client_hello \
--lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \
--lua-desync=multidisorder:pos=1,midsld:seqovl=midsld-1:seqovl_pattern=0x1603030000Сначала отправляется фейковый TLS ClientHello (с fooling), затем реальный — нарезанный на 3 сегмента в обратном порядке с seqovl. Как устроен сам фейк и его опции tls_mod, разобрано в заметке fake.
13. Боевой пример для YouTube
--filter-tcp=443 --hostlist=youtube.txt \
--lua-desync=fake:blob=fake_default_tls:repeats=11:tcp_md5 \
--lua-desync=multidisorder:pos=1,midsld11 фейков подряд + реальный payload разрезан на 3 части в обратном порядке. Профиль срабатывает только на TCP-порт 443 и на домены из списка youtube.txt; фейки испорчены опцией tcp_md5, чтобы сервер их отбросил.
14. С TCP timestamp + IP ID
--payload=tls_client_hello --lua-desync=multidisorder:pos=1:tcp_ts_up:ip_id=seq:ip_id_connПервый байт уходит отдельным сегментом. tcp_ts_up поднимает опцию TCP timestamp в начало заголовка, а ip_id=seq вместе с ip_id_conn дают сегментам последовательные IP ID со сквозной нумерацией в рамках соединения. Эти опции не мешают серверу принять данные, поэтому их можно применять ко всем сегментам (см. нюанс 9).
15. Повторы отправки
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld:repeats=2Каждый сегмент отправляется 2 раза (бинарные повторы). Подробнее про repeats — в разделе H) Standard rawsend.
16. Вариант из каталога GUI: fake + multidisorder с badseq
В каталоге стратегий Zapret 2 GUI (strategy_catalogs/winws2/tcp.txt, запись multidisorder_badseq_pos, проверено в сентябре 2026) есть такая связка. Подписана она как «original bol-van v2 (badsum)», хотя badsum в ней нет: используется tcp_ack=-66000, то есть badseq. В самом каталоге перед ней стоят общие фильтры категории, --payload и --out-range ниже добавлены для наглядности:
--payload=tls_client_hello --out-range=-d10 \
--lua-desync=fake:blob=fake_default_tls:repeats=6:tcp_ack=-66000 \
--lua-desync=multidisorder:pos=1,midsld:tcp_ack=-66000Сначала шесть фейковых ClientHello со сдвинутым номером подтверждения (fake), затем настоящий ClientHello режется на три части по первому байту и середине домена второго уровня и уходит в обратном порядке.
Разрез до сервера не доходит
У
multidisorderfooling применяется ко всем отправляемым сегментам, поэтому сtcp_ack=-66000Linux-сервер отбрасывает все настоящие части ClientHello. На стенде (нюанс 9) эта стратегия соединялась только с задержкой около 0,2 с — после того как клиент повторно передал ClientHello целиком, без разреза. По сути от неё работают только шесть фейков, а настоящие данные уходят к серверу в открытом виде. Если нужна именно нарезка, уберитеtcp_ackизmultidisorderи оставьте fooling только наfake, как в примерах 12 и 13; если нужны «мёртвые» копии как дополнительные фейки, поставьте перед ними--lua-desync=send:repeats=2, как в профилях заметки preset. Проверяйте результат новым соединением по verify-strategy.
📚 См. также
- desync — обзор всех функций
--lua-desyncи общий контракт: кто вызывает функцию, что ей передаёт и как складываются вердикты - жизненный цикл desync-функции — восемь стадий, общих для всех техник дурения: отсев транспорта, выбор данных, replay, вердикты
- структура desync и диссекта — подробное устройство таблицы
desyncи диссекта пакета - multisplit — базовая техника: тот же разрез, но сегменты уходят по порядку; там
seqovlвыводит фейк за окно приёма - multidisorder_legacy — вариант
multidisorder, полностью совместимый с алгоритмом nfqws1 - fakedsplit · fakeddisorder — разрез по одной позиции с подмешиванием фейковых сегментов
- hostfakesplit — разрез по границам имени хоста с подмешиванием фейкового имени
- tcpseg — отправка произвольного диапазона данных, ограниченного двумя маркерами
- oob — сегментация с urgent-байтом вместо разреза
- fake — отдельный фейковый пакет, с которым
multidisorderчаще всего комбинируют - blob — как объявлять и передавать данные для аргументов
blobиseqovl_pattern - payload — распознавание типов протоколов, от которого зависит работа относительных маркеров
- ts-and-fooling — опции fooling и почему в
multidisorderони портят и настоящие данные - verify-strategy — как честно проверить, работает ли подобранная комбинация
- последовательность аргументов — как выстраивается цепочка инстансов в профиле
- profile · preset — где
--lua-desyncживёт среди остальных настроек - DPI и ТСПУ — как устроена инспекция трафика, против которой работает
multidisorder
Источники:
lua/zapret-antidpi.lua:546-629,lua/zapret-lib.lua,docs/manual.md:4067-4097,docs/readme.mdиз репозитория zapret2.
🤖 Эти статьи открыты — можно обучать на них ИИ
При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: исходник этой заметки · скачать весь репозиторий одним zip-архивом.