💀 ts и fooling: почему фейк должен умереть

Главная мысль

Fake-пакет в десинхронизации должен дойти до DPI, но умереть до того, как его примет настоящий сервер. Способ “правильно убить fake” называется fooling. Самый капризный из них — ts: он не добавляет TCP timestamps с нуля, а работает только если настоящий TCP-сеанс уже идёт с timestamp option. На Windows timestamps обычно выключены, поэтому ts как единственный TCP fooling там опасен — fake может перестать быть “плохим” и сломать соединение.

Эта заметка отвечает на три вопроса, которые чаще всего возникают про fake-пакеты: зачем они нужны, почему они обязаны умирать, и как заставить их умирать правильно на разных платформах. Параметр ts разобран как главный пример, потому что именно он ломается тише и обиднее всего.

Полный справочник параметров --lua-desync (включая tcp_ts, tcp_md5, tcp_seq и прочие) живёт в desync — здесь без дублирования, упор на смысл и подводные камни.

Связанные заметки: desync, fake, fakedsplit, fakeddisorder, основные флаги, оркестратор, windivert.


TL;DR

  • Fake обязан умереть до сервера. Иначе сервер получит мусорный ClientHello/HTTP-запрос и нормальное соединение сломается.
  • fooling — это контролируемая порча fake, чтобы серверный TCP/IP стек отбросил его, а DPI успел разобрать и сделать неверный вывод.
  • У каждого fooling свой “слой смерти” (checksum, seq, ack, timestamp, TTL, IPv6-заголовки) и своё место, где он не проходит (NAT, роутер, сервер).
  • ts смещает TSval настоящего timestamp назад, чтобы сервер счёл fake “слишком старым”. Работает только если timestamps согласованы в начале TCP-сеанса.
  • Платформа решает всё. Windows по умолчанию без timestamps; за домашним NAT отмирают badsum/datanoack; на самом роутере наоборот могут ожить. Один и тот же fooling бывает отличным в одном месте и бесполезным в другом.

Зачем нужен fake и почему он обязан умереть

Начнём с того, что вообще делает fake, потому что без этого ts и fooling выглядят набором магических флагов.

Когда zapret отправляет fake, у этого пакета две разные аудитории, и им нужно прямо противоположное.

Для DPI fake должен выглядеть достаточно настоящим: DPI должен успеть разобрать его как TLS, HTTP, QUIC или другой payload и сделать неправильный вывод о соединении (например, увидеть в SNI безобидный сайт).

Для сервера этот же fake должен быть плохим: серверный TCP/IP стек должен отбросить его ещё до прикладного уровня. Тогда настоящий пакет придёт следом и будет обработан нормально, как будто никакого fake и не было.

На пальцах: разговор под прослушкой

Представь, что между тобой и собеседником (сервером) сидит прослушка (DPI). Ты хочешь, чтобы прослушка решила, будто разговор безобидный. Для этого ты вслух произносишь подставную фразу — её должна услышать прослушка (она ближе), но не должен услышать собеседник, иначе он переспросит “что?” и собьётся. Значит, подставную фразу надо сказать так, чтобы она “не долетела” до собеседника: либо слишком тихо (это TTL), либо на ломаном языке, который его телефон отбракует как помеху (это badsum, badseq, ts). Вот эта “управляемая порча” и есть fooling — способ заставить fake умереть, не дойдя до сервера.

Формула fooling

Fake должен быть «достаточно настоящим» для DPI и «достаточно неправильным» для сервера. fooling добавляет ровно ту неправильность, которая убивает fake на пути к серверу, но не мешает DPI его разобрать.

Что будет, если fake НЕ умрёт

Самая неприятная поломка — это не “обход не сработал”. Хуже, когда fake доходит до сервера живым: сервер примет его как реальную часть TCP-потока, поверх ляжет настоящий пакет, и нормальный TLS/HTTP-поток развалится. Поэтому “fake не дошёл до сервера” — это не побочный эффект, а обязательное условие работы стратегии.


Что такое fooling

fooling — это и есть та самая контролируемая “неправильность”, которой портят fake, чтобы он гарантированно умер на пути к серверу.

В zapret1 fooling задаётся готовыми режимами: ts, md5sig, badseq, badsum, datanoack и так далее через флаг --dpi-desync-fooling=.

В zapret2 то же самое разложено на низкоуровневые параметры Lua-функций: tcp_ts=N, tcp_md5, tcp_seq=N, tcp_ack=N, badsum, tcp_flags_unset=ACK и другие. Это даёт больше гибкости, но требует понимать, что именно делает каждый параметр — потому что “магического” поведения по умолчанию у них меньше (см. desync для полного списка).


