todo/Zapret2/desync/fakeddisorder.md
loop-uh f4099f81c5 Чистка битых wikilinks по всему хранилищу
Исправлены ссылки с лишним префиксом Privacy/ (Hysteria/00-overview,
Zapret/download), несуществующие цели заменены: fooling → ts-and-fooling,
wssize и маркеры → обзор desync, genhost и instance_cutoff → инлайн-код.

Проверка obsidian unresolved: было 28 неразрешённых ссылок, осталась одна
в Zapret/home.md (отсутствующая картинка, файл владельца не трогаем).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 01:30:39 +03:00

64 KiB
Raw Permalink Blame History

date tags aliases
2026-02-17
zapret
zapret2
nfqws2
lua
lua-desync
antidpi
tcp
fakeddisorder
fakedsplit
disorder
seqovl
fakeddisorder

fakeddisorder — TCP-сегментация с фейками в обратном порядке (zapret2 / nfqws2)

Файл: lua/zapret-antidpi.lua:931 nfqws1 эквивалент: --dpi-desync=fakeddisorder Сигнатура: function fakeddisorder(ctx, desync)

fakeddisorder — функция TCP-десинхронизации с двойным запутыванием DPI: по содержимому (реальные vs фейковые сегменты) и по порядку (обратная последовательность — disorder). Payload разрезается по одной позиции на 2 части, и каждая часть обрамляется фейковыми сегментами. Отправка идёт в обратном порядке: сначала вторая часть (с фейками), затем первая (с фейками). Итого — 6 пакетов. После успешной отправки выносит VERDICT_DROP, чтобы оригинальный пакет не ушёл.

Родственные функции: multisplit (прямой порядок, без фейков), multidisorder (обратный порядок, без фейков), fakedsplit (с фейками, прямой порядок), hostfakesplit (по hostname), tcpseg (диапазон), oob (urgent byte).

Общий для всех техник дурения порядок работы — восемь стадий от отсева чужого транспорта до вердикта — разобран в жизненный цикл desync-функции; здесь описано только то, чем fakeddisorder от этого скелета отличается.


Оглавление


Зачем нужен fakeddisorder

DPI анализирует TCP-поток, пытаясь собрать полный payload и найти в нём сигнатуры (hostname в HTTP, SNI в TLS). fakeddisorder атакует DPI двумя способами одновременно:

  1. Запутывание по содержимому (fake). Между реальными сегментами посылаются фейковые, содержащие мусорные данные. DPI должен отличить настоящие сегменты от поддельных. Фейки отбрасываются сервером благодаря Zapret2/ts-and-fooling (badseq, badsum, TTL и т.д.), но DPI может их проглотить.

  2. Запутывание по порядку (disorder). Сегменты отправляются в обратном порядке: сначала вторая часть payload, затем первая. DPI, ожидающий последовательного потока, может быть сбит с толку.

Двойное запутывание (содержимое + порядок) эффективнее, чем каждый приём по отдельности:

multisplit:       [часть 1] → [часть 2]                    — только разрез
multidisorder:    [часть 2] → [часть 1]                    — разрез + обратный порядок
fakedsplit:       [F1][часть 1][F2] → [F3][часть 2][F4]    — разрез + фейки
fakeddisorder:    [F1][часть 2][F2] → [F3][часть 1][F4]    — разрез + фейки + обратный порядок

Сервер при этом корректно собирает поток — TCP-стек гарантирует это через sequence numbers, а фейки отбрасываются благодаря fooling.


Быстрый старт

Минимально (разрез по позиции 2, payload=known, dir=out):

--lua-desync=fakeddisorder:tcp_ack=-66000

Типовой TLS-разрез посередине SNI:

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

TLS с seqovl для тройного запутывания:

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:seqovl=5:seqovl_pattern=0x1603030000:tcp_ack=-66000:tcp_ts_up

HTTP с кастомным паттерном фейков:

--payload=http_req --lua-desync=fakeddisorder:pos=host:pattern=0x474554202F:tcp_ack=-66000

Откуда берутся данные для нарезки

Внутри fakeddisorder данные (data) выбираются в следующем порядке приоритетов:

1. blob_or_def(desync, desync.arg.blob)    — если задан blob= и он существует
2. desync.reasm_data                        — если есть реассемблированные данные (multi-packet payload)
3. desync.dis.payload                       — текущий пакет (fallback)

Следствие: все маркеры pos, seqovl и прочие аргументы применяются именно к тем данным, которые реально выбраны. Если вы задали blob=myblob, маркеры вроде midsld будут работать только если myblob содержит валидный TLS/HTTP payload, который zapret может распознать.


Маркер позиции (pos)

pos — главный аргумент fakeddisorder. Определяет где внутри payload будет произведён разрез. В отличие от multisplit и multidisorder, pos — это один маркер, а не список. Payload всегда разрезается на ровно 2 части.

Типы маркеров

Тип Описание Пример
Абсолютный положительный Смещение от начала payload, считая с 0. pos=N → первые N байт становятся отдельным сегментом 2, 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      — два байта после начала метода
sniext+1      — один байт после начала SNI extension data
host+3        — три байта после начала hostname
-1            — последний байт payload (абсолютный, не относительный)

