fakedsplit — TCP-сегментация с замешиванием фейков (zapret2 / nfqws2)

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

fakedsplit разрезает TCP payload на две части по одной позиции и отправляет их вперемешку с фейковыми сегментами того же размера. Фейки содержат мусор (pattern), но имеют те же TCP sequence и размеры, что и оригиналы. Для DPI это выглядит как серия ретрансмиссий — он не может понять, какой сегмент настоящий, а какой — подделка. Для сервера фейки отбрасываются благодаря fooling (badseq, md5sig, badsum и т.д.).

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

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


Оглавление


Зачем нужен fakedsplit

Обычная сегментация (multisplit) разрезает payload, но DPI может просто реассемблировать поток. Отдельная отправка фейка (fake) работает, но DPI может отбросить фейк по эвристикам (слишком другой TTL, плохой sequence).

fakedsplit комбинирует оба подхода: он одновременно разрезает и замешивает фейки. При этом фейки:

  1. Совпадают по размеру с реальными частями
  2. Имеют те же TCP sequence numbers — выглядят как ретрансмиссии
  3. Окружают каждую реальную часть с двух сторон (до и после)

Для DPI поток выглядит так: 6 TCP-сегментов, из которых по 3 пары — “ретрансмиссии” одного и того же. Какой из трёх сегментов каждой пары настоящий? DPI не знает. Сервер знает — потому что фейки имеют невалидные заголовки (fooling), и TCP-стек их молча отбросит.


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

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

--lua-desync=fakedsplit:tcp_ack=-66000

Типовой TLS-разрез с badseq:

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

HTTP с разрезом по методу:

--payload=http_req --lua-desync=fakedsplit:pos=method+2:tcp_ack=-66000:tcp_ts_up

С seqovl для дополнительной маскировки:

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

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