Что такое TCP options (и зачем fooling с ними играет)

Чтобы понять ts, md5sig и tcp_ts_up, надо сначала понять, куда они вообще пишут. У TCP-пакета есть две разные зоны, которые fooling может портить.

Фиксированные поля заголовка есть всегда: sequence number (порядковый номер байта в потоке), acknowledgment number (что подтверждено), флаги (SYN, ACK, RST…), checksum (контрольная сумма), window (размер окна). По ним бьют badseq (портит seq), badack/tcp_ack (портит ack), badsum (портит checksum), datanoack (снимает флаг ACK).

TCP options — это переменная часть в конце TCP-заголовка, куда складываются «дополнительные возможности», о которых стороны договариваются. Timestamp — одна из таких опций. По опциям бьют ts/tcp_ts (меняет timestamp-опцию), md5sig/tcp_md5 (добавляет MD5-опцию), tcp_ts_up (переставляет timestamp-опцию вперёд), tcp_nop_del (удаляет опции-заполнители NOP).

Как устроена одна TCP option

Почти каждая опция — это три части подряд: kind (1 байт — что за опция), length (1 байт — полная длина опции вместе с этими двумя байтами) и data (содержимое). Исключение — две однобайтовые опции без length и data: End of Option List (kind 0) и No-Operation / NOP (kind 1). Пример timestamp-опции в байтах (kind 8, длина 10, дальше 4 байта TSval и 4 байта TSecr):

08 0A | 11 22 33 44 | 00 00 00 00
▲  ▲     ▲             ▲
│  │     │             └ TSecr (4 байта) — эхо timestamp той стороны
│  │     └ TSval (4 байта) — timestamp отправителя
│  └ length = 10 (2 байта заголовка + 8 байт данных)
└ kind = 8 (Timestamps)

На практике перед timestamp часто идут два NOP (01 01) — просто чтобы выровнять опцию по 4 байта. Поэтому порядок опций вообще можно «двигать» — это и делает tcp_ts_up.

Распространённые TCP options (номера kind — по реестру IANA):

kindОпцияЗачем нужнаСтандарт
0End of Option ListОтметка конца списка опцийRFC 9293
1No-Operation (NOP)Заполнитель для выравниванияRFC 9293
2Maximum Segment Size (MSS)Максимальный размер сегментаRFC 9293
3Window ScaleМножитель размера окнаRFC 7323
4SACK PermittedСогласие на выборочные подтвержденияRFC 2018
5SACKСами выборочные подтвержденияRFC 2018
8Timestamps (TSval/TSecr)Замер RTT и защита от старых пакетов (PAWS)RFC 7323
19MD5 SignatureПодпись сегмента (используется в BGP)RFC 2385
29Authentication Option (AO)Современная замена MD5RFC 5925
34TCP Fast OpenДанные уже в SYNRFC 7413

Исходники zapret = стандарт

Номера kind в zapret заданы в nfq2/darkmagic.h (TCP_KIND_TS=8, TCP_KIND_MD5=19, TCP_KIND_SCALE=3, TCP_KIND_MSS=2, TCP_KIND_SACK_PERM=4, TCP_KIND_NOOP=1, TCP_KIND_END=0) и совпадают с реестром IANA и RFC. zapret1 в nfq/darkmagic.c использует те же числа напрямую (tcp_find_option(tcp,8) — поиск timestamp по kind 8). То есть это не «выдумка zapret», а ровно те значения, что лежат в спецификации.

Главное свойство: возможность согласуется один раз — в начале

Ключевой момент для fooling по опциям: поддержка timestamp (как и window scale, SACK) согласуется один раз — в первом обмене (SYN и SYN,ACK), и её нельзя «включить» в середине соединения. Если timestamp не предложили в SYN — дальше в этом сеансе его не будет. (Это не значит, что опции вообще не появляются по ходу: например, SACK-блоки и MD5-подпись идут в обычных сегментах. Но именно timestamp-сеанс задаётся на старте.) Поэтому нельзя просто «дописать timestamp в один пакет»: для сервера это не сделает сеанс timestamp-сеансом. Отсюда и растёт вся капризность ts — подробно ниже.


Как заставить fake умереть: каталог способов

