todo/Zapret2/desync/tcpseg.md

53 KiB
Raw Permalink Blame History

date tags aliases
2026-02-17
zapret
zapret2
nfqws2
lua
lua-desync
antidpi
tcp
tcpseg
seqovl
tcpseg

tcpseg -- отправка TCP-сегмента из диапазона (zapret2 / nfqws2)

Файл: lua/zapret-antidpi.lua:1043 nfqws1 эквивалент: отсутствует (NEW функция в nfqws2) Сигнатура: function tcpseg(ctx, desync)

tcpseg -- функция отправки части текущего payload (или reasm, или blob), ограниченной двумя маркерами позиций (диапазоном), в виде TCP-сегмента. В отличие от multisplit, которая разрезает весь payload на множество сегментов по списку позиций, tcpseg вырезает и отправляет один конкретный диапазон данных. При этом tcpseg не выносит вердикт (ни VERDICT_DROP, ни VERDICT_PASS) -- для замещения оригинального пакета необходимо комбинировать с функцией drop.

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

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


Оглавление


Зачем нужен tcpseg

tcpseg решает задачу, которую не решает multisplit: отправить конкретный фрагмент payload (или весь payload целиком) как отдельный TCP-сегмент, с опциональным seqovl, без автоматического вынесения вердикта.

Основные сценарии использования:

  1. seqovl без сегментации. Отправить весь payload целиком с seqovl-префиксом, подменяющим начало потока для DPI. Маркеры pos=0,-1 задают диапазон "от начала до конца" -- весь payload
  2. Повторная отправка начала потока. Отправить первые N байт payload многократно (repeats=N), чтобы забить буфер DPI мусором. Маркеры pos=0,method+2 или pos=0,midsld отправляют только начало
  3. Модульная композиция. Поскольку tcpseg не выносит вердикт, его можно комбинировать с другими инстансами (например, drop, luaexec) в цепочке

Ключевое отличие от multisplit: multisplit разрезает и отправляет весь payload, затем дропает оригинал. tcpseg отправляет часть (или весь) payload и не дропает оригинал -- нужна отдельная команда drop.


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

Минимальный seqovl (весь payload + 1 байт нулевого seqovl, dir=out, payload=known):

--lua-desync=tcpseg:pos=0,-1:seqovl=1 --lua-desync=drop

seqovl с кастомным паттерном (TLS):

--payload=tls_client_hello \
  --lua-desync=tcpseg:pos=0,-1:seqovl=5:seqovl_pattern=0x1603030000 \
  --lua-desync=drop

Повторная отправка начала HTTP-запроса:

--payload=http_req --lua-desync=tcpseg:pos=0,method+2:ip_id=rnd:repeats=20

Динамический seqovl со случайным паттерном:

--lua-desync=luaexec:code='desync.rnd=brandom_az(math.random(5,10))' \
--lua-desync=tcpseg:pos=0,-1:seqovl=#rnd:seqovl_pattern=rnd \
--lua-desync=drop:payload=known

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

