oob — TCP Out-of-Band десинхронизация (zapret2 / nfqws2)

Коротко и по-простому

Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке Host: запроса. ТСПУ (Технические средства противодействия угрозам — оборудование DPI, то есть глубокой инспекции трафика, которое стоит у провайдера) ищет это имя в первом пакете соединения и по нему решает, что делать с соединением дальше.

HTTP-запросы и TLS-соединения идут по протоколу TCP. Соединение начинается с рукопожатия: первым пакетом клиент отправляет SYN («хочу соединиться») и называет в нём стартовый номер — sequence number, коротко seq. Дальше у каждого байта соединения есть порядковый номер, а сервер раскладывает принятые данные в свой буфер строго по этим номерам. Стартовый номер из SYN он запоминает и ждёт первый байт данных сразу за ним: если клиент начал с номера 999, то данные ожидаются с 1000 (999 плюс единица за сам SYN). Как с такими номерами считать в Lua (они 32-битные беззнаковые и заворачиваются по кругу) — в Работа с sequence numbers.

Кроме обычных данных, в TCP есть редко используемый механизм срочных данных, по-английски Out-of-Band, сокращённо OOB («вне основного потока»). Отправитель ставит в заголовке пакета флаг URG («urgent», «срочно») и записывает в поле th_urp (urgent pointer, «указатель срочности») положение срочного байта. TCP-стек операционной системы сервера — та её часть, что принимает пакеты и собирает из них поток для программ, — этот байт из потока выбрасывает, и программа получает чистые данные без вставки. DPI устроен иначе: он может не понимать механизм срочных данных и разбирать поток вместе с лишним байтом. Как именно два стандарта указывают на срочный байт, разобрано в разделе Два стандарта th_urp.

Функция oob в Zapret 2 (движок nfqws2) использует этот механизм, чтобы подложить DPI запрос с посторонним байтом внутри. Она делает четыре шага:

  1. Ловит соединение с самого первого пакета — SYN — и уменьшает в нём стартовый номер seq на 1; то же делает в первом ACK («подтверждаю», пакете-подтверждении) — следующем исходящем пакете после SYN (по коду это пустой ACK или уже пакет с данными). Так в нумерации байтов освобождается место под один лишний байт.
  2. Во входящих пакетах начала соединения (прежде всего в SYN-ACK — ответе сервера на SYN) увеличивает номер подтверждения ack на 1, чтобы операционная система клиента не запуталась: она ждёт подтверждений по своей исходной нумерации. Если входящий пакет оказывается на неожиданной позиции (по коду — дальше начала соединения, позиция больше 1), oob отключается.
  3. Когда уходит первый пакет с данными (например, ClientHello или HTTP-запрос), вставляет в него один лишний байт — по умолчанию 0x00 — в место, которое задаёт аргумент urp, ставит флаг URG и заполняет указатель th_urp. Готовый пакет отправляется вместо перехваченного оригинала, а оригинал выбрасывается.
  4. Отключается для этого соединения. Такое отключение называют cutoff («отсечка»): движок больше не вызывает функцию на пакетах этого соединения, и они проходят без изменений.

Соединение нужно ловить именно с SYN, потому что сдвиг нумерации начинается там, а отказаться от него по дороге нельзя: если oob не вставит байт, нумерации клиента и сервера разойдутся и соединение сломается. Поэтому функция обязана видеть рукопожатие с первого пакета; если первым она увидела не SYN, она отключается. Проще говоря: в самом начале клиент сообщает серверу «мои номера начинаются на единицу раньше», в освободившееся место встаёт лишний байт, а сервер выбрасывает его как срочный. Подробно — в разделе Принцип работы и в нюансах «Соединение должно быть перехвачено с SYN» и «Длящаяся десинхронизация…».

Сервер получает поток, из которого срочный байт вынут, и отвечает как обычно: приложение видит ровно тот запрос, который отправил браузер. DPI, который не вынимает срочные байты, читает поток с лишним байтом посередине (например, exa\x00mple.com вместо example.com) и не находит имя сайта. Всё держится на слове «может»: приём рассчитан на DPI, который срочный байт не вынимает; на другом DPI он может не сработать. Чем oob отличается от простого разреза на куски, разобрано в разделе Зачем нужен oob.

Команда из быстрого старта --in-range=-s1 --lua-desync=oob:urp=midsld сначала уменьшает seq в SYN на единицу, резервируя место под будущий байт, а затем вставляет в первый пакет с данными посередине имени сайта байт срочных данных (Out-of-Band) с флагом URG; на схеме он обведён. ТСПУ, который не вынимает такой байт из потока, видит имя с лишним байтом внутри; TCP-стек сервера этот байт вынимает, и имя в буфере смыкается. Как читать схему: строки — пакеты в порядке отправки, подписанные в тех же обозначениях, что и таблицы в этой статье; по горизонтали — место данных в TCP-потоке (номер последовательности, seq); в колонке справа — что сделал с пакетом сервер; внизу — буфер сервера, в который принятые куски встают по своим seq.

oob: байт срочных данных внутри имени сайта Карта потока для oob с urp=midsld: сначала SYN с номером последовательности на единицу меньше, затем ClientHello с байтом срочных данных (флаг URG) посреди имени youtube.com. TCP-стек сервера вынимает этот байт, и имя смыкается. пакет в порядке отправки место в потоке (seq) → сервер 0 urp=midsld 27 #1 SYN seq−1 SYN с номером на 1 меньше: место под лишний байт принят #2 DATA URG len=27 ···········you·tube.com···· байт URG вынут буфер сервера ···········you tube.com···· поток собран DPI в имени лишний байт: you·tube.com не совпадает с youtube.com сервер байт с флагом URG вынимает из потока как срочные данные, имя смыкается

Самая короткая команда включает oob со значениями по умолчанию: лишний байт 0x00 ставится перед самым первым байтом данных (urp=b). Строку пишут в профиль — набор правил Zapret 2 для определённого трафика. Для HTTP и TLS флаг --in-range=-s1 обязателен: он задаёт, какие входящие пакеты вообще доходят до функции (формат разобран в заметке про —out-range и —in-range), и без него oob не увидит рукопожатие и не включится; подробнее — в нюансе «Обязательно —in-range=-s1».

--in-range=-s1 --lua-desync=oob

Режим urp=b работает только на Linux-серверах: Windows и BSD с нулевым th_urp ломаются (нюанс 3). Самый популярный вариант — urp=midsld, как на схеме: байт ставится посередине домена второго уровня (SLD, главной части имени сайта: у www.youtube.com это youtube); все режимы и маркеры разобраны в разделе Режимы urp, готовые строки — в Быстром старте и Практических примерах.