Универсального fooling нет. Каждый способ убивает fake на своём слое (контрольная сумма, sequence, ack, timestamp, TTL, IPv6-заголовки) и ломается в своём месте (NAT, роутер, конкретный сервер, конкретный DPI). Выбор зависит не от названия, а от среды.

Способ (zapret1 / zapret2)Как убивает fakeГде ломается
ts / tcp_ts=NСмещает TSval так, что сервер считает пакет слишком старым (PAWS)Нужны согласованные TCP timestamps. На Windows по умолчанию выключены
md5sig / tcp_md5Добавляет TCP MD5 option со случайными байтами — Linux-сервер обычно отбрасываетНе универсально. Увеличивает TCP header → на больших TLS ClientHello (Kyber, малая split-позиция) возможен message too long (переполнение MTU)
badsum / badsumПортит контрольную сумму — пакет битый для любого узлаNAT/conntrack/роутер часто отбрасывает fake раньше DPI. На самом роутере живёт лучше, чем за ним
badseq / tcp_seq=NУводит sequence за TCP-окно — сервер отбрасываетВнимательный DPI тоже следит за окном и может не поверить fake. В zapret1 дефолт смещения -10000
(bad ack) / tcp_ack=NПортит ACK при валидном seq — сервер отбрасываетНа Linux надёжно дропается только если timestamp option идёт первой → нужен tcp_ts_up (см. ниже)
datanoack / tcp_flags_unset=ACKСнимает ACK с data-пакета — сервер такое не принимаетNAT с masquerade (типично на домашних роутерах) часто режет. Зависит от места запуска
tcp_flags_set=SYNДелает невалидную комбинацию флагов — сервер не примет, DPI приметРаботает не на всех серверах; через NAT может отбрасываться
TTL / ip_ttl=N, ip_autottlFake “не долетает”: умирает по дороге, не дойдя до сервераНадо угадать число хопов до DPI. Маршрут меняется → fake умрёт рано или пробьётся до сервера. Стоковые прошивки роутеров фиксируют исходящий TTL
hopbyhop / ip6_hopbyhopIPv6 extension header: DPI разберёт, сервер отбросит (или провайдер отфильтрует)Только IPv6. hopbyhop2 (два заголовка) дропается всеми ОС гарантированно, но может резаться сетью

Как выбирать fooling

Не по названию, а по месту запуска, протоколу, NAT, серверу и pcap. Один и тот же режим бывает отличным на роутере и бесполезным за роутером. Подбор делается тестами (blockcheck/pcap), а не на слух.


ts подробно: что делает и почему капризный

ts стоит особняком, потому что ломается тише всех: он не падает с ошибкой, а просто молча перестаёт портить fake.

Что именно делает ts

ts работает с TCP timestamp option (kind 8 — см. раздел про TCP options выше). Внутри неё два 32-битных значения: TSval (timestamp отправителя) и TSecr (timestamp второй стороны, который отправитель возвращает обратно). Для ts важен именно TSval.

Смысл техники: взять timestamp из настоящего пакета и сместить TSval назад. Это запускает на сервере механизм PAWS (Protection Against Wrapped Sequences — защита от «обернувшихся» порядковых номеров, RFC 7323): сервер отбрасывает сегменты со «слишком старым» timestamp. “Состаренный” fake умирает на сервере по PAWS, а DPI обычно не проверяет timestamp так строго и спокойно разбирает payload.

RFC 7323, правило PAWS (R1)

«If there is a Timestamps option in the arriving segment, SEG.TSval < TS.Recent, TS.Recent is valid … then treat the arriving segment as not acceptable … and drop the segment.» Суть: если в пришедшем сегменте timestamp меньше последнего запомненного сервером (TS.Recent), сегмент считается старым и отбрасывается. Сдвиг TSval на -600000 искусственно делает fake «старым» — и сервер дропает его по PAWS.

Смещение — со знаком, и диапазон имеет значение

Смещение ts может быть и отрицательным, и положительным, но на практике используют отрицательное (“назад”). По документации zapret1 рабочий инкремент — примерно от -100 до -0x80000000. Поэтому и -1000, и -600000 попадают в рабочий диапазон. В zapret1 дефолт ts-600000.

zapret1 и zapret2

В zapret1 это выглядит так:

--dpi-desync-fooling=ts
--dpi-desync-ts-increment=-600000

В zapret2 прямого режима ts нет — он пишется как параметр fooling:

--lua-desync=fake:tcp_ts=-600000

В zapret2 нужно явное значение

