# `multisplit` — последовательная TCP-сегментация (zapret2 / nfqws2) **Файл:** `lua/zapret-antidpi.lua:471` **nfqws1 эквивалент:** `--dpi-desync=multisplit` **Сигнатура:** `function multisplit(ctx, desync)` `multisplit` — базовая функция TCP-сегментации в zapret2. Она берёт текущий payload (или reasm, или blob), разрезает его на несколько TCP-сегментов по заданным позициям и отправляет их **последовательно** (от первого к последнему). После успешной отправки выносит `VERDICT_DROP`, чтобы оригинальный пакет не ушёл. Родственные функции: [[multidisorder]] (обратный порядок), [[fakedsplit]] (с фейками), [[fakeddisorder]] (фейки + обратный порядок), [[hostfakesplit]] (по hostname), [[tcpseg]] (диапазон), [[oob]] (urgent byte). --- ## Оглавление - [Зачем нужен multisplit](#зачем-нужен-multisplit) - [Быстрый старт](#быстрый-старт) - [Что на входе, что на выходе, что происходит внутри](#что-на-входе-что-на-выходе-что-происходит-внутри) - [Вход: объект desync](#вход-объект-desync) - [Выход: сырые сегменты + вердикт](#выход-сырые-сегменты--вердикт) - [Что происходит внутри: разбор по шагам](#что-происходит-внутри-разбор-по-шагам) - [Откуда берутся данные для нарезки](#откуда-берутся-данные-для-нарезки) - [Маркеры позиций (pos)](#маркеры-позиций-pos) - [Типы маркеров](#типы-маркеров) - [Относительные маркеры](#относительные-маркеры) - [Арифметика маркеров](#арифметика-маркеров) - [Как маркеры разрешаются в коде](#как-маркеры-разрешаются-в-коде) - [Важные нюансы pos](#важные-нюансы-pos) - [seqovl — скрытый фейк внутри сегмента](#seqovl--скрытый-фейк-внутри-сегмента) - [Принцип работы seqovl](#принцип-работы-seqovl) - [Зачем seqovl лучше обычного fooling](#зачем-seqovl-лучше-обычного-fooling) - [seqovl_pattern](#seqovl_pattern) - [Полный список аргументов](#полный-список-аргументов) - [A) Собственные аргументы multisplit](#a-собственные-аргументы-multisplit) - [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) - [Порядок отправки сегментов](#порядок-отправки-сегментов) - [Поведение при replay / reasm](#поведение-при-replay--reasm) - [Автосегментация по MSS](#автосегментация-по-mss) - [Псевдокод алгоритма](#псевдокод-алгоритма) - [Нюансы и подводные камни](#нюансы-и-подводные-камни) - [Отличия от других функций сегментации](#отличия-от-других-функций-сегментации) - [Миграция с nfqws1](#миграция-с-nfqws1) - [Практические примеры](#практические-примеры) --- ## Зачем нужен multisplit DPI анализирует TCP-поток, пытаясь собрать полный payload и найти в нём сигнатуры (hostname в HTTP, SNI в TLS). Если мы разрежем пакет на несколько TCP-сегментов, DPI может: 1. **Не собрать данные:** некоторые DPI работают попакетно и не реассемблируют TCP 2. **Не найти сигнатуру:** если разрез проходит через `Host:` или SNI, ни в одном отдельном сегменте полного hostname нет 3. **Принять фейк за реальные данные:** с помощью `seqovl` можно замешать ложную информацию, которую DPI проглотит, а сервер — нет Сервер при этом корректно собирает поток — TCP-стек гарантирует это через sequence numbers. **multisplit** — самый простой вариант: режем и шлём **по порядку**. Для обратного порядка есть [[multidisorder]], для замешивания фейковых сегментов — [[fakedsplit]]/[[fakeddisorder]]. --- ## Быстрый старт Минимально (разрез по позиции 2, payload=known, dir=out): ```bash --lua-desync=multisplit ``` Типовой TLS-разрез: ```bash --payload=tls_client_hello --lua-desync=multisplit:pos=1,midsld ``` TLS с seqovl: ```bash --payload=tls_client_hello --lua-desync=multisplit:pos=1:seqovl=5:seqovl_pattern=0x1603030000 ``` HTTP с разрезом по hostname: ```bash --payload=http_req --lua-desync=multisplit:pos=host,midsld,endhost ``` --- ## Что на входе, что на выходе, что происходит внутри Прежде чем разбирать отдельные аргументы, полезно увидеть функцию целиком как «чёрный ящик»: что в неё влетает, что происходит с данными и что вылетает наружу. `multisplit` — это Lua-функция, которую движок nfqws2 вызывает на каждом захваченном пакете, попавшем в её инстанс. Общая механика вызова `--lua-desync`-функций (как C-ядро перехватывает пакет, диссектирует его и передаёт в Lua) описана в обзорной заметке [[desync]]; здесь разбирается именно то, что делает с этими данными сама `multisplit`. Сигнатура одна строка: ```lua function multisplit(ctx, desync) ``` Два аргумента: `ctx` — низкоуровневый контекст очереди netfilter/WinDivert (через него функции `rawsend_*` реально выпускают пакеты в сеть, а `instance_cutoff` отключают инстанс), его напрямую трогать не нужно; и `desync` — таблица со всем состоянием текущего пакета. Вся работа идёт через `desync`. ### Вход: объект desync `desync` — это не «пакет», а собранный движком контекст: разобранный (диссектированный) пакет плюс аргументы инстанса плюс состояние соединения. `multisplit` читает из него следующие поля. **`desync.dis` — диссектированный пакет.** Это результат разбора L3/L4-заголовков C-ядром. Внутри: `desync.dis.tcp` (таблица TCP-заголовка с полями вроде `th_seq`, либо `nil`, если пакет не TCP), `desync.dis.udp`, `desync.dis.icmp` — флаги/таблицы протоколов, и `desync.dis.payload` — сам прикладной payload (тело TCP-сегмента, то есть уже без заголовков). Именно `payload` в итоге и режется, если нет источника поприоритетнее. **`desync.reasm_data` — реассемблированный payload.** Если прикладные данные не поместились в один TCP-сегмент (типичный случай — большой TLS ClientHello с post-quantum ключами Kyber, который приходит несколькими пакетами), движок собирает все части в единый буфер `reasm_data`. Пока сборка не завершена, `multisplit` ждёт, а на первой же «полной» части получает целый ClientHello и режет уже его. Подробнее о реассемблировании — в разделе [Поведение при replay / reasm](#поведение-при-replay--reasm). **`desync.l7payload` — распознанный тип протокола.** Строка: `"tls_client_hello"`, `"http_req"`, `"quic_initial"`, либо `"unknown"` / `"empty"`. От этого значения зависит, сможет ли `multisplit` разрешить относительные маркеры вроде `midsld` или `host` — они привязаны к структуре конкретного протокола (см. [[payload]] про распознавание типов). **`desync.arg` — разобранные аргументы инстанса.** Всё, что вы написали после `multisplit:` в командной строке, движок разложил в поля `desync.arg`: `desync.arg.pos`, `desync.arg.seqovl`, `desync.arg.seqovl_pattern`, `desync.arg.blob`, `desync.arg.optional`, `desync.arg.nodrop`, `desync.arg.dir`, `desync.arg.payload` и т. д. Важный нюанс: **все значения приходят строками**, а флаг без значения (`:optional`, `:nodrop`) превращается в пустую строку `""`, которая в Lua истинна — поэтому проверка `if desync.arg.optional then` срабатывает. **`desync.outgoing`, `desync.replay`, `desync.replay_piece`, `desync.track`** — служебные поля направления, режима перепроигрывания и состояния трекинга соединения. Через них работают фильтр направления (`direction_check`) и логика однократной отправки reasm (`replay_first` / `replay_drop`), разобранные ниже. > [!note] Проще говоря > На вход `multisplit` получает не «пакет с проводом», а уже распотрошённую движком структуру: отдельно заголовки, отдельно прикладные данные, отдельно ваши аргументы и отдельно память о соединении. Функция это читает и решает, как перекроить данные. ### Выход: сырые сегменты + вердикт У `multisplit` два вида «выхода», и это ключ к пониманию. **Побочный эффект — сырые пакеты в сеть.** Основную работу функция делает не через `return`, а вызовами `rawsend_payload_segmented(desync, part, seq)`. Каждый такой вызов формирует новый TCP-сегмент с нужным payload и сдвинутым sequence number и **сразу выпускает его в сеть** через raw-сокет (на Linux) или инъекцию WinDivert (на Windows). То есть к моменту, когда функция дойдёт до `return`, нарезанные сегменты уже улетели адресату. **Возвращаемое значение — вердикт оригинальному пакету.** Оригинальный перехваченный пакет всё ещё «висит» в очереди и ждёт решения. `multisplit` возвращает одно из: - `VERDICT_DROP` — выбросить оригинал (значение по умолчанию после успешной нарезки). Логично: раз данные уже отправлены нарезанными сегментами, оригинальный целый пакет отправлять нельзя — иначе сервер получит их дважды. - `VERDICT_PASS` — пропустить оригинал как есть. Возвращается при флаге `nodrop`, а также как аварийный выход, если `rawsend` не смог отправить сегмент (тогда лучше отдать оригинал, чем потерять данные вовсе). - «ничего» (`nil`/без `return`) — когда функция решила не действовать (не TCP, не то направление, пустой payload, не разрешилось ни одной позиции). Оригинал при этом идёт дальше по цепочке инстансов без изменений. ```lua replay_drop_set(desync) return desync.arg.nodrop and VERDICT_PASS or VERDICT_DROP ``` > [!important] Главное про выход > `multisplit` не «модифицирует» перехваченный пакет и не «возвращает нарезку». Она **отдельно** отправляет заранее нарезанные сегменты сырыми пакетами, а перехваченному оригиналу выносит `VERDICT_DROP`, чтобы тот не продублировал уже отправленные данные. ### Что происходит внутри: разбор по шагам Ниже — реальный код тела функции (`lua/zapret-antidpi.lua:483`), разобранный по шагам. Он короткий, и каждый фрагмент делает ровно одну вещь. **Шаг 1. Отсекаем не-TCP.** `multisplit` работает только с TCP. Если пакет не TCP — инстанс отключается от этого потока навсегда (`instance_cutoff_shim`), кроме связанного ICMP, который не должен вызывать отключение: ```lua if not desync.dis.tcp then if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end ``` **Шаг 2. Отсекаем противоположное направление.** По умолчанию `dir=out` (только исходящие). `direction_cutoff_opposite` при первом вызове отключает инстанс от входящего направления, чтобы движок зря не гонял входящие пакеты через эту функцию: ```lua direction_cutoff_opposite(ctx, desync) ``` **Шаг 3. Мягкий пропуск отсутствующего blob.** Если задан `optional` и `blob=имя`, но такого blob нет — тихо выходим, без ошибки (защита от опечаток и от blob, который может не сгенерироваться): ```lua if desync.arg.optional and desync.arg.blob and not blob_exist(desync, desync.arg.blob) then DLOG("multisplit: blob '"..desync.arg.blob.."' not found. skipped") return end ``` **Шаг 4. Выбор данных для нарезки.** Одна строка, но важная — именно она решает, *что* режется. Приоритет: явный blob → reasm → payload текущего пакета (подробный разбор — в следующем разделе): ```lua local data = blob_or_def(desync, desync.arg.blob) or desync.reasm_data or desync.dis.payload ``` **Шаг 5. Три проверки-гварда.** Дальше идём только если есть что резать (`#data>0`), пакет того направления, что нужно (`direction_check`), и его тип попадает под фильтр `payload` (`payload_check`): ```lua if #data>0 and direction_check(desync) and payload_check(desync) then ``` **Шаг 6. Только первая часть replay.** Если это перепроигрывание многопакетного payload, действуем лишь на первой части (`replay_first`), где `reasm_data` уже собран целиком. Последующие части попадут в ветку `else` и будут просто дропнуты. **Шаг 7. Разрешение позиций разреза.** Строку маркеров (`desync.arg.pos` или дефолт `"2"`) превращаем в массив конкретных числовых позиций, затем убираем позицию 1 (по первому байту резать нельзя): ```lua local pos = resolve_multi_pos(data, desync.l7payload, spos) delete_pos_1(pos) -- cannot split at the first byte ``` **Шаг 8–10. Цикл нарезки и отправки.** Если после чистки осталась хоть одна позиция, идём по частям. Для части `i` начало — `pos[i]` (для нулевой части — 1), конец — `pos[i+1]-1` (для последней — весь остаток `#data`). Вырезаем кусок, для первой части (`i==0`) при заданном `seqovl` дописываем слева паттерн-фейк, и отправляем сегмент со сдвинутым sequence number: ```lua for i=0,#pos do local pos_start = pos[i] or 1 local pos_end = i<#pos and pos[i+1]-1 or #data local part = string.sub(data,pos_start,pos_end) local seqovl=0 if i==0 and desync.arg.seqovl and tonumber(desync.arg.seqovl)>0 then seqovl = tonumber(desync.arg.seqovl) local pat="\x00" if desync.arg.seqovl_pattern then -- ... взять blob или нулевой паттерн ... pat = blob(desync,desync.arg.seqovl_pattern) end part = pattern(pat,1,seqovl)..part end if not rawsend_payload_segmented(desync,part,pos_start-1-seqovl) then return VERDICT_PASS end end ``` Третий аргумент `rawsend_payload_segmented` — это сдвиг sequence number: `pos_start-1` даёт 0-based смещение байта в payload (то есть правильный seq для сегмента), а вычитание `seqovl` уводит первый сегмент влево на длину фейка. Если `rawsend` вернул `false` (не смог отправить) — цикл прерывается и оригинал пропускается (`VERDICT_PASS`), чтобы данные не потерялись. **Шаг 11. Пометка «reasm уже отправлен» и вердикт.** После успешной отправки всех частей ставим флаг, чтобы последующие replay-части не переслали то же самое, и выносим вердикт оригиналу: ```lua replay_drop_set(desync) return desync.arg.nodrop and VERDICT_PASS or VERDICT_DROP ``` Полный псевдокод с ветками `else` — в разделе [Псевдокод алгоритма](#псевдокод-алгоритма). --- ## Откуда берутся данные для нарезки Прежде чем что-то резать, `multisplit` должна выбрать, *какие именно* данные она режет. Это делает всего одна строка, но за ней стоит цепочка приоритетов: ```lua local data = blob_or_def(desync, desync.arg.blob) or desync.reasm_data or desync.dis.payload ``` Lua-оператор `or` возвращает первый истинный (не `nil` и не `false`) операнд, поэтому строка читается как «сверху вниз, берём первое, что есть». Разберём три источника по порядку. **Первый приоритет — явный blob.** Если в аргументах задан `blob=имя`, функция `blob_or_def` достаёт содержимое этого blob и режет именно его, полностью игнорируя реальный payload пакета. Это способ отправить *произвольные* данные вместо того, что реально шлёт приложение: заранее заготовленный фейковый ClientHello, модифицированный запрос, содержимое файла. Что такое blob и как их объявлять (inline-hex `0x...`, `@файл`, именованные переменные) — см. отдельную заметку [[blob]]. Если `blob=` не задан, `blob_or_def` возвращает `nil`, и `or` идёт дальше. **Второй приоритет — реассемблированные данные.** Если blob нет, но движок собрал многопакетный payload в `desync.reasm_data` (например, TLS ClientHello, растянувшийся на несколько TCP-сегментов), режется этот собранный буфер целиком. Благодаря этому разрез по `midsld` попадёт в реальную середину доменного имени, даже если само имя физически лежало во втором или третьем пакете исходного потока. Механику сборки и однократной отправки reasm разбирает раздел [Поведение при replay / reasm](#поведение-при-replay--reasm). **Третий приоритет — payload текущего пакета.** Если ни blob, ни reasm нет (обычный одиночный пакет, всё поместилось), берётся `desync.dis.payload` — тело именно этого перехваченного TCP-сегмента. Это дефолтный путь для большинства простых случаев. **Почему порядок именно такой и что из этого следует.** Все дальнейшие аргументы — маркеры `pos`, длина `seqovl`, проверки — применяются к тем данным, которые реально выбраны на этом шаге, а не к «пакету вообще». Практическое следствие: если вы задали `blob=myblob`, то относительные маркеры вроде `midsld` или `host` сработают только при условии, что содержимое `myblob` — это валидный TLS- или HTTP-payload, который движок сумеет распознать (иначе `desync.l7payload` для blob окажется `unknown` и такие маркеры молча отвалятся, см. [Важные нюансы pos](#важные-нюансы-pos)). > [!note] Проще говоря > Функция сначала спрашивает: «мне явно подсунули blob? — режу его». Если нет: «есть собранный из кусков payload? — режу его». Если и его нет: «режу тело текущего пакета». Маркеры и seqovl всегда работают по тем данным, которые выиграли этот выбор. --- ## Маркеры позиций (pos) `pos` — главный аргумент `multisplit`. Определяет **где** внутри payload будет произведён разрез. Задаётся как строка со списком маркеров через запятую. ### Типы маркеров | Тип | Описание | Пример | |:----|:---------|:-------| | **Абсолютный положительный** | Смещение от начала payload. В Lua позиции начинаются с 1 | `1`, `5`, `100` | | **Абсолютный отрицательный** | Смещение от конца payload. `-1` = последний байт | `-1`, `-10`, `-50` | | **Относительный** | Логическая позиция внутри распознанного payload. Привязана к структуре протокола | `midsld`, `host`, `sniext` | ### Относительные маркеры | Маркер | Описание | Для каких 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 частей (если все маркеры успешно разрешатся и дадут различные позиции). ### Как маркеры разрешаются в коде Внутри `multisplit` вызывается: ```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) Затем вызывается: ```lua delete_pos_1(pos) -- удалить позицию 1 (нельзя разрезать на самом первом байте) ``` ### Важные нюансы pos - **Нельзя разрезать по позиции 1** (первый байт). Позиция 1 автоматически удаляется из списка. Это означает, что `pos=1` по факту не создаст разреза — вместо этого данные отправятся целиком. Для разреза "после 1-го байта" используйте `pos=2` (это дефолт) - **Дублирующиеся позиции объединяются.** `pos=5,5,5` = `pos=5` - **Неразрешимые маркеры пропускаются.** Если `midsld` не разрешается (payload = unknown), он просто исчезает из списка. Если все маркеры не разрешились — multisplit ничего не делает (логирует "no valid split positions") - **Позиции сортируются.** Независимо от порядка записи, `pos=100,5,50` будет обработано как `5,50,100` - **По умолчанию pos="2".** Если `pos` не задан, разрез по позиции 2 → payload делится на 2 части: 1-й байт отдельно, остальное отдельно --- ## seqovl — скрытый фейк внутри сегмента **seqovl** (Sequence Overlap) — техника скрытого замешивания фейковых данных в реальный TCP-сегмент через манипуляцию TCP sequence number. В `multisplit` seqovl применяется **только к первому** отправляемому сегменту. ### Принцип работы seqovl Чтобы понять seqovl, нужно вспомнить, как TCP-получатель решает, какие байты принять. У каждого байта в TCP-потоке есть свой sequence number — сквозной номер позиции в потоке. Приёмная сторона держит «окно» (receive window) и принимает только байты, чей номер попадает в него, начиная с номера следующего *ожидаемого* байта. Всё, что левее этой границы (номера, которые сторона уже получила или ещё не ждёт с этого места), молча отбрасывается как дубликат или мусор — это штатное поведение TCP-стека, а не ошибка. seqovl эксплуатирует ровно этот механизм. Возьмём для примера первый сегмент, который в норме начинался бы с sequence number 1000 и нёс реальные данные. **Без seqovl** сегмент уходит как есть: seq = 1000, тело — реальная первая часть, сервер принимает её целиком, потому что 1000 — как раз следующий ожидаемый им байт. **С `seqovl=10`** функция делает две вещи одновременно. Во-первых, дописывает слева к телу сегмента 10 байт паттерна-фейка (`seqovl_pattern`, по умолчанию нули). Во-вторых, уменьшает объявленный в заголовке sequence number на те же 10 — с 1000 до 990. В результате в сеть уходит один-единственный, совершенно обычный с виду TCP-сегмент: seq = 990, тело = `[10 байт паттерна][реальная первая часть]`. Дальше этот сегмент по-разному воспринимают две стороны. **Что видит DPI.** Для системы глубокого анализа это цельный TCP-сегмент, начинающийся с sequence 990. Многие DPI разбирают содержимое сегмента «как есть», от его начала, не сверяясь с тем, какие именно байты примет получатель. Поэтому DPI анализирует весь блок — вместе с 10 байтами паттерна. Если в паттерн подложен, например, фрагмент, похожий на начало легитимного TLS-record или ложный SNI, DPI может принять фейк за реальные данные и вынести решение по нему, а не по настоящему домену. **Что видит сервер (TCP-стек получателя).** Следующий ожидаемый им байт по-прежнему имеет номер 1000. Байты с номерами 990–999 (те самые 10 байт паттерна) оказываются левее границы окна — сервер отбрасывает их автоматически, как повтор/устаревшие данные. А байты, начиная с 1000 (реальная первая часть), попадают в окно и принимаются. В итоге сервер собирает ровно исходный поток, будто никакого паттерна и не было. Наглядно расклад по одному sequence-байту такой: | Диапазон seq | Содержимое | DPI | Сервер (TCP-стек) | |:-------------|:-----------|:----|:------------------| | 990–999 | `PATTERN` (10 байт фейка) | анализирует вместе со всем сегментом | **отбрасывает** — левее границы окна | | с 1000 | реальная первая часть | анализирует | **принимает** | Ключевая идея: одни и те же 10 байт для DPI — «полноценные данные для анализа», а для сервера — «мусор за левым краем окна». Расхождение в трактовке и есть рабочий механизм обхода. ### Зачем seqovl лучше обычного fooling | Критерий | Обычный fooling (TTL, badseq, md5sig) | seqovl | |:---------|:---------------------------------------|:-------| | Заголовки | Модифицируются (TTL, seq, ack, md5) | **Не модифицируются** — пакет выглядит полностью легитимным | | Обнаружение | DPI может детектировать подозрительные заголовки | DPI видит "честный" сегмент с правильными заголовками | | Механизм отбрасывания | Сервер отбрасывает весь пакет из-за невалидных заголовков | Сервер отбрасывает только часть, выходящую за TCP window | | Надёжность | Зависит от поведения конкретного стека | Основан на фундаментальном свойстве TCP | **Вывод:** seqovl — средство создания скрытых фейков, не требующее fooling. Это его ключевое преимущество. ### seqovl_pattern Паттерн, которым заполняется seqovl-область (N байт слева от реальных данных). По умолчанию — `0x00` (нули). В `multisplit` `seqovl_pattern` — это **имя blob**. Паттерн повторяется до нужной длины `seqovl`. ```bash # Inline hex blob (маскировка под начало TLS record) --lua-desync=multisplit:pos=1:seqovl=5:seqovl_pattern=0x1603030000 # Предзагруженный blob --blob=tlspat:0x1603030100 \ --lua-desync=multisplit:pos=1:seqovl=8:seqovl_pattern=tlspat ``` Если `optional` задан и blob `seqovl_pattern` отсутствует — используется нулевой паттерн (операция не отменяется). **Важно в multisplit:** `seqovl` — только **число**, маркеры не поддерживаются (в отличие от `multidisorder` и `fakeddisorder`, где seqovl может быть маркером). --- ## Полный список аргументов Формат вызова: ``` --lua-desync=multisplit[:arg1[=val1][:arg2[=val2]]...] ``` Все `val` приходят в Lua как строки. Если `=val` не указан, значение = пустая строка `""` (в Lua это truthy), поэтому флаги пишутся просто как `:optional`, `:nodrop`, `:tcp_ts_up`. ### A) Собственные аргументы multisplit #### `pos` - **Формат:** `pos=<marker[,marker2,...]>` - **Тип:** строка со списком маркеров через запятую - **По умолчанию:** `"2"` - **Описание:** Точки разреза. Каждый маркер определяет позицию, по которой payload будет разрезан. N маркеров → до N+1 сегментов - **Примеры:** - `pos=2` — разрез после 1-го байта (дефолт) - `pos=midsld` — разрез посередине SLD - `pos=1,midsld` — два разреза: после 1-го байта и посередине SLD → 3 сегмента - `pos=host,midsld,endhost-2,-10` — четыре разреза → до 5 сегментов - `pos=method+2` — после первых 2 символов HTTP-метода #### `seqovl` - **Формат:** `seqovl=N` (где N > 0) - **Тип:** только число (маркеры **не поддерживаются** — в отличие от multidisorder) - **По умолчанию:** не задан (нет seqovl) - **Описание:** Применяется **только к первому** отправляемому сегменту. К данным первого сегмента слева добавляется N байт `seqovl_pattern`, а TCP `th_seq` уменьшается на N. Сервер отбросит левую часть, DPI — может не отбросить - **Примеры:** - `seqovl=5` — 5 байт фейка слева - `seqovl=13` — 13 байт фейка слева - `seqovl=10000` — 10000 байт (если превысит MSS — автосегментация разобьёт на несколько TCP-сегментов) #### `seqovl_pattern` - **Формат:** `seqovl_pattern=<blobName>` - **Тип:** имя blob-переменной - **По умолчанию:** один байт `0x00`, повторяемый до длины `seqovl` - **Описание:** Данные для заполнения seqovl-области. Blob повторяется функцией `pattern()` до нужного размера - **Поведение с `optional`:** если `optional` задан и blob отсутствует — используется нулевой паттерн, seqovl не отменяется - **Примеры:** - `seqovl_pattern=0x1603030000` — inline hex (маскировка под TLS) - `seqovl_pattern=my_pattern_blob` — предзагруженный blob #### `blob` - **Формат:** `blob=<blobName>` - **Тип:** имя blob-переменной - **По умолчанию:** не задан - **Описание:** Заменить текущий payload/reasm на указанный blob и резать/слать его. Используется для отправки произвольных данных (фейковых payload, модифицированных ClientHello и т.д.) - **Примеры:** - `blob=fake_default_tls` — стандартный TLS-фейк - `blob=0xDEADBEEF` — inline hex - `blob=my_custom_ch` — предзагруженный blob #### `optional` - **Формат:** `optional` (флаг, без значения) - **Описание:** Мягкий режим: - Если задан `blob=...` и blob отсутствует → multisplit **ничего не делает** (тихий skip, без ошибок) - Если задан `seqovl_pattern=...` и blob отсутствует → используется нулевой паттерн (seqovl не отменяется) - **Использование:** защита от ошибок при использовании blob, которые могут отсутствовать (например, если blob генерируется другой функцией) #### `nodrop` - **Формат:** `nodrop` (флаг, без значения) - **Описание:** После успешной отправки сегментов **не выносить** `VERDICT_DROP` (вместо этого вернуть `VERDICT_PASS`). Это означает, что оригинальный пакет тоже будет отправлен (наряду с нарезанными сегментами) - **Использование:** для отладки, для отправки произвольных данных без блокировки оригинала - **Предупреждение:** в боевых профилях `nodrop` обычно нежелателен — оригинал ещё раз уйдёт, что создаст дублирование и может ухудшить обход --- ### B) Standard direction | Параметр | Значения | По умолчанию | |:---------|:---------|:-------------| | `dir` | `in`, `out`, `any` | `out` | Фильтр по направлению пакета. `multisplit` по умолчанию работает только с исходящими (`out`). - `dir=out` — только исходящие (от клиента к серверу) - `dir=in` — только входящие (от сервера к клиенту) - `dir=any` — оба направления При первом вызове с указанным `dir` функция делает `direction_cutoff_opposite` — отсекает себя от противоположного направления. --- ### C) Standard payload | Параметр | Значения | По умолчанию | |:---------|:---------|:-------------| | `payload` | список типов через запятую | `known` | Фильтр по типу payload на уровне Lua. Это **дополнительный** фильтр к `--payload=...` на уровне профиля. - `payload=known` — только распознанные протоколы (`http_req`, `tls_client_hello`, `quic_initial` и т.д.) - `payload=all` — любой payload, включая `unknown` - `payload=tls_client_hello,http_req` — конкретные типы - `payload=~unknown` — инверсия: всё кроме unknown **Важно:** лучше ставить `--payload=...` на уровне профиля (C-код, быстрее), а не полагаться только на Lua-фильтр. --- ### D) Standard fooling Модификации L3/L4 заголовков. В `multisplit` применяются **ко всем** отправляемым сегментам (в отличие от fakedsplit, где 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=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-сегментации). --- ### F) Standard ipfrag IP-фрагментация **поверх** TCP-сегментации. Каждый TCP-сегмент дополнительно фрагментируется на уровне IP. | Параметр | Описание | По умолчанию | |:---------|:---------|:-------------| | `ipfrag[=func]` | Включить IP-фрагментацию. Если без значения → `ipfrag2` | — | | `ipfrag_disorder` | Отправить IP-фрагменты в обратном порядке | — | | `ipfrag_pos_tcp=N` | Позиция фрагментации TCP (кратно 8) | `32` | | `ipfrag_pos_udp=N` | Позиция фрагментации UDP (кратно 8). Для multisplit бесполезно — он только TCP | `8` | | `ipfrag_next=N` | IPv6: next protocol во 2-м фрагменте (penetration атака на фаерволы) | — | --- ### G) Standard reconstruct | Параметр | Описание | |:---------|:---------| | `badsum` | Испортить L4 (TCP) checksum при реконструкции raw-пакета. Сервер отбросит такой пакет | --- ### H) Standard rawsend | Параметр | Описание | |:---------|:---------| | `repeats=N` | Отправить каждый сегмент N раз (идентичные повторы) | | `ifout=<iface>` | Интерфейс для отправки (по умолчанию определяется автоматически) | | `fwmark=N` | Firewall mark (только Linux, nftables/iptables) | --- ## Порядок отправки сегментов `multisplit` всегда отправляет сегменты **последовательно** — от первого к последнему (в порядке возрастания TCP sequence). ### Пример с 3 позициями разреза Возьмём payload длиной 600 байт и три позиции разреза: `pos=100,300,450`. Они делят данные на четыре куска (0–99, 100–299, 300–449, 450–599), и `multisplit` отправляет их четырьмя TCP-сегментами по возрастанию sequence number. Sequence каждого сегмента равен смещению его первого байта в исходном payload, поэтому сервер собирает поток обратно без пропусков: | Сегмент | Байты payload | Длина | Смещение (seq) | |:--------|:--------------|:------|:---------------| | 1 | 0–99 | 100 | 0 | | 2 | 100–299 | 200 | 100 | | 3 | 300–449 | 150 | 300 | | 4 | 450–599 | 150 | 450 | ### Пример с seqovl=10 Тот же payload 600 байт, но `pos=100` (один разрез, два сегмента) и `seqovl=10`. К первому сегменту слева дописываются 10 байт паттерна, а его sequence сдвигается на те же 10 в минус — поэтому паттерн уходит за левую границу окна и сервер его отбрасывает, приняв только реальную часть с seq 0: | Сегмент | Содержимое | Длина | Смещение (seq) | Что с ним на сервере | |:--------|:-----------|:------|:---------------|:---------------------| | 1 | 10 байт паттерна + байты 0–99 | 110 | −10 | паттерн отброшен, реальная часть принята | | 2 | байты 100–599 | 500 | 100 | принят целиком | --- ## Поведение при replay / reasm При многопакетных payload (например, большой TLS ClientHello с post-quantum Kyber, который не влезает в один TCP-сегмент) zapret собирает все части в `reasm_data`. При перепроигрывании (replay): 1. **Первая часть replay:** multisplit берёт **весь** `reasm_data`, нарезает и отправляет. Устанавливает флаг `replay_drop_set` 2. **Все последующие части replay:** multisplit видит, что отправка уже произошла, и выносит `VERDICT_DROP` (если не `nodrop`) — потому что весь reasm уже отправлен нарезанным, нет смысла отправлять оригинальные части **Исключение:** если первая отправка неуспешна (rawsend вернул false), флаг не устанавливается и последующие части проходят как есть. --- ## Автосегментация по MSS О размерах TCP-сегментов думать **не нужно**. Функция `rawsend_payload_segmented` из `zapret-lib.lua` автоматически: 1. Отслеживает MSS для каждого TCP-соединения 2. Если часть payload превышает MSS — дополнительно режет по MSS 3. Каждый под-сегмент отправляется с корректным TCP sequence **Пример:** если вы задали `seqovl=10000`, это не вызовет ошибку. `rawsend_payload_segmented` отправит несколько TCP-сегментов с начальным sequence -10000, общим размером 10000 байт seqovl-pattern, и в последнем сегменте — начало реальных данных. --- ## Псевдокод алгоритма ```lua function multisplit(ctx, desync) -- 1. Проверка: только TCP if not desync.dis.tcp then cutoff; return end -- 2. Cutoff противоположного направления direction_cutoff_opposite(ctx, desync) -- 3. Проверка optional blob if optional and blob specified and blob not exists then 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) -- нельзя резать по позиции 1 if #pos > 0 then -- 8. Цикл по частям (i=0 до #pos) for i = 0, #pos 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) -- 9. seqovl для первого сегмента seqovl = 0 if i == 0 and arg.seqovl > 0 then seqovl = tonumber(arg.seqovl) pat = seqovl_pattern_blob or "\x00" part = pattern(pat, 1, seqovl) .. part end -- 10. Отправка с автосегментацией rawsend_payload_segmented(part, pos_start - 1 - seqovl) end -- 11. Пометить как отправленное replay_drop_set() return nodrop and VERDICT_PASS or VERDICT_DROP end else -- 12. Не первый replay — дропнуть если ранее успешно отправлено if replay_drop() then return nodrop and VERDICT_PASS or VERDICT_DROP end end end end ``` --- ## Нюансы и подводные камни ### 1. Работает только с TCP — и почему `multisplit` обрабатывает исключительно TCP. Это первое, что проверяет функция: если текущий пакет не TCP (UDP, QUIC поверх UDP, ICMP и т. д.), она делает `instance_cutoff` — отключает свой инстанс для этого потока навсегда (исключение — связанный ICMP, он не вызывает отключения): ```lua if not desync.dis.tcp then if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end ``` Ограничение не техническая недоработка, а следствие сути приёма — он опирается на два свойства, которые есть у TCP и отсутствуют у UDP. **TCP — это поток байтов со сквозной нумерацией, UDP — набор независимых датаграмм.** В TCP у каждого байта есть свой sequence number, и получатель обязан по этим номерам собрать поток в исходном порядке — это гарантия протокола. Поэтому один payload можно разрезать на несколько сегментов, разослать их (по порядку — `multisplit`, вперемешку — [[multidisorder]]), и сервер прозрачно склеит всё обратно: ему безразлично, пришли данные одним пакетом или пятью. В UDP такого нет — каждая датаграмма самодостаточна, никакой пересборки «потока» по номерам байт на приёмной стороне не происходит. Разрезать UDP-payload на две датаграммы бессмысленно: принимающее приложение получит два отдельных куска и не обязано (и не будет) склеивать их так, как задумано. Резать в TCP-стиле там просто нечего. **seqovl тем более завязан на TCP.** Трюк [seqovl](#seqovl--скрытый-фейк-внутри-сегмента) держится на конкретной детали TCP-стека: получатель отбрасывает байты, чей sequence number ушёл левее границы окна приёма. Дописываем фейк слева, сдвигаем seq в минус — сервер выкидывает фейк, а DPI его анализирует. У UDP нет ни sequence number, ни окна приёма, ни понятия «байты левее границы» — эксплуатировать нечего. **Что делают с UDP вместо этого.** UDP-протоколы (в первую очередь QUIC) обходят другими desync-функциями: [[fake]] (фейковая датаграмма перед реальной), IP-фрагментация `ipfrag` (режется на уровне IP, а не TCP), манипуляции с длиной и прочее. То есть UDP работают на других уровнях, а TCP-сегментация как класс к нему неприменима. Обзор всех функций дурения — в [[desync]]. > [!note] Проще говоря > `multisplit` режет один payload на куски и рассчитывает, что получатель склеит их обратно «по номерам». Такое склеивание умеет только TCP. UDP обрабатывает каждый пакет сам по себе и склеивать по номерам не умеет — значит, и резать в этом стиле нечего. ### 2. Позиция 1 удаляется `delete_pos_1(pos)` убирает позицию 1 из списка. Если после этого не осталось ни одной позиции — multisplit ничего не делает. Это значит, что `pos=1` **бесполезна** как единственная позиция. ### 3. Все маркеры могут не разрешиться Если вы указали `pos=midsld,sniext` для HTTP-payload, оба маркера (специфичные для TLS) не разрешатся. Multisplit напишет в лог "no valid split positions" и ничего не сделает. ### 4. nodrop создаёт дублирование С `nodrop` multisplit отправляет нарезанные сегменты И пропускает оригинальный пакет. Сервер получит данные дважды. Используйте `nodrop` только для отладки или когда это осознанно нужно. ### 5. seqovl=10000 не вызовет ошибку В отличие от nfqws1, где большие значения seqovl вызывали ошибку, nfqws2 автоматически сегментирует по MSS. Большой seqovl просто создаст много под-сегментов. ### 6. Fooling применяется ко ВСЕМ сегментам В отличие от `fakedsplit`/`fakeddisorder`, где fooling идёт только на фейки, в `multisplit` все сегменты получают fooling. Если задать `tcp_ack=-66000`, **все** сегменты получат инвалидный ack — сервер их отбросит, и ничего не заработает. Fooling в multisplit имеет смысл только для специфических вещей (например, `tcp_ts_up`, `ip_id`, IPv6 extension headers). ### 7. Порядок инстансов важен Если перед `multisplit` стоит `pktmod` с fooling — fooling применится к диссекту, и multisplit порежет уже модифицированный пакет. Если после multisplit стоит ещё один инстанс — он увидит VERDICT_DROP и не получит оригинальный payload. --- ## Отличия от других функций сегментации | Аспект | `multisplit` | `multidisorder` | `fakedsplit` | `fakeddisorder` | |:-------|:-------------|:----------------|:-------------|:----------------| | Количество позиций | Список (любое кол-во) | Список (любое кол-во) | **Одна** | **Одна** | | Порядок отправки | Прямой (1→2→3) | Обратный (3→2→1) | Прямой | Обратный | | Фейковые сегменты | **Нет** | **Нет** | Да (до 4 шт.) | Да (до 4 шт.) | | seqovl тип | Только число | **Маркер** | Только число | **Маркер** | | seqovl к какому сегменту | 1-й | 2-й (предпоследний) | 1-й реальный | 2-й реальный | | Fooling к | Всем сегментам | Всем сегментам | Только к фейкам | Только к фейкам | | ipfrag | Да | Да | **Нет** | **Нет** | --- ## Миграция с nfqws1 ### Соответствие параметров | nfqws1 | nfqws2 | |:-------|:-------| | `--dpi-desync=multisplit` | `--lua-desync=multisplit` | | `--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=fake,multisplit \ --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=multisplit:pos=1,midsld:seqovl=5:seqovl_pattern=0x1603030000 ``` ```bash # nfqws1: nfqws --dpi-desync=syndata,multisplit --dpi-desync-split-pos=midsld --wssize 1:6 # nfqws2 (порядок инстансов важен!): nfqws2 \ --lua-desync=wssize:wsize=1:scale=6 \ --lua-desync=syndata \ --lua-desync=multisplit:pos=midsld ``` --- ## Практические примеры ### Минимальный (дефолт: pos=2, dir=out, payload=known) ```bash --lua-desync=multisplit ``` Разрезает payload после 1-го байта → 2 сегмента. ### HTTP: разрез после метода ```bash --payload=http_req --lua-desync=multisplit:pos=method+2 ``` Для `GET /path...` разрежет после `GE` → DPI не увидит полный метод. ### HTTP: несколько разрезов вокруг hostname ```bash --payload=http_req --lua-desync=multisplit:pos=host,midsld,endhost ``` Разрезает: до hostname | первая половина | вторая половина | после hostname → 4 сегмента. ### TLS: разрез посередине SNI ```bash --payload=tls_client_hello --lua-desync=multisplit:pos=midsld ``` SNI разрезан пополам — ни в одном сегменте нет полного домена. ### TLS: два разреза + seqovl ```bash --payload=tls_client_hello --lua-desync=multisplit:pos=1,midsld:seqovl=5:seqovl_pattern=0x1603030000 ``` 3 сегмента: первый с 5-байтовым TLS-фейком слева (DPI может принять за начало TLS record). ### Произвольный blob вместо payload ```bash --blob=mydata:@custom_payload.bin \ --lua-desync=multisplit:blob=mydata:pos=10,100,-20 ``` Режет и отправляет произвольные данные из файла вместо реального payload. ### Защита от отсутствующего blob ```bash --lua-desync=multisplit:blob=maybe_missing:optional:pos=2 ``` Если blob не существует — тихий пропуск, без ошибок и без VERDICT_DROP. ### Отладка: не блокировать оригинал ```bash --payload=http_req --lua-desync=multisplit:pos=method+2:nodrop ``` Отправляет нарезанные сегменты И пропускает оригинальный пакет (для экспериментов). ### С TCP timestamp + IP ID ```bash --payload=tls_client_hello --lua-desync=multisplit:pos=1:tcp_ts_up:ip_id=seq:ip_id_conn ``` ### Повторы отправки ```bash --payload=tls_client_hello --lua-desync=multisplit:pos=1:repeats=2 ``` Каждый сегмент отправляется 2 раза (бинарные повторы). ### IP-фрагментация поверх TCP-сегментации ```bash --payload=tls_client_hello --lua-desync=multisplit:pos=1,midsld:ipfrag:ipfrag_disorder:ipfrag_pos_tcp=32 ``` Каждый TCP-сегмент дополнительно фрагментируется на IP-уровне в обратном порядке. ### Комбинация: fake → multisplit ```bash --payload=tls_client_hello \ --lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \ --lua-desync=multisplit:pos=1,midsld:seqovl=5:seqovl_pattern=0x1603030000 ``` Сначала отправляется фейковый TLS ClientHello (с fooling), затем реальный — нарезанный на 3 сегмента с seqovl. ### Боевой пример для YouTube ```bash --filter-tcp=443 --hostlist=youtube.txt \ --lua-desync=fake:blob=fake_default_tls:repeats=11:tcp_md5 \ --lua-desync=multisplit:pos=1,midsld ``` 11 фейков подряд + реальный payload разрезан на 3 части. --- > **Источники:** `lua/zapret-antidpi.lua:483-539` (тело `multisplit`), `lua/zapret-lib.lua` (`blob_or_def`, `pattern`, `delete_pos_1`, `rawsend_payload_segmented`, `direction_check`, `replay_first`/`replay_drop`), `nfq2/lua.c` и `nfq2/protocol.c` (`resolve_pos`/`resolve_multi_pos`/`ResolveMultiPos`), `docs/manual.md`, `docs/readme.md` из репозитория zapret2. --- > [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ > При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны на GitHub: [исходник этой заметки](https://github.com/youtubediscord/todo/blob/main/Zapret2/desync/multisplit.md) · [весь репозиторий](https://github.com/youtubediscord/todo/tree/main).