Внутри tcpseg данные (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 -- обязательный аргумент tcpseg. Если pos не задан, функция вызовет error("tcpseg: no pos specified"). Это отличает tcpseg от multisplit, где pos по умолчанию "2".

Задаётся как строка с ровно двумя маркерами через запятую. Первый маркер -- начало диапазона, второй -- конец.

Отличие от multisplit: resolve_range vs resolve_multi_pos

Аспект tcpseg multisplit
Функция разрешения resolve_range (C-код) resolve_multi_pos (C-код)
Количество маркеров Ровно 2 (ошибка, если не 2) Любое количество
Семантика маркеров Начало и конец диапазона Точки разреза
Результат Таблица из 2 позиций {start, end} или nil Массив уникальных позиций
Что отправляется data:sub(pos[1], pos[2]) -- данные между маркерами Весь payload, разрезанный по позициям

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

Тип Описание Пример
Абсолютный положительный Смещение от начала payload. 0 = первый байт (в resolve_range используется zero-based нумерация на входе, но внутренне конвертируется в 1-based Lua) 0, 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 /" -> диапазон 0..method+2 захватит "GE")
sniext+1      -- один байт после начала SNI extension data
host+3        -- три байта после начала hostname
0             -- начало payload
-1            -- последний байт payload

Поведение resolve_range при неразрешённых маркерах

resolve_range реализована в C-коде (nfq2/lua.c:3281). Поведение при неразрешённых маркерах:

Ситуация Поведение (non-strict режим)
Оба маркера разрешены Возвращает {pos[0], pos[1]} -- нормальный диапазон
Первый маркер не разрешён, второй разрешён Первый маркер заменяется на 0 (начало данных)
Первый маркер разрешён, второй не разрешён Второй маркер заменяется на len-1 (конец данных)
Оба маркера не разрешены Возвращает nil -- диапазон не может быть определён
pos[0] > pos[1] (начало после конца) Возвращает nil -- невалидный диапазон

Важно: tcpseg вызывает resolve_range без флага strict. Это означает, что если один из маркеров не разрешается, он расширяется до границы данных. Если нужна строгая логика -- используйте абсолютные маркеры.

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

  • pos обязателен. В отличие от multisplit, где pos по умолчанию "2", в tcpseg отсутствие pos вызывает error(). Это жёсткое требование
  • Ровно 2 маркера. Если передать 1 или 3+ маркера, resolve_range вызовет luaL_error("resolve_range require 2 markers")
  • Маркер 0 -- начало данных. В отличие от multisplit, где позиция 1 удаляется (delete_pos_1), в tcpseg маркер 0 -- это валидное начало диапазона
  • Маркеры 0,-1 -- весь payload. Самый частый паттерн: pos=0,-1 означает "от начала до конца" -- весь payload целиком. Используется для seqovl без сегментации

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

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

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

Без seqovl (pos=0,-1):
  TCP seq: 1000
  Данные:  [ВЕСЬ_PAYLOAD]
  Сервер:  принимает [ВЕСЬ_PAYLOAD] целиком

С seqovl=10 (pos=0,-1):
  TCP seq: 990             (уменьшен на 10)
  Данные:  [PATTERN_10_БАЙТ][ВЕСЬ_PAYLOAD]

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

  Что видит сервер (TCP-стек):
    TCP window начинается с seq 1000.
    Байты 990-999 выходят за левую границу window -> отбрасываются.
    Байты с 1000 (ВЕСЬ_PAYLOAD) -> принимаются.

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

           TCP window boundary
                 |
  |  ОТБРОСИТЬ  | ПРИНЯТЬ             |
  | PATTERN(10) | ВЕСЬ_PAYLOAD        |
  ^seq=990       ^seq=1000

Визуализация с pos=0,midsld (частичная отправка):

  Payload: [====НАЧАЛО====|====midsld====|====КОНЕЦ====]
                           ^pos[1]

  Отправляется только [====НАЧАЛО====|====midsld====]
  (от начала до midsld включительно)

  С seqovl=5:
  TCP seq: -5 (относительно начала отправляемого куска)
  Данные:  [PAT 5][====НАЧАЛО====|====midsld====]

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

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

Вывод: seqovl -- средство создания скрытых фейков, не требующее fooling. Это его ключевое преимущество. В tcpseg seqovl особенно силён в комбинации с pos=0,-1, где весь payload отправляется с фейковым префиксом.

seqovl_pattern

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

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

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

# Предзагруженный blob
--blob=tlspat:0x1603030100 \
--lua-desync=tcpseg:pos=0,-1:seqovl=8:seqovl_pattern=tlspat

# Динамически сгенерированный blob через luaexec
--lua-desync=luaexec:code='desync.rnd=brandom_az(math.random(5,10))' \
--lua-desync=tcpseg:pos=0,-1:seqovl=#rnd:seqovl_pattern=rnd

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

Важно в tcpseg: seqovl -- только число, маркеры не поддерживаются (как и в multisplit, в отличие от multidisorder и fakeddisorder, где seqovl может быть маркером).


Вердикт и комбинация с drop

Ключевая особенность tcpseg: функция не выносит вердикт. Она не возвращает ни VERDICT_DROP, ни VERDICT_PASS. Это означает:

  1. После выполнения tcpseg оригинальный пакет не блокируется
  2. Следующие инстансы в цепочке продолжают обработку
  3. Если после tcpseg нет других инстансов -- оригинальный пакет уйдёт как есть

Для замещения оригинального пакета tcpseg нужно комбинировать с drop:

# tcpseg отправляет payload с seqovl, drop блокирует оригинал
--lua-desync=tcpseg:pos=0,-1:seqovl=5 --lua-desync=drop

Нюанс с payload-фильтрами: по умолчанию tcpseg работает только с known payload, а drop -- с all. Если вы хотите дропать только известные payload (чтобы неизвестные проходили без изменений), укажите:

--lua-desync=tcpseg:pos=0,-1:seqovl=5 --lua-desync=drop:payload=known

Диаграмма прохождения пакета:

Пакет приходит
    |
    v
[tcpseg] -- отправляет TCP-сегмент (часть payload с seqovl)
    |       НЕ выносит вердикт
    v
[drop]   -- выносит VERDICT_DROP, блокируя оригинальный пакет
    |
    v
Оригинальный пакет заблокирован.
Сервер получает только то, что отправил tcpseg.

Без drop:

Пакет приходит
    |
    v
[tcpseg] -- отправляет TCP-сегмент
    |       НЕ выносит вердикт
    v
Нет больше инстансов -> VERDICT_PASS (по умолчанию)
    |
    v
Оригинальный пакет тоже уходит!
Сервер получает данные ДВАЖДЫ (tcpseg + оригинал).

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

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

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

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

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

pos

  • Формат: pos=<marker1,marker2>
  • Тип: строка с ровно двумя маркерами через запятую
  • По умолчанию: нет (обязательный параметр; если не задан -- error())
  • Описание: Диапазон данных для отправки. Первый маркер -- начало, второй -- конец (включительно). Из данных вырезается data:sub(pos[1], pos[2]) и отправляется как TCP-сегмент
  • Примеры:
    • pos=0,-1 -- весь payload (от начала до конца)
    • pos=0,midsld -- от начала до середины SLD
    • pos=0,method+2 -- от начала до 2 байт после начала HTTP-метода
    • pos=host,endhost -- только hostname
    • pos=0,1 -- только первый байт (для TLS: первый байт TLS record)
    • pos=sniext,endhost -- от начала SNI extension data до конца hostname

seqovl

  • Формат: seqovl=N (где N > 0)
  • Тип: только число (маркеры не поддерживаются -- как и в multisplit)
  • По умолчанию: не задан (нет seqovl)
  • Описание: К данным сегмента слева добавляется N байт seqovl_pattern, а TCP th_seq уменьшается на N. Сервер отбросит левую часть, DPI -- может не отбросить
  • Примеры:
    • seqovl=1 -- 1 байт фейка слева (минимальный)
    • seqovl=5 -- 5 байт фейка слева
    • seqovl=#rnd -- размер blob rnd (C-подстановка через #)
    • 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=rnd -- динамически сгенерированный blob
    • seqovl_pattern=fake_default_tls -- стандартный TLS-фейк как паттерн

blob

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

optional

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

B) Standard direction

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

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