oob не режет данные на куски, как multisplit, и не посылает отдельных фейковых пакетов, как fake (фейк — пакет-обманка с ложными данными, который нарочно портят приёмом fooling, чтобы сервер его отбросил, а DPI прочитал; см. Что такое fooling): лишний байт вставляется внутрь единого настоящего потока. Поэтому oob нельзя сочетать в одном потоке с multisplit, fakedsplit и другими функциями, которые пересылают данные, — они отправят копии без срочного байта и без учёта сдвига (нюанс 6). Соседи по приёму: syndata кладёт данные прямо в SYN, а tcpseg отправляет диапазон данных между двумя маркерами. Есть и другие ограничения. У oob нет фильтров dir и payload (нюанс 4), поэтому отбирать соединения приходится на уровне профиля. Ей нужен conntrack — отслеживание состояния соединений в системе (нюанс 8). Режим urp=e как правило бесполезен для обхода DPI (почему). Сдвиг seq на SYN, отключение при подключении «на ходу» и переключение профилей — в нюансах 7 и 9. Подходящую позицию urp подбирают перебором и проверяют по чек-листу честной проверки; пример перебора — как в blockcheck2.


Оглавление


Справка: где функция в коде и родственники

Паспорт функции для тех, кто сверяется с исходниками zapret2: в каком файле и на какой строке лежит код, есть ли у неё аналог в первой версии zapret и в прокси tpws, как она объявлена.

Файл: lua/zapret-antidpi.lua:1097 nfqws1 эквивалент: отсутствует (новая функция zapret2) tpws аналог: --split-pos=.. --oob (близкий, но не идентичный механизм) Сигнатура: function oob(ctx, desync)

oob — функция TCP-десинхронизации через механизм Out-of-Band (Urgent) данных. Она перехватывает TCP handshake с самого начала (SYN), сдвигает sequence на 1 байт влево, а затем вставляет 1 байт OOB (помеченный флагом TH_URG) в первый исходящий payload. TCP-стек получателя выбрасывает OOB-байт из потока, но DPI может этого не делать — и тогда DPI видит payload со вставленным посторонним байтом, что ломает распознавание сигнатур.

После отработки функция уходит в instance_cutoff по обоим направлениям.

Проще говоря

Out-of-Band (OOB, «вне основного потока») — срочные данные TCP; флаг TH_URG — отметка «в этом пакете есть срочный байт», а th_urp — поле-указатель на него. SYN — первый пакет соединения, с которого oob обязана начать работу; sequence — порядковый номер байта в потоке. instance_cutoff — команда движку «больше не вызывай эту функцию на пакетах данного соединения»; как она устроена, описано в стадии 1 жизненного цикла. Поля th_flags и th_urp — части диссекта, то есть пакета, разобранного на поля, — описаны в структуре desync и диссекта. Чем oob отличается от tpws --oob, разобрано в разделе Сравнение с tpws —oob.

Родственные функции: multisplit (TCP-сегментация), fakedsplit (с фейками), fake (фейковые пакеты), syndata (данные в SYN), tcpseg (диапазон).

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


Зачем нужен oob

DPI анализирует TCP-поток, ищет сигнатуры — заранее известные строки, по которым узнаётся сайт (hostname в HTTP, SNI в TLS). Если вставить в поток лишний байт, помеченный как “urgent” (Out-of-Band), происходит следующее:

  1. Сервер (TCP-стек ОС): выбрасывает OOB-байт из потока. Приложение получает чистые данные без вставки
  2. DPI: может не понимать OOB-механизм и анализировать поток вместе со вставленным байтом. Если байт вставлен посередине hostname/SNI, сигнатура разрушается

Ключевое отличие от сегментации: oob не разрезает данные на части — он вставляет лишний байт, который принимающая ОС удаляет, а DPI — нет.

Ключевое отличие от фейков: oob не отправляет отдельных фейковых пакетов — вставка происходит внутри единого легитимного потока. Это делает oob труднодетектируемым.

Проще говоря

Обычно приём против DPI либо режет запрос на части (сегменты), либо посылает отдельные фейковые пакеты. oob делает третье: запрос остаётся целым, но в него добавляется один байт с пометкой «срочный». Сервер выбрасывает этот байт из потока, а DPI, если не знает такой пометки, читает его как часть имени сайта: exa\x00mple.com вместо example.com.

Соседние приёмы разобраны в Зачем нужен multisplit (разрез на сегменты), Зачем нужен fake и Что такое fooling (фейковые пакеты и их порча). Что такое --lua-desync и как движок вызывает такие функции — в desync.


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

Три готовые строки для профиля Zapret 2, от самой короткой к более гибким. Во всех есть --in-range=-s1; зачем он нужен, сказано сразу под примерами. Значения urp, char и byte разобраны в разделах Режимы urp и Собственные аргументы oob.

Минимально (urp=b по умолчанию, OOB-байт = 0x00):

--in-range=-s1 --lua-desync=oob

С маркером по середине SLD:

--in-range=-s1 --lua-desync=oob:urp=midsld

С конкретным OOB-символом:

--in-range=-s1 --lua-desync=oob:char=X

Важно: --in-range=-s1 обязателен для HTTP/TLS (протоколов, где сервер ждёт запроса клиента). Без него oob не увидит SYN-пакет и не активируется.

Проще говоря

--in-range задаёт, какие входящие пакеты движок передаёт функциям, а запись -s1 читается как «от начала соединения до seq 1» (по таблице префиксов: s — относительный sequence number, разделитель - включает конечную позицию). Так до oob доходят пакеты самого начала соединения, по статье — в их числе входящий SYN-ACK от сервера; без этого функция не видит рукопожатие и не включается. HTTP и TLS — протоколы, в которых сервер ждёт запроса клиента; протоколы, где сервер заговаривает первым (SMTP, FTP, SSH), описаны в нюансе 2.


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

Работа идёт в три фазы, а параллельно с ними — обработка входящих пакетов: перехват SYN со сдвигом seq, вставка срочного байта в первый пакет с данными, отключение и коррекция ack в пакетах от сервера. Как эти шаги вписаны в общую схему всех функций, показано в разделе Место в общем скелете.

Фаза 1: перехват SYN

oob должен начать работу с самого первого пакета TCP-соединения — SYN. Функция проверяет флаг TH_SYN и запоминает, что соединение перехвачено с начала.

На этапе SYN (pos=0) и первого ACK (pos=1) функция уменьшает sequence на 1:

Оригинальный SYN:
  th_seq = 1000

После oob:
  th_seq = 999    (уменьшен на 1)