tcp_ts требует число. Нельзя просто написать tcp_ts и ждать “дефолта как в zapret1”. При портировании стратегии из zapret1 смещение надо выбрать руками (часто -600000, чтобы повторить старое поведение).

Почему ts не работает без согласованных timestamps

Это главный подвох. TCP timestamps — не произвольная добавка к любому пакету. Они согласуются в начале соединения.

Клиент должен предложить timestamp option в SYN. Сервер должен ответить timestamp option в SYN,ACK. Только после этого весь TCP-сеанс считается сеансом с timestamps, и у data-пакетов появляется timestamp option.

Если клиент не предложил timestamps или сервер их не поддержал, дальше соединение идёт без них — и портить ts-ом будет попросту нечего.

RFC 7323, согласование timestamp

«A TCP MAY send the TSopt in an initial <SYN> segment … and MAY send a TSopt in <SYN,ACK> only if it received a TSopt in the initial <SYN> segment.» И далее: «Once TSopt has been successfully negotiated, that is both <SYN> and <SYN,ACK> contain TSopt, the TSopt MUST be sent in every non-<RST> segment for the duration of the connection.» Перевод сути: сервер вставит timestamp в ответ, только если увидел его в SYN клиента. Если согласовали — timestamp обязан быть в каждом дальнейшем пакете. Если не согласовали — его не будет нигде, и ts нечего портить. RFC 7323 обновляет более старый RFC 1323.

Главное ограничение ts

Если в настоящем TCP-пакете нет timestamp option, ts не на чем работать. “Вставить timestamp в fake руками” недостаточно: TCP-сеанс не был согласован как timestamp-сеанс, и сервер не обязан применять к такому fake логику PAWS. Fake перестаёт быть “плохим” — и может дойти до сервера живым.

Поэтому zapret2 не создаёт timestamp option с нуля. В lua/zapret-lib.lua он ищет TCP_KIND_TS, проверяет длину данных (8 байт) и меняет TSval, только если option уже есть. Если её нет — в debug уходит сообщение:

apply_fooling: timestamp tcp option not present or invalid

Это не ошибка запуска и не падение стратегии, а сигнал: к этому конкретному пакету tcp_ts=N по сути не применился.

Как читать это сообщение

Оно не говорит “Windows виновата”. Оно сообщает только факт: в данном TCP-пакете не было валидной timestamp option. Причина может быть в Windows, в настройках клиента, в сервере или просто в том, что Профиль применился не к тому пакету.

cond_tcp_has_ts — не стрелять вхолостую

Вместо того чтобы вручную гадать, есть ли в пакете timestamp, zapret2 даёт встроенную проверку времени выполнения.

Функция cond_tcp_has_ts(desync) возвращает true, только если диссект — это TCP и в нём присутствует timestamp option (в lua/zapret-auto.lua). В документации (docs/manual.md) она же фигурирует под именем cond_tcp_ts.

Зачем это нужно

Через условный оркестратор можно повесить ts-зависимый fake на условие cond_tcp_has_ts — тогда этот вариант стратегии сработает только на пакетах, реально несущих timestamp option, а не будет молча “пустеть” на всех остальных. Это честнее, чем надеяться на ручную проверку pcap, и полезно для стратегий “поставил и забыл”.


tcp_ts_up — это не ts

tcp_ts_up легко спутать с tcp_ts, но это разные вещи. tcp_ts=N меняет значение TSval. А tcp_ts_up только переносит уже существующую timestamp option в начало списка TCP options.

Автор zapret2 описывает это как странный, но практический workaround: Linux стабильно отбрасывает пакеты с валидным seq и плохим ack только если timestamp option идёт первой. Старый nfqws1 случайно всегда ставил timestamp первым, потому что не сохранял порядок TCP options — и потому “старое работало, а новое нет”. tcp_ts_up повторяет старое поведение.

Главное про tcp_ts_up

Он не включает timestamps и не создаёт timestamp option. Если в пакете её нет — поднимать нечего.

Типичная zapret2-связка для HTTP:

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

Это порт "badseq без badseq" из zapret1

Связка tcp_ack=-66000:tcp_ts_up — это документированный эквивалент zapret1-конструкции --dpi-desync=fakedsplit --dpi-desync-fooling=badseq --dpi-desync-badseq-increment=0, то есть плохой ACK при валидном seq. tcp_ts_up здесь нужен, чтобы воспроизвести старый порядок опций (timestamp первым), на котором Linux надёжно дропает такие сегменты.

Куда именно вешается tcp_ts_up в split-функциях