Внутри fakedsplit данные (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 — главный аргумент fakedsplit. Определяет где внутри payload будет произведён разрез. В отличие от multisplit, pos — это один маркер, а не список. Payload всегда делится ровно на две части.

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

ТипОписаниеПример
Абсолютный положительныйСмещение от начала 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_methodeolhttp_req
hostПервый байт имени хоста (Host: в HTTP, SNI в TLS)http_req, tls_client_hello
endhostБайт, следующий за последним байтом имени хоста. Т.е. host..endhost-1 = полный hostnamehttp_req, tls_client_hello
sldПервый байт домена второго уровня (SLD). Для www.example.com — это e в examplehttp_req, tls_client_hello
endsldБайт, следующий за последним байтом SLD. Для example.com — это . после examplehttp_req, tls_client_hello
midsldСередина SLD (самый популярный маркер). Для example (7 символов) — позиция 3-го или 4-го символаhttp_req, tls_client_hello
sniextНачало поля данных SNI extension в TLS ClientHello. Extension состоит из type (2 байта) + length (2 байта) + данные — sniext указывает на начало данныхtls_client_hello
extlenПоле длины всех TLS extensionstls_client_hello

Арифметика маркеров

К любому маркеру можно прибавить (+) или вычесть (-) целое число:

midsld+1      — один байт ПОСЛЕ середины SLD
midsld-1      — один байт ДО середины SLD
endhost-2     — два байта до конца hostname
method+2      — два байта после начала метода (разрежет "GET " после "GE")
sniext+1      — один байт после начала SNI extension data
host+3        — три байта после начала hostname
-1            — последний байт payload (абсолютный, не относительный)

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

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

local pos = resolve_pos(data, desync.l7payload, spos)

Функция resolve_pos:

  1. Разбирает маркер (имя + арифметика)
  2. Если маркер не может быть разрешён (например, midsld для unknown payload) — возвращает nil
  3. При успехе возвращает абсолютную позицию (1-based, как в Lua)

Далее проверяется:

if pos == 1 then
    DLOG("fakedsplit: split pos resolved to 0. cannot split.")
    -- ничего не делает
end

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

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

Последовательность отправки (6 пакетов)

fakedsplit отправляет до 6 пакетов (при включённых всех фейках):

#ЧтоТипОтключениеОпции
1Фейк 1-й частиfakenofake1opts_fake (полный fooling + reconstruct + repeats)
2Реальная 1-я часть (+seqovl)origopts_orig (только tcp_ts_up, без repeats)
3Фейк 1-й части (повтор)fakenofake2opts_fake
4Фейк 2-й частиfakenofake3opts_fake
5Реальная 2-я частьorigopts_orig
6Фейк 2-й части (повтор)fakenofake4opts_fake

Ключевой момент: каждая реальная часть окружена фейками того же размера и с тем же TCP sequence. DPI видит “тройки ретрансмиссий” и не может определить, какая из трёх — настоящая.

ASCII-диаграмма

Payload: [=======ЧАСТЬ 1=======|=======ЧАСТЬ 2=======]
                               ^pos

Время отправки (сверху вниз):

  #1  FAKE1  [XXXXXXXXXXXXXXXXXXXXX]  seq=0       len=pos-1      opts_fake
  #2  REAL1  [=====================]  seq=-seqovl len=pos-1+sovl  opts_orig
  #3  FAKE1  [XXXXXXXXXXXXXXXXXXXXX]  seq=0       len=pos-1      opts_fake
  #4  FAKE2  [XXXXXXXXXXXXXXXXXXXXX]  seq=pos-1   len=#data-pos+1 opts_fake
  #5  REAL2  [=====================]  seq=pos-1   len=#data-pos+1 opts_orig
  #6  FAKE2  [XXXXXXXXXXXXXXXXXXXXX]  seq=pos-1   len=#data-pos+1 opts_fake

  X = мусор (pattern)
  = = реальные данные

Что видит DPI

Поток TCP-сегментов на проводе:

  seq=0      [XXXXXXX]  ← fake 1  (мусор)
  seq=-sovl  [PPP|REAL1] ← real 1  (seqovl pattern + данные)
  seq=0      [XXXXXXX]  ← fake 1  (мусор, "ретрансмиссия")
  seq=pos    [XXXXXXX]  ← fake 2  (мусор)
  seq=pos    [==REAL2=]  ← real 2  (данные)
  seq=pos    [XXXXXXX]  ← fake 2  (мусор, "ретрансмиссия")

DPI видит: "3 сегмента с seq=0, 3 сегмента с seq=pos — похоже на ретрансмиссии.
           Какой из каждой тройки настоящий? Непонятно."

Что видит сервер

Сервер (TCP-стек):

  seq=0,     данные=мусор   → fooling (badseq/md5/badsum) → ОТБРОШЕН
  seq=-sovl, данные=PPP+R1  → seqovl-часть за window → отброшена; R1 → ПРИНЯТ
  seq=0,     данные=мусор   → fooling → ОТБРОШЕН
  seq=pos,   данные=мусор   → fooling → ОТБРОШЕН
  seq=pos,   данные=REAL2   → нормальный пакет → ПРИНЯТ
  seq=pos,   данные=мусор   → fooling → ОТБРОШЕН

Результат: сервер собрал [REAL1][REAL2] = полный оригинальный payload

Два набора опций: opts_orig vs opts_fake

Это ключевое отличие fakedsplit от multisplit. Внутри функции создаются два набора опций:

-- Для оригинальных частей:
local opts_orig = {
    rawsend    = rawsend_opts_base(desync),  -- БЕЗ repeats!
    reconstruct = {},                         -- пустой (без badsum)
    ipfrag     = {},                          -- пустой (без IP-фрагментации)
    ipid       = desync.arg,                  -- ip_id применяется
    fooling    = {tcp_ts_up = desync.arg.tcp_ts_up}  -- ТОЛЬКО tcp_ts_up
}
 
-- Для фейковых частей:
local opts_fake = {
    rawsend    = rawsend_opts(desync),        -- С repeats!
    reconstruct = reconstruct_opts(desync),    -- badsum если задан
    ipfrag     = {},                          -- пустой (без IP-фрагментации)
    ipid       = desync.arg,                  -- ip_id применяется
    fooling    = desync.arg                    -- ВСЁ fooling целиком
}

Сводная таблица:

Чтоopts_orig (оригиналы)opts_fake (фейки)
foolingТолько tcp_ts_upВсё (tcp_ack, tcp_seq, tcp_md5, ip_ttl, badseq, …)
reconstructНет (пустой {})Да (badsum если задан)
rawsend.repeatsНет (rawsend_opts_base)Да (rawsend_opts)
rawsend.ifoutДаДа
rawsend.fwmarkДаДа
ipidДаДа
ipfragНет (пустой {})Нет (пустой {})

Почему так:

  • Оригиналы должны быть приняты сервером никакого fooling (кроме безвредного tcp_ts_up), никакого badsum
  • Фейки должны быть отброшены сервером полный fooling + badsum
  • repeats — множественная отправка имеет смысл только для фейков (больше шума для DPI)
  • ipfrag не используется ни для чего — fakedsplit работает исключительно на уровне TCP-сегментов
  • ipid и базовый rawsend (ifout, fwmark) нужны обоим — это уровень IP/маршрутизации

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

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

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

Без seqovl:
  TCP seq: 0
  Данные:  [РЕАЛЬНАЯ_ЧАСТЬ_1]
  Сервер:  принимает [РЕАЛЬНАЯ_ЧАСТЬ_1] целиком

С seqovl=10:
  TCP seq: -10            (уменьшен на 10)
  Данные:  [PATTERN_10_БАЙТ][РЕАЛЬНАЯ_ЧАСТЬ_1]

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

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

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

           TCP window boundary
                 |
  |  ОТБРОСИТЬ  | ПРИНЯТЬ          |
  | PATTERN(10) | РЕАЛЬНАЯ_ЧАСТЬ_1 |
  ^seq=-10       ^seq=0

seqovl в fakedsplit — дополнительный уровень защиты поверх фейков. Даже если DPI научился отбрасывать фейковые сегменты, seqovl-данные внутри реального сегмента он может не распознать.

Важно: в fakedsplit seqovl — только число (не маркер). Маркеры поддерживаются в fakeddisorder и multidisorder, но не здесь.

seqovl_pattern

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

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

# Inline hex blob (маскировка под начало TLS record)
--lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:seqovl=5:seqovl_pattern=0x1603030000
 
# Предзагруженный blob
--blob=tlspat:0x1603030100 \
--lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:seqovl=8:seqovl_pattern=tlspat

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


pattern — заполнение фейков

Аргумент pattern задаёт содержимое фейковых сегментов. Это имя blob, из которого генерируется паттерн для заполнения фейков.

fakepat = desync.arg.pattern and blob(desync, desync.arg.pattern) or "\x00"
  • По умолчанию: \x00 (нулевые байты)
  • Фейк первой части: pattern(fakepat, 1, pos-1) — паттерн со смещением 1, длиной pos-1
  • Фейк второй части: pattern(fakepat, pos, #data-pos+1) — паттерн со смещением pos, длиной остатка

Смещение паттерна (pos) соответствует смещению TCP sequence отсылаемой части. Это значит, что если DPI сравнивает байты фейка и оригинала побайтово — он увидит разные данные в одних и тех же позициях. Но если он просто реассемблирует поток — паттерн не сможет “обмануть” конкретную сигнатуру (для этого нужен осмысленный blob).

# Фейки заполнены случайными данными из blob
--blob=rndpat:0xDEADBEEFCAFEBABE \
--lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:pattern=rndpat
 
# Фейки заполнены нулями (по умолчанию)
--lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000

nofake1..nofake4 — отключение отдельных фейков

Каждый из 4 фейковых пакетов можно отключить индивидуально:

ФлагОтключаетПозиция в потоке
nofake1Фейк 1-й части (до реальной 1-й)#1
nofake2Фейк 1-й части (после реальной 1-й)#3
nofake3Фейк 2-й части (до реальной 2-й)#4
nofake4Фейк 2-й части (после реальной 2-й)#6

Минимально необходимые пакеты — только два реальных (#2, #5). Все 4 фейка можно отключить:

# Без фейков вообще (по сути = multisplit с одной позицией)
--lua-desync=fakedsplit:pos=midsld:nofake1:nofake2:nofake3:nofake4

Типичные комбинации:

# Только "передние" фейки (до реальных частей)
--lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:nofake2:nofake4
 
# Только "задние" фейки (после реальных частей)
--lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:nofake1:nofake3
 
# Фейки только для 1-й части
--lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:nofake3:nofake4

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

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

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

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

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

pos

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

pattern

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

seqovl

  • Формат: seqovl=N (где N > 0)
  • Тип: только число (маркеры не поддерживаются — в отличие от fakeddisorder)
  • По умолчанию: не задан (нет seqovl)
  • Описание: Применяется только к первому реальному сегменту (пакет #2). К данным первого сегмента слева добавляется N байт seqovl_pattern, а TCP th_seq уменьшается на N. Сервер отбросит левую часть, DPI — может не отбросить
  • Примеры:
    • seqovl=5 — 5 байт фейка слева
    • seqovl=13 — 13 байт фейка слева

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 отсутствует fakedsplit ничего не делает (тихий skip, без ошибок)
    • Если задан seqovl_pattern=... и blob отсутствует используется нулевой паттерн (seqovl не отменяется)
  • Использование: защита от ошибок при использовании blob, которые могут отсутствовать

nodrop

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

nofake1

  • Формат: nofake1 (флаг, без значения)
  • Описание: Не отправлять фейк 1-й части до реальной 1-й части (пакет #1)

nofake2

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

nofake3

  • Формат: nofake3 (флаг, без значения)
  • Описание: Не отправлять фейк 2-й части до реальной 2-й части (пакет #4)

nofake4

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

B) Standard direction

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

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

ПараметрОписаниеПример
ip_ttl=NУстановить IPv4 TTLip_ttl=6
ip6_ttl=NУстановить IPv6 Hop Limitip6_ttl=6
ip_autottl=delta,min-maxАвтоматический TTL (delta от серверного TTL)ip_autottl=-2,40-64
ip6_autottl=delta,min-maxАналогично для IPv6ip6_autottl=-2,40-64
ip6_hopbyhop[=HEX]Вставить extension header hop-by-hop (по умолчанию 6 нулей)ip6_hopbyhop
ip6_hopbyhop2[=HEX]Второй hop-by-hop headerip6_hopbyhop2
ip6_destopt[=HEX]Destination options headerip6_destopt
ip6_destopt2[=HEX]Второй destination optionsip6_destopt2
ip6_routing[=HEX]Routing headerip6_routing
ip6_ah[=HEX]Authentication headerip6_ah
tcp_seq=NСместить TCP sequence (+ или -)tcp_seq=-10000
tcp_ack=NСместить TCP ack (+ или -)tcp_ack=-66000
tcp_ts=NСместить TCP timestamptcp_ts=-100
tcp_md5[=HEX]Добавить TCP MD5 option (16 байт; по умолчанию случайные)tcp_md5
tcp_flags_set=LISTУстановить TCP-флагиtcp_flags_set=FIN,PUSH
tcp_flags_unset=LISTСнять TCP-флагиtcp_flags_unset=ACK
tcp_ts_upПоднять TCP timestamp option в начало заголовкаtcp_ts_up
tcp_nop_delУдалить все TCP NOP опцииtcp_nop_del
fool=<func>Кастомная Lua-функция foolingfool=my_fooler

Важно для fakedsplit: tcp_ts_up — единственный параметр fooling, который применяется и к оригиналам. Все остальные — только к фейкам. Это значит, что tcp_ack=-66000 безопасен — оригиналы будут иметь валидный ACK, а фейки — невалидный.

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


E) Standard ipid

ПараметрОписаниеПо умолчанию
ip_id=seqПоследовательные IP IDseq
ip_id=rndСлучайные IP ID
ip_id=zeroНулевые IP ID
ip_id=noneНе менять IP ID
ip_id_connСквозная нумерация IP ID в рамках соединения (требует tracking)

ip_id применяется и к фейкам, и к оригиналам (оба набора опций содержат ipid = desync.arg).


F) Standard reconstruct

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

В fakedsplit badsum применяется только к фейкам (opts_fake). Оригиналы имеют пустой reconstruct = {}.


G) Standard rawsend

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

Важная деталь: repeats применяется только к фейкам. Оригиналы используют rawsend_opts_base, который не включает repeats. ifout и fwmark применяются к обоим.


Порядок отправки — подробно с seqovl

Без seqovl

Payload "ABCDEFGHIJ" (10 байт), pos=4, pattern=0xFF

Пакет #1 (fake1):   seq=0  data=[FF FF FF]     len=3   opts_fake
Пакет #2 (real1):   seq=0  data=[A  B  C ]     len=3   opts_orig
Пакет #3 (fake1'):  seq=0  data=[FF FF FF]     len=3   opts_fake
Пакет #4 (fake2):   seq=3  data=[FF FF FF FF FF FF FF]  len=7  opts_fake
Пакет #5 (real2):   seq=3  data=[D  E  F  G  H  I  J]  len=7  opts_orig
Пакет #6 (fake2'):  seq=3  data=[FF FF FF FF FF FF FF]  len=7  opts_fake

С seqovl=5

Payload "ABCDEFGHIJ" (10 байт), pos=4, pattern=0xFF, seqovl=5, seqovl_pattern=0x00

Пакет #1 (fake1):   seq=0   data=[FF FF FF]                  len=3   opts_fake
Пакет #2 (real1):   seq=-5  data=[00 00 00 00 00 A  B  C ]   len=8   opts_orig
Пакет #3 (fake1'):  seq=0   data=[FF FF FF]                  len=3   opts_fake
Пакет #4 (fake2):   seq=3   data=[FF FF FF FF FF FF FF]      len=7   opts_fake
Пакет #5 (real2):   seq=3   data=[D  E  F  G  H  I  J]      len=7   opts_orig
Пакет #6 (fake2'):  seq=3   data=[FF FF FF FF FF FF FF]      len=7   opts_fake

Сервер:
  Пакет #2: seq=-5, байты -5..-1 за window => отброшены; байты 0..2 = [A B C] => приняты
  Пакет #5: seq=3, байты 3..9 = [D E F G H I J] => приняты
  Итого: [A B C D E F G H I J] = полный payload

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

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

Специфика функции здесь одна, зато существенная: фейковые сегменты строятся по размерам реальных частей всего реасма, а не отдельного пакета. Поэтому при многопакетном ClientHello фейки получаются крупными — ровно такими же, как реальные куски.


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

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

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

Это относится и к фейкам, и к оригиналам.


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

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

Стадия общего скелетаЧто делает fakedsplit
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. Своя техникаодна позиция разреза (resolve_pos, не список), два реальных сегмента и до четырёх фейков между ними — этому посвящена вся заметка
8. ВердиктVERDICT_DROP после успешной отправки, VERDICT_PASS при nodrop или сбое rawsend

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


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

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

function fakedsplit(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_pos(data, l7payload, pos_arg or "2")
 
            if pos == nil then
                DLOG("cannot resolve pos")
            elseif pos == 1 then
                DLOG("split pos resolved to 0. cannot split.")
            else
                -- 8. Создание двух наборов опций
                opts_orig = {rawsend=base, reconstruct={}, ipfrag={},
                             ipid=arg, fooling={tcp_ts_up=arg.tcp_ts_up}}
                opts_fake = {rawsend=full, reconstruct=reconstruct_opts,
                             ipfrag={}, ipid=arg, fooling=arg}
 
                -- 9. Паттерн для фейков
                fakepat = arg.pattern blob or "\x00"
 
                -- 10. ФЕЙК 1-й части
                fake = pattern(fakepat, 1, pos-1)
                if not nofake1 then
                    rawsend_payload_segmented(fake, 0, opts_fake)
                end
 
                -- 11. РЕАЛЬНАЯ 1-я часть (+seqovl)
                part = data:sub(1, pos-1)
                seqovl = 0
                if arg.seqovl and tonumber(arg.seqovl) > 0 then
                    seqovl = tonumber(arg.seqovl)
                    pat = "\x00"
                    if arg.seqovl_pattern then
                        if optional and blob not exists then
                            pat = "\x00"  -- fallback
                        else
                            pat = blob(seqovl_pattern)
                        end
                    end
                    part = pattern(pat, 1, seqovl) .. part
                end
                rawsend_payload_segmented(part, -seqovl, opts_orig)
 
                -- 12. ФЕЙК 1-й части (повтор)
                if not nofake2 then
                    rawsend_payload_segmented(fake, 0, opts_fake)
                end
 
                -- 13. ФЕЙК 2-й части
                fake = pattern(fakepat, pos, #data-pos+1)
                if not nofake3 then
                    rawsend_payload_segmented(fake, pos-1, opts_fake)
                end
 
                -- 14. РЕАЛЬНАЯ 2-я часть
                part = data:sub(pos)
                rawsend_payload_segmented(part, pos-1, opts_orig)
 
                -- 15. ФЕЙК 2-й части (повтор)
                if not nofake4 then
                    rawsend_payload_segmented(fake, pos-1, opts_fake)
                end
 
                -- 16. Пометить как отправленное
                replay_drop_set()
                return nodrop and VERDICT_PASS or VERDICT_DROP
            end
        else
            -- 17. Не первый replay
            DLOG("not acting on further replay pieces")
        end
 
        -- 18. Drop replayed packets если ранее успешно отправлено
        if replay_drop() then
            return nodrop and VERDICT_PASS or VERDICT_DROP
        end
    end
end

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

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

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

2. Только ОДНА позиция разреза

В отличие от multisplit, fakedsplit принимает один маркер, не список. pos=midsld,endhost не будет работать как два разреза — строка целиком передастся в resolve_pos, который не умеет парсить запятые. Если нужно несколько точек разреза с фейками — используйте цепочку инстансов или fakeddisorder.

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

Если resolve_pos возвращает Lua-позицию 1, fakedsplit логирует “split pos resolved to 0. cannot split.” и ничего не делает. Это соответствует маркеру 0 (смещения в командной строке считаются с нуля), а не pos=1.

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

Без fooling фейки будут приняты сервером как валидные данные, что сломает TCP-поток. Всегда указывайте хотя бы один метод fooling:

# НЕПРАВИЛЬНО — фейки будут приняты сервером!
--lua-desync=fakedsplit:pos=midsld
 
# ПРАВИЛЬНО — фейки отброшены по невалидному ACK
--lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:tcp_ts_up

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

На Linux-серверах tcp_ack=-66000 надёжно отбрасывает пакеты только если TCP timestamp option идёт первой. tcp_ts_up обеспечивает это. Всегда комбинируйте:

:tcp_ack=-66000:tcp_ts_up

6. repeats применяются ТОЛЬКО к фейкам

Если задать repeats=5, каждый фейк отправится 5 раз, а каждый оригинал — 1 раз. Это сделано специально: больше фейков больше шума для DPI, но данные не дублируются.

7. ipfrag НЕ используется

В отличие от multisplit и multidisorder, fakedsplit не поддерживает IP-фрагментацию. Оба набора опций имеют пустой ipfrag = {}. Если нужна IP-фрагментация поверх фейков — это не поддерживается напрямую.

8. badsum идёт только на фейки

badsum — часть reconstruct, который применяется только к фейкам. Оригиналы всегда имеют валидный checksum. Это безопасно и является ещё одним методом fooling.

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

С nodrop fakedsplit отправляет до 6 сегментов (фейки + оригиналы) И пропускает оригинальный пакет. Сервер получит данные как минимум дважды. Используйте nodrop только для отладки.

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

Если перед fakedsplit стоит pktmod с модификацией — pktmod изменит payload, и fakedsplit порежет уже изменённые данные. Если после fakedsplit стоит ещё один инстанс — он увидит VERDICT_DROP и не получит оригинальный payload.

11. Неразрешённый маркер != ошибка

Если resolve_pos не смог разрешить маркер (например, midsld для unknown payload), fakedsplit логирует “cannot resolve pos” и ничего не делает — ни фейков, ни разреза. Оригинальный пакет проходит как есть.


Отличия от других функций сегментации

Аспектmultisplitmultidisorderfakedsplitfakeddisorder
Количество позицийСписок (любое кол-во)Список (любое кол-во)ОднаОдна
Резолвер позицийresolve_multi_posresolve_multi_posresolve_posresolve_pos
Порядок отправкиПрямой (123)Обратный (321)Прямой (с фейками)Обратный (с фейками)
Фейковые сегментыНетНетДа (до 4 шт.)Да (до 4 шт.)
Пакетов на выходеN+1 (N позиций)N+1 (N позиций)До 6 (2 реальных + 4 фейка)До 6 (2 реальных + 4 фейка)
seqovl типТолько числоМаркерТолько числоМаркер
seqovl к какому сегменту1-й2-й (предпоследний)1-й реальный2-й реальный
Fooling кВсем сегментамВсем сегментамТолько к фейкамТолько к фейкам
reconstruct кВсем сегментамВсем сегментамТолько к фейкамТолько к фейкам
repeats кВсем сегментамВсем сегментамТолько к фейкамТолько к фейкам
ipfragДаДаНетНет
ipid кВсем сегментамВсем сегментамИ фейкам, и оригиналамИ фейкам, и оригиналам
tcp_ts_up к оригиналамДа (как часть fooling)Да (как часть fooling)Да (единственный fooling)Да (единственный fooling)

Миграция с nfqws1

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

nfqws1nfqws2
--dpi-desync=fakedsplit--lua-desync=fakedsplit
--dpi-desync-split-pos=midsld:pos=midsld
--dpi-desync-split-pos=method+2:pos=method+2
--dpi-desync-fooling=badseq:tcp_ack=-66000:tcp_ts_up (или другой fooling)
--dpi-desync-badseq-increment=0:tcp_seq=0 (если нужен нулевой seq инкремент)
--dpi-desync-split-seqovl=5:seqovl=5
--dpi-desync-split-seqovl-pattern=0x1603030000:seqovl_pattern=0x1603030000
--dpi-desync-any-protocolНе нужно; или payload=all в инстансе

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

# nfqws1:
nfqws --dpi-desync=fakedsplit \
  --dpi-desync-fooling=badseq \
  --dpi-desync-badseq-increment=0 \
  --dpi-desync-split-pos=method+2
 
# nfqws2 (эквивалент):
nfqws2 \
  --payload=http_req \
    --lua-desync=fakedsplit:pos=method+2:tcp_ack=-66000:tcp_ts_up
# nfqws1:
nfqws --dpi-desync=fakedsplit \
  --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=fakedsplit:pos=midsld:tcp_md5:tcp_ts_up:seqovl=5:seqovl_pattern=0x1603030000
# nfqws1 с несколькими protocol:
nfqws --dpi-desync=fakedsplit \
  --dpi-desync-fooling=badseq \
  --dpi-desync-split-pos=2
 
# nfqws2 (разделяем по payload):
nfqws2 \
  --payload=tls_client_hello,http_req \
    --lua-desync=fakedsplit:pos=2:tcp_ack=-66000:tcp_ts_up

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

1. Минимальный (дефолт: pos=2, fooling=badseq)

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

Разрезает payload после 2-го байта 2 реальных + 4 фейка = 6 пакетов. Фейки отбрасываются по невалидному ACK.

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

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

SNI разрезан пополам. В каждом фейке — нули вместо реальных данных. DPI не может собрать hostname ни из одного отдельного сегмента.

3. HTTP: разрез после метода

--payload=http_req --lua-desync=fakedsplit:pos=method+2:tcp_ack=-66000:tcp_ts_up

Для GET /path... разрежет после GE. DPI видит 6 сегментов — 3 “ретрансмиссии” для GE* и 3 для T /path....

4. С seqovl (двойная защита)

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

Фейки + seqovl в первом реальном сегменте. Даже если DPI отбросит фейки, seqovl-данные (выглядящие как начало TLS record) могут его сбить.

5. С кастомным паттерном для фейков

--blob=fakepat:0x474554202F HTTP --payload=http_req \
  --lua-desync=fakedsplit:pos=host:tcp_ack=-66000:tcp_ts_up:pattern=fakepat

Фейки заполнены данными, похожими на HTTP-запрос. DPI ещё больше запутывается.

6. С badsum вместо badseq

--payload=tls_client_hello --lua-desync=fakedsplit:pos=midsld:badsum:tcp_ts_up

Фейки отбрасываются сервером из-за невалидного TCP checksum. Не все DPI проверяют checksum, поэтому для многих этого достаточно.

7. С TCP MD5 signature

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

Фейки содержат TCP MD5 option. Сервер без настроенного MD5 отбросит такие пакеты.

8. Без “задних” фейков (оптимизация трафика)

--payload=tls_client_hello \
  --lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:tcp_ts_up:nofake2:nofake4

Только 4 пакета вместо 6. Фейки идут только до реальных частей. Для многих DPI этого достаточно.

9. С повторами фейков (максимальный шум)

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

Каждый фейк отправляется 3 раза 4*3 + 2 = 14 пакетов. Оригиналы — по 1 разу. Максимальное замусоривание потока для DPI.

10. С TTL-fooling (вместо badseq)

--payload=tls_client_hello \
  --lua-desync=fakedsplit:pos=midsld:ip_ttl=4:tcp_ts_up

Фейки имеют TTL=4 — не доживут до сервера, но DPI на пути их увидит. Менее надёжно, чем badseq (зависит от числа хопов).

11. Цепочка: fake + fakedsplit

--payload=tls_client_hello \
  --lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \
  --lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:tcp_ts_up

Сначала отправляется полноценный TLS-фейк (поддельный ClientHello), затем реальный payload нарезается с замешиванием фейков. Двойной удар по DPI.

12. Комбинация с wssize и syndata

--lua-desync=wssize:wsize=1:scale=6 \
--lua-desync=syndata \
--payload=tls_client_hello \
  --lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:tcp_ts_up:seqovl=5

Window size manipulation + SYN data + fakedsplit с seqovl. Многоуровневая защита.

13. Защита от отсутствующего blob

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

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

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

--payload=http_req --lua-desync=fakedsplit:pos=method+2:tcp_ack=-66000:tcp_ts_up:nodrop

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

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

--filter-tcp=443 --hostlist=youtube.txt \
  --payload=tls_client_hello \
    --lua-desync=fakedsplit:pos=midsld:tcp_ack=-66000:tcp_ts_up:seqovl=5:seqovl_pattern=0x1603030000

Разрез SNI пополам + seqovl маскировка + badseq fooling. Оптимальная комбинация для TLS-трафика.

16. Боевой пример для HTTP

--filter-tcp=80 --hostlist=blocked.txt \
  --payload=http_req \
    --lua-desync=fakedsplit:pos=host:tcp_ack=-66000:tcp_ts_up

Разрез перед hostname в HTTP Host header. Ни один сегмент не содержит полный Host: blocked.example.com.


Источники: lua/zapret-antidpi.lua:803-906, lua/zapret-lib.lua:385-422, docs/manual.md:4122-4164 из репозитория zapret2.