Как маркер разрешается в коде

В отличие от multisplit (который использует resolve_multi_pos для списка маркеров), fakeddisorder использует resolve_pos — функцию для одного маркера:

local spos = desync.arg.pos or "2"
local pos = resolve_pos(data, desync.l7payload, spos)

resolve_pos возвращает одно число (абсолютную Lua-позицию, 1-based) или nil, если маркер не может быть разрешён.

Важные нюансы pos

  • Только ОДИН маркер. pos=midsld,endhost — невалидно (в отличие от multisplit/multidisorder, которые принимают списки). Если вам нужно несколько позиций разреза с фейками — используйте несколько инстансов fakeddisorder в цепочке.
  • Разрез в самом начале данных запрещён. Если маркер разрешается в Lua-позицию 1 — это маркер 0, поскольку смещения в командной строке считаются с нуля, — fakeddisorder логирует "split pos resolved to 0. cannot split." и ничего не делает. Не работает именно pos=0; pos=1 (первый байт отдельным сегментом) корректен. Частая жертва правила — pos=method для HTTP, где метод лежит на смещении 0.
  • Неразрешимый маркер = ничего не делает. Если маркер не может быть разрешён (например, midsld для unknown payload), fakeddisorder логирует "cannot resolve pos" и пропускает операцию.
  • По умолчанию pos="2". Если pos не задан, разрез по позиции 2 — payload делится на 1-й байт и остальное.

seqovl — скрытый фейк внутри реального сегмента

seqovl (Sequence Overlap) — техника скрытого замешивания фейковых данных в реальный TCP-сегмент через манипуляцию TCP sequence number. В fakeddisorder seqovl применяется только ко второму реальному сегменту (real part 2 — первый реальный пакет в порядке отправки).

Принцип работы seqovl в fakeddisorder

Без seqovl:
  Пакет 2 (real part 2):
    TCP seq: pos-1
    Данные:  [РЕАЛЬНАЯ_ЧАСТЬ_2]
    Сервер:  принимает [РЕАЛЬНАЯ_ЧАСТЬ_2] целиком

С seqovl=10:
  Пакет 2 (real part 2):
    TCP seq: pos-1-10           (уменьшен на 10)
    Данные:  [PATTERN_10_БАЙТ][РЕАЛЬНАЯ_ЧАСТЬ_2]

  Что видит DPI:
    Единый TCP-сегмент начиная с seq pos-1-10.
    DPI анализирует весь блок, включая PATTERN.
    PATTERN содержит мусор → DPI может не распознать SNI.

  Что видит сервер (TCP-стек):
    Байты до pos-1 выходят за левую границу window → отбрасываются.
    Байты с pos-1 (РЕАЛЬНАЯ_ЧАСТЬ_2) → принимаются.

Визуализация:

           TCP window boundary (= pos-1)
                 ↓
  |  ОТБРОСИТЬ  | ПРИНЯТЬ              |
  | PATTERN(10) | РЕАЛЬНАЯ_ЧАСТЬ_2     |
  ^seq=pos-11    ^seq=pos-1

seqovl — маркер, а не число

Критическое отличие от multisplit и fakedsplit: в fakeddisorder (как и в multidisorder) seqovl — это маркер, а не просто число. Он разрешается через resolve_pos:

seqovl = resolve_pos(data, desync.l7payload, desync.arg.seqovl)
seqovl = seqovl - 1  -- Lua→0-based

Это значит, что можно использовать:

  • seqovl=5 — абсолютное число (разрешится в 5, станет 4 после -1)
  • seqovl=host — относительный маркер (разрешится в позицию начала hostname, -1)
  • seqovl=midsld-2 — маркер с арифметикой

Ограничение: результат seqovl (после -1) должен быть строго меньше pos-1. Иначе seqovl отменяется с логом:

fakeddisorder: seqovl cancelled because seqovl N is not less than the split pos M

Если маркер не разрешается, seqovl тоже отменяется:

fakeddisorder: seqovl cancelled because could not resolve marker 'xxx'

Зачем seqovl лучше обычного fooling

Критерий Обычный fooling (TTL, badseq, md5sig) seqovl
Заголовки Модифицируются (TTL, seq, ack, md5) Не модифицируются — пакет выглядит полностью легитимным
Обнаружение DPI может детектировать подозрительные заголовки DPI видит "честный" сегмент с правильными заголовками
Механизм отбрасывания Сервер отбрасывает весь пакет из-за невалидных заголовков Сервер отбрасывает только часть, выходящую за TCP window
Надёжность Зависит от поведения конкретного стека Основан на фундаментальном свойстве TCP

Вывод: seqovl добавляет третий уровень запутывания (к фейкам и disorder), причём реальный сегмент с seqovl выглядит абсолютно легитимно для DPI.

seqovl_pattern

Паттерн, которым заполняется seqovl-область. По умолчанию — 0x00 (нули).

В fakeddisorder seqovl_pattern — это имя blob. Паттерн повторяется до нужной длины seqovl.