Параметр Описание Пример
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

Предупреждение: fooling в tcpseg применяется к реальному сегменту (не к фейку). Если задать tcp_ack=-66000, сервер отбросит сегмент. Fooling в tcpseg имеет смысл только для безопасных вещей: tcp_ts_up, ip_id, IPv6 extension headers.


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). Для tcpseg бесполезно -- он только 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)

Порядок отправки

tcpseg отправляет один сегмент (диапазон данных между двумя маркерами), опционально с seqovl-префиксом.

Пример без seqovl (pos=host,endhost)

Payload (600 байт):
[===ЗАГОЛОВКИ===][example.com][===ОСТАЛЬНОЕ===]
                 ^pos[1]=host  ^pos[2]=endhost

Отправляется:
  Сегмент: [example.com]  seq=host_offset  len=(endhost-host)

Оригинальный пакет: НЕ блокируется (нет вердикта)

Пример с seqovl=10 (pos=0,-1)

Payload (600 байт), pos=0,-1, seqovl=10:

  Сегмент: [PATTERN(10)][ВЕСЬ_PAYLOAD_600_БАЙТ]
           seq=-10   len=610

  Сервер: отбросит первые 10 байт, примет 600
  DPI: проанализирует все 610 байт

Пример с частичной отправкой и seqovl (pos=0,midsld, seqovl=5)

