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

Аспектtcpsegmultisplit
Функция разрешения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_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 /" -> диапазон 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

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

Фильтр по направлению пакета. 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 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

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


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 применяется к отправляемому сегменту (включая под-сегменты при 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 бесполезно — он только TCP8
ipfrag_next=NIPv6: next protocol во 2-м фрагменте (penetration атака на фаерволы)

G) Standard reconstruct

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

H) Standard rawsend

ПараметрОписание
repeats=NОтправить сегмент N раз (идентичные повторы)
ifout=<iface>Интерфейс для отправки (по умолчанию определяется автоматически)
fwmark=NFirewall 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).


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

Аспектtcpsegmultisplitmultidisorderfakedsplitfakeddisorder
Количество маркеровРовно 2 (диапазон)Список (любое кол-во)Список (любое кол-во)ОднаОдна
Что отправляетсяЧасть payload (диапазон)Весь payload (разрезанный)Весь payload (разрезанный)Весь payloadВесь payload
Функция разрешения posresolve_rangeresolve_multi_posresolve_multi_posresolve_posresolve_pos
pos обязателенДа (error если нет)Нет (дефолт “2”)Нет (дефолт “2”)Нет (дефолт “2”)Нет (дефолт “2”)
ВердиктНет (не выносит)VERDICT_DROPVERDICT_DROPVERDICT_DROPVERDICT_DROP
nodropНет (не нужен)ДаДаДаДа
Порядок отправкиОдин сегментПрямой (123)Обратный (321)ПрямойОбратный
Фейковые сегментыНетНетНетДа (до 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.