# Inline hex blob (маскировка под начало TLS record)
--lua-desync=fakeddisorder:pos=midsld:seqovl=5:seqovl_pattern=0x1603030000:tcp_ack=-66000

# Предзагруженный blob
--blob=tlspat:0x1603030100 \
--lua-desync=fakeddisorder:pos=midsld:seqovl=8:seqovl_pattern=tlspat:tcp_ack=-66000

Если optional задан и blob seqovl_pattern отсутствует — используется нулевой паттерн (seqovl не отменяется).


Фейковые сегменты и pattern

Фейковые сегменты генерируются функцией pattern():

fakepat = desync.arg.pattern and blob(desync, desync.arg.pattern) or "\x00"
fake_part2 = pattern(fakepat, pos, #data - pos + 1)   -- фейк для части 2
fake_part1 = pattern(fakepat, 1, pos - 1)             -- фейк для части 1

Ключевые свойства:

  • Фейк совпадает по размеру с реальной частью, которую он имитирует
  • pattern() принимает смещение (pos или 1), что означает, что фейк заполняется так, как будто он начинается с той же позиции в payload. Это делает его TCP sequence корректным для DPI
  • По умолчанию pattern = \x00 (нули). Можно задать свой blob через pattern=<blob>
  • Blob повторяется до нужной длины

Важно: фейки отправляются с полным fooling (badseq, TTL и т.д.), чтобы сервер их отбросил. Без fooling сервер примет фейки как реальные данные и поток будет повреждён.


Полный список аргументов

Формат вызова:

--lua-desync=fakeddisorder[:arg1[=val1][:arg2[=val2]]...]

Все val приходят в Lua как строки. Если =val не указан, значение = пустая строка "" (в Lua это truthy), поэтому флаги пишутся просто как :optional, :nodrop, :tcp_ts_up.

A) Собственные аргументы fakeddisorder

pos

  • Формат: pos=<marker>
  • Тип: строка с одним маркером (НЕ список!)
  • По умолчанию: "2"
  • Описание: Точка разреза. Payload делится на 2 части: data[1..pos-1] и data[pos..#data]
  • Примеры:
    • pos=2 — разрез после 2-го байта (дефолт)
    • pos=1 — первый байт уходит отдельным сегментом
    • pos=midsld — разрез посередине SLD
    • pos=host — разрез в начале hostname
    • pos=sniext+1 — один байт после начала SNI extension data
    • pos=endhost-2 — два байта до конца hostname

seqovl

  • Формат: seqovl=<marker>
  • Тип: маркер (не только число — в отличие от multisplit/fakedsplit!)
  • По умолчанию: не задан (нет seqovl)
  • Описание: Применяется ко второму реальному сегменту (real part 2). К данным слева добавляется seqovl байт seqovl_pattern, а TCP th_seq уменьшается на seqovl. Маркер разрешается через resolve_pos, затем результат уменьшается на 1 (Lua→0-based). Результат должен быть строго меньше pos-1
  • Примеры:
    • seqovl=5 — 4 байта фейка слева (5→resolve→5, 5-1=4)
    • seqovl=host — seqovl равен позиции начала hostname (минус 1)
    • seqovl=sld-2 — маркер с арифметикой

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

pattern

  • Формат: pattern=<blobName>
  • Тип: имя blob-переменной
  • По умолчанию: один байт 0x00
  • Описание: Данные для заполнения фейковых сегментов. Blob повторяется до размера соответствующей реальной части
  • Примеры:
    • pattern=0x474554202F — inline hex (маскировка под начало HTTP GET)
    • pattern=fake_tls_pat — предзагруженный blob

blob

  • Формат: blob=<blobName>
  • Тип: имя blob-переменной
  • По умолчанию: не задан
  • Описание: Заменить текущий payload/reasm на указанный blob и резать/слать его. Используется для отправки произвольных данных (фейковых payload, модифицированных ClientHello и т.д.)
  • Примеры:
    • blob=fake_default_tls — стандартный TLS-фейк
    • blob=0xDEADBEEF — inline hex
    • blob=my_custom_ch — предзагруженный blob

nofake1

  • Формат: nofake1 (флаг, без значения)
  • Описание: Не отправлять первый фейковый пакет (fake part 2, первый по порядку отправки)

nofake2

  • Формат: nofake2 (флаг, без значения)
  • Описание: Не отправлять третий пакет (fake part 2 повторно, после реального part 2)

nofake3

  • Формат: nofake3 (флаг, без значения)
  • Описание: Не отправлять четвёртый пакет (fake part 1, перед реальным part 1)

nofake4

  • Формат: nofake4 (флаг, без значения)
  • Описание: Не отправлять шестой пакет (fake part 1 повторно, после реального part 1)

optional

  • Формат: optional (флаг, без значения)
  • Описание: Мягкий режим:
    • Если задан blob=... и blob отсутствует — fakeddisorder ничего не делает (тихий skip, без ошибок)
    • Если задан seqovl_pattern=... и blob отсутствует — используется нулевой паттерн (seqovl не отменяется)

nodrop

  • Формат: nodrop (флаг, без значения)
  • Описание: После успешной отправки не выносить VERDICT_DROP (вместо этого вернуть VERDICT_PASS). Оригинальный пакет тоже будет отправлен (наряду с 6 нарезанными пакетами)
  • Предупреждение: в боевых профилях nodrop обычно нежелателен — оригинал ещё раз уйдёт, что создаст дублирование

B) Standard direction