Payload: [====ДО_MIDSLD====][====ПОСЛЕ_MIDSLD====]
                             ^midsld

Отправляется:
  Сегмент: [PAT(5)][====ДО_MIDSLD====]
           seq=-5   len=(midsld+5)

Оригинальный пакет: НЕ блокируется (нужен drop)

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

Многопакетный запрос nfqws2 придерживает, собирает в буфер reasm_data и перепроигрывает через desync-функции (жизненный цикл desync-функции, стадия 6). tcpseg использует из этой развилки только первую половину: на первой части вырезает диапазон из собранного буфера и отправляет его, на остальных частях логирует «not acting on further replay pieces» и возвращает nil.

Вторую половину — дроп последующих частей — функция не применяет сознательно: она ничего не заменяет, а лишь добавляет в поток дополнительный сегмент.

Важный нюанс: поскольку tcpseg не выносит вердикт, на последующих частях replay оригинальные пакеты пройдут без изменений. Если drop стоит после tcpseg -- он заблокирует все пакеты (и первую часть, и последующие). Поэтому связка tcpseg + drop корректно обрабатывает reasm: tcpseg отправляет весь reasm при первой части, drop блокирует все оригинальные части.


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

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

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

Пример: если вы задали pos=0,-1:seqovl=10000, это не вызовет ошибку. rawsend_payload_segmented отправит несколько TCP-сегментов с начальным sequence -(10000), общим размером 10000 байт seqovl-pattern, и в последних сегментах -- реальные данные payload.


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

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

Стадия общего скелета Что делает tcpseg
1. Отсев транспорта только TCP; на не-TCP делает instance_cutoff (кроме связанного ICMP)
2. Направление dir=out по умолчанию, отключается от входящего
3. Аргументы pos обязателен — без него error(); optional + отсутствующий blob → тихий выход
4. Данные штатная цепочка blob → reasm → payload
5. Гварды штатные #data>0, direction_check, payload_check (по умолчанию known)
6. replay использует только первую половину развилки: работает на первой части, остальные не дропает
7. Своя техника resolve_range разрешает ровно два маркера в диапазон, отправляется один сегмент с этим куском данных
8. Вердикт не выносит никакого — оригинальный пакет уходит как обычно

Последние две строки таблицы объясняют, почему tcpseg ведёт себя не как остальные техники сегментации. Он ничего не дропает и не заменяет исходный поток, а лишь дополнительно отправляет выбранный кусок данных. Поэтому его применяют как строительный блок в связке с другими инстансами, а не как самостоятельную стратегию.


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

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