Это “резервирует” место для будущего OOB-байта. Сервер запоминает начальный sequence = 999, и ожидает данные начиная с позиции 1000 (999 + 1 за SYN).

Проще говоря

Пометка pos — относительная позиция пакета в потоке по seq: pos=0 у SYN, pos=1 у первого пакета после него (по коду — пустой ACK или первый пакет с данными); в псевдокоде ниже её возвращает pos_get. Для этих двух позиций oob вычитает из seq единицу. Сервер поэтому считает стартовым номер 999 и ждёт данные с 1000, а сдвиг на единицу резервирует место под один лишний байт. Сдвиг, однажды начатый, должен дойти до конца: почему «соскочить» нельзя, объяснено в нюансе 4. Как с такими номерами считать в Lua (они 32-битные беззнаковые и заворачиваются по кругу) — Работа с sequence numbers.

Фаза 2: вставка OOB-байта

Когда приходит первый исходящий пакет с данными (pos >= 1, payload непуст), oob:

  1. Берёт payload (или reasm, если многопакетный)
  2. Определяет OOB-байт: char= или byte= или 0x00 по умолчанию
  3. Определяет позицию вставки по urp
  4. Вставляет OOB-байт в payload по этой позиции
  5. Устанавливает флаг TH_URG и значение th_urp
  6. Отправляет модифицированный пакет через rawsend_dissect_segmented
  7. Дропает оригинальный пакет (VERDICT_DROP)

Payload — полезные данные пакета без сетевых заголовков; reasm — те же данные, собранные движком из нескольких пакетов, если запрос в один пакет не влез (стадия 4 жизненного цикла). rawsend_dissect_segmented отправляет пакет, разобранный на поля (диссект), и сама разбивает его на сегменты по MSS — наибольшему объёму данных в одном TCP-сегменте (подробнее ниже); среди способов выпустить пакет она описана в разделе Четыре способа выпустить пакет. VERDICT_DROP — решение «перехваченный пакет не отправлять» (стадия 8).

Визуализация вставки (urp=b, th_urp=0):

Оригинальный payload:
  [G][E][T][ ][/][p][a][t][h]...

После вставки OOB-байта (0x00) в позицию 1:
  [OOB][G][E][T][ ][/][p][a][t][h]...
   ^
   th_urp=0, TH_URG=1

Что видит сервер (TCP-стек удаляет OOB):
  [G][E][T][ ][/][p][a][t][h]...   <-- оригинал

Что видит DPI (не удаляет OOB):
  [0x00][G][E][T][ ][/][p][a][t][h]...   <-- "GET" сдвинут, сигнатура сломана

Квадратные скобки — по одному байту; OOB — вставленный срочный байт, ^ указывает на него, а th_urp=0 и TH_URG=1 — значения полей заголовка. DPI, не вынимающий срочный байт, видит 0x00 перед GET: сигнатура сдвинута и сломана. Режим b подробнее — в разделе urp=b.

Визуализация вставки (urp=midsld, TLS SNI):

Payload (TLS ClientHello), SNI = "example.com", midsld указывает на "m" в "example":

Оригинал:
  ...[e][x][a][m][p][l][e][.][c][o][m]...

После вставки OOB (0x00) в позицию midsld:
  ...[e][x][a][OOB][m][p][l][e][.][c][o][m]...
              ^
              th_urp = (позиция midsld + 1 по RFC)

DPI видит: "exa\x00mple.com" -- нет совпадения с "example.com"
Сервер видит: "example.com" -- OOB-байт удалён

Здесь midsld — маркер «середина домена второго уровня» (у example.com домен второго уровня — example), а «по RFC» означает стандарт, по которому th_urp указывает на байт, следующий за срочным (Два стандарта th_urp). Маркеры разобраны в Типы маркеров.

Фаза 3: cutoff

После успешной отправки OOB-пакета (если это не replay) функция выполняет instance_cutoff_shim — отключает себя от обоих направлений данного соединения. Дальнейшие пакеты проходят без модификации.

При replay (многопакетный payload): все replay-части дропаются, cutoff выполняется после последней части.

Проще говоря

Cutoff — отключение функции для этого соединения: instance_cutoff_shim говорит движку не вызывать её на следующих пакетах, и они проходят как есть (стадия 1 жизненного цикла). Replay («перепроигрывание») — это когда движок придержал части большого запроса, собрал их и прогоняет по очереди через функции: oob при этом дропает каждую придержанную часть, а отключается только на последней (стадия 6).

Обработка входящих пакетов

Для входящих пакетов (от сервера) oob увеличивает th_ack на 1, компенсируя сдвиг sequence:

Входящий от сервера:
  th_ack = 999   (сервер подтверждает sequence, сдвинутый на 1)

oob модифицирует:
  th_ack = 1000  (возвращает ожидаемое значение для клиента)

Это необходимо, чтобы клиентская ОС не запуталась — она ожидает ack на основе своего оригинального sequence.

Проще говоря

ack — номер, которым получатель подтверждает, до какого байта принял данные. Сервер считает от сдвинутого номера, а клиентская система — от своего исходного, поэтому oob во входящих пакетах начала соединения (прежде всего в SYN-ACK) прибавляет к ack единицу, и подтверждения совпадают с тем, что клиент ожидает; если входящий пакет оказывается на неожиданной позиции (по коду — дальше начала соединения), она отключается (cutoff). Это «входящая ветка» функции: она только правит пакет и отпускает его дальше (Три типа функций).


Два стандарта th_urp

RFC (Request for Comments) — документы, в которых описаны стандарты интернета. Поле th_urp в TCP-заголовке — указатель срочности: он подсказывает получателю, где в данных срочный байт. Два документа понимают его по-разному, и это расхождение определяет выбор режима urp.

Существуют два RFC-интерпретации поля th_urp (Urgent Pointer) в TCP-заголовке:

Стандартth_urp указывает наПоддержка
RFC 793 (оригинальный)Сам OOB-байт (0-based)Старые реализации
RFC 1122 (уточнённый)Байт, следующий за OOB (1-based)Современные ОС (Linux, Windows, BSD)

Из-за этого расхождения значение th_urp=0 невалидно по одному из стандартов, но может работать на практике.

В коде oob:

  • Для urp=b: th_urp устанавливается в 0 (согласно RFC 793)
  • Для urp=e и маркеров: th_urp устанавливается как позиция + 1 (согласно RFC 1122)

Проще говоря