Плохой ACK (tcp_ack) применяется к fake — именно fake должен умереть на сервере. К оригинальным (настоящим) сегментам применяется только tcp_ts_up (плюс ip_id), их ACK остаётся валидным, поэтому они нормально доходят до сервера. Сам tcp_ts_up ставит timestamp option первой: без этого Linux ненадёжно отбрасывает пакет с валидным seq и плохим ACK.


Отличие zapret1 и zapret2

В zapret1 ts — готовый режим fooling:

--dpi-desync-fooling=ts
--dpi-desync-ts-increment=-600000

Внутри код ищет TCP option kind 8. Если timestamps есть — option копируется в fake, TSval смещается, TSecr остаётся без изменения. Если timestamps нет — option в fake не добавляется. То есть zapret1 тоже не превращает обычный TCP-сеанс в timestamp-сеанс.

В zapret2 готовое слово ts разложено на явные Lua-параметры:

--lua-desync=fake:tcp_ts=-600000

или, например, связка с плохим ACK:

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

При портировании из zapret1

zapret1 ts по умолчанию ближе к tcp_ts=-600000. А в blockcheck2 стандартный тест использует tcp_ts=-1000. Оба смещения рабочие, но это разные значения, и нельзя молча считать их одним и тем же поведением.


Как правильно атаковать сервер на разных платформах

Это самая практическая часть. Один и тот же Профиль ведёт себя по-разному в зависимости от того, где запущен zapret и чья ОС открывает исходящий TCP-сеанс. Именно платформа определяет, какой fooling вообще имеет шанс сработать.

Почему место запуска решает всё

fooling убивает fake на пути к серверу. Но на этом пути стоят промежуточные узлы — NAT домашнего роутера, провайдерский NAT, conntrack. Они могут убить fake раньше, чем тот дойдёт до DPI (тогда обход не сработает), или, наоборот, пропустить то, что должно было умереть. Поэтому “рабочий” fooling — всегда привязан к конкретной точке запуска.

Windows-клиент напрямую

TCP-сеанс открывает сама Windows, а она по умолчанию держит timestamps выключенными. Значит, ts “из коробки” работать не будет — fake не получит порчу и может дойти до сервера живым.

Проверка состояния:

netsh interface tcp show global

Включение:

netsh interface tcp set global timestamps=enabled

Три практических нюанса для Windows

  1. Настройку надо включать на каждом клиентском устройстве — нельзя молча рассчитывать, что у всех пользователей пресета timestamps уже включены.
  2. После включения проверяй новые соединения. Уже открытый TCP-сеанс не станет timestamp-сеансом задним числом.
  3. Профиль, который держится только на ts, без этой настройки — ненадёжный Windows-профиль.

Linux-клиент напрямую

Linux обычно включает TCP timestamps по умолчанию. Поэтому --dpi-desync-fooling=ts на Linux часто работает сразу, без дополнительных настроек — это ровно та ситуация, на которую ts и рассчитан.

За домашним роутером (через NAT)

Если трафик клиента проходит через NAT домашнего роутера (masquerade), часть fooling умирает в NAT раньше DPI:

  • badsum — битую контрольную сумму роутер/conntrack часто режет на месте.
  • datanoack / tcp_flags_unset=ACK — пакеты с битыми/снятыми флагами Linux-NAT с masquerade обычно не пропускает.

То, что зависит от заголовков самого TCP, а не от валидности пакета для NAT (ts, badseq, разумный tcp_ack, сегментация), переживает NAT лучше. Но финальное слово — за pcap.

На самом роутере

Если zapret запущен на роутере (исходящий сеанс открывает роутер), картина меняется: badsum и datanoack отсюда могут работать, потому что fake уже не проходит через “свой” NAT по дороге наружу.

Transparent proxy ( tpws, squid) — отдельный случай

Пакетный фильтр (nfqws/winws) не открывает TCP-сеанс сам — его открывает конечный клиент, и timestamps согласует ОС клиента (Windows). А вот transparent proxy (tpws, squid) терминирует соединение клиента и открывает новый наружу — тогда timestamps для внешнего плеча согласует ОС роутера (Linux), и настройка Windows-клиента на это плечо уже не влияет. Поэтому при proxy проверять timestamps надо на устройстве, которое реально открывает соединение к серверу.

