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 запрос с посторонним байтом внутри. Она делает четыре шага:
- Ловит соединение с самого первого пакета — SYN — и уменьшает в нём стартовый номер seq на 1; то же делает в первом ACK («подтверждаю», пакете-подтверждении) — следующем исходящем пакете после SYN (по коду это пустой ACK или уже пакет с данными). Так в нумерации байтов освобождается место под один лишний байт.
- Во входящих пакетах начала соединения (прежде всего в SYN-ACK — ответе сервера на SYN) увеличивает номер подтверждения ack на 1, чтобы операционная система клиента не запуталась: она ждёт подтверждений по своей исходной нумерации. Если входящий пакет оказывается на неожиданной позиции (по коду — дальше начала соединения, позиция больше 1),
oobотключается. - Когда уходит первый пакет с данными (например, ClientHello или HTTP-запрос), вставляет в него один лишний байт — по умолчанию
0x00— в место, которое задаёт аргументurp, ставит флаг URG и заполняет указательth_urp. Готовый пакет отправляется вместо перехваченного оригинала, а оригинал выбрасывается. - Отключается для этого соединения. Такое отключение называют 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 со значениями по умолчанию: лишний байт 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.
Оглавление
- Коротко и по-простому
- Справка: где функция в коде и родственники
- Зачем нужен oob
- Быстрый старт
- Принцип работы
- Два стандарта th_urp
- Режимы urp
- Полный список аргументов
- Место в общем скелете
- Псевдокод алгоритма
- Поведение при reasm (многопакетный payload)
- Сравнение с tpws —oob
- Нюансы и подводные камни
- Практические примеры
- 📚 См. также
Справка: где функция в коде и родственники
Паспорт функции для тех, кто сверяется с исходниками 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), происходит следующее:
- Сервер (TCP-стек ОС): выбрасывает OOB-байт из потока. Приложение получает чистые данные без вставки
- 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:
- Берёт payload (или reasm, если многопакетный)
- Определяет OOB-байт:
char=илиbyte=или0x00по умолчанию - Определяет позицию вставки по
urp - Вставляет OOB-байт в payload по этой позиции
- Устанавливает флаг
TH_URGи значениеth_urp - Отправляет модифицированный пакет через
rawsend_dissect_segmented - Дропает оригинальный пакет (
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 | Байт после последнего байта hostname | http_req, tls_client_hello |
sld | Первый байт домена второго уровня | http_req, tls_client_hello |
endsld | Байт после SLD | http_req, tls_client_hello |
midsld | Середина SLD | http_req, tls_client_hello |
sniext | Начало данных SNI extension | tls_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-байт = 0x00byte=255— OOB-байт = 0xFFbyte=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— середина SLDurp=host— начало hostnameurp=2— после первого байта payloadurp=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 TTL | ip_ttl=6 |
ip6_ttl=N | Установить IPv6 Hop Limit | ip6_ttl=6 |
ip_autottl=delta,min-max | Автоматический TTL (delta от серверного TTL) | ip_autottl=-2,40-64 |
ip6_autottl=delta,min-max | Аналогично для IPv6 | ip6_autottl=-2,40-64 |
ip6_hopbyhop[=HEX] | Вставить extension header hop-by-hop (по умолчанию 6 нулей) | ip6_hopbyhop |
ip6_hopbyhop2[=HEX] | Второй hop-by-hop header | ip6_hopbyhop2 |
ip6_destopt[=HEX] | Destination options header | ip6_destopt |
ip6_destopt2[=HEX] | Второй destination options | ip6_destopt2 |
ip6_routing[=HEX] | Routing header | ip6_routing |
ip6_ah[=HEX] | Authentication header | ip6_ah |
tcp_seq=N | Сместить TCP sequence (+ или -) | tcp_seq=-10000 |
tcp_ack=N | Сместить TCP ack (+ или -) | tcp_ack=-66000 |
tcp_ts=N | Сместить TCP timestamp | tcp_ts=-100 |
tcp_md5[=HEX] | Добавить TCP MD5 option (16 байт; по умолчанию случайные) | tcp_md5 |
tcp_flags_set=LIST | Установить TCP-флаги | tcp_flags_set=FIN,PUSH |
tcp_flags_unset=LIST | Снять TCP-флаги | tcp_flags_unset=ACK |
tcp_ts_up | Поднять TCP timestamp option в начало заголовка | tcp_ts_up |
tcp_nop_del | Удалить все TCP NOP опции | tcp_nop_del |
fool=<func> | Кастомная Lua-функция fooling | fool=my_fooler |
Заметка: fooling в 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 ID | seq |
ip_id=rnd | Случайные IP ID | — |
ip_id=zero | Нулевые IP ID | — |
ip_id=none | Не менять IP ID | — |
ip_id_conn | Сквозная нумерация IP ID в рамках соединения (требует tracking) | — |
ip_id применяется к каждому отправляемому сегменту (при автосегментации по MSS).
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=N | IPv6: 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=N | Firewall 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 обходится с ней по-своему:
- Берёт весь
reasm_data(а не отдельные части) - Вставляет OOB-байт в позицию
urpвнутри полного reasm - Отправляет через
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=2OOB-байт вставляется после первого байта 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=midsldOOB-байт = ASCII ‘Z’ вместо 0x00.
Аргумент char — один символ; описание — в Собственные аргументы oob.
6. OOB-байт по числовому значению
--in-range=-s1 --lua-desync=oob:byte=255:urp=midsldOOB-байт = 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=32OOB-пакет дополнительно фрагментируется на 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=midsldoob дублируется в оба профиля, чтобы переключение не сломало 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-архивом.