Старый стандарт указывает на сам срочный байт и считает от нуля, новый — на байт сразу за ним. Ноль в th_urp невалиден по одному из стандартов, но может работать на практике: Linux его принимает, а Windows и BSD ломаются, поэтому режим b с его нулём годится только для Linux-серверов (нюанс 3). Для e и маркеров oob пишет позицию плюс единица.


Режимы urp

Аргумент urp отвечает на вопрос «в какое место данных вставить лишний байт»: b — в самое начало, e — в самый конец, маркер — в произвольную позицию (подробно про маркеры). От выбора зависит и значение th_urp (Два стандарта th_urp).

urp=b — начало (по умолчанию)

OOB-байт вставляется перед первым байтом payload. th_urp = 0.

th_urp = 0
Payload: [OOB][оригинальные данные...]

Ограничение: работает только на Linux-серверах. Windows и BSD серверы ломаются при th_urp=0, поскольку интерпретируют его как невалидный по RFC 1122.

Это наиболее эффективный режим для обхода DPI, т.к. OOB-байт вставляется в самое начало, ломая распознавание протокола (HTTP-метод, TLS record header).

Проще говоря

HTTP-метод — слово в начале запроса (GET, POST), а TLS record header — заголовок записи, с которого начинается ClientHello. Лишний байт перед ними сдвигает всё начало, и DPI, не вынимающий срочный байт, может не узнать протокол. Цена — только Linux-серверы (th_urp=0 невалиден по RFC 1122; нюанс 3); для не-Linux серверов статья называет более безопасным urp=0 (пример 2).

urp=e — конец

OOB-байт вставляется после последнего байта payload. th_urp = len(payload) + 1.

th_urp = len+1
Payload: [оригинальные данные...][OOB]

Как правило бесполезен для обхода DPI: DPI получает весь оригинальный payload целиком, OOB-байт идёт после него и не мешает анализу.

urp=маркер — произвольная позиция

urp может быть любым маркером, поддерживаемым функцией resolve_pos. OOB-байт вставляется в позицию, куда разрешается маркер. th_urp = позиция + 1 (по RFC 1122).

Маркер — это не число, а имя места в запросе: программа сама находит, где в данном ClientHello или HTTP-запросе начинается имя хоста, где оно кончается, где середина домена второго уровня, и подставляет позицию в байтах (resolve_pos; как это делается). Домен второго уровня (SLD) — главная часть имени сайта: у www.youtube.com это youtube. Расширение SNI (sniext) — место в ClientHello, где лежит имя сервера. Таблица ниже перечисляет маркеры и типы данных, для которых они определены; сами типы (http_req, tls_client_hello) разобраны в заметке payload.

Поддерживаемые маркеры (те же, что в multisplit):

МаркерОписаниеДля каких payload
hostПервый байт имени хостаhttp_req, tls_client_hello
endhostБайт после последнего байта hostnamehttp_req, tls_client_hello
sldПервый байт домена второго уровняhttp_req, tls_client_hello
endsldБайт после SLDhttp_req, tls_client_hello
midsldСередина SLDhttp_req, tls_client_hello
sniextНачало данных SNI extensiontls_client_hello
methodНачало HTTP-методаhttp_req
ЧислаАбсолютная позиция (1-based)Любой

Арифметика маркеров поддерживается: midsld+1, host-1, sniext+2 и т.д.

Проще говоря: midsld+1 — «на байт правее середины SLD», host-1 — «на байт левее начала имени хоста». Подробнее — в разделах Арифметика маркеров и Важные нюансы pos.

Пример: urp=midsld — OOB-байт вставляется посередине SLD. DPI видит домен с лишним байтом посередине.

Если маркер не разрешается (например, midsld для unknown payload), функция логирует ошибку и выполняет instance_cutoff.

Проще говоря

«Не разрешается» значит, что в данных нет места, на которое указывает маркер: у пакета неизвестного протокола нет имени хоста, а значит нет и его середины. Тогда функция пишет ошибку в лог и отключается (cutoff — см. Фаза 3).


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

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

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

Важно: у oob нет фильтров dir и payload. Функция не может быть отфильтрована по payload, поскольку после начала модификации TCP handshake (сдвиг sequence) соскок невозможен — иначе поедут sequence numbers. Направление обрабатывается автоматически: исходящие — вставка OOB, входящие — коррекция ack.

Проще говоря

Фильтр dir («в какую сторону») и фильтр payload («для какого типа данных») в других функциях описаны в B) Standard direction и C) Standard payload у multisplit. У oob их нет, потому что после сдвига seq на SYN она обязана довести соединение до конца. Направление она определяет сама: исходящие — вставка байта, входящие — правка ack. Отбирать соединения приходится профилем (фильтры профиля), см. нюанс 4.

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

Три аргумента: char и byte задают сам лишний байт, urp — место, куда он вставляется. Если не задать ничего, в начало данных вставляется 0x00.

char

  • Формат: char=<символ>
  • Тип: строка длиной 1 байт
  • По умолчанию: 0x00
  • Описание: Символ, используемый как OOB-байт. Должен быть ровно 1 байт
  • Приоритет: char проверяется первым, затем byte, затем 0x00
  • Примеры:
    • char=X — OOB-байт = ASCII ‘X’
    • char=A — OOB-байт = ASCII ‘A’

byte

  • Формат: byte=<число 0..255>
  • Тип: числовое значение байта
  • По умолчанию: не задан
  • Описание: Числовое значение OOB-байта. Используется, если char не задан
  • Примеры:
    • byte=0 — OOB-байт = 0x00
    • byte=255 — OOB-байт = 0xFF
    • byte=65 — OOB-байт = 0x41 (ASCII ‘A’)

urp

  • Формат: urp=<маркер> или urp=b или urp=e
  • Тип: строка — специальное значение (b, e) или маркеры
  • По умолчанию: b
  • Описание: Позиция, куда будет вставлен OOB-байт
    • b — начало payload (th_urp=0). Только для Linux-серверов
    • e — конец payload (th_urp=len+1). Бесполезно для обхода DPI
    • маркер — произвольная позиция. th_urp = позиция + 1 (RFC 1122)
  • Примеры:
    • urp=b — перед первым байтом (дефолт)
    • urp=e — после последнего байта
    • urp=midsld — середина SLD
    • urp=host — начало hostname
    • urp=2 — после первого байта payload
    • urp=sniext+1 — один байт после начала SNI extension data

B) Standard fooling

Модификации L3/L4 заголовков. Применяются к отправляемому OOB-пакету.

Fooling («портить») — приём, которым обычно портят фейковый пакет, чтобы сервер его отбросил, а DPI прочитал (Что такое fooling). L3 — сетевой уровень (заголовок IP), L4 — транспортный (заголовок TCP). Тот же набор параметров описан в D) Standard fooling у multisplit.