function tcpseg(ctx, desync)
    -- 1. Проверка: только TCP
    if not desync.dis.tcp then
        if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end
        return   -- без вердикта
    end

    -- 2. Cutoff противоположного направления
    direction_cutoff_opposite(ctx, desync)

    -- 3. pos ОБЯЗАТЕЛЕН
    if not desync.arg.pos then
        error("tcpseg: no pos specified")
    end

    -- 4. Проверка optional blob
    if optional and blob specified and blob not exists then
        DLOG("tcpseg: blob not found. skipped")
        return   -- без вердикта, тихий skip
    end

    -- 5. Выбор данных
    data = blob_or_def(blob) or reasm_data or dis.payload

    -- 6. Проверки: данные не пусты, направление OK, payload OK
    if #data > 0 and direction_check() and payload_check() then

        -- 7. Только первый replay
        if replay_first(desync) then

            -- 8. Разрешение ДИАПАЗОНА (ровно 2 маркера)
            pos = resolve_range(data, l7payload, pos_arg)
            -- pos = {start, end} или nil

            if pos then
                -- 9. Вырезать диапазон
                part = data:sub(pos[1], pos[2])

                -- 10. seqovl
                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 not blob_exist(seqovl_pattern) then
                            -- используем нулевой паттерн
                        else
                            pat = blob(seqovl_pattern)
                        end
                    end
                    part = pattern(pat, 1, seqovl) .. part
                end

                -- 11. Отправка с автосегментацией
                rawsend_payload_segmented(desync, part, pos[1]-1-seqovl)

            else
                DLOG("tcpseg: range cannot be resolved")
            end
        else
            -- 12. Не первый replay -- ничего не делаем
            DLOG("tcpseg: not acting on further replay pieces")
        end
    end

    -- 13. ВЕРДИКТ НЕ ВЫНОСИТСЯ -- return без значения
end

Ключевые отличия от псевдокода multisplit:

  • Шаг 3: error() при отсутствии pos (в multisplit -- дефолт "2")
  • Шаг 8: resolve_range вместо resolve_multi_pos (2 маркера, не список)
  • Шаг 9: data:sub(pos[1], pos[2]) -- диапазон, не цикл по частям
  • Шаг 11: одна отправка (не цикл)
  • Шаг 13: нет return VERDICT_DROP / VERDICT_PASS -- нет replay_drop_set(), нет nodrop

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

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

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

2. pos обязателен -- нет дефолта

В отличие от multisplit (дефолт "2"), tcpseg без pos вызовет error(). Всегда указывайте pos.

3. Ровно 2 маркера -- не больше и не меньше

resolve_range в C-коде проверяет ctm != 2 и вызывает luaL_error. Один маркер или три маркера -- ошибка.

4. Не выносит вердикт -- нужен drop

Самая частая ошибка: забыть drop после tcpseg. Без drop оригинальный пакет уйдёт, и сервер получит данные дважды. Это может работать (TCP-стек дедуплицирует), но неоптимально и может путать DPI.

5. drop по умолчанию дропает ВСЕ payload

Функция drop по умолчанию работает с payload=all. Если tcpseg работает только с known, то drop без ограничений заблокирует и unknown payload. Решение:

--lua-desync=tcpseg:pos=0,-1:seqovl=5 --lua-desync=drop:payload=known

6. Маркер 0 -- это начало, не "нулевая позиция Lua"

В resolve_range маркер 0 -- это абсолютный маркер начала данных. Внутри C-кода pos[0]=0 соответствует первому байту. При возврате в Lua конвертируется в 1-based (pos[0]+1).

7. Fooling применяется к реальному сегменту

В отличие от fakedsplit, где fooling идёт только на фейки, в tcpseg fooling модифицирует реальный отправляемый сегмент. Использование tcp_ack=-66000 приведёт к тому, что сервер отбросит сегмент. Безопасные fooling-опции: ip_id=rnd, tcp_ts_up, IPv6 extension headers.

8. seqovl=10000 не вызовет ошибку

В отличие от nfqws1, где большие значения seqovl вызывали ошибку, nfqws2 автоматически сегментирует по MSS. Большой seqovl просто создаст много под-сегментов.