Платформа / местоTCP timestampsЧто обычно живётЧто чаще ломается
Windows-клиент напрямуюВыкл. по умолчанию (включается netsh)После включения timestamps — ts; сегментацияts без netsh; зависит от устройства
Linux-клиент напрямуюВкл. по умолчаниюts из коробки; большинство fooling
За домашним роутером (NAT/masquerade)Зависит от клиентаts, badseq, сегментацияbadsum, datanoack (режет NAT)
На самом роутереЗависит от ОС роутераbadsum, datanoack, TTLTTL требует подбора хопа
Через transparent proxy (tpws/squid)Зависит от ОС роутера, не клиентаfooling по ОС роутера (Linux)настройка Windows-клиента на внешнее плечо не влияет

Почему blockcheck может вводить в заблуждение

blockcheck.sh (zapret1) и blockcheck2.sh (zapret2) на Windows сами пытаются включить timestamps через netsh. Это удобно для теста, но искажает интерпретацию результата.

Если blockcheck показал, что ts сработал — это может означать не “у пользователя всё было готово”, а “blockcheck сначала включил системную настройку, а потом тестировал”.

Как правильно читать результат

Нашёл blockcheck рабочий ts — отдельно помни, что на Windows он мог предварительно выполнить netsh interface tcp set global timestamps=enabled. Для пользовательского пресета эту настройку всё равно надо документировать.

В zapret1 blockcheck после успешного ts печатает предупреждение:

WARNING ! although ts fooling worked it will not work without timestamps being enabled in the client OS. In windows timestamps are DISABLED by default.

Предупреждение есть в blockcheck, но обычный runtime winws.exe такую фразу при каждом запуске писать не обязан.


Что видно в логах

В zapret1 winws.exe с --dpi-desync-fooling=ts может молча собрать fake без timestamp-порчи, если в исходном пакете timestamps нет. В коде сначала вызывается tcp_find_timestamps(); если option нет, указатель timestamps равен NULL, и fill_tcphdr() не добавляет timestamp option в fake. Никакого крика в лог.

В zapret2 чуть прозрачнее — Lua-функция пишет debug-сообщение:

apply_fooling: timestamp tcp option not present or invalid

Но это именно debug: без --debug обычный DLOG не виден.

Debug в zapret2

На консоль:

winws2 --debug ...

В файл:

winws2 --debug=@ts-debug.log ...

Отсутствие строки не доказывает успех

Нет строки про timestamp — это не значит, что ts сработал. Возможно, debug не включён, пакет не попал в нужный Профиль или Lua-функция не вызывалась на этом payload.


Как проверять ts правильно

Сначала — настройка Windows:

netsh interface tcp show global

Если timestamps выключены:

netsh interface tcp set global timestamps=enabled

После этого проверяй новые соединения.

Лучший способ проверки — pcap. Смотреть надо не только fake, а весь TCP handshake, потому что timestamps согласуются именно там.

Признаки, что ts действительно применим

  • В SYN от клиента есть TCP timestamp option.
  • В SYN,ACK от сервера тоже есть timestamp option.
  • В data-пакете, к которому применяется fake/fakedsplit, timestamp option на месте.
  • В fake виден смещённый TSval, и сервер не отвечает на fake как на валидный payload.

Если timestamp option нет уже в SYN — дальше искать ts бессмысленно: такой TCP-сеанс не timestamp-сеанс.


Как писать это в пресетах и UI

Если в пресете есть TCP-Профиль, который держится только на ts, для Windows рядом нужен явный текст. Хорошая формулировка:

Windows и ts

Для ts-fooling на Windows нужны включённые TCP timestamps:

netsh interface tcp set global timestamps=enabled

Без этого TCP-фейки могут доходить до сервера и ломать соединение.

Если нужен пресет “поставил и забыл” — не держи весь TCP только на ts. Тестируй альтернативы и комбинации: md5sig, badseq, datanoack, TTL/autottl, seqovl. Либо гейти ts-вариант через cond_tcp_has_ts, чтобы он не работал вхолостую. Но каждую комбинацию проверяй pcap/debug, а не выбирай на ощущение.


Где здесь seqovl

seqovl лучше не воспринимать как ещё один обычный fooling — это другой приём.

В обычном fake мы стараемся, чтобы сервер полностью отбросил поддельный сегмент. В seqovl идея тоньше: сервер принимает только нужную часть потока из-за пересечения TCP sequence numbers (подробный разбор seqovl — в multisplit).

Поэтому seqovl интересен там, где header-fooling плохо проходит или слишком зависит от NAT. Но это не “автоматическая замена”, а отдельная логика, требующая аккуратной настройки позиции и понимания payload.