ПараметрОписаниеПример
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 в oob имеет ограниченный смысл. В отличие от fakedsplit, где fooling применяется к фейковым пакетам (чтобы сервер их отбросил), в oob fooling применяется к реальному OOB-пакету. Если сервер отбросит этот пакет из-за fooling — данные не дойдут. Используйте fooling осторожно (например, tcp_ts_up, IPv6 extension headers).

Проще говоря

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


C) Standard ipid

IP ID — числовой идентификатор пакета в IP-заголовке; аргументы решают, как его нумеровать. Тот же набор описан в E) Standard ipid у multisplit.

ПараметрОписаниеПо умолчанию
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).


D) Standard ipfrag

IP-фрагментация. Каждый отправляемый TCP-сегмент дополнительно фрагментируется на уровне IP.

IP-фрагментация — деление одного пакета на несколько на уровне IP, в отличие от сегментации TCP. Тот же набор описан в F) Standard ipfrag у multisplit.

ПараметрОписаниеПо умолчанию
ipfrag[=func]Включить IP-фрагментацию. Если без значения — ipfrag2—
ipfrag_disorderОтправить IP-фрагменты в обратном порядке—
ipfrag_pos_tcp=NПозиция фрагментации TCP (кратно 8)32
ipfrag_next=NIPv6: next protocol во 2-м фрагменте—

E) Standard reconstruct

Reconstruct — сборка сырого пакета из полей перед отправкой. Тот же набор описан в G) Standard reconstruct у multisplit.

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

Предупреждение: badsum в oob сделает OOB-пакет невалидным — сервер его отбросит, OOB-байт не будет вставлен, а sequence останутся сдвинутыми. TCP-соединение будет сломано. Не используйте badsum в oob в боевых профилях.

Проще говоря

Checksum — контрольная сумма, по которой получатель проверяет целость TCP-пакета; badsum портит её нарочно. В фейках это удобно: сервер отбрасывает обманку. У oob отдельного фейка нет, и порча суммы убила бы единственный настоящий пакет.


F) Standard rawsend

Rawsend — отправка сформированного «сырого» пакета в сеть помимо обычной очереди перехваченных пакетов. Тот же набор описан в H) Standard rawsend у multisplit.

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

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

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

Стадия общего скелетаЧто делает oob
1. Отсев транспортасамая строгая проверка из всех: нужен TCP, нужен работающий conntrack (desync.track), и первый увиденный пакет обязан быть SYN — иначе instance_cutoff
2. Направлениеотклонение: аргумента dir нет; вместо направления функция смотрит позицию пакета в соединении (pos_get) и по-разному обрабатывает исходящие и входящие
3. Аргументыобязательных нет; char/byte задают OOB-байт, urp — указатель
4. Данныеreasm_data → payload, без поддержки blob
5. Гвардыотклонение: штатных гвардов нет, вместо них — проверка позиции пакета и непустого payload
6. replayотклонение: своя логика — части replay дропаются, а на последней инстанс отключается
7. Своя техникасдвиг sequence на SYN и первом ACK, вставка OOB-байта с флагом URG и указателем th_urp
8. ВердиктVERDICT_MODIFY на служебных пакетах (сдвиг seq/ack), VERDICT_DROP после отправки OOB-сегмента

oob — самая непохожая на остальные функция набора: она вмешивается в соединение с самого рукопожатия и держит собственное состояние в track.lua_state. Отсюда и её главное эксплуатационное ограничение: без conntrack она не работает вовсе, а если инстанс подключился к уже установленному соединению, он сразу отключается.

Проще говоря

Общий скелет — одинаковый порядок действий у всех функций дурения; oob выполняет их не все и не так, как остальные. Вместо фильтра направления она смотрит позицию пакета, вместо штатных проверок (гвардов) — позицию и непустой payload. desync.track — запись о текущем соединении в системе отслеживания (conntrack), в ней oob держит своё состояние (track.lua_state). Стадии подробнее: 1, 4, 5, 6, 8; сводка отличий по всем функциям — Карта отличий по всем функциям.


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

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

