Zapret2: start/cutoff и почему n2<n3 “вдруг” попадает на ClientHello

Эта заметка — ответ на типичную ситуацию:

--filter-tcp=443 --payload=all --out-range="n1<n2" --lua-desync=send:ip_ttl=1

→ дюпается SYN

--filter-tcp=443 --payload=all --out-range="n2<n3" --lua-desync=send:ip_ttl=1

→ дюпается ClientHello, хотя “по идее должен ACK”

1) Ключевой факт: n — это номер ПЕРЕХВАЧЕННОГО пакета, а не “всех пакетов TCP”

--out-range работает по счётчикам conntrack внутри движка. Счётчик n увеличивается только на те исходящие пакеты, которые:

  1. реально перехвачены (NFQUEUE/WinDivert), и
  2. реально дошли до обработки профилем (фильтры не выкинули их раньше).

Если какой-то пакет “не попал” — он не существует для счётчика n.

2) Почему именно ACK часто “пропадает”

Windows / winws2

В winws2 по умолчанию часто включено:

  • --wf-tcp-empty=0

Это не перехватывает пустые TCP ACK (ACK без payload). Сильный профит по CPU, но n перестаёт совпадать с тем, что вы ожидаете как “номер пакета в соединении”.

Итог: исходящий порядок “в движке” становится таким:

  • n1 = SYN (перехвачен)
  • n2 = ClientHello (первый перехваченный packet с данными)

А “реальный” handshake ACK существует, но не перехвачен.

Linux / nfqws2

Если ваши правила NFQUEUE (nft/iptables) перехватывают только “пакеты с данными” или только часть фаз — ситуация аналогичная: ACK просто не попадёт в userspace, и n сдвинется.

3) Вторая причина (реже): ACK может быть “склеен” с ClientHello

Иногда стек может отправить ACK на SYN-ACK вместе с первым сегментом данных (ACK+payload). Тогда “второй исходящий пакет” реально будет ClientHello (и это нормально). Проверяется только захватом трафика (Wireshark).

4) Эквивалент nfqws1 --dup-start=n2 --dup-cutoff=n3 в zapret2

Если вы хотите строго применить действие к “2-му исходящему пакету” по счётчику n:

--out-range="n2<n3" --lua-desync=send:ip_ttl=1

Но это будет работать “как ожидаете” только если этот пакет реально перехвачен.

5) Как применить “дурение” именно ко 2-му исходящему ACK (если он пустой)

Вариант A (Windows / winws2): включить перехват пустых ACK

  1. включить перехват пустых TCP пакетов:
--wf-tcp-empty=1
  1. сузить по типу payload (очень полезно, чтобы убедиться что вы бьёте именно по пустому):
--payload=empty --out-range="n2<n3" --lua-desync=send:ip_ttl=1

Если после этого “ничего не дюпается” — значит либо ACK не перехвачен фильтрами, либо ACK не пустой.

Вариант B: если цель — “первый пакет с данными” (ClientHello), используйте d

Это устойчиво даже если пустые ACK не перехватываются:

--payload=tls_client_hello --out-range="d1<d2" --lua-desync=send:ip_ttl=1

6) Самый быстрый способ понять, что реально происходит

Добавьте на время дебага:

--lua-desync=posdebug
--lua-desync=luaexec:code="DLOG('flags '..tostring(desync.dis.tcp and desync.dis.tcp.th_flags)..' payload '..#desync.dis.payload..' l7payload '..tostring(desync.l7payload))"

И смотрите:

  • какая длина payload у “n2”
  • какой l7payload (empty/tls_client_hello/unknown)
  • увеличивается ли n на пустых ACK