🎣 Готовые обманки: как fooling собирают на практике

Вся теория выше — про то, почему fake умирает. А вот как этих «убийц fake» совмещают в реальных стратегиях. Примеры взяты из официального zapret2/docs/readme.md — это типовые комбинации, показанные автором как «примерно так строятся стратегии».

Это примеры, а не магия

Показаны только desync-части (без обвязки --filter/--qnum). Что именно сработает у тебя — зависит от конкретного DPI и провайдера, поэтому любую комбинацию гоняй через blockcheck2 и проверяй pcap, а не копируй вслепую. Ценность ниже — увидеть, как складывают fooling и что в каждой обманке убивает fake.

1. TLS-фейк, который сервер отбросит по MD5-опции (readme.md:107)

--payload=tls_client_hello \
--lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid

DPI видит поддельный ClientHello (со случайным SNI) и решает «сайт безобидный». Сервер дропает fake из-за лишней TCP MD5-опции (tcp_md5). Настоящий ClientHello проходит следом.

2. HTTP-фейк + MD5 (readme.md:108)

--payload=http_req --lua-desync=fake:blob=fake_default_http:tcp_md5

То же для HTTP: подставной запрос отравляет DPI, MD5-опция убивает fake на сервере.

3. Сегментация + seqovl, вообще без порчи заголовков (readme.md:109)

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

Здесь отдельный fake не нужен: ClientHello режется, а перекрытие sequence (seqovl) подсовывает DPI ложное начало потока. Хорошо там, где header-fooling не проходит через NAT (см. multisplit).

4. Fake, который «не долетает» до сервера по TTL (readme.md:155)

--lua-desync=fake:blob=0x00000000:ip_ttl=5:ip6_ttl=3 \
--lua-desync=multidisorder:pos=5,endhost-1

fake с маленьким TTL умирает по дороге, не дойдя до сервера, — а DPI его уже увидел. Следом идёт реальная сегментация с обратным порядком.

5. Плохой ACK + timestamp сверху (= zapret1 badseq с increment=0) (readme.md:237)

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

Наша главная связка из разбора tcp_ts_up: fake с валидным seq и плохим ACK сервер надёжно отбрасывает, потому что timestamp-опция поднята вперёд.

6. Автоподбор TTL + MD5 как страховка (readme.md:355)

--lua-desync=fake:blob=fake_default_http:ip_autottl=-2,3-20:ip6_autottl=-2,3-20:tcp_md5 \
--lua-desync=fakedsplit:ip_autottl=-2,3-20:ip6_autottl=-2,3-20:tcp_md5

ip_autottl сам подбирает TTL по hop-расстоянию, а tcp_md5 — подстраховка: если fake всё-таки долетит до сервера, тот отбросит его по MD5-опции.

7. Fake + MD5 + сдвиг seq + повторы (readme.md:367)

--lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000:repeats=6 \
--lua-desync=multidisorder:pos=midsld

Несколько «убийц fake» сразу (MD5-опция + увод seq за окно), а repeats=6 шлёт fake шесть раз, чтобы наверняка попасть в окно анализа DPI.

«Обманка на ts» для Windows — отдельно

Самостоятельная ts-обманка вроде --lua-desync=fake:tcp_ts=-600000 имеет смысл только при включённых TCP timestamps (Linux — да, Windows — после netsh). Чтобы она не работала вхолостую, повесь её на условие cond_tcp_has_ts (см. оркестратор) или держи запасной вариант на md5sig/TTL.


Частые ошибки

"В fake можно просто добавить timestamp option"

Нет. Timestamp option согласуется в начале TCP-сеанса. Если сеанс без timestamps, добавленная руками option не делает его timestamp-сеансом — и сервер не отбросит fake по PAWS.

"Если в логах нет warning, значит всё хорошо"

Нет. В zapret1 runtime может молчать. В zapret2 сообщение про отсутствие timestamp идёт через DLOG, то есть видно только с --debug.

"blockcheck показал ts, значит у пользователей Windows всё готово"

Нет. blockcheck на Windows сам пытается включить timestamps через netsh.

" tcp_ts_up включит timestamps"

Нет. tcp_ts_up только переставляет уже существующую timestamp option в начало списка TCP options.

" ts самый безопасный fooling"

Он аккуратный, когда timestamps есть. Если timestamps нет — fake перестаёт быть плохим для сервера и может его сломать.


Мини-шпаргалка

zapret1:
--dpi-desync-fooling=ts
--dpi-desync-ts-increment=-600000        (диапазон ~ -100 .. -0x80000000)
 