function oob(ctx, desync)
    -- 1. Требуется conntrack
    if not desync.track then return end
 
    -- 2. Только TCP (ICMP пропускается, остальное -- cutoff)
    if not desync.dis.tcp then
        if not desync.dis.icmp then instance_cutoff_shim() end
        return
    end
 
    -- 3. Проверка: начали с SYN?
    if not lua_state[key.."_syn"] then
        if th_flags ~= TH_SYN then
            -- Соединение началось без нас -- невозможно вставить OOB
            DLOG("must be applied since the very beginning - SYN packet")
            instance_cutoff_shim()
            return
        end
        lua_state[key.."_syn"] = true  -- запомнить, что видели SYN
    end
 
    if desync.outgoing then
        -- === ИСХОДЯЩИЕ ПАКЕТЫ ===
        local pos = pos_get(desync, 's', false)  -- relative seq position
 
        if pos <= 1 then
            -- SYN (pos=0) или первый ACK (pos=1): уменьшить seq на 1
            desync.dis.tcp.th_seq = th_seq - 1
        end
 
        if pos == 0 then
            -- SYN: просто модифицировать seq и пропустить
            return VERDICT_MODIFY
 
        elseif pos == 1 then
            -- Первый пакет с данными
            local data = desync.reasm_data or desync.dis.payload
 
            if #data == 0 then
                -- Пустой ACK: модифицировать seq и пропустить
                return VERDICT_MODIFY
            else
                -- Определить OOB-байт (char > byte > 0x00)
                local oob_byte = desync.arg.char
                    or (desync.arg.byte and bu8(desync.arg.byte))
                    or "\x00"
                assert(#oob_byte == 1)
 
                -- Создать копию диссекта
                local dis_oob = deepcopy(desync.dis)
 
                -- Определить urp позицию
                if not urp or urp == 'b' then
                    -- Начало: вставить перед 1-м байтом
                    insert_pos = 1
                    dis_oob.tcp.th_urp = 0
                elseif urp == 'e' then
                    -- Конец: вставить после последнего байта
                    insert_pos = #data + 1
                    dis_oob.tcp.th_urp = insert_pos
                else
                    -- Маркер: разрешить позицию
                    insert_pos = resolve_pos(data, l7payload, urp)
                    if not insert_pos then
                        DLOG("cannot resolve urp marker")
                        instance_cutoff_shim()
                        return
                    end
                    dis_oob.tcp.th_urp = insert_pos  -- +1 по RFC
                end
 
                -- Вставить OOB-байт
                dis_oob.payload = data[1..urp-1] .. oob_byte .. data[urp..]
                -- Установить TH_URG
                dis_oob.tcp.th_flags |= TH_URG
 
                -- Отправить с автосегментацией по MSS
                rawsend_dissect_segmented(desync, dis_oob)
 
                -- Cutoff (если не replay)
                if not desync.replay then
                    instance_cutoff_shim()  -- оба направления
                end
 
                return VERDICT_DROP
            end
 
        else
            -- pos > 1: replay-часть -- дропнуть
            if desync.replay then
                if desync.replay_piece_last then
                    instance_cutoff_shim()
                end
                return VERDICT_DROP
            end
            instance_cutoff_shim()
        end
 
    else
        -- === ВХОДЯЩИЕ ПАКЕТЫ ===
        local pos = pos_get(desync, 's', true)  -- reverse pos
        if pos > 1 then
            -- Неожиданная позиция -- cutoff
            instance_cutoff_shim()
            return
        end
        -- Увеличить ack на 1 (компенсация сдвига seq)
        desync.dis.tcp.th_ack = th_ack + 1
        return VERDICT_MODIFY
    end
end

Читать код удобнее по трём веткам. Если соединение началось не с SYN, функция отключается. В исходящих пакетах у SYN и первого пакета после него из seq вычитается единица, а первый пакет с данными заменяется пакетом со срочным байтом. Во входящих к ack прибавляется единица. Для тех, кто не читает Lua: return VERDICT_MODIFY значит «пропусти пакет дальше с моими правками», VERDICT_DROP — «этот пакет не отправляй» (нужный пакет функция отправила сама), а instance_cutoff_shim() — «отключи функцию на этом соединении».


Поведение при reasm (многопакетный payload)

Если payload не помещается в один TCP-сегмент (например, большой TLS ClientHello с post-quantum ключами), nfqws2 собирает все части в буфер reasm_data — общая механика описана в жизненный цикл desync-функции (стадия 6). oob обходится с ней по-своему:

  1. Берёт весь reasm_data (а не отдельные части)
  2. Вставляет OOB-байт в позицию urp внутри полного reasm
  3. Отправляет через rawsend_dissect_segmented, которая автоматически разбивает на сегменты по MSS

Проще говоря

Reasm («повторная сборка») — целый запрос, собранный движком из нескольких пакетов; replay — прогон придержанных частей через функции; MSS — наибольший объём данных в одном TCP-сегменте. Если запрос не влез в один пакет, oob вставляет байт в собранное целое и сама режет результат на сегменты. Общая механика описана в reasm и Автосегментация по MSS у multisplit.

При сегментации rawsend_dissect_segmented корректно обрабатывает OOB:

Сегментация по MSS с OOB:

Полный payload с OOB (3000 байт, MSS=1460):
  [данные_до_urp][OOB][данные_после_urp]

Сегмент 1 (1460 байт): th_urp нормализуется, TH_URG если urp попал сюда
Сегмент 2 (1460 байт): TH_URG снимается если urp не попал сюда
Сегмент 3 (80 байт):   аналогично

В каждом сегменте:

  • Если urp попадает в диапазон [pos, pos+len) — устанавливается TH_URG, th_urp нормализуется по смещению сегмента (th_urp = urp - pos + 1)
  • Если urp не попадает в сегмент — TH_URG снимается, th_urp = 0

Replay-части (2-я и далее) дропаются. Cutoff выполняется после последней replay-части.

Проще говоря

Флаг TH_URG остаётся только на том сегменте, куда попал срочный байт; в остальных он снят, а указатель обнулён. В нужном сегменте указатель считается от начала этого сегмента, а не от начала всего запроса.


Сравнение с tpws —oob

tpws — прокси-вариант Zapret: он работает не с пакетами, а с потоком данных, а рукопожатие делает сама операционная система. Свою вставку срочного байта он делает через флаг MSG_OOB обычного вызова send(). Таблица ниже сравнивает два подхода построчно.

Аспектoob (nfqws2)--split-pos=.. --oob (tpws)
Уровень работыПакетный (L3/L4, raw sockets)Потоковый (L7, прокси)
Способ вставкиМодификация raw TCP-пакетов с TH_URGИспользование MSG_OOB в send()
Позиция OOBЛюбая (маркер, b, e)Привязана к --split-pos
Сегментация + OOBНевозможна (несовместима с multisplit)Возможна (split + oob в одном вызове)
Модификация handshakeДа (сдвиг seq на SYN)Нет (ОС делает handshake)
Контроль th_urpПолный (можно задать b, e, маркер)Нет (ОС решает)
Контроль OOB-байтаДа (char, byte)Ограниченный
Работа с reasmДа (весь reasm, MSS-сегментация)Нет (потоковый режим)
NAT совместимостьДаДа
Необходимость —in-rangeДа (--in-range=-s1)Нет (прокси)

Вывод: oob в nfqws2 даёт больше контроля над позицией и содержимым OOB-байта, но не может быть скомбинирован с TCP-сегментацией. tpws позволяет комбинировать split + OOB, но без тонкого контроля th_urp.

Проще говоря

oob в nfqws2 сама собирает и отправляет пакеты, поэтому может выбрать любой байт и любое место, но не сочетается с разрезом на сегменты. tpws умеет разрезать поток и вставить срочный байт одним вызовом, но тонко управлять указателем и самим байтом не даёт.


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

1. Обязательно —in-range=-s1

Для HTTP и TLS (протоколов, где сервер ждёт запроса клиента) необходим --in-range=-s1. Это разрешает перехват входящего SYN-ACK от сервера, что нужно для корректной работы oob (коррекция ack). Без --in-range=-s1 oob не увидит SYN и откажется работать.

На Windows --wf-tcp-in для HTTP/TLS не нужен — достаточно автоматически перехватываемых SYN-пакетов.

Проще говоря

--in-range=-s1 пускает oob к самому началу соединения: без этого она не увидит SYN и откажется работать. Формат --in-range разобран в заметке out-range. --wf-tcp-in — параметр перехвата входящих TCP-портов на Windows (wf; сами режимы перехвата — в заметке про WinDivert).

2. Для server-first протоколов нужен —wf-tcp-in (Windows)

Для протоколов, где сервер отправляет данные до первого запроса клиента (например, SMTP, FTP, SSH), требуется разрешить все входящие пакеты до первого исходящего с данными. На Windows это означает обязательный --wf-tcp-in.

Проще говоря

Server-first протоколы — те, где первым говорит сервер: баннер SMTP, FTP или SSH приходит раньше любого запроса клиента. oob должна видеть все входящие пакеты до первого исходящего с данными, поэтому на Windows входящие приходится разрешать целиком через --wf-tcp-in (wf).

3. urp=b работает только на Linux-серверах

Значение th_urp=0 невалидно по RFC 1122. Linux-серверы его обрабатывают корректно (удаляют OOB-байт), но Windows и BSD серверы ломаются — соединение может разорваться или данные будут повреждены. Используйте urp=b только если уверены, что целевой сервер работает на Linux.

Проще говоря

Если не уверены, что сервер работает на Linux, urp=b не используйте; для не-Linux серверов в примере 2 безопаснее назван urp=0. О самих стандартах — Два стандарта th_urp.

4. Нет фильтрации по payload

oob не может быть отфильтрован по типу payload (payload=tls_client_hello и т.д.). Причина: модификация начинается на SYN (до появления любого payload). После сдвига sequence “соскочить” невозможно — если oob не вставит байт, sequence будут сдвинуты, и TCP-соединение сломается.

Фильтрация возможна только на уровне профиля (--filter-tcp, --hostlist через --ipcache-hostname и т.д.).

Проще говоря

Профиль — набор правил для определённого трафика (Анатомия профиля); отбирают трафик фильтрами вроде --filter-tcp (фильтры профиля) и списками доменов (hostlist). Тип payload (tls_client_hello, http_req и другие) — то, как движок распознаёт протокол; подробнее в заметке payload. Причина запрета — сдвиг seq на SYN: Фаза 1.

5. Нет фильтрации по направлению (dir)

Стандартный аргумент dir отсутствует. Функция автоматически обрабатывает оба направления: исходящие — вставка OOB и сдвиг seq, входящие — коррекция ack.

6. Несовместимость с multisplit, fakedsplit и другими функциями отправки payload

oob не может работать совместно с multisplit, multidisorder, fakedsplit, fakeddisorder и другими функциями, которые пересылают текущий payload. Причина: эти функции отправляют копии payload, но без OOB-модификации. В результате:

  • oob вставит OOB-байт и сдвинет sequence
  • multisplit отправит свои сегменты без OOB и без учёта сдвига
  • Сервер получит дублирующиеся данные с рассогласованными sequence

Получить эффект “разбить на сегменты и всунуть OOB” (как в tpws) через комбинацию oob + multisplit невозможно.

Проще говоря

Соседние функции отправляют собственные копии данных: multisplit и multidisorder режут запрос на сегменты, fakedsplit и fakeddisorder подмешивают к разрезу фейки. Ни одна из них не знает о срочном байте и сдвиге seq, поэтому сервер получил бы дубли с рассогласованной нумерацией. С fake в разных профилях oob ужиться может: пример 10.

7. Длящаяся десинхронизация и переключение профилей

oob — длящаяся десинхронизация: между SYN (сдвиг seq) и первым payload (вставка OOB) проходит несколько пакетов. Если за это время происходит переключение профиля (например, --ipcache-hostname разрешил домен и переключил на другой профиль), новый профиль должен также содержать oob. Иначе:

  • Старый профиль сдвинул seq на SYN
  • Новый профиль не знает об этом и не вставит OOB
  • TCP-соединение сломано (sequence рассогласованы)

Решение: дублировать oob во всех профилях, которые могут получить управление в процессе handshake.

Проще говоря

Длящаяся десинхронизация — та, что растянута на несколько пакетов соединения: сначала SYN, потом данные. Профиль — набор правил для определённого трафика (Анатомия профиля); движок может сменить профиль соединения по ходу, например когда кэш --ipcache-hostname выдал имя сайта (Как выбирается профиль). Если новый профиль не содержит oob, сдвиг seq, сделанный старым, никто не компенсирует. Поэтому строку oob дублируют во всех профилях, которые могут получить управление на рукопожатии; пример — 14.

8. Требуется conntrack (tracking)

oob использует desync.track для хранения состояния (видели ли SYN, текущая позиция). Без tracking функция немедленно возвращает nil.

Проще говоря

Tracking (conntrack) — система, которая запоминает состояние каждого соединения между пакетами. oob хранит там, видела ли она SYN и на каком пакете соединения сейчас находится. Без такой памяти функция сразу выходит и ничего не делает. Устройство записи track описано в структуре desync и диссекта.

9. Соединение должно быть перехвачено с SYN

Если oob получает управление после SYN (например, после перескока профиля или при подключении к уже установленному соединению), она немедленно выполняет cutoff. Специальный флаг lua_state[key.."_syn"] гарантирует, что oob видела SYN самого первого пакета.

10. Фильтрация по хостлистам

Фильтрация через --hostlist возможна только при использовании --ipcache-hostname. Если в кэше хоста ещё нет, и профиль получает управление не с начала соединения — срабатывает cutoff (см. п.9).

Проще говоря

Hostlist — файл со списком доменов (hostlist): профиль срабатывает только для сайтов из списка. Имя сайта из SNI или Host: появляется лишь в первом пакете с данными, то есть позже SYN, а oob должна начать раньше; поэтому список доменов с oob работает только вместе с кэшем --ipcache-hostname (в пресетах он включается строкой --ipcache-hostname=1, см. пресет).

11. OOB-байт ровно 1

OOB-байт обязательно 1 байт. Попытка задать char длиной != 1 вызовет Lua error.

12. Пустой ACK не ломает поток

Если первый исходящий пакет с данными оказался пустым ACK (пустой payload), oob просто уменьшает seq и возвращает VERDICT_MODIFY. Вставка OOB произойдёт при появлении следующего непустого пакета.


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

Пятнадцать примеров для профиля. Во всех строках с oob есть --in-range=-s1: без него функция не включится (нюанс 1). Если urp не указан, действует b. Порядок — от простого к комбинациям с другими опциями; самый популярный вариант — пример 4.

1. Минимальный: urp=b (по умолчанию), OOB=0x00

--in-range=-s1 --lua-desync=oob

Вставляет 0x00 перед первым байтом payload. Работает только на Linux-серверах.

2. Позиция urp=0 (нулевая, 0-based = первый байт)

--in-range=-s1 --lua-desync=oob:urp=0

Маркер 0 разрешается в абсолютную позицию. th_urp = 0 + 1 = 1 (RFC 1122). Безопаснее urp=b для не-Linux серверов.

Проще говоря: маркер 0 даёт указатель th_urp=1, а не 0, как режим b; для не-Linux серверов статья называет его более безопасным, чем urp=b. Про два стандарта — Два стандарта th_urp.

3. Позиция urp=2 (после первого байта)

--in-range=-s1 --lua-desync=oob:urp=2

OOB-байт вставляется после первого байта payload. th_urp = 3. Для HTTP: G[OOB]ET /... — DPI не распознает метод.

Проще говоря: сначала идёт буква G, затем лишний байт, затем ET; DPI, не вынимающий срочный байт, читает метод как G\x00ET, а не как GET.

4. Середина SLD (самый популярный вариант)

--in-range=-s1 --lua-desync=oob:urp=midsld

Для example.com: OOB вставляется посередине example. DPI видит exa[OOB]mple.com.

Маркер midsld разобран в Типы маркеров; схема этого варианта — в анимации в начале статьи (Коротко и по-простому).

5. Произвольный OOB-символ

--in-range=-s1 --lua-desync=oob:char=Z:urp=midsld

OOB-байт = ASCII ‘Z’ вместо 0x00.

Аргумент char — один символ; описание — в Собственные аргументы oob.

6. OOB-байт по числовому значению

--in-range=-s1 --lua-desync=oob:byte=255:urp=midsld

OOB-байт = 0xFF.

7. Для HTTPS (TLS) с фильтром по порту

--filter-tcp=443 --in-range=-s1 --lua-desync=oob:urp=midsld

Применяется только к HTTPS-соединениям.

--filter-tcp=443 — фильтр профиля по порту: 443 — порт HTTPS (фильтры профиля).

8. Для HTTP и HTTPS одновременно

--filter-tcp=80,443 --in-range=-s1 --lua-desync=oob:urp=midsld

Порты 80 и 443 — HTTP и HTTPS: oob вставляет байт и в HTTP-запрос, и в TLS ClientHello.

9. С хостлистом и ipcache

--filter-tcp=443 --hostlist=blocked.txt --ipcache-hostname \
  --in-range=-s1 --lua-desync=oob:urp=midsld

Фильтрация по хостлисту. --ipcache-hostname обязателен для работы хостлиста с oob.

Проще говоря: хостлист — файл со списком доменов; кэш --ipcache-hostname нужен, чтобы хостлист вообще работал с oob (нюанс 10).

10. Комбинация: fake + oob (в разных профилях)

# Профиль 1: фейк для TLS
--filter-tcp=443 --payload=tls_client_hello \
  --lua-desync=fake:blob=fake_default_tls:tcp_md5
 
# Профиль 2: oob
--filter-tcp=443 --in-range=-s1 \
  --lua-desync=oob:urp=midsld

Важно: fake и oob могут быть в разных профилях, но oob несовместим с multisplit/fakedsplit в рамках одного потока.

Проще говоря: в первом профиле fake посылает отдельный фейковый ClientHello (blob=fake_default_tls) с добавленной опцией tcp_md5 — это fooling, порча, чтобы сервер фейк отбросил (blob, Что такое fooling); во втором профиле oob работает с настоящим запросом.

11. Перебор позиций urp (как в blockcheck2)

# blockcheck2 перебирает: b, 0, 2, midsld
--in-range=-s1 --lua-desync=oob:urp=b
--in-range=-s1 --lua-desync=oob:urp=0
--in-range=-s1 --lua-desync=oob:urp=2
--in-range=-s1 --lua-desync=oob:urp=midsld

Это паттерн из blockcheck2.d/standard/17-oob.sh — попробовать разные позиции urp для нахождения рабочей.

Проще говоря: blockcheck2 — проверочный скрипт из репозитория zapret2, который перебирает варианты стратегий; для oob он пробует позиции b, 0, 2 и midsld. Как честно проверять результат — чек-лист честной проверки.

12. С IP-фрагментацией

--in-range=-s1 --lua-desync=oob:urp=midsld:ipfrag:ipfrag_pos_tcp=32

OOB-пакет дополнительно фрагментируется на IP-уровне.

13. С IPv6 extension headers

--in-range=-s1 --lua-desync=oob:urp=midsld:ip6_hopbyhop

Добавляет hop-by-hop extension header к OOB-пакету.

IPv6 extension header — дополнительный заголовок IPv6 между основным заголовком и TCP; hop-by-hop — один из таких заголовков (таблица в B) Standard fooling).