9. Нет nodrop -- потому что нет drop

В tcpseg нет параметра nodrop, потому что функция и так никогда не дропает. Для управления вердиктом используйте отдельный инстанс drop.

10. При неразрешённом диапазоне -- тихий пропуск

Если оба маркера не разрешаются (например, pos=midsld,endhost для unknown payload), resolve_range вернёт nil, и tcpseg залогирует "range cannot be resolved" и ничего не сделает. Без ошибки, без вердикта.

11. Повторная отправка (repeats) без drop -- атака повторами

tcpseg с repeats=N без drop отправит N копий сегмента, а затем оригинальный пакет тоже уйдёт. Это осознанная стратегия для забивания буфера DPI (см. blockcheck2/15-misc.sh).


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

Аспект tcpseg multisplit multidisorder fakedsplit fakeddisorder
Количество маркеров Ровно 2 (диапазон) Список (любое кол-во) Список (любое кол-во) Одна Одна
Что отправляется Часть payload (диапазон) Весь payload (разрезанный) Весь payload (разрезанный) Весь payload Весь payload
Функция разрешения pos resolve_range resolve_multi_pos resolve_multi_pos resolve_pos resolve_pos
pos обязателен Да (error если нет) Нет (дефолт "2") Нет (дефолт "2") Нет (дефолт "2") Нет (дефолт "2")
Вердикт Нет (не выносит) VERDICT_DROP VERDICT_DROP VERDICT_DROP VERDICT_DROP
nodrop Нет (не нужен) Да Да Да Да
Порядок отправки Один сегмент Прямой (1->2->3) Обратный (3->2->1) Прямой Обратный
Фейковые сегменты Нет Нет Нет Да (до 4 шт.) Да (до 4 шт.)
seqovl тип Только число Только число Маркер Только число Маркер
Fooling к Реальному сегменту Всем сегментам Всем сегментам Только к фейкам Только к фейкам
ipfrag Да Да Да Нет Нет
Нужен отдельный drop Да Нет (встроен) Нет (встроен) Нет (встроен) Нет (встроен)

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

1. Минимальный seqovl без сегментации (TLS)

--payload=tls_client_hello \
  --lua-desync=tcpseg:pos=0,-1:seqovl=1 \
  --lua-desync=drop

Отправляет весь payload с 1 нулевым байтом seqovl слева. Drop блокирует оригинал.

2. seqovl с TLS-фейковым паттерном

--payload=tls_client_hello \
  --lua-desync=tcpseg:pos=0,-1:seqovl=5:seqovl_pattern=0x1603030000 \
  --lua-desync=drop

5-байтовый TLS record header как seqovl-prefix. DPI видит начало "нового" TLS record перед настоящим ClientHello.

3. seqovl с предзагруженным фейком и tls_mod

--payload=tls_client_hello \
  --blob=seqovl_pat:@fake_tls.bin \
  --lua-init=seqovl_pat=tls_mod(seqovl_pat,'rnd') \
  --lua-desync=tcpseg:pos=0,-1:seqovl=#seqovl_pat:seqovl_pattern=seqovl_pat \
  --lua-desync=drop

Blob загружается из файла, рандомизируется через tls_mod, используется как seqovl_pattern с размером blob в качестве seqovl.

4. Динамический случайный seqovl через luaexec

--lua-desync=luaexec:code='desync.rnd=brandom_az(math.random(5,10))' \
--lua-desync=tcpseg:pos=0,-1:seqovl=#rnd:seqovl_pattern=rnd \
--lua-desync=drop:payload=known

На каждый пакет генерируется случайная строка из букв a-z длиной 5-10 символов. Используется как seqovl_pattern, а её размер -- как значение seqovl. Drop только для known payload.

5. seqovl с padencap и tls_mod (продвинутый)