zapret2:
--lua-desync=fake:tcp_ts=-600000         (значение обязательно)
--lua-desync=fakedsplit:tcp_ack=-66000:tcp_ts_up   (= badseq increment=0)
 
Проверить Windows:
netsh interface tcp show global
 
Включить Windows timestamps:
netsh interface tcp set global timestamps=enabled
 
zapret2 debug-признак, что ts не применился:
apply_fooling: timestamp tcp option not present or invalid
 
Гейт "только если есть timestamp" (zapret2):
cond_tcp_has_ts   (в мануале: cond_tcp_ts)

Источники в коде

Проверено по локальным исходникам

Заметка не основана на догадках про DPI. Основные утверждения сверены с локальными исходниками zapret1 и zapret2.

zapret1:

  • docs/readme.mdts прибавляет к TSval инкремент (дефолт -600000, рабочий диапазон -100 .. -0x80000000); сервер отбрасывает пакеты с TSval в определённых пределах; Windows timestamps выключены по умолчанию (netsh ... timestamps=enabled); там же слабые места md5sig, badsum, badseq (дефолт -10000), TTL, datanoack (NAT/masquerade).
  • nfq/darkmagic.ctcp_find_timestamps() ищет TCP option kind 8; fill_tcphdr() добавляет timestamp option только если timestamps != NULL; при FOOL_TS меняется TSval, TSecr копируется без изменения.
  • blockcheck.sh — на Windows включает timestamps через netsh; после успешного ts печатает warning про Windows.

zapret2:

  • lua/zapret-lib.luatcp_ts=N ищет существующий TCP_KIND_TS (длина данных 8 байт); если option нет, пишет apply_fooling: timestamp tcp option not present or invalid; tcp_ts_up только двигает существующую option в начало; tcp_md5 по умолчанию добавляет 16 случайных байт.
  • lua/zapret-antidpi.lua — в fakedsplit/fakeddisorder/hostfakesplit к оригинальным сегментам применяется только tcp_ts_up (+ ip_id), остальной fooling — на fake.
  • lua/zapret-auto.luacond_tcp_has_ts(desync) возвращает true, если диссект TCP и присутствует timestamp option.
  • docs/readme.md — связка tcp_ack=-66000:tcp_ts_up — эквивалент zapret1 --dpi-desync-fooling=badseq --dpi-desync-badseq-increment=0; объяснение, почему Linux дропает плохой ack только при timestamp-опции сверху.
  • docs/manual.mdtcp_ts работает только при наличии timestamp option; --debug включает DLOG; conditional описан как cond_tcp_ts.
  • blockcheck2.sh — на Windows тоже включает TCP timestamps через netsh; стандартный тест использует tcp_ts=-1000.
  • nfq2/darkmagic.h — числовые kind TCP options (TCP_KIND_TS=8, TCP_KIND_MD5=19, TCP_KIND_SCALE=3, TCP_KIND_MSS=2, TCP_KIND_SACK_PERM=4, TCP_KIND_NOOP=1, TCP_KIND_END=0) — совпадают с IANA/RFC.

Стандарты:

  • RFC 7323 (обновляет RFC 1323) — формат TSopt (Kind 8, Length 10, TSval/TSecr по 4 байта), правило согласования (TSopt в SYN и SYN,ACK) и механизм PAWS (отбрасывание сегмента при SEG.TSval < TS.Recent).
  • IANA TCP Option Kind Numbers — официальный реестр номеров kind; значения в darkmagic.h совпадают с ним.

📚 См. также

  • desync — общий справочник по --lua-desync и полный список fooling-параметров (tcp_ts, tcp_md5, tcp_seq, ip_ttl…)
  • fake — что такое fake-пакет и как он отравляет состояние DPI
  • fakedsplit, fakeddisorder — стратегии, где fake совмещён с сегментацией (там и применяется tcp_ack/tcp_ts_up)
  • оркестратор — условная логика, через которую вешается cond_tcp_has_ts
  • multisplit — разбор seqovl (скрытый фейк внутри сегмента) и сегментации
  • основные флаги — где fooling стоит в общей картине аргументов
  • 🔗 RFC 7323 — TCP Timestamps, согласование и PAWS (обновляет RFC 1323)
  • 🔗 Официальный manual zapret2 — первоисточник по параметрам

🤖 Эти статьи открыты — можно обучать на них ИИ

При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: исходник этой заметки · весь репозиторий.