14. Дублирование oob при переключении профилей

# Профиль "по умолчанию" (до разрешения hostname)
--filter-tcp=443 --in-range=-s1 \
  --lua-desync=oob:urp=midsld
 
# Профиль "для заблокированных" (после ipcache-hostname)
--filter-tcp=443 --hostlist=blocked.txt --ipcache-hostname --in-range=-s1 \
  --lua-desync=oob:urp=midsld

oob дублируется в оба профиля, чтобы переключение не сломало TCP.

Проще говоря: первый профиль работает, пока имя сайта ещё неизвестно, второй — когда --ipcache-hostname его выдал; oob есть в обоих, иначе сдвиг seq останется без пары (нюанс 7).

15. OOB в позиции host (начало hostname)

--in-range=-s1 --lua-desync=oob:urp=host:byte=0

Вставляет нулевой байт прямо перед hostname. DPI видит \x00example.com вместо example.com.

Маркер host — первый байт имени хоста; byte=0 — то же, что значение по умолчанию (0x00).


📚 См. также

  • desync — обзор всех функций --lua-desync и общий контракт: кто вызывает функцию, что ей передаёт и как складываются вердикты
  • жизненный цикл desync-функции — восемь стадий, общих для всех техник дурения; отдельно разобрано, чем от них отличается oob
  • структура desync и диссекта — поля th_flags, th_urp и работа с sequence numbers
  • out-range — формат --in-range и --out-range, в том числе -s1
  • multisplit · multidisorder · fakedsplit · fakeddisorder — функции отправки payload, с которыми oob в одном потоке несовместима
  • syndata — данные в SYN, другая техника, работающая с рукопожатием
  • tcpseg — отправка диапазона данных между двумя маркерами
  • fake — отдельные фейковые пакеты; их можно держать в соседнем профиле
  • payload — как распознаются типы данных и почему у oob нет фильтра payload
  • фильтры профиля · hostlist — как отбирать соединения на уровне профиля
  • WinDivert-фильтры — --wf-tcp-in для протоколов, где сервер говорит первым
  • ts-and-fooling — что такое fooling и почему в oob он почти не нужен
  • verify-strategy — как честно проверить подобранную позицию urp
  • profile · preset — где --lua-desync живёт среди остальных настроек
  • DPI и ТСПУ — как устроена инспекция трафика, против которой работает oob

Источники: lua/zapret-antidpi.lua:1084-1176, lua/zapret-lib.lua:1148-1192, docs/manual.md:4276-4309, docs/manual.en.md:4095-4127, blockcheck2.d/standard/17-oob.sh из репозитория zapret2.


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

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