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: части в обратном порядке Карта потока для multidisorder с pos=midsld: сначала уходит вторая часть ClientHello (seq=14), затем первая (seq=0). Сервер держит вторую часть в буфере и собирает поток по seq. пакет в порядке отправки место в потоке (seq) → сервер 0 midsld 26 #1 PART2 seq=14 len=12 tube.com···· ждёт в буфере #2 PART1 seq=0 len=14 ···········you принят буфер сервера tube.com···· ···········you поток собран DPI конец запроса приходит раньше начала; без пересборки потока имени целиком не видно сервер держит #1 в буфере и ставит #2 перед ним по 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). Все отличия сведены в таблицу в разделе Отличия от других функций сегментации.


Оглавление


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

Паспорт функции для тех, кто сверяется с исходниками 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), но обратный порядок отправки создаёт дополнительную проблему:

  1. DPI не реассемблирует out-of-order сегменты: многие DPI работают потоково (stream-based) и ожидают сегменты в порядке sequence number. Получив сначала хвост потока, DPI может потерять контекст или не дождаться головы
  2. DPI не может сопоставить hostname: если разрез проходит через SNI/Host, а сегменты приходят задом наперёд, ни попакетный, ни потоковый DPI не найдёт сигнатуру
  3. 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_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      — два байта после начала метода (разрежет "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:

  1. Разбивает строку spos по запятым
  2. Для каждого маркера вызывает resolve_pos(blob, l7payload_type, marker)
  3. Если маркер не может быть разрешён (например, midsld для unknown payload) — он молча пропускается
  4. Результаты дедуплицируются и сортируются
  5. Возвращается массив уникальных позиций, уже переведённых в 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 байта отдельным сегментом, весь остаток — вторым

Проще говоря

pos=0 и pos=method (когда метод стоит на смещении 0) разреза не дают; маркеры, которые не нашлись в данных этого типа, выпадают молча; если не осталось ни одного маркера, функция ничего не делает. Разобрано в нюансах 2 и 3.


Обратный порядок отправки — суть disorder

Ключевое отличие multidisorder от multisplit: сегменты отправляются от последнего к первому (в порядке убывания sequence number). Цикл в multidisorder_send:

for i=#pos,0,-1 do
multisplit:      отправка 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, как правило, не имеет такой роскоши:

  1. Потоковые DPI обрабатывают данные по мере поступления. Получив последний сегмент первым, DPI видит данные без начала (без заголовков HTTP/TLS) и не может распознать протокол
  2. Попакетные DPI анализируют каждый пакет отдельно. В каждом отдельном сегменте нет полной сигнатуры
  3. 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 не нужен, а без него техника работает везде. В multisplit seqovl устроен иначе и на 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 в multisplitseqovl в 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 — разрез посередине SLD
    • pos=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, а TCP th_seq уменьшается на seqovl-1. Последний отправляемый сегмент (1-й в оригинале) перезаписывает фейковые данные в буфере сервера. Здесь th_seq — поле номера последовательности в TCP-заголовке. Механика — в разделе seqovl — перезапись буфера сокета через перекрытие
  • Ограничение: разрешённое значение seqovl должно быть строго меньше pos[1], иначе seqovl отменяется. Подробнее — Валидация seqovl
  • Примеры:
    • seqovl=5 — 4 байта фейка слева (ovl = 5-1)
    • seqovl=midsld-1 — типичное использование с pos=midsld
    • seqovl=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 hex
    • blob=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

Направление говорит, в какую сторону идёт пакет: от вашего компьютера к серверу (исходящий) или обратно (входящий).

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

Фильтр по направлению пакета. 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, включая unknown
  • payload=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 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: На Linux-серверах пакеты с инвалидным ACK стабильно отбрасываются только если TCP timestamp option идёт первой в заголовке. tcp_ts_up перемещает её в начало, обеспечивая корректную работу badseq-fooling.


E) Standard ipid

IP ID — идентификатор в заголовке IPv4-пакета. Эти параметры задают, какие значения получат отправляемые сегменты.

ПараметрОписаниеПо умолчанию
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 применяется к каждому отправляемому сегменту (включая под-сегменты при 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 бесполезно — он только TCP8
ipfrag_next=NIPv6: 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=NFirewall 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, и она сама доводит куски до допустимого размера:

  1. MSS берётся из desync.tcp_mss — движок отслеживает его для каждого TCP-соединения
  2. Если кусок вместе с заголовками превышает MSS — он дополнительно режется по MSS
  3. Каждый под-сегмент отправляется с корректным 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.

Аспектmultisplitmultidisorderfakedsplitfakeddisorder
Количество позицийСписок (любое кол-во)Список (любое кол-во)ОднаОдна
Порядок отправкиПрямой (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:….

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

nfqws1nfqws2
--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=midsld

SNI разрезан пополам, хвост отправлен первым — 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=0x1603030000

seqovl-область заполнена данными, похожими на начало 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=sld

3 сегмента, 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,midsld

11 фейков подряд + реальный 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 режется на три части по первому байту и середине домена второго уровня и уходит в обратном порядке.

Разрез до сервера не доходит

У multidisorder fooling применяется ко всем отправляемым сегментам, поэтому с tcp_ack=-66000 Linux-сервер отбрасывает все настоящие части 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-архивом.