todo/Zapret2/desync/multidisorder.md
loop-uh baf0091fa3
Some checks failed
Published content check / validate (push) Failing after 17s
git.zapret.moe → git2.zapret.moe во всём хранилище, защита от возврата старых имён
git.zapret.moe в России заблокирован по имени, как и wiki.zapret.moe. Ссылки
на Forgejo во всех заметках, футерах, wizard.html и главной заменены на
git2.zapret.moe (включая закодированные адреса значков shields.io).

Какое имя рабочее, теперь решают реестры хоста /etc/zapret-*-domains.conf через
zapret-site-names: pre-commit хук vault отклоняет добавленные строки с
заблокированным именем, сборка Quartz сама переводит ссылки и baseUrl на
рабочие имена. Правило в AGENTS.md переписано под этот механизм.

scripts/wiki-stats.py: переходы с любых имён вики и git (wikiN, gitN) больше
не считаются внешними сайтами. content-check: проверка зеркал сторонних
проектов узнаёт любое имя git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 15:51:32 -04:00

1273 lines
132 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
date: 2026-02-17
tags:
- zapret
- zapret2
- nfqws2
- lua
- lua-desync
- antidpi
- tcp
- multidisorder
- seqovl
- disorder
aliases:
- multidisorder
description: "Разбор multidisorder в zapret2/nfqws2: TCP-сегменты в обратном порядке, маркеры pos, seqovl с перезаписью буфера и миграция с nfqws1."
---
> [!mirror] Резервное зеркало
> Актуальная версия этой страницы — на основной вики: [wiki2.zapret.moe/Zapret2/desync/multidisorder](https://wiki2.zapret.moe/Zapret2/desync/multidisorder)
# `multidisorder` — TCP-сегментация в обратном порядке (zapret2 / nfqws2)
## Коротко и по-простому
Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке `Host:` запроса. ТСПУ (Технические средства противодействия угрозам — оборудование [[DPI/DPI|DPI]], то есть глубокой инспекции трафика, которое стоит у провайдера) ищет это имя в первом пакете соединения и по нему решает, что делать с соединением дальше.
HTTP-запросы и TLS-соединения передаются по протоколу TCP, а TCP отправляет данные кусками — сегментами. У каждого байта в соединении есть порядковый номер (sequence number, коротко seq), и сервер раскладывает принятые куски в свой буфер по этим номерам. Если пришёл кусок из середины или из конца запроса, сервер держит его в буфере и ждёт недостающие: приложению данные отдаются только тогда, когда набралась непрерывная последовательность байтов. Поэтому порядок, в котором куски доехали, серверу безразличен — исходный поток он соберёт всё равно. Почему такую сборку гарантирует именно TCP, а у UDP её нет, объяснено в нюансе [[Zapret2/desync/multisplit#1. Работает только с TCP — и почему|«Работает только с TCP — и почему»]] статьи про multisplit.
На этой особенности TCP и построен `multidisorder` (по-английски disorder — «беспорядок»). Движок Zapret 2 (`nfqws2`) перехватывает исходящий пакет с ClientHello или HTTP-запросом, а `multidisorder` делает три шага. Сначала режет данные пакета в заданных местах — так же, как [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]], например посередине домена второго уровня, то есть главной части имени сайта (у `www.youtube.com` это `youtube`). Места разреза задаёт аргумент `pos`: число («первые N байт уходят отдельным сегментом») или маркер, привязанный к устройству запроса, например `midsld` — середина домена второго уровня; маркеров можно перечислить несколько через запятую, все они разобраны в разделе [[#Маркеры позиций (pos)|Маркеры позиций (pos)]]. Потом отправляет получившиеся куски отдельными TCP-сегментами в обратном порядке — от последнего к первому, в порядке убывания seq; каждый сегмент несёт свой номер seq, соответствующий месту куска в исходном запросе. В конце выбрасывает перехваченный оригинал, чтобы те же данные не ушли второй раз. Подробно про обратный порядок — в разделе [[#Обратный порядок отправки — суть disorder|Обратный порядок отправки — суть disorder]].
Сервер получает все куски, кладёт их в буфер по номерам seq и, когда приходит самый первый кусок, получает непрерывный поток — ровно тот запрос, который отправил браузер, — и отвечает как обычно. DPI видит другое: сначала конец запроса без начала, а имя сайта разорвано между пакетами. Потоковый DPI (тот, что разбирает данные по мере поступления) при таком порядке может потерять контекст, попакетный (разбирающий каждый пакет отдельно) не находит сигнатуру — заранее известную строку — целиком ни в одном сегменте, а DPI, который умеет пересобирать поток, теоретически справится, но у многих из них на практике ограничены буфер или время ожидания, и они не дожидаются всех частей. Гарантий здесь нет, всё зависит от конкретного DPI; подробнее — в разделе [[#Почему disorder ломает DPI|Почему disorder ломает DPI]].
Команда из быстрого старта `multidisorder:pos=midsld` режет ClientHello посередине домена второго уровня и первой отправляет вторую часть. ТСПУ видит конец запроса раньше начала; если DPI не пересобирает поток, пришедший не по порядку, имя целиком он не видит. Сервер держит пришедшую вперёд часть в буфере, пока не придёт первая. Как читать схему: строки — пакеты в порядке отправки, подписанные в тех же обозначениях, что и таблицы в этой статье; по горизонтали — место данных в TCP-потоке (номер последовательности, seq); в колонке справа — что сделал с пакетом сервер; внизу — буфер сервера, в который принятые куски встают по своим seq.
![[desync-anim-multidisorder.svg|Анимация multidisorder: вторая часть ClientHello уходит раньше первой, сервер собирает их по seq]]
Самая короткая команда включает `multidisorder` со значениями по умолчанию: один разрез после второго байта (`pos=2`), обработка только распознанных протоколов (`payload=known`) и только исходящих пакетов (`dir=out`). Строку `--lua-desync=…` пишут в [[Zapret2/profile#Анатомия профиля|профиль]] — набор правил Zapret 2 для определённого трафика:
```bash
--lua-desync=multidisorder
```
Для HTTPS обычно берут типовой вариант `--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld`; здесь `--payload=tls_client_hello` значит «срабатывать только на TLS ClientHello». Остальные готовые команды собраны в разделах [[#Быстрый старт|Быстрый старт]] и [[#Практические примеры|Практические примеры]].
Кроме обратного порядка отправки, `multidisorder` отличается от [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]] приёмом `seqovl` (Sequence Overlap, «перекрытие по номерам последовательности»). В `multisplit` он выводит лишние байты фальшивки за левую границу окна приёма (окно — диапазон номеров, которые сервер готов принять), и сервер их выбрасывает сразу; так устроен приём в [[Zapret2/desync/multisplit#Принцип работы seqovl|multisplit]]. В `multidisorder` всё иначе. К куску запроса, который в оригинале стоит вторым, слева приписывают несколько байт паттерна (фейка) и сдвигают его seq назад на ту же длину, так что паттерн занимает номера, принадлежащие первому куску. Этот сегмент уходит предпоследним и попадает в буфер сокета — место, где стек сервера держит принятые байты, пока приложение их не забрало. Последним уходит первый кусок запроса: он приходит с теми же номерами и на Linux, BSD и macOS перезаписывает паттерн настоящими байтами, так что приложение получает верный запрос. Фейк здесь пропадает потому, что его перезаписывает следующий сегмент. Поэтому на Windows-сервере техника с `seqovl` ломается: он сохраняет первые полученные данные и не перезаписывает их перекрывающимся сегментом, поэтому вместо запроса получит мусор из паттерна. Без `seqovl` `multidisorder` работает на всех системах — простое разупорядочивание от поведения при перекрытии не зависит. Раскладка по байтам — в разделе [[#seqovl — перезапись буфера сокета через перекрытие|seqovl — перезапись буфера сокета через перекрытие]], сравнение с `multisplit` — в таблице [[#Отличие seqovl от multisplit|Отличие seqovl от multisplit]], про Windows — в разделе [[#Ограничение: Windows-серверы|Ограничение: Windows-серверы]]. Если паттерн сделать похожим на начало TLS record (заголовка записи TLS), DPI может принять такие байты за легитимный TLS-трафик ([[#4. TLS: seqovl + кастомный паттерн|пример 4]]).
Когда `multidisorder` помогает, а когда нет. Он рассчитан на DPI, который не справляется со сборкой сегментов, пришедших вразнобой; против DPI, умеющего пересобирать поток, одного обратного порядка может не хватить, и его сочетают с [[Zapret2/desync/fake#Зачем нужен fake|fake]] — отдельным пакетом-обманкой с ложными данными (примеры 12 и 13 в разделе [[#Практические примеры|Практические примеры]]). Сработает ли комбинация у вашего провайдера, проверяйте по [[Zapret2/verify-strategy#Короткий чек-лист честной проверки|чек-листу честной проверки]]. Как и `multisplit`, `multidisorder` работает только с TCP: UDP-трафик, включая QUIC, так не режут (нюанс [[#1. Работает только с TCP|«Работает только с TCP»]]). Есть и ловушка с fooling — так называют намеренную порчу заголовков пакета (TTL, номера seq или ack, TCP-опцию MD5 — в `nfqws2` это `tcp_md5`), обычно чтобы сервер отбросил такой пакет, а DPI его прочитал; подробно — [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]. В `multidisorder` fooling применяется ко всем отправляемым сегментам, а не только к фейкам, как у [[Zapret2/desync/fakeddisorder#Зачем нужен fakeddisorder|fakeddisorder]]. Если задать, например, `tcp_ack=-66000`, настоящие данные тоже получат неверный ack, и Linux-сервер их отбросит: на стенде (сентябрь 2026) соединение устанавливалось только с задержкой, после того как клиент повторно передал ClientHello целиком, без разреза. Разбор — в нюансе [[#9. Fooling применяется ко ВСЕМ сегментам|9. Fooling применяется ко ВСЕМ сегментам]].
У `multidisorder` есть близкие родственники. [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]] режет так же, но отправляет куски по порядку, принимает в `seqovl` только число, а сам `seqovl` работает и на Windows-серверах. [[Zapret2/desync/fakedsplit#Зачем нужен fakedsplit|fakedsplit]] и [[Zapret2/desync/fakeddisorder#Зачем нужен fakeddisorder|fakeddisorder]] делают один разрез и подмешивают к настоящим кускам фейковые сегменты, причём fakeddisorder, как и `multidisorder`, отправляет в обратном порядке. [[Zapret2/desync/hostfakesplit#Зачем нужен hostfakesplit|hostfakesplit]] режет запрос по границам имени хоста и подмешивает фейковое имя, [[Zapret2/desync/tcpseg#Зачем нужен tcpseg|tcpseg]] отправляет диапазон данных между двумя маркерами, а [[Zapret2/desync/oob#Зачем нужен oob|oob]] вместо разреза вставляет «срочный» (urgent) байт. [[Zapret2/desync/multidisorder_legacy#Зачем нужен multidisorder_legacy|multidisorder_legacy]] — вариант, совместимый с алгоритмом nfqws1: при многопакетных запросах порядок сегментов у него другой (см. [[Zapret2/desync/multidisorder_legacy#Ключевое отличие: попакетная обработка vs целый reasm|Ключевое отличие: попакетная обработка vs целый reasm]]). Все отличия сведены в таблицу в разделе [[#Отличия от других функций сегментации|Отличия от других функций сегментации]].
---
## Оглавление
- [Коротко и по-простому](#коротко-и-по-простому)
- [Справка: где функция в коде и родственники](#справка-где-функция-в-коде-и-родственники)
- [Зачем нужен multidisorder](#зачем-нужен-multidisorder)
- [Быстрый старт](#быстрый-старт)
- [Откуда берутся данные для нарезки](#откуда-берутся-данные-для-нарезки)
- [Маркеры позиций (pos)](#маркеры-позиций-pos)
- [Типы маркеров](#типы-маркеров)
- [Относительные маркеры](#относительные-маркеры)
- [Арифметика маркеров](#арифметика-маркеров)
- [Пример списка маркеров](#пример-списка-маркеров)
- [Как маркеры разрешаются в коде](#как-маркеры-разрешаются-в-коде)
- [Важные нюансы pos](#важные-нюансы-pos)
- [Обратный порядок отправки — суть disorder](#обратный-порядок-отправки--суть-disorder)
- [Почему disorder ломает DPI](#почему-disorder-ломает-dpi)
- [Ограничение: Windows-серверы](#ограничение-windows-серверы)
- [seqovl — перезапись буфера сокета через перекрытие](#seqovl--перезапись-буфера-сокета-через-перекрытие)
- [Принцип работы seqovl в disorder](#принцип-работы-seqovl-в-disorder)
- [Отличие seqovl от multisplit](#отличие-seqovl-от-multisplit)
- [seqovl как маркер](#seqovl-как-маркер)
- [Валидация seqovl: должен быть меньше pos[1]](#валидация-seqovl-должен-быть-меньше-pos1)
- [Нюанс реализации: ovl = seqovl - 1](#нюанс-реализации-ovl--seqovl---1)
- [seqovl_pattern](#seqovl_pattern)
- [Полный список аргументов](#полный-список-аргументов)
- [A) Собственные аргументы multidisorder](#a-собственные-аргументы-multidisorder)
- [B) Standard direction](#b-standard-direction)
- [C) Standard payload](#c-standard-payload)
- [D) Standard fooling](#d-standard-fooling)
- [E) Standard ipid](#e-standard-ipid)
- [F) Standard ipfrag](#f-standard-ipfrag)
- [G) Standard reconstruct](#g-standard-reconstruct)
- [H) Standard rawsend](#h-standard-rawsend)
- [Порядок отправки сегментов](#порядок-отправки-сегментов)
- [Пример с 3 позициями разреза (без seqovl)](#пример-с-3-позициями-разреза-без-seqovl)
- [Пример с seqovl (2 сегмента)](#пример-с-seqovl-2-сегмента)
- [ASCII-диаграмма: хронология буфера сокета на сервере](#ascii-диаграмма-хронология-буфера-сокета-на-сервере)
- [Пример с 3 позициями + seqovl](#пример-с-3-позициями--seqovl)
- [Поведение при replay / reasm](#поведение-при-replay--reasm)
- [Автосегментация по MSS](#автосегментация-по-mss)
- [Место в общем скелете](#место-в-общем-скелете)
- [Псевдокод алгоритма](#псевдокод-алгоритма)
- [Нюансы и подводные камни](#нюансы-и-подводные-камни)
- [1. Работает только с TCP](#1-работает-только-с-tcp)
- [2. Разрез в самом начале данных отбрасывается](#2-разрез-в-самом-начале-данных-отбрасывается)
- [3. Все маркеры могут не разрешиться](#3-все-маркеры-могут-не-разрешиться)
- [4. seqovl как маркер тоже может не разрешиться](#4-seqovl-как-маркер-тоже-может-не-разрешиться)
- [5. seqovl НЕ работает на Windows-серверах](#5-seqovl-не-работает-на-windows-серверах)
- [6. ovl = seqovl - 1: размер перекрытия на 1 меньше](#6-ovl--seqovl---1-размер-перекрытия-на-1-меньше)
- [7. seqovl >= pos[1] -> отмена seqovl](#7-seqovl--pos1---отмена-seqovl)
- [8. nodrop создаёт дублирование](#8-nodrop-создаёт-дублирование)
- [9. Fooling применяется ко ВСЕМ сегментам](#9-fooling-применяется-ко-всем-сегментам)
- [10. Алгоритм отличается от nfqws1](#10-алгоритм-отличается-от-nfqws1)
- [11. Порядок инстансов важен](#11-порядок-инстансов-важен)
- [Отличия от других функций сегментации](#отличия-от-других-функций-сегментации)
- [Миграция с nfqws1](#миграция-с-nfqws1)
- [Соответствие параметров](#соответствие-параметров)
- [Простая миграция](#простая-миграция)
- [Миграция с fake + multidisorder](#миграция-с-fake--multidisorder)
- [Миграция с seqovl](#миграция-с-seqovl)
- [Полная миграция: fake + disorder + seqovl + fooling](#полная-миграция-fake--disorder--seqovl--fooling)
- [Практические примеры](#практические-примеры)
- [📚 См. также](#-см-также)
---
## Справка: где функция в коде и родственники
Паспорт функции для тех, кто сверяется с исходниками 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`, чтобы оригинальный пакет не ушёл.
> [!note] Проще говоря
> Payload — полезные данные пакета без сетевых заголовков: сам HTTP-запрос или сам ClientHello. Reasm — те же данные, собранные движком из нескольких пакетов, если запрос в один пакет не влез. Blob — заранее заготовленные данные, которые можно подсунуть вместо настоящих. Какой из трёх источников выбирается, разобрано в разделе [[#Откуда берутся данные для нарезки|Откуда берутся данные для нарезки]]. Реассемблирование — склейка сегментов обратно в один поток; «DPI не справляется с реассемблированием разупорядоченных данных» значит, что он не умеет правильно склеить сегменты, пришедшие не по порядку. `VERDICT_DROP` — решение «перехваченный пакет не отправлять»; как движок складывает такие решения от разных функций, описано в [[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадии 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|Почему disorder ломает DPI]]. Про [[Zapret2/desync/multisplit#seqovl — скрытый фейк внутри сегмента|seqovl]] в `multisplit` и разницу с `multidisorder` — в разделе [[#seqovl — перезапись буфера сокета через перекрытие|seqovl — перезапись буфера сокета через перекрытие]].
> [!note] Проще говоря
> Обратный порядок — это те же куски запроса, отправленные от конца к началу. Сервер по правилам TCP дождётся недостающего начала и соберёт запрос. DPI, которому нужны куски по порядку, может потерять нить. Отдельный трюк `seqovl` в этой технике работает иначе, чем в [[multisplit]]: он опирается на перезапись данных в буфере сервера.
**multidisorder** vs **multisplit:** disorder работает за счёт разупорядочивания, а не за счёт линейного дробления. Для замешивания фейковых сегментов (отдельные пакеты с fooling) есть [[fakeddisorder]].
Сводное сравнение с [[multisplit]], [[fakedsplit]] и [[fakeddisorder]] по всем признакам — в таблице раздела [[#Отличия от других функций сегментации|Отличия от других функций сегментации]]. Про [[Zapret2/ts-and-fooling#Что такое fooling|fooling]] (порчу заголовков, из-за которой сервер отбрасывает пакет) — в разделе [[#D) Standard fooling|D) Standard fooling]].
---
## Быстрый старт
Ниже четыре готовые строки для [[Zapret2/profile#Анатомия профиля|профиля]] Zapret 2, от самой короткой к более сложным. Флаг `--payload=…` работает как фильтр для всех вызовов `--lua-desync`, которые идут после него (до следующего `--payload` или до конца профиля): они срабатывают только на данных указанного типа. Поэтому `--payload` пишут перед `--lua-desync`, а не после (подробно — в [[последовательность аргументов]]).
Минимально (разрез по позиции 2, payload=known, dir=out):
```bash
--lua-desync=multidisorder
```
Здесь `payload=known` означает «только распознанные протоколы», а `dir=out` — «только исходящие пакеты»; оба аргумента разобраны в разделах [[#B) Standard direction|B) Standard direction]] и [[#C) Standard payload|C) Standard payload]]. Без `pos` действует дефолт `pos=2`: первые 2 байта уходят отдельным сегментом, остаток — вторым, и сначала отправляется остаток.
Типовой TLS-разрез:
```bash
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld
```
Функция срабатывает только на TLS ClientHello, режет его посередине домена второго уровня на два сегмента, и вторая половина уходит раньше первой.
TLS с seqovl (маркер):
```bash
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld:seqovl=midsld-1
```
К сегменту с хвостом ClientHello слева приклеивается паттерн-фейк (`seqovl=midsld-1` — маркер с арифметикой, см. [[#seqovl как маркер|seqovl как маркер]]), а последним уходит начало запроса и перезаписывает этот паттерн в буфере сервера. Работает такое не на всех серверах: [[#Ограничение: Windows-серверы|Ограничение: Windows-серверы]].
HTTP с разрезом по hostname:
```bash
--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 и диссекта]]). Та же цепочка общая для большинства функций дурения и описана в [[Zapret2/жизненный цикл desync-функции#Стадия 4. Выбор данных: blob → reasm → payload|стадии 4 жизненного цикла]]; подробный разбор трёх источников есть в статье про multisplit: [[Zapret2/desync/multisplit#Откуда берутся данные для нарезки|Откуда берутся данные для нарезки]].
**Следствие:** все маркеры `pos`, `seqovl` и прочие аргументы применяются именно к тем данным, которые реально выбраны. Если вы задали `blob=myblob`, маркеры вроде `midsld` будут работать только если `myblob` содержит валидный TLS/HTTP payload, который zapret может распознать.
Поле `desync.l7payload` хранит [[Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)|тип payload]] — название распознанного протокола вроде `tls_client_hello` или `http_req`; по нему функция решает, найдётся ли в данных место для относительного маркера.
> [!note] Проще говоря
> Функция сначала спрашивает: «мне явно подсунули blob? — режу его». Если нет: «есть собранный из кусков payload? — режу его». Если и его нет: «режу тело текущего пакета». Маркеры и seqovl всегда работают по тем данным, которые выиграли этот выбор.
---
## Маркеры позиций (pos)
![[desync-anim-hub-markers.svg|Анимация: маркеры позиций pos на HTTP-запросе и на TLS ClientHello, разрез по pos=host,midsld,endhost]]
`pos` — главный аргумент `multidisorder`. Определяет **где** внутри payload будет произведён разрез. Задаётся как строка со списком маркеров через запятую.
Маркер — это смещение от начала данных, считая с нуля: разрез происходит *перед* байтом с таким номером. Отсюда простое правило: `pos=N` означает «первые N байт становятся отдельным сегментом». `pos=1` → первый сегмент длиной 1 байт, `pos=5` → 5 байт, дефолтный `pos=2` → 2 байта. Внутри Lua те же позиции хранятся как 1-based индексы строки, то есть с нумерацией от единицы (`resolve_multi_pos` прибавляет единицу), но в командной строке считать нужно от нуля. Набор маркеров и арифметика здесь такие же, как у [[Zapret2/desync/multisplit#Маркеры позиций (pos)|multisplit]] (список маркеров в таблицах совпадает); главное отличие — готовые куски потом уходят в обратном порядке.
> [!note] Проще говоря
> В командной строке пишите, сколько байт должно уйти в кусок, который в запросе стоит первым; отправлен этот кусок будет последним. Сдвиг на единицу внутри 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. Как движок определяет тип, описано в заметке [[Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)|о типах 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` вызывается:
```lua
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`)
Затем вызывается:
```lua
delete_pos_1(pos) -- удалить Lua-позицию 1, то есть маркер 0
```
Дедупликация — это удаление повторов. Зачем нужен `delete_pos_1`, объяснено ниже в списке нюансов и в нюансе [[#2. Разрез в самом начале данных отбрасывается|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 байта отдельным сегментом, весь остаток — вторым
> [!note] Проще говоря
> `pos=0` и `pos=method` (когда метод стоит на смещении 0) разреза не дают; маркеры, которые не нашлись в данных этого типа, выпадают молча; если не осталось ни одного маркера, функция ничего не делает. Разобрано в нюансах [[#2. Разрез в самом начале данных отбрасывается|2]] и [[#3. Все маркеры могут не разрешиться|3]].
---
## Обратный порядок отправки — суть disorder
Ключевое отличие `multidisorder` от [[multisplit]]: сегменты отправляются **от последнего к первому** (в порядке убывания sequence number). Цикл в `multidisorder_send`:
```lua
for i=#pos,0,-1 do
```
![[wiki-anim-z2-direction.svg|Анимация: multisplit отправляет куски 1, 2, 3, 4, multidisorder — 4, 3, 2, 1, у каждого свой seq]]
Цикл идёт от `#pos` (число позиций разреза, оно же номер последнего куска) вниз до 0. Кусков на один больше, чем разрезов: они нумеруются с 0 (начало данных) до `#pos` (хвост). Поэтому отправка начинается с хвоста и заканчивается началом. У [[Zapret2/desync/multisplit#Порядок отправки сегментов|multisplit]] порядок прямой.
> [!note] Проще говоря
> Куски запроса нумеруются 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 не хватает буфера или времени ждать все части. Результат проверяют на своём провайдере по [[Zapret2/verify-strategy#Короткий чек-лист честной проверки|чек-листу честной проверки]].
> [!note] Проще говоря
> Потоковый DPI ждёт данные по порядку и теряет нить, попакетный ищет имя в одном пакете и не находит его целиком, а DPI с пересборкой потока может справиться, если у него хватает буфера и терпения.
### Ограничение: Windows-серверы
Перекрытие номеров — ситуация, когда два принятых сегмента претендуют на одни и те же байты потока; что сервер делает в этом случае, зависит от его операционной системы.
Техника seqovl в режиме disorder **не работает с Windows-серверами**. При перекрытии sequence numbers:
- **Linux/BSD/macOS:** более поздний сегмент **перезаписывает** ранее буферизированные данные
- **Windows:** **сохраняет** первые полученные данные, игнорируя перекрывающиеся
Это означает, что seqovl_pattern, записанный в буфер предпоследним сегментом, на Windows **не будет перезаписан** реальными данными из последнего сегмента — сервер получит мусор.
**Без seqovl** multidisorder работает нормально на всех системах — простое разупорядочивание не зависит от поведения при перекрытии.
> [!note] Проще говоря
> На Windows-сервере фейк не затирается настоящими данными: он остаётся в буфере, и запрос приходит испорченным. Если целевой сервер работает под Windows, `seqovl` в `multidisorder` не нужен, а без него техника работает везде. В `multisplit` `seqovl` устроен иначе и на Windows работает: см. таблицу в разделе [[#Отличие seqovl от multisplit|Отличие seqovl от multisplit]] и нюанс [[#5. seqovl НЕ работает на Windows-серверах|5]].
---
## seqovl — перезапись буфера сокета через перекрытие
В `multidisorder` техника seqovl работает **принципиально иначе**, чем в [[multisplit]]. Здесь seqovl эксплуатирует переписывание данных в буфере сокета при получении перекрывающихся сегментов.
**seqovl** (Sequence Overlap, «перекрытие по номерам последовательности») — приём, при котором к настоящему сегменту слева дописываются лишние байты-фейк, а его sequence number сдвигается назад на их длину; как это устроено в `multisplit`, разобрано в разделе [[Zapret2/desync/multisplit#Принцип работы seqovl|Принцип работы seqovl]]. Буфер сокета — место, где стек сервера держит принятые байты, пока приложение их не забрало; байты, пришедшие раньше времени, лежат там, пока не придут недостающие. В `multisplit` фейк за левой границей окна приёма сервер выбрасывает сразу. В `multidisorder` фейк остаётся в буфере и затирается позднее следующим сегментом.
### Принцип работы seqovl в disorder
Рассмотрим payload из 2 частей: `pos=100` (одна точка разреза).
На схемах ниже ЧАСТЬ_1 и ЧАСТЬ_2 — два куска запроса, PATTERN — паттерн-фейк (по умолчанию нули), `i` — номер куска в оригинальном порядке (0 — начало данных), `seq` — номер первого байта сегмента, `len` — длина сегмента в байтах, число в скобках — размер блока в байтах. Значение `seqovl` в примере — 50, но паттерна приписывается на один байт меньше, то есть `ovl` = 49: причина разобрана в разделе [[#Нюанс реализации: ovl = seqovl - 1|Нюанс реализации: ovl = seqovl - 1]].
Payload из 300 байт, `pos=100`. `seqovl=midsld` разрешился в 50, поэтому `ovl = 50 - 1 = 49`. Порядок отправки обратный: `i` идёт от `#pos` до 0.
| Шаг | Что уходит | seq | len | Порядок |
|:--|:--|:--|:--|:--|
| `i=1` | ЧАСТЬ_2 с seqovl: слева приписано 49 байт PATTERN, `[PATTERN(49)][ЧАСТЬ_2(200)]` | `pos_start - 1 - ovl = 100 - 1 - 49 = 50` | 249 | первым |
| `i=0` | ЧАСТЬ_1 без seqovl, `[ЧАСТЬ_1(100)]` | 0 | 100 | последним |
Сначала уходит ЧАСТЬ_2 с приклеенным слева паттерном, потом ЧАСТЬ_1 — без него. Схема ниже показывает, что при этом лежит в буфере сервера.
**Что происходит в буфере сервера:**
![[wiki-anim-z2-seqovl-buffer.svg|Анимация: буфер сокета при seqovl в disorder — Linux и BSD перезаписывают паттерн, Windows сохраняет]]
Пока ЧАСТИ_1 нет, сервер данные приложению не отдаёт: непрерывной последовательности от нуля нет. Когда ЧАСТЬ_1 приходит, она занимает те же номера, что и PATTERN, и на Linux/BSD затирает его: приложение получает непрерывные байты 0..299, и PATTERN в них уже нет.
> [!note] Проще говоря
> Сервер сначала получил фейк, потом настоящие данные поверх. Linux, BSD и macOS принимают более позднее и затирают фейк. Windows оставляет первое: байты 50..98 остаются паттерном, и приложение получает испорченный запрос.
### Отличие seqovl от multisplit
Таблица ниже сравнивает приём с одним названием в двух функциях по пяти признакам: как устроен механизм, к какому сегменту он применяется, что принимает в качестве значения, кто отбрасывает фейк и работает ли он на Windows. Как это устроено в `multisplit`, — в разделе [[Zapret2/desync/multisplit#seqovl — скрытый фейк внутри сегмента|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()` — полноценный маркер:
```lua
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`:
```lua
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 |
| 10 | CANCELLED: 10 >= 10, не строго меньше |
| 15 | CANCELLED: 15 >= 10 |
Проще говоря: если разрешённое значение seqovl оказалось не меньше первой позиции разреза, `seqovl` отключается, а сегментация в обратном порядке остаётся. В логе тогда будет строка про "seqovl cancelled". Подробнее — нюанс [нюанс 7](#7-seqovl--pos1---отмена-seqovl).
### Нюанс реализации: ovl = seqovl - 1
В коде `multidisorder_send` есть важная деталь:
```lua
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|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.
```bash
# 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)]]
- **Примеры:**
- `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 — перезапись буфера сокета через перекрытие]]
- **Ограничение:** разрешённое значение seqovl **должно быть строго меньше** pos[1], иначе seqovl отменяется. Подробнее — [Валидация seqovl](#валидация-seqovl-должен-быть-меньше-pos1)
- **Примеры:**
- `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-фейк (что внутри, см. [[Zapret2/desync/fake#Стандартные blob-ы|стандартные 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. nodrop создаёт дублирование|нюанс 8]])
---
### B) Standard direction
Направление говорит, в какую сторону идёт пакет: от вашего компьютера к серверу (исходящий) или обратно (входящий).
| Параметр | Значения | По умолчанию |
|:---------|:---------|:-------------|
| `dir` | `in`, `out`, `any` | `out` |
Фильтр по направлению пакета. `multidisorder` по умолчанию работает только с исходящими (`out`).
- `dir=out` — только исходящие (от клиента к серверу)
- `dir=in` — только входящие (от сервера к клиенту)
- `dir=any` — оба направления
При первом вызове с указанным `dir` функция делает `direction_cutoff_opposite` — отсекает себя от противоположного направления. Cutoff (отсечение) значит, что движок больше не будет вызывать функцию на пакетах этого направления в текущем соединении; подробно — в [[Zapret2/жизненный цикл desync-функции#Стадия 2. Отсечение противоположного направления|стадии 2 жизненного цикла]].
---
### C) Standard payload
| Параметр | Значения | По умолчанию |
|:---------|:---------|:-------------|
| `payload` | список типов через запятую | `known` |
Фильтр по [[Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)|типу payload]] на уровне Lua. Это **дополнительный** фильтр к `--payload=...` на уровне [[Zapret2/profile#Анатомия профиля|профиля]].
- `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 его прочитал (подробно — [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]); L3/L4 — заголовки IP (сетевой уровень) и TCP (транспортный уровень). Многие опции из таблицы портят и настоящие данные: почему — в нюансе [[#9. Fooling применяется ко ВСЕМ сегментам|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|Автосегментация по MSS]].
---
### F) Standard ipfrag
IP-фрагментация **поверх** TCP-сегментации. Каждый TCP-сегмент дополнительно фрагментируется на уровне IP.
Проще говоря: TCP-сегмент, уже отрезанный `multidisorder`, при отправке ещё раз делится на части, но уже средствами протокола IP, а собирает их обратно получатель на уровне IP. Вместе с `ipfrag_disorder` получается двойной disorder, см. [[#11. IP-фрагментация поверх TCP-сегментации (двойной 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|Автосегментация по 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`), поэтому сервер собирает поток без пропусков, хотя куски приходят в обратном порядке. Для сравнения — [[Zapret2/desync/multisplit#Порядок отправки сегментов|Порядок отправки сегментов]] в 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|Нюанс реализации: 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|Принцип работы seqovl в disorder]], собранная в одну схему: t1 — приходит первый сегмент, t2 — второй; ниже показаны Linux/BSD и Windows. PATRN на схеме — то же, что PATTERN, сокращённо.
![[wiki-anim-z2-seqovl-buffer.svg|Анимация: буфер сокета при seqovl в disorder — Linux и BSD перезаписывают паттерн, Windows сохраняет]]
### Пример с 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
![[desync-anim-hub-reasm.svg|Анимация: большой ClientHello приходит тремя пакетами, ядро собирает reasm_data, функция режет весь буфер на первой части, остальные части дропаются]]
Многопакетный запрос (большой TLS ClientHello с post-quantum ключами и т. п.) `nfqws2` придерживает, собирает в буфер `reasm_data` и перепроигрывает через desync-функции. `multidisorder` использует штатную развилку перепроигрывания из общего скелета desync-функций ([[жизненный цикл desync-функции]], стадия 6) без изменений: нарезает и отправляет **весь** собранный буфер на первой части, а остальные части дропает, потому что их содержимое уже ушло в составе нарезки. Если отправка сорвалась, флаг «реасм уже отправлен» не ставится и оставшиеся части проходят как есть. Реассемблирование (reasm) — склейка запроса из нескольких TCP-пакетов; перепроигрывание (replay) — вызов функций по очереди на каждой придержанной части, уже с собранным целым на руках. Стадия 6 общего скелета разобрана в [[Zapret2/жизненный цикл desync-функции#Стадия 6. Развилка перепроигрывания (replay)|Стадия 6. Развилка перепроигрывания (replay)]], та же тема для multisplit — в разделе [[Zapret2/desync/multisplit#Поведение при replay / reasm|Поведение при replay / reasm]].
Специфика `multidisorder` — в следствии этого поведения. Раз режется весь реасм целиком, обратный порядок распространяется на весь запрос, а не на отдельные пакеты: последний сегмент всего ClientHello уйдёт первым, даже если исходно он приехал третьим пакетом.
**Отличие от [[multidisorder_legacy]]:** legacy-версия развилку не использует вовсе — она обрабатывает каждую часть replay отдельно, нормализуя позиции разреза под границы конкретного пакета, как это делал nfqws1. Из-за этого порядок сегментов при многопакетных запросах у двух функций разный. Для полной совместимости с nfqws1 нужен именно legacy-вариант. Разбор с диаграммами — [[Zapret2/desync/multidisorder_legacy#Ключевое отличие: попакетная обработка vs целый reasm|Ключевое отличие: попакетная обработка vs целый reasm]].
---
## Автосегментация по MSS
![[desync-anim-hub-mss.svg|Анимация: кусок данных больше MSS автоматически режется на под-сегменты с растущим seq]]
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 — [[Zapret2/desync/multisplit#Автосегментация по MSS|Автосегментация по 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 подходят ([[Zapret2/жизненный цикл desync-функции#Стадия 5. Три гварда|стадия 5]]). `instance_cutoff` отключает функцию для этого потока навсегда ([[Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff|стадия 1]]). `VERDICT_DROP` — решение «перехваченный оригинал не отправлять», `VERDICT_PASS` — «пропустить»; см. [[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадию 8]].
Отличие от [[multisplit]] сосредоточено ровно в одной стадии — седьмой. Всё остальное у двух функций совпадает буквально построчно, вплоть до общего хелпера `multidisorder_send`, который и разворачивает порядок отправки.
---
## Псевдокод алгоритма
Тело функции целиком, включая общие для всех техник стадии — они пронумерованы так же, как в [[жизненный цикл desync-функции]]:
```lua
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` и отправка. Остальное — общий скелет, разобранный в [[Zapret2/жизненный цикл desync-функции#Восемь стадий целиком|Восемь стадий целиком]].
> [!note] Проще говоря
> Функция проверяет, что пакет TCP и направление подходит, выбирает данные, на первой части перепроигрывания режет их на куски, отправляет куски от последнего к первому (паттерн-фейк приписывается только второму куску запроса) и выносит вердикт «оригинал не отправлять».
---
## Нюансы и подводные камни
### 1. Работает только с TCP
Если текущий пакет не TCP (UDP, ICMP и т.д.), `multidisorder` делает `instance_cutoff_shim` — отключает себя для этого потока навсегда (кроме ICMP-ответов, для которых cutoff не вызывается). Механика описана в [[Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff|стадии 1 жизненного цикла]]; почему резать в TCP-стиле нельзя в UDP — в [[Zapret2/desync/multisplit#1. Работает только с TCP — и почему|нюансе про TCP в multisplit]].
### 2. Разрез в самом начале данных отбрасывается
`delete_pos_1(pos)` убирает из списка Lua-позицию 1 — она соответствует маркеру `0`, то есть разрезу перед первым байтом, от которого первый сегмент вышел бы пустым. Если после этой чистки не осталось ни одной позиции, `multidisorder` не делает ничего.
Путаться тут легко из-за разной нумерации: в командной строке маркеры считаются от нуля, внутри Lua — от единицы. Поэтому под удаление попадает не `pos=1`, а `pos=0`. Самая частая реальная жертва — `pos=method` для HTTP: метод обычно лежит на смещении 0, маркер разрешается в удаляемую позицию, и разреза не происходит. Рабочий вариант — `pos=method+2`. Тот же нюанс есть в списке [[#Важные нюансы pos|Важные нюансы pos]].
### 3. Все маркеры могут не разрешиться
Если вы указали `pos=midsld,sniext` для HTTP-payload, оба маркера (специфичные для TLS) не разрешатся. Multidisorder напишет в лог "no valid split positions" и ничего не сделает. Проще говоря: если ни один маркер из списка не нашёлся в данных этого [[Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)|типа payload]], функция ничего не делает. Какие маркеры к каким типам подходят, показано в таблице [[#Относительные маркеры|Относительные маркеры]].
### 4. seqovl как маркер тоже может не разрешиться
В отличие от [[multisplit]] (где seqovl — число и всегда валидно), в multidisorder `seqovl=midsld-1` может не разрешиться (например, для unknown payload). В этом случае seqovl отменяется (логируется), но сегментация в обратном порядке всё равно происходит. См. [[#seqovl как маркер|seqovl как маркер]].
### 5. seqovl НЕ работает на Windows-серверах
Если целевой сервер работает под Windows, seqovl бесполезен: Windows сохраняет первые полученные данные и не перезаписывает буфер при получении перекрывающихся сегментов. Результат — сервер получит мусор (seqovl_pattern) вместо реальных данных. **Без seqovl** multidisorder работает на всех ОС нормально. Подробнее — [[#Ограничение: Windows-серверы|Ограничение: Windows-серверы]].
### 6. ovl = seqovl - 1: размер перекрытия на 1 меньше
Из-за строки `ovl = seqovl - 1` в `multidisorder_send`, если seqovl разрешился в 1, то ovl будет 0 — фактически seqovl не применяется. Минимальный эффективный seqovl — значение 2 (ovl = 1, 1 байт перекрытия). Разбор — в разделе [[#Нюанс реализации: ovl = seqovl - 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](#валидация-seqovl-должен-быть-меньше-pos1).
### 8. nodrop создаёт дублирование
С `nodrop` multidisorder отправляет нарезанные сегменты И пропускает оригинальный пакет. Сервер получит данные дважды. Используйте `nodrop` только для отладки или когда это осознанно нужно. Сам флаг описан в разделе [[#A) Собственные аргументы multidisorder|A) Собственные аргументы multidisorder]].
### 9. Fooling применяется ко ВСЕМ сегментам
Fooling — намеренная порча заголовков пакета, обычно для того, чтобы сервер отбросил пакет, а DPI его прочитал; полный разбор — в разделе [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]], список опций — в разделе [[#D) Standard fooling|D) Standard fooling]]. Своих фейковых сегментов у `multidisorder` нет, поэтому порче подвергаются настоящие данные; как это устроено у fakeddisorder, описано в разделе [[Zapret2/desync/fakeddisorder#Применение fooling и reconstruct|Применение 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` описан в заметке [[Zapret2/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]]. Разбор различий с диаграммами — [[Zapret2/desync/multidisorder_legacy#Ключевое отличие: попакетная обработка vs целый reasm|Ключевое отличие: попакетная обработка vs целый reasm]]; когда какой вариант брать — [[Zapret2/desync/multidisorder_legacy#Когда использовать multidisorder_legacy|Когда использовать multidisorder_legacy]].
### 11. Порядок инстансов важен
Если перед `multidisorder` стоит `fake`, он отправит фейк первым. Если после multidisorder стоит ещё один инстанс — он увидит VERDICT_DROP и не получит оригинальный payload. Инстанс — одна запись `--lua-desync=…` в профиле; движок вызывает их по очереди. Как аргументы и инстансы выстраиваются в цепочку, описано в [[последовательность аргументов]] и [[desync]].
Уточнение из разбора вердиктов: `VERDICT_DROP` не обрывает цепочку. Движок проходит все инстансы профиля и складывает их вердикты (`VERDICT_DROP` перебивает `VERDICT_PASS` и `VERDICT_MODIFY`), так что инстансы после `multidisorder` вызываются как обычно, а дроп означает лишь то, что оригинальный пакет в итоге не уйдёт ([[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадия 8 жизненного цикла]]; то же — в [[Zapret2/desync/multisplit#7. Порядок инстансов важен|нюансе о порядке инстансов в multisplit]]).
---
## Отличия от других функций сегментации
Четыре функции ниже режут данные на TCP-сегменты, но различаются числом разрезов, порядком отправки, наличием фейков и тем, как устроен `seqovl`. Подробности о каждой — в разделах [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]], [[Zapret2/desync/fakedsplit#Последовательность отправки (6 пакетов)|fakedsplit]] и [[Zapret2/desync/fakeddisorder#Порядок отправки сегментов (6 пакетов)|fakeddisorder]]; сравнение с вариантом для nfqws1 — в [[Zapret2/desync/multidisorder_legacy#Сравнительная таблица: multidisorder vs multidisorder_legacy|таблице 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` в инстансе |
### Простая миграция
```bash
# 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`; о самом [[Zapret2/desync/fake#Зачем нужен fake|fake]] — в его статье.
```bash
# 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 как маркер|seqovl как маркер]]).
```bash
# 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`, потому что им нужны разные [[Zapret2/desync/fake#Стандартные blob-ы|стандартные blob-ы]] (в коде ниже `fake_default_tls` и `fake_default_http`).
```bash
# 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, описано в разделе [[Zapret2/desync/multidisorder_legacy#Миграция с нового multidisorder на legacy|Миграция с нового multidisorder на legacy]].
---
## Практические примеры
Каждый пример — готовая строка для [[Zapret2/profile#Анатомия профиля|профиля]]; под ней объяснено, что получится на выходе.
### 1. Минимальный (дефолт: pos=2, dir=out, payload=known)
```bash
--lua-desync=multidisorder
```
Разрезает payload после 2-го байта -> 2 сегмента (первые 2 байта и остаток), отправляемых в обратном порядке.
### 2. TLS: разрез посередине SNI
```bash
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld
```
SNI разрезан пополам, хвост отправлен первым — DPI не может собрать домен.
### 3. TLS: разрез + seqovl маркером (классическая комбинация)
```bash
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld:seqovl=midsld-1
```
Разрез по midsld, seqovl перекрывает почти всю первую часть фейковыми данными. Типичный паттерн для disorder. Механика — в разделе [[#seqovl — перезапись буфера сокета через перекрытие|seqovl — перезапись буфера сокета через перекрытие]].
### 4. TLS: seqovl + кастомный паттерн
```bash
--payload=tls_client_hello \
--lua-desync=multidisorder:pos=midsld:seqovl=midsld-1:seqovl_pattern=0x1603030000
```
seqovl-область заполнена данными, похожими на начало TLS record — DPI может принять за легитимный TLS-трафик.
### 5. HTTP: разрез после метода (disorder)
```bash
--payload=http_req --lua-desync=multidisorder:pos=method+2
```
Для `GET /path...` разрежет после `GE` -> хвост уйдёт первым, голова — вторым. Сдвиг `+2` нужен потому, что голый `pos=method` указывает на самое начало данных и отбрасывается (см. [[#2. Разрез в самом начале данных отбрасывается|нюанс 2]]).
### 6. HTTP: несколько разрезов вокруг hostname
```bash
--payload=http_req --lua-desync=multidisorder:pos=host,midsld,endhost
```
Разрезает: до hostname | первая половина | вторая половина | после hostname -> 4 сегмента в обратном порядке.
### 7. Два разреза с seqovl
```bash
--payload=tls_client_hello --lua-desync=multidisorder:pos=2,midsld:seqovl=sld
```
3 сегмента, seqovl применяется к сегменту i=1 (второй в оригинале). Маркер `sld` определяет размер перекрытия. Ограничение на значение — [Валидация seqovl](#валидация-seqovl-должен-быть-меньше-pos1).
### 8. Произвольный blob вместо payload
```bash
--blob=mydata:@custom_payload.bin \
--lua-desync=multidisorder:blob=mydata:pos=10,100,-20
```
Режет и отправляет произвольные данные из файла вместо реального payload, в обратном порядке. Как объявлять такие данные, описано в заметке [[blob]].
### 9. Защита от отсутствующего blob
```bash
--lua-desync=multidisorder:blob=maybe_missing:optional:pos=midsld
```
Если blob не существует — тихий пропуск, без ошибок и без VERDICT_DROP. Флаг `optional` описан в разделе [[#A) Собственные аргументы multidisorder|A) Собственные аргументы multidisorder]].
### 10. Отладка: не блокировать оригинал
```bash
--payload=http_req --lua-desync=multidisorder:pos=host,midsld:nodrop
```
Отправляет нарезанные сегменты в обратном порядке И пропускает оригинальный пакет (для экспериментов). В рабочей настройке так не делают: сервер получит данные дважды (см. [[#8. nodrop создаёт дублирование|нюанс 8]]).
### 11. IP-фрагментация поверх TCP-сегментации (двойной disorder)
```bash
--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|F) Standard ipfrag]].
### 12. Комбинация: fake -> multidisorder (типичный боевой профиль)
```bash
--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`, разобрано в заметке [[Zapret2/desync/fake#Зачем нужен fake|fake]].
### 13. Боевой пример для YouTube
```bash
--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
```bash
--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. Fooling применяется ко ВСЕМ сегментам|нюанс 9]]).
### 15. Повторы отправки
```bash
--payload=tls_client_hello --lua-desync=multidisorder:pos=midsld:repeats=2
```
Каждый сегмент отправляется 2 раза (бинарные повторы). Подробнее про `repeats` — в разделе [[#H) Standard rawsend|H) Standard rawsend]].
### 16. Вариант из каталога GUI: fake + multidisorder с badseq
В каталоге стратегий [[guide|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` ниже добавлены для наглядности:
```bash
--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 режется на три части по первому байту и середине домена второго уровня и уходит в обратном порядке.
> [!warning] Разрез до сервера не доходит
> У `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/DPI|DPI и ТСПУ]] — как устроена инспекция трафика, против которой работает `multidisorder`
---
> **Источники:** `lua/zapret-antidpi.lua:546-629`, `lua/zapret-lib.lua`, `docs/manual.md:4067-4097`, `docs/readme.md` из репозитория zapret2.
---
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: [исходник этой заметки](https://git2.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/Zapret2/desync/multidisorder.md) · [скачать весь репозиторий одним zip-архивом](https://git2.zapret.moe/zapretdiscordyoutube/todo/archive/main.zip).