Some checks failed
Published content check / validate (push) Failing after 17s
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>
1273 lines
132 KiB
Markdown
1273 lines
132 KiB
Markdown
---
|
||
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).
|