Параметр Значения По умолчанию
dir in, out, any out

Фильтр по направлению пакета. fakeddisorder по умолчанию работает только с исходящими (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 заголовков. В fakeddisorder fooling применяется только к фейковым сегментам. К реальным применяется только tcp_ts_up.

Параметр Описание Пример
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 — единственный параметр fooling, который применяется и к реальным, и к фейковым сегментам. Все остальные fooling-параметры применяются только к фейкам.

Заметка про tcp_ts_up: На Linux-серверах пакеты с инвалидным ACK стабильно отбрасываются только если TCP timestamp option идёт первой в заголовке. tcp_ts_up перемещает её в начало, обеспечивая корректную работу badseq-fooling. Поэтому tcp_ts_up рекомендуется включать всегда вместе с tcp_ack.


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 применяется и к фейкам, и к оригиналам.


F) Standard reconstruct

Параметр Описание
badsum Испортить L4 (TCP) checksum при реконструкции raw-пакета

reconstruct применяется только к фейкам. К оригиналам reconstruct не применяется.


G) Standard rawsend

Параметр Описание
repeats=N Отправить каждый пакет N раз (применяется только к фейкам)
ifout=<iface> Интерфейс для отправки (по умолчанию определяется автоматически)
fwmark=N Firewall mark (только Linux, nftables/iptables)

Важно: repeats в fakeddisorder применяется только к фейковым сегментам (через rawsend_opts), а не ко всем. Реальные сегменты отправляются через rawsend_opts_base, где repeats не задействуется.

Важно: ipfrag не задействуется в fakeddisorder — ни для фейков, ни для оригиналов. Объект ipfrag установлен в пустую таблицу {} для обоих типов пакетов.


Порядок отправки сегментов (6 пакетов)

fakeddisorder всегда отправляет 6 пакетов (если все nofake-флаги не установлены). Порядок обратный по отношению к fakedsplit: сначала идёт вторая часть (с фейками), затем первая (с фейками).

ASCII-диаграмма полной последовательности

Payload (100 байт), pos=midsld (разрешился в позицию 40):

  Часть 1: data[1..39]     (39 байт, seq offset = 0)
  Часть 2: data[40..100]   (61 байт, seq offset = 39)

Порядок отправки (хронологический):

  №  Пакет              Тип      seq offset  Размер  Opts         nofake
  ─────────────────────────────────────────────────────────────────────────
  1  fake  part 2       ФЕЙК     39          61      opts_fake    nofake1
  2  real  part 2       РЕАЛ     39 (-seqovl) 61(+ovl) opts_orig    —
  3  fake  part 2       ФЕЙК     39          61      opts_fake    nofake2
  4  fake  part 1       ФЕЙК     0           39      opts_fake    nofake3
  5  real  part 1       РЕАЛ     0           39      opts_orig    —
  6  fake  part 1       ФЕЙК     0           39      opts_fake    nofake4
  ─────────────────────────────────────────────────────────────────────────

opts_orig: только tcp_ts_up из fooling, пустой reconstruct, пустой ipfrag
opts_fake: полный fooling + полный reconstruct, пустой ipfrag

Визуализация на временной оси:

Время ──────────────────────────────────────────────────────────────────→

  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
  │ FAKE P2  │  │ REAL P2  │  │ FAKE P2  │  │ FAKE P1  │  │ REAL P1  │  │ FAKE P1  │
  │ nofake1  │  │ (+seqovl)│  │ nofake2  │  │ nofake3  │  │          │  │ nofake4  │
  │ seq=39   │  │ seq=39-N │  │ seq=39   │  │ seq=0    │  │ seq=0    │  │ seq=0    │
  │ [МУСОР]  │  │[PAT][DAT]│  │ [МУСОР]  │  │ [МУСОР]  │  │ [ДАННЫЕ] │  │ [МУСОР]  │
  └──────────┘  └──────────┘  └──────────┘  └──────────┘  └──────────┘  └──────────┘
  ^^^^ФЕЙК^^^^  ^^РЕАЛЬНЫЙ^^  ^^^^ФЕЙК^^^^  ^^^^ФЕЙК^^^^  ^^РЕАЛЬНЫЙ^^  ^^^^ФЕЙК^^^^
       ↓              ↓             ↓             ↓              ↓             ↓
   сервер            сервер      сервер       сервер          сервер       сервер
   ОТБРОСИТ          ПРИМЕТ      ОТБРОСИТ     ОТБРОСИТ        ПРИМЕТ       ОТБРОСИТ
  (fooling)                     (fooling)    (fooling)                    (fooling)

Сравнение с fakedsplit (прямой порядок):

fakedsplit:      [F1_P1] [REAL_P1] [F2_P1] → [F3_P2] [REAL_P2] [F4_P2]
fakeddisorder:   [F1_P2] [REAL_P2] [F2_P2] → [F3_P1] [REAL_P1] [F4_P1]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                 Сначала ВТОРАЯ часть            Потом ПЕРВАЯ часть