--payload=tls_client_hello \
  --blob=seqovl_pat:@fake_tls.bin \
  --lua-desync=luaexec:code="desync.patmod=tls_mod(seqovl_pat,'rnd,dupsid,padencap',desync.reasm_data)" \
  --lua-desync=tcpseg:pos=0,-1:seqovl=#patmod:seqovl_pattern=patmod \
  --lua-desync=drop

Динамическая модификация фейка с учётом реального payload (через desync.reasm_data). Паттерн маскируется под TLS с padding, рандомизацией и дублированием session ID.

6. Повторная отправка начала HTTP-запроса (без drop)

--payload=http_req \
  --lua-desync=tcpseg:pos=0,method+2:ip_id=rnd:repeats=20

Отправляет первые 2 байта HTTP-метода (например, GE из GET) 20 раз со случайными IP ID. Оригинальный пакет тоже уходит (нет drop). Стратегия забивания буфера DPI.

7. Повторная отправка до midsld (TLS)

--payload=tls_client_hello \
  --lua-desync=tcpseg:pos=0,midsld:ip_id=rnd:repeats=100

Отправляет начало ClientHello до середины SNI 100 раз. DPI может переполнить свой буфер и перестать анализировать поток.

8. Отправка только hostname

--payload=tls_client_hello \
  --lua-desync=tcpseg:pos=host,endhost

Вырезает и отправляет только hostname из SNI. Без drop и без seqovl -- чисто для экспериментов или в составе сложной цепочки.

9. seqovl без сегментации + IP-фрагментация

--payload=tls_client_hello \
  --lua-desync=tcpseg:pos=0,-1:seqovl=5:ipfrag:ipfrag_disorder:ipfrag_pos_tcp=32 \
  --lua-desync=drop

Весь payload с seqovl отправляется, TCP-сегмент дополнительно фрагментируется на IP-уровне в обратном порядке.

10. Только первый байт TLS record (повторы)

--payload=tls_client_hello \
  --lua-desync=tcpseg:pos=0,1:ip_id=rnd:repeats=260

Отправляет только первый байт TLS record 260 раз. Используется в blockcheck2 для тестирования misc-стратегий.

11. Комбинация: fake + tcpseg + drop (полная стратегия)

--payload=tls_client_hello \
  --lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \
  --lua-desync=tcpseg:pos=0,-1:seqovl=5:seqovl_pattern=0x1603030000 \
  --lua-desync=drop
  1. fake отправляет фейковый TLS ClientHello с md5sig fooling
  2. tcpseg отправляет настоящий payload с seqovl
  3. drop блокирует оригинальный пакет

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

--lua-desync=tcpseg:pos=0,-1:blob=maybe_missing:optional:seqovl=5

Если blob maybe_missing не существует -- tcpseg тихо пропускает. Без ошибок.

13. HTTP: seqovl=1 + drop (минимальный рабочий пример)

--payload=http_req \
  --lua-desync=tcpseg:pos=0,-1:seqovl=1 \
  --lua-desync=drop

Один нулевой байт seqovl перед HTTP-запросом. Минимальная модификация, но может обмануть DPI, который ожидает HTTP-метод с первого байта TCP-потока.

14. wssize + tcpseg + drop (контроль TCP window)

--payload=tls_client_hello \
  --lua-desync=wssize:wsize=1:scale=6 \
  --lua-desync=tcpseg:pos=0,-1:seqovl=5:seqovl_pattern=fake_default_tls \
  --lua-desync=drop

Сначала wssize устанавливает маленький TCP window, затем tcpseg отправляет payload с seqovl, и drop блокирует оригинал.


Источники: lua/zapret-antidpi.lua:1030-1076, nfq2/lua.c:3281-3328 (resolve_range), docs/manual.md:4248-4274, docs/readme.md:394-412, blockcheck2.d/standard/15-misc.sh, blockcheck2.d/standard/23-seqovl.sh из репозитория zapret2.