Пример с seqovl

Payload (100 байт), pos=midsld (40), seqovl=10:

  №  Пакет              seq offset   Данные
  ──────────────────────────────────────────────────────────────────────────
  1  fake  part 2       39           [PATTERN 61 байт]
  2  real  part 2       29           [SEQOVL_PAT 10 байт][data[40..100] 61 байт]
  3  fake  part 2       39           [PATTERN 61 байт]
  4  fake  part 1       0            [PATTERN 39 байт]
  5  real  part 1       0            [data[1..39] 39 байт]
  6  fake  part 1       0            [PATTERN 39 байт]

  Сервер для пакета 2:
    TCP window начинается с seq=39
    Байты 29-38 (SEQOVL_PAT) → отбрасываются
    Байты 39-99 (data[40..100]) → принимаются

Пример с nofake-флагами

fakeddisorder:pos=midsld:tcp_ack=-66000:nofake2:nofake4

Отправляется 4 пакета вместо 6:

  №  Пакет              Тип
  ────────────────────────────
  1  fake  part 2       ФЕЙК
  2  real  part 2       РЕАЛ
  —  (пропущен nofake2)
  3  fake  part 1       ФЕЙК
  4  real  part 1       РЕАЛ
  —  (пропущен nofake4)

Можно отключить все фейки кроме одного, или вообще все:

# Только 2 реальных пакета (все фейки отключены) = multidisorder с одной позицией
fakeddisorder:pos=midsld:nofake1:nofake2:nofake3:nofake4

Применение fooling и reconstruct

Это ключевое отличие от multisplit и multidisorder, где fooling применяется ко всем сегментам.

                          opts_orig                      opts_fake
                    (реальные сегменты)            (фейковые сегменты)
  ───────────────────────────────────────────────────────────────────
  fooling           ТОЛЬКО tcp_ts_up               ВСЁ (tcp_ack, tcp_seq,
                                                    ip_ttl, tcp_md5, ...)
  reconstruct       пусто ({})                     ВСЁ (badsum и т.д.)
  ipfrag            пусто ({})                     пусто ({})
  ipid              desync.arg (полный)            desync.arg (полный)
  rawsend           rawsend_opts_base              rawsend_opts (с repeats)
  ───────────────────────────────────────────────────────────────────

Почему так: реальные сегменты должны дойти до сервера в неповреждённом виде. Fooling вроде tcp_ack=-66000 сделал бы их невалидными для сервера. Поэтому fooling идёт только на фейки — чтобы сервер их отбросил, а DPI — запутался.

Исключение — tcp_ts_up. Он не портит пакет, а лишь перемещает TCP timestamp в начало заголовка. Это полезно для обеспечения корректной работы badseq/badack на принимающей стороне и применяется ко всем сегментам.


Поведение при replay / reasm

Многопакетный запрос nfqws2 придерживает, собирает в буфер reasm_data и перепроигрывает через desync-функции. fakeddisorder использует штатную развилку из общего скелета desync-функций (жизненный цикл desync-функции, стадия 6) без изменений: на первой части отправляет всю шестёрку пакетов (2 реальных + 4 фейка) по собранному буферу, остальные части дропает. Если отправка сорвалась, флаг «реасм уже отправлен» не ставится и оставшиеся части проходят как есть.

Специфика функции: раз работа идёт по всему реасму, обратный порядок и размеры фейков определяются целым запросом, а не отдельным пакетом.


Автосегментация по MSS

О размерах TCP-сегментов думать не нужно. Функция rawsend_payload_segmented из zapret-lib.lua автоматически:

  1. Отслеживает MSS для каждого TCP-соединения
  2. Если часть payload превышает MSS — дополнительно режет по MSS
  3. Каждый под-сегмент отправляется с корректным TCP sequence

Место в общем скелете

Тело fakeddisorder построено по тому же шаблону, что и все остальные функции дурения: восемь стадий от отсева чужого транспорта до вердикта. Разобраны они один раз в жизненный цикл desync-функции — здесь только отклонения:

Стадия общего скелета Что делает fakeddisorder
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

Как и fakedsplit, функция собирает два разных набора опций: фейкам достаются fooling, reconstruct и repeats в полном объёме, реальным частям — только tcp_ts_up и политика ip_id. Смысл в том, что фейк сервер обязан отвергнуть, а реальные данные — принять. Общий разбор наборов опций — в жизненный цикл desync-функции.


Псевдокод алгоритма

Тело функции целиком, включая общие для всех техник стадии — они пронумерованы так же, как в жизненный цикл desync-функции:

function fakeddisorder(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. Разрешение маркера (ОДИН, не список!)
            spos = desync.arg.pos or "2"
            pos = resolve_pos(data, l7payload, spos)

            if pos == nil then
                DLOG("cannot resolve pos")
            elseif pos == 1 then
                DLOG("split pos resolved to 0. cannot split.")
            else
                -- 8. Подготовка opts
                opts_orig = {
                    fooling = {tcp_ts_up = desync.arg.tcp_ts_up},  -- ТОЛЬКО tcp_ts_up
                    reconstruct = {},                                -- пусто
                    ipfrag = {},                                     -- пусто
                    ipid = desync.arg,
                    rawsend = rawsend_opts_base(desync)
                }
                opts_fake = {
                    fooling = desync.arg,           -- ПОЛНЫЙ fooling
                    reconstruct = reconstruct_opts(desync),  -- ПОЛНЫЙ reconstruct
                    ipfrag = {},                     -- пусто
                    ipid = desync.arg,
                    rawsend = rawsend_opts(desync)   -- с repeats
                }

                fakepat = pattern_blob or "\x00"

                -- ============ ЧАСТЬ 2 (ОБРАТНЫЙ ПОРЯДОК!) ============

                -- 9. Fake part 2 (пакет 1)
                fake2 = pattern(fakepat, pos, #data - pos + 1)
                if not nofake1 then
                    rawsend_payload_segmented(fake2, pos-1, opts_fake)
                end

                -- 10. Real part 2 + seqovl (пакет 2)
                part2 = data:sub(pos)
                seqovl = 0
                if desync.arg.seqovl then
                    seqovl = resolve_pos(data, l7payload, desync.arg.seqovl)
                    if seqovl then
                        seqovl = seqovl - 1    -- Lua→0-based
                        if seqovl >= (pos-1) then
                            DLOG("seqovl cancelled: not less than split pos")
                            seqovl = 0
                        else
                            pat = seqovl_pattern_blob or "\x00"
                            part2 = pattern(pat, 1, seqovl) .. part2
                        end
                    else
                        DLOG("seqovl cancelled: cannot resolve marker")
                        seqovl = 0
                    end
                end
                rawsend_payload_segmented(part2, pos-1-seqovl, opts_orig)

                -- 11. Fake part 2 again (пакет 3)
                if not nofake2 then
                    rawsend_payload_segmented(fake2, pos-1, opts_fake)
                end

                -- ============ ЧАСТЬ 1 ============

                -- 12. Fake part 1 (пакет 4)
                fake1 = pattern(fakepat, 1, pos-1)
                if not nofake3 then
                    rawsend_payload_segmented(fake1, 0, opts_fake)
                end

                -- 13. Real part 1 (пакет 5)
                part1 = data:sub(1, pos-1)
                rawsend_payload_segmented(part1, 0, opts_orig)

                -- 14. Fake part 1 again (пакет 6)
                if not nofake4 then
                    rawsend_payload_segmented(fake1, 0, opts_fake)
                end

                -- 15. Пометить как отправленное
                replay_drop_set()
                return nodrop and VERDICT_PASS or VERDICT_DROP
            end
        else
            -- 16. Не первый replay
            DLOG("not acting on further replay pieces")
        end
        -- 17. Дропнуть если ранее успешно отправлено
        if replay_drop() then
            return nodrop and VERDICT_PASS or VERDICT_DROP
        end
    end
end

Нюансы и подводные камни

1. Работает только с TCP

Если текущий пакет не TCP (UDP, ICMP и т.д.), fakeddisorder делает instance_cutoff_shim — отключает себя для этого потока навсегда. Исключение — ICMP-пакеты (related), для которых cutoff не выполняется (просто return).

2. Только ОДИН маркер в pos

В отличие от multisplit и multidisorder, fakeddisorder принимает один маркер, а не список. pos=midsld,endhost не будет работать как два разреза. Если нужно несколько разрезов с фейками, используйте цепочку инстансов (хотя это нетривиально из-за VERDICT_DROP).

3. Позиция 1 не работает

Если маркер разрешается в Lua-позицию 1 (то есть задан маркер 0), fakeddisorder логирует "split pos resolved to 0. cannot split." и ничего не делает. Минимальный работающий разрез — pos=1, дефолт — pos=2.

4. Fooling ОБЯЗАТЕЛЕН для фейков

Без fooling-параметров (tcp_ack, tcp_seq, ip_ttl, tcp_md5, badsum и т.д.) сервер примет фейковые сегменты как реальные данные. Это повредит TCP-поток. Всегда указывайте хотя бы один fooling-параметр:

# ПЛОХО: фейки без fooling — сервер их примет!
--lua-desync=fakeddisorder:pos=midsld

# ХОРОШО: фейки с badseq
--lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

5. tcp_ts_up рекомендуется с tcp_ack

На Linux-серверах пакеты с инвалидным ACK стабильно отбрасываются только если TCP timestamp option идёт первой в заголовке. Без tcp_ts_up есть риск, что сервер примет фейк с tcp_ack=-66000.

6. ipfrag НЕ задействуется

В отличие от multisplit и multidisorder, fakeddisorder не поддерживает IP-фрагментацию. Объект ipfrag установлен в {} и для фейков, и для оригиналов. Если нужна IP-фрагментация — используйте multidisorder (без фейков, но с ipfrag).

7. seqovl — маркер, а не число

В multisplit и fakedsplit seqovl — просто tonumber(desync.arg.seqovl). В fakeddisorder (как и в multidisorder) seqovl разрешается через resolve_pos. Это значит:

  • seqovl=5 работает (разрешится в абсолютную позицию 5, затем -1 = 4)
  • seqovl=host тоже работает
  • Но результат должен быть < pos-1

8. seqovl применяется только к real part 2

В отличие от fakedsplit (где seqovl к real part 1 — первому реальному), в fakeddisorder seqovl применяется к real part 2 — первому реальному по порядку отправки (но второму по порядку в payload). Это логично: в disorder-порядке сначала отправляется вторая часть, и именно она получает seqovl.

9. nodrop создаёт дублирование

С nodrop fakeddisorder отправляет 6 нарезанных пакетов И пропускает оригинальный. Сервер получит данные дважды. Используйте nodrop только для отладки.

10. Ошибка rawsend прерывает цепочку

Если любой из 6 вызовов rawsend_payload_segmented возвращает false, fakeddisorder немедленно возвращает VERDICT_PASS (оригинальный пакет пропускается). Остальные пакеты из 6 не отправляются, replay_drop_set не вызывается.

11. Порядок инстансов важен

Если перед fakeddisorder стоит fake — он отправит свой фейк до того, как fakeddisorder начнёт свою последовательность. Если после fakeddisorder стоит ещё один инстанс — он увидит VERDICT_DROP и не получит оригинальный payload.


Отличия от fakedsplit, multisplit и multidisorder

Аспект multisplit multidisorder fakedsplit fakeddisorder
Количество позиций Список (любое кол-во) Список (любое кол-во) Одна Одна
Порядок отправки Прямой (1-2-3) Обратный (3-2-1) Прямой (1-2) Обратный (2-1)
Фейковые сегменты Нет Нет Да (до 4 шт.) Да (до 4 шт.)
Всего пакетов (max) N+1 (по кол-ву позиций) N+1 6 6
seqovl тип Только число Маркер Только число Маркер
seqovl к какому сегменту 1-й (прямой) 2-й (предпоследний) 1-й реальный (part 1) 2-й реальный (part 2)
Fooling к Всем сегментам Всем сегментам Только к фейкам Только к фейкам
reconstruct к Всем Всем Только к фейкам Только к фейкам
repeats к Всем Всем Только к фейкам Только к фейкам
ipfrag Да Да Нет Нет
Уровень запутывания Один (разрез) Два (разрез + порядок) Два (разрез + фейки) Три (разрез + фейки + порядок)

Когда что использовать

Сценарий Рекомендация
DPI не реассемблирует TCP, достаточно разрезать multisplit
DPI реассемблирует, но не проверяет порядок multidisorder
DPI проверяет порядок, но не отличает фейки fakedsplit
DPI проверяет и порядок, и содержимое fakeddisorder
Нужна IP-фрагментация поверх TCP multisplit или multidisorder
Нужно несколько позиций разреза с фейками Цепочка fakedsplit / fakeddisorder

Миграция с nfqws1

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

nfqws1 nfqws2
--dpi-desync=fakeddisorder --lua-desync=fakeddisorder
--dpi-desync-split-pos=midsld :pos=midsld
--dpi-desync-fooling=badseq :tcp_ack=-66000:tcp_ts_up
--dpi-desync-fooling=badack :tcp_ack=-66000:tcp_ts_up
--dpi-desync-fooling=md5sig :tcp_md5
--dpi-desync-fooling=datanoack :tcp_flags_unset=ACK
--dpi-desync-fooling=hopbyhop :ip6_hopbyhop
--dpi-desync-fooling=hopbyhop2 :ip6_hopbyhop2
--dpi-desync-split-seqovl=N :seqovl=N
--dpi-desync-split-seqovl-pattern=HEX :seqovl_pattern=HEX
--dpi-desync-fake-tls=FILE :pattern=<blobname> (предзагрузить через --blob=)
--dpi-desync-any-protocol Не нужно; или payload=all в инстансе

Пример полной миграции

# nfqws1:
nfqws --dpi-desync=fakeddisorder \
  --dpi-desync-fooling=badseq \
  --dpi-desync-split-pos=midsld

# nfqws2 (эквивалент):
nfqws2 \
  --payload=tls_client_hello \
    --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up
# nfqws1 (комплексный):
nfqws --dpi-desync=fakeddisorder \
  --dpi-desync-fooling=md5sig \
  --dpi-desync-split-pos=midsld \
  --dpi-desync-split-seqovl=5 \
  --dpi-desync-split-seqovl-pattern=0x1603030000

# nfqws2 (эквивалент):
nfqws2 \
  --payload=tls_client_hello \
    --lua-desync=fakeddisorder:pos=midsld:tcp_md5:seqovl=5:seqovl_pattern=0x1603030000
# nfqws1 (фулл):
nfqws --dpi-desync=fake,fakeddisorder \
  --dpi-desync-fooling=badseq \
  --dpi-desync-split-pos=midsld \
  --dpi-desync-fake-tls-mod=rnd,rndsni,dupsid

# nfqws2 (эквивалент — fake отдельным инстансом):
nfqws2 \
  --payload=tls_client_hello \
    --lua-desync=fake:blob=fake_default_tls:tcp_ack=-66000:tcp_ts_up:tls_mod=rnd,rndsni,dupsid \
    --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

Практические примеры

1. Минимальный (с fooling)

--lua-desync=fakeddisorder:tcp_ack=-66000:tcp_ts_up

Разрезает payload после 2-го байта (pos=2 по умолчанию) с badseq fooling для фейков. 6 пакетов.

2. TLS: разрез посередине SNI

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

SNI разрезан пополам. DPI видит 6 пакетов с перемешанными фейковыми и реальными сегментами в обратном порядке — двойное запутывание.

3. TLS: разрез + seqovl (тройное запутывание)

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:seqovl=5:seqovl_pattern=0x1603030000:tcp_ack=-66000:tcp_ts_up

Три уровня: фейки + disorder + seqovl. Real part 2 имеет 4-байтовый TLS-фейк слева (5→resolve→5, 5-1=4), который DPI может принять за начало TLS record.

4. TLS: seqovl как маркер

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:seqovl=sld:tcp_ack=-66000:tcp_ts_up

seqovl разрешается в позицию начала SLD. Фейковая часть слева от real part 2 — от начала SLD до позиции разреза.

5. HTTP: разрез в начале hostname

--payload=http_req --lua-desync=fakeddisorder:pos=host:tcp_ack=-66000:tcp_ts_up

Для GET / HTTP/1.1\r\nHost: example.com\r\n... разрежет так, что часть 1 содержит заголовки до hostname, а часть 2 — hostname и далее.

6. HTTP: кастомный паттерн для фейков

--payload=http_req --lua-desync=fakeddisorder:pos=host:pattern=0x474554202F20485454502F312E310D0A:tcp_ack=-66000:tcp_ts_up

Фейки заполняются паттерном, похожим на начало HTTP-запроса (GET / HTTP/1.1\r\n). DPI может принять фейк за реальный запрос к другому хосту.

7. С tcp_md5 fooling

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_md5:tcp_ts_up

Вместо badseq используется TCP MD5 signature option. Сервер отбрасывает пакеты с неверной MD5 подписью.

8. С badsum reconstruct

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:badsum

Фейки отправляются с испорченным TCP checksum. Двойная защита: и badseq, и badsum.

9. Без первого и последнего фейка (4 пакета)

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:nofake1:nofake4

Отключены фейки "снаружи" (первый и последний). Остаётся: real part 2, fake part 2 (после), fake part 1 (перед), real part 1. 4 пакета.

10. Только внутренние фейки (4 пакета)

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:nofake1:nofake2

Вся часть 2 — только реальная (без обрамления фейками). Часть 1 — с фейками. Полезно, когда DPI анализирует только начало потока.

11. Без всех фейков (эквивалент disorder)

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:nofake1:nofake2:nofake3:nofake4

Отключены все 4 фейка. Остаются только 2 реальных пакета в обратном порядке. Фактически — multidisorder с одной позицией. Fooling не нужен (применяется только к фейкам, которых нет).

12. Цепочка: fake + fakeddisorder

--payload=tls_client_hello \
  --lua-desync=fake:blob=fake_default_tls:repeats=5:tcp_md5 \
  --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

Сначала 5 фейковых TLS ClientHello (с md5sig fooling), затем fakeddisorder с 6 пакетами. Итого 11 пакетов. Массированная атака на DPI.

13. С произвольным blob вместо payload

--blob=mydata:@custom_payload.bin \
--lua-desync=fakeddisorder:blob=mydata:pos=50:tcp_ack=-66000:tcp_ts_up

Режет и отправляет произвольные данные из файла вместо реального payload.

14. С optional для необязательного blob

--lua-desync=fakeddisorder:blob=maybe_missing:optional:pos=midsld:tcp_ack=-66000:tcp_ts_up

Если blob maybe_missing не существует — тихий пропуск, без ошибок.

15. Боевой пример для YouTube

--filter-tcp=443 --hostlist=youtube.txt \
  --payload=tls_client_hello \
    --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up

Разрезает TLS ClientHello посередине SNI домена YouTube с фейками в обратном порядке.

16. Комбинация fooling + repeats для фейков

--payload=tls_client_hello \
  --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:repeats=3

Каждый из 4 фейковых пакетов отправляется 3 раза. Реальные — по 1 разу. Итого 14 пакетов (4*3 + 2).

17. IPv6: hop-by-hop fooling

--payload=tls_client_hello \
  --lua-desync=fakeddisorder:pos=midsld:ip6_hopbyhop:tcp_ts_up

Для IPv6: фейки отправляются с extension header hop-by-hop, который сбрасывается на первом маршрутизаторе. Реальные сегменты — без hop-by-hop.

18. Отладка: не блокировать оригинал

--payload=tls_client_hello --lua-desync=fakeddisorder:pos=midsld:tcp_ack=-66000:tcp_ts_up:nodrop

Отправляет 6 нарезанных пакетов И пропускает оригинальный (для экспериментов/отладки).


Источники: lua/zapret-antidpi.lua:908-1021, docs/manual.md:4165-4206, docs/manual.en.md:3984-4025 из репозитория zapret2.