Some checks failed
Published content check / validate (push) Failing after 17s
git.zapret.moe в России заблокирован по имени, как и wiki.zapret.moe. Ссылки на Forgejo во всех заметках, футерах, wizard.html и главной заменены на git2.zapret.moe (включая закодированные адреса значков shields.io). Какое имя рабочее, теперь решают реестры хоста /etc/zapret-*-domains.conf через zapret-site-names: pre-commit хук vault отклоняет добавленные строки с заблокированным именем, сборка Quartz сама переводит ссылки и baseUrl на рабочие имена. Правило в AGENTS.md переписано под этот механизм. scripts/wiki-stats.py: переходы с любых имён вики и git (wikiN, gitN) больше не считаются внешними сайтами. content-check: проверка зеркал сторонних проектов узнаёт любое имя git. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
971 lines
91 KiB
Markdown
971 lines
91 KiB
Markdown
---
|
||
date: 2026-02-17
|
||
tags:
|
||
- zapret
|
||
- zapret2
|
||
- nfqws2
|
||
- lua
|
||
- lua-desync
|
||
- antidpi
|
||
- tcp
|
||
- oob
|
||
- urgent
|
||
aliases:
|
||
- oob
|
||
description: "Техника oob в zapret2: вставка TCP Out-of-Band байта с флагом URG ломает сигнатуры DPI. Режимы urp, полный список аргументов, подводные камни, примеры."
|
||
---
|
||
|
||
> [!mirror] Резервное зеркало
|
||
> Актуальная версия этой страницы — на основной вики: [wiki2.zapret.moe/Zapret2/desync/oob](https://wiki2.zapret.moe/Zapret2/desync/oob)
|
||
|
||
# `oob` -- TCP Out-of-Band десинхронизация (zapret2 / nfqws2)
|
||
|
||
## Коротко и по-простому
|
||
|
||
Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке `Host:` запроса. ТСПУ (Технические средства противодействия угрозам — оборудование [[DPI/DPI|DPI]], то есть глубокой инспекции трафика, которое стоит у провайдера) ищет это имя в первом пакете соединения и по нему решает, что делать с соединением дальше.
|
||
|
||
HTTP-запросы и TLS-соединения идут по протоколу TCP. Соединение начинается с рукопожатия: первым пакетом клиент отправляет SYN («хочу соединиться») и называет в нём стартовый номер — sequence number, коротко seq. Дальше у каждого байта соединения есть порядковый номер, а сервер раскладывает принятые данные в свой буфер строго по этим номерам. Стартовый номер из SYN он запоминает и ждёт первый байт данных сразу за ним: если клиент начал с номера 999, то данные ожидаются с 1000 (999 плюс единица за сам SYN). Как с такими номерами считать в Lua (они 32-битные беззнаковые и заворачиваются по кругу) — в [[Zapret2/структура desync и диссекта#Работа с sequence numbers|Работа с sequence numbers]].
|
||
|
||
Кроме обычных данных, в TCP есть редко используемый механизм срочных данных, по-английски Out-of-Band, сокращённо OOB («вне основного потока»). Отправитель ставит в заголовке пакета флаг URG («urgent», «срочно») и записывает в поле `th_urp` (urgent pointer, «указатель срочности») положение срочного байта. TCP-стек операционной системы сервера — та её часть, что принимает пакеты и собирает из них поток для программ, — этот байт из потока выбрасывает, и программа получает чистые данные без вставки. [[DPI/DPI|DPI]] устроен иначе: он может не понимать механизм срочных данных и разбирать поток вместе с лишним байтом. Как именно два стандарта указывают на срочный байт, разобрано в разделе [[#Два стандарта th_urp|Два стандарта th_urp]].
|
||
|
||
Функция `oob` в [[Zapret2/Zapret2|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, она отключается. Проще говоря: в самом начале клиент сообщает серверу «мои номера начинаются на единицу раньше», в освободившееся место встаёт лишний байт, а сервер выбрасывает его как срочный. Подробно — в разделе [[#Принцип работы|Принцип работы]] и в нюансах [[#9. Соединение должно быть перехвачено с SYN|«Соединение должно быть перехвачено с SYN»]] и [[#7. Длящаяся десинхронизация и переключение профилей|«Длящаяся десинхронизация…»]].
|
||
|
||
Сервер получает поток, из которого срочный байт вынут, и отвечает как обычно: приложение видит ровно тот запрос, который отправил браузер. DPI, который не вынимает срочные байты, читает поток с лишним байтом посередине (например, `exa\x00mple.com` вместо `example.com`) и не находит имя сайта. Всё держится на слове «может»: приём рассчитан на DPI, который срочный байт не вынимает; на другом DPI он может не сработать. Чем `oob` отличается от простого разреза на куски, разобрано в разделе [[#Зачем нужен oob|Зачем нужен oob]].
|
||
|
||
Команда из быстрого старта `--in-range=-s1 --lua-desync=oob:urp=midsld` сначала уменьшает seq в SYN на единицу, резервируя место под будущий байт, а затем вставляет в первый пакет с данными посередине имени сайта байт срочных данных (Out-of-Band) с флагом URG; на схеме он обведён. ТСПУ, который не вынимает такой байт из потока, видит имя с лишним байтом внутри; TCP-стек сервера этот байт вынимает, и имя в буфере смыкается. Как читать схему: строки — пакеты в порядке отправки, подписанные в тех же обозначениях, что и таблицы в этой статье; по горизонтали — место данных в TCP-потоке (номер последовательности, seq); в колонке справа — что сделал с пакетом сервер; внизу — буфер сервера, в который принятые куски встают по своим seq.
|
||
|
||
![[desync-anim-oob.svg|Анимация oob: байт срочных данных с флагом URG посреди имени, сервер вынимает его и имя смыкается]]
|
||
|
||
Самая короткая команда включает `oob` со значениями по умолчанию: лишний байт `0x00` ставится перед самым первым байтом данных (`urp=b`). Строку пишут в [[Zapret2/profile#Анатомия профиля|профиль]] — набор правил Zapret 2 для определённого трафика. Для HTTP и TLS флаг `--in-range=-s1` обязателен: он задаёт, какие входящие пакеты вообще доходят до функции (формат разобран в заметке [[Zapret2/out-range|про --out-range и --in-range]]), и без него `oob` не увидит рукопожатие и не включится; подробнее — в нюансе [[#1. Обязательно --in-range=-s1|«Обязательно --in-range=-s1»]].
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob
|
||
```
|
||
|
||
Режим `urp=b` работает только на Linux-серверах: Windows и BSD с нулевым `th_urp` ломаются ([[#3. urp=b работает только на Linux-серверах|нюанс 3]]). Самый популярный вариант — `urp=midsld`, как на схеме: байт ставится посередине домена второго уровня (SLD, главной части имени сайта: у `www.youtube.com` это `youtube`); все режимы и маркеры разобраны в разделе [[#Режимы urp|Режимы urp]], готовые строки — в [[#Быстрый старт|Быстром старте]] и [[#Практические примеры|Практических примерах]].
|
||
|
||
`oob` не режет данные на куски, как [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]], и не посылает отдельных фейковых пакетов, как [[Zapret2/desync/fake#Зачем нужен fake|fake]] (фейк — пакет-обманка с ложными данными, который нарочно портят приёмом fooling, чтобы сервер его отбросил, а DPI прочитал; см. [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]): лишний байт вставляется внутрь единого настоящего потока. Поэтому `oob` нельзя сочетать в одном потоке с `multisplit`, `fakedsplit` и другими функциями, которые пересылают данные, — они отправят копии без срочного байта и без учёта сдвига ([[#6. Несовместимость с multisplit, fakedsplit и другими функциями отправки payload|нюанс 6]]). Соседи по приёму: [[Zapret2/desync/syndata#Зачем нужен syndata|syndata]] кладёт данные прямо в SYN, а [[Zapret2/desync/tcpseg#Зачем нужен tcpseg|tcpseg]] отправляет диапазон данных между двумя маркерами. Есть и другие ограничения. У `oob` нет фильтров `dir` и `payload` ([[#4. Нет фильтрации по payload|нюанс 4]]), поэтому отбирать соединения приходится на уровне профиля. Ей нужен conntrack — отслеживание состояния соединений в системе ([[#8. Требуется conntrack (tracking)|нюанс 8]]). Режим `urp=e` как правило бесполезен для обхода DPI ([[#urp=e -- конец|почему]]). Сдвиг seq на SYN, отключение при подключении «на ходу» и переключение профилей — в нюансах 7 и 9. Подходящую позицию `urp` подбирают перебором и проверяют по [[Zapret2/verify-strategy#Короткий чек-лист честной проверки|чек-листу честной проверки]]; пример перебора — [[#11. Перебор позиций urp (как в blockcheck2)|как в blockcheck2]].
|
||
|
||
---
|
||
|
||
## Оглавление
|
||
|
||
- [Коротко и по-простому](#коротко-и-по-простому)
|
||
- [Справка: где функция в коде и родственники](#справка-где-функция-в-коде-и-родственники)
|
||
- [Зачем нужен oob](#зачем-нужен-oob)
|
||
- [Быстрый старт](#быстрый-старт)
|
||
- [Принцип работы](#принцип-работы)
|
||
- [Фаза 1: перехват SYN](#фаза-1-перехват-syn)
|
||
- [Фаза 2: вставка OOB-байта](#фаза-2-вставка-oob-байта)
|
||
- [Фаза 3: cutoff](#фаза-3-cutoff)
|
||
- [Обработка входящих пакетов](#обработка-входящих-пакетов)
|
||
- [Два стандарта th_urp](#два-стандарта-th_urp)
|
||
- [Режимы urp](#режимы-urp)
|
||
- [urp=b -- начало (по умолчанию)](#urpb----начало-по-умолчанию)
|
||
- [urp=e -- конец](#urpe----конец)
|
||
- [urp=маркер -- произвольная позиция](#urpмаркер----произвольная-позиция)
|
||
- [Полный список аргументов](#полный-список-аргументов)
|
||
- [A) Собственные аргументы oob](#a-собственные-аргументы-oob)
|
||
- [B) Standard fooling](#b-standard-fooling)
|
||
- [C) Standard ipid](#c-standard-ipid)
|
||
- [D) Standard ipfrag](#d-standard-ipfrag)
|
||
- [E) Standard reconstruct](#e-standard-reconstruct)
|
||
- [F) Standard rawsend](#f-standard-rawsend)
|
||
- [Место в общем скелете](#место-в-общем-скелете)
|
||
- [Псевдокод алгоритма](#псевдокод-алгоритма)
|
||
- [Поведение при reasm (многопакетный payload)](#поведение-при-reasm-многопакетный-payload)
|
||
- [Сравнение с tpws --oob](#сравнение-с-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` по обоим направлениям.
|
||
|
||
> [!note] Проще говоря
|
||
> Out-of-Band (OOB, «вне основного потока») — срочные данные TCP; флаг `TH_URG` — отметка «в этом пакете есть срочный байт», а `th_urp` — поле-указатель на него. SYN — первый пакет соединения, с которого `oob` обязана начать работу; `sequence` — порядковый номер байта в потоке. `instance_cutoff` — команда движку «больше не вызывай эту функцию на пакетах данного соединения»; как она устроена, описано в [[Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff|стадии 1 жизненного цикла]]. Поля `th_flags` и `th_urp` — части диссекта, то есть пакета, разобранного на поля, — описаны в [[Zapret2/структура desync и диссекта|структуре desync и диссекта]]. Чем `oob` отличается от `tpws --oob`, разобрано в разделе [[#Сравнение с tpws --oob|Сравнение с tpws --oob]].
|
||
|
||
Родственные функции: [[multisplit]] (TCP-сегментация), [[fakedsplit]] (с фейками), [[fake]] (фейковые пакеты), [[syndata]] (данные в SYN), [[tcpseg]] (диапазон).
|
||
|
||
Общий для всех техник дурения порядок работы — восемь стадий от отсева чужого транспорта до вердикта — разобран в [[жизненный цикл desync-функции]]; здесь описано только то, чем `oob` от этого скелета отличается.
|
||
|
||
---
|
||
|
||
## Зачем нужен oob
|
||
|
||
[[DPI/DPI|DPI]] анализирует TCP-поток, ищет сигнатуры — заранее известные строки, по которым узнаётся сайт (hostname в HTTP, SNI в TLS). Если вставить в поток лишний байт, помеченный как "urgent" (Out-of-Band), происходит следующее:
|
||
|
||
1. **Сервер (TCP-стек ОС):** выбрасывает OOB-байт из потока. Приложение получает чистые данные без вставки
|
||
2. **DPI:** может не понимать OOB-механизм и анализировать поток вместе со вставленным байтом. Если байт вставлен посередине hostname/SNI, сигнатура разрушается
|
||
|
||
**Ключевое отличие от сегментации:** oob не разрезает данные на части -- он вставляет **лишний байт**, который принимающая ОС удаляет, а DPI -- нет.
|
||
|
||
**Ключевое отличие от фейков:** oob не отправляет отдельных фейковых пакетов -- вставка происходит внутри единого легитимного потока. Это делает oob труднодетектируемым.
|
||
|
||
> [!note] Проще говоря
|
||
> Обычно приём против DPI либо режет запрос на части (сегменты), либо посылает отдельные фейковые пакеты. `oob` делает третье: запрос остаётся целым, но в него добавляется один байт с пометкой «срочный». Сервер выбрасывает этот байт из потока, а DPI, если не знает такой пометки, читает его как часть имени сайта: `exa\x00mple.com` вместо `example.com`.
|
||
|
||
Соседние приёмы разобраны в [[Zapret2/desync/multisplit#Зачем нужен multisplit|Зачем нужен multisplit]] (разрез на сегменты), [[Zapret2/desync/fake#Зачем нужен fake|Зачем нужен fake]] и [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]] (фейковые пакеты и их порча). Что такое `--lua-desync` и как движок вызывает такие функции — в [[Zapret2/desync|desync]].
|
||
|
||
---
|
||
|
||
## Быстрый старт
|
||
|
||
Три готовые строки для [[Zapret2/profile#Анатомия профиля|профиля]] Zapret 2, от самой короткой к более гибким. Во всех есть `--in-range=-s1`; зачем он нужен, сказано сразу под примерами. Значения `urp`, `char` и `byte` разобраны в разделах [[#Режимы urp|Режимы urp]] и [[#A) Собственные аргументы oob|Собственные аргументы oob]].
|
||
|
||
Минимально (urp=b по умолчанию, OOB-байт = `0x00`):
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob
|
||
```
|
||
|
||
С маркером по середине SLD:
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob:urp=midsld
|
||
```
|
||
|
||
С конкретным OOB-символом:
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob:char=X
|
||
```
|
||
|
||
**Важно:** `--in-range=-s1` обязателен для HTTP/TLS (протоколов, где сервер ждёт запроса клиента). Без него oob не увидит SYN-пакет и не активируется.
|
||
|
||
> [!note] Проще говоря
|
||
> `--in-range` задаёт, какие входящие пакеты движок передаёт функциям, а запись `-s1` читается как «от начала соединения до seq 1» (по [[Zapret2/out-range|таблице префиксов]]: `s` — относительный sequence number, разделитель `-` включает конечную позицию). Так до `oob` доходят пакеты самого начала соединения, по статье — в их числе входящий SYN-ACK от сервера; без этого функция не видит рукопожатие и не включается. HTTP и TLS — протоколы, в которых сервер ждёт запроса клиента; протоколы, где сервер заговаривает первым (SMTP, FTP, SSH), описаны в нюансе [[#2. Для server-first протоколов нужен --wf-tcp-in (Windows)|2]].
|
||
|
||
---
|
||
|
||
## Принцип работы
|
||
|
||
Работа идёт в три фазы, а параллельно с ними — обработка входящих пакетов: [[#Фаза 1: перехват SYN|перехват SYN]] со сдвигом seq, [[#Фаза 2: вставка OOB-байта|вставка]] срочного байта в первый пакет с данными, [[#Фаза 3: cutoff|отключение]] и [[#Обработка входящих пакетов|коррекция 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).
|
||
|
||
> [!note] Проще говоря
|
||
> Пометка `pos` — относительная позиция пакета в потоке по seq: `pos=0` у SYN, `pos=1` у первого пакета после него (по коду — пустой ACK или первый пакет с данными); в псевдокоде ниже её возвращает `pos_get`. Для этих двух позиций `oob` вычитает из seq единицу. Сервер поэтому считает стартовым номер 999 и ждёт данные с 1000, а сдвиг на единицу резервирует место под один лишний байт. Сдвиг, однажды начатый, должен дойти до конца: почему «соскочить» нельзя, объяснено в нюансе [[#4. Нет фильтрации по payload|4]]. Как с такими номерами считать в Lua (они 32-битные беззнаковые и заворачиваются по кругу) — [[Zapret2/структура desync и диссекта#Работа с sequence numbers|Работа с 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 — те же данные, собранные движком из нескольких пакетов, если запрос в один пакет не влез ([[Zapret2/жизненный цикл desync-функции#Стадия 4. Выбор данных: blob → reasm → payload|стадия 4 жизненного цикла]]). `rawsend_dissect_segmented` отправляет пакет, разобранный на поля (диссект), и сама разбивает его на сегменты по MSS — наибольшему объёму данных в одном TCP-сегменте ([[#Поведение при reasm (многопакетный payload)|подробнее ниже]]); среди способов выпустить пакет она описана в разделе [[Zapret2/жизненный цикл desync-функции#Четыре способа выпустить пакет|Четыре способа выпустить пакет]]. `VERDICT_DROP` — решение «перехваченный пакет не отправлять» ([[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадия 8]]).
|
||
|
||
**Визуализация вставки (urp=b, th_urp=0):**
|
||
|
||
![[wiki-anim-z2-oob-bytes.svg|Анимация: OOB-байт вставлен в запрос, сервер его удаляет, DPI читает сдвинутую строку]]
|
||
|
||
Квадратные скобки — по одному байту; `OOB` — вставленный срочный байт, `^` указывает на него, а `th_urp=0` и `TH_URG=1` — значения полей заголовка. DPI, не вынимающий срочный байт, видит `0x00` перед `GET`: сигнатура сдвинута и сломана. Режим `b` подробнее — в разделе [[#urp=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|Два стандарта th_urp]]). Маркеры разобраны в [[Zapret2/desync/multisplit#Типы маркеров|Типы маркеров]].
|
||
|
||
### Фаза 3: cutoff
|
||
|
||
После успешной отправки OOB-пакета (если это не replay) функция выполняет `instance_cutoff_shim` -- отключает себя от обоих направлений данного соединения. Дальнейшие пакеты проходят без модификации.
|
||
|
||
При replay (многопакетный payload): все replay-части дропаются, cutoff выполняется после последней части.
|
||
|
||
> [!note] Проще говоря
|
||
> Cutoff — отключение функции для этого соединения: `instance_cutoff_shim` говорит движку не вызывать её на следующих пакетах, и они проходят как есть ([[Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff|стадия 1 жизненного цикла]]). Replay («перепроигрывание») — это когда движок придержал части большого запроса, собрал их и прогоняет по очереди через функции: `oob` при этом дропает каждую придержанную часть, а отключается только на последней ([[Zapret2/жизненный цикл desync-функции#Стадия 6. Развилка перепроигрывания (replay)|стадия 6]]).
|
||
|
||
### Обработка входящих пакетов
|
||
|
||
Для входящих пакетов (от сервера) oob **увеличивает th_ack на 1**, компенсируя сдвиг sequence:
|
||
|
||
```
|
||
Входящий от сервера:
|
||
th_ack = 999 (сервер подтверждает sequence, сдвинутый на 1)
|
||
|
||
oob модифицирует:
|
||
th_ack = 1000 (возвращает ожидаемое значение для клиента)
|
||
```
|
||
|
||
Это необходимо, чтобы клиентская ОС не запуталась -- она ожидает ack на основе своего оригинального sequence.
|
||
|
||
> [!note] Проще говоря
|
||
> ack — номер, которым получатель подтверждает, до какого байта принял данные. Сервер считает от сдвинутого номера, а клиентская система — от своего исходного, поэтому `oob` во входящих пакетах начала соединения (прежде всего в SYN-ACK) прибавляет к ack единицу, и подтверждения совпадают с тем, что клиент ожидает; если входящий пакет оказывается на неожиданной позиции (по коду — дальше начала соединения), она отключается (cutoff). Это «входящая ветка» функции: она только правит пакет и отпускает его дальше ([[Zapret2/жизненный цикл desync-функции#Три типа функций|Три типа функций]]).
|
||
|
||
---
|
||
|
||
## Два стандарта 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)
|
||
|
||
> [!note] Проще говоря
|
||
> Старый стандарт указывает на сам срочный байт и считает от нуля, новый — на байт сразу за ним. Ноль в `th_urp` невалиден по одному из стандартов, но может работать на практике: Linux его принимает, а Windows и BSD ломаются, поэтому режим `b` с его нулём годится только для Linux-серверов ([[#3. urp=b работает только на Linux-серверах|нюанс 3]]). Для `e` и маркеров `oob` пишет позицию плюс единица.
|
||
|
||
---
|
||
|
||
## Режимы urp
|
||
|
||
Аргумент `urp` отвечает на вопрос «в какое место данных вставить лишний байт»: `b` — в самое начало, `e` — в самый конец, маркер — в произвольную позицию ([[Zapret2/desync/multisplit#Маркеры позиций (pos)|подробно про маркеры]]). От выбора зависит и значение `th_urp` ([[#Два стандарта 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).
|
||
|
||
> [!note] Проще говоря
|
||
> HTTP-метод — слово в начале запроса (`GET`, `POST`), а TLS record header — заголовок записи, с которого начинается ClientHello. Лишний байт перед ними сдвигает всё начало, и DPI, не вынимающий срочный байт, может не узнать протокол. Цена — только Linux-серверы (`th_urp=0` невалиден по RFC 1122; [[#3. urp=b работает только на Linux-серверах|нюанс 3]]); для не-Linux серверов статья называет более безопасным `urp=0` ([[#2. Позиция urp=0 (нулевая, 0-based = первый байт)|пример 2]]).
|
||
|
||
### urp=e -- конец
|
||
|
||
OOB-байт вставляется **после последнего байта** payload. `th_urp = len(payload) + 1`.
|
||
|
||
```
|
||
th_urp = len+1
|
||
Payload: [оригинальные данные...][OOB]
|
||
```
|
||
|
||
**Как правило бесполезен для обхода DPI:** DPI получает весь оригинальный payload целиком, OOB-байт идёт после него и не мешает анализу.
|
||
|
||
### urp=маркер -- произвольная позиция
|
||
|
||
`urp` может быть любым [[Zapret2/desync|маркером]], поддерживаемым функцией `resolve_pos`. OOB-байт вставляется в позицию, куда разрешается маркер. `th_urp = позиция + 1` (по RFC 1122).
|
||
|
||
Маркер — это не число, а имя места в запросе: программа сама находит, где в данном ClientHello или HTTP-запросе начинается имя хоста, где оно кончается, где середина домена второго уровня, и подставляет позицию в байтах (`resolve_pos`; [[Zapret2/desync/multisplit#Как маркеры разрешаются в коде|как это делается]]). Домен второго уровня (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` — «на байт левее начала имени хоста». Подробнее — в разделах [[Zapret2/desync/multisplit#Арифметика маркеров|Арифметика маркеров]] и [[Zapret2/desync/multisplit#Важные нюансы pos|Важные нюансы pos]].
|
||
|
||
**Пример:** `urp=midsld` -- OOB-байт вставляется посередине SLD. DPI видит домен с лишним байтом посередине.
|
||
|
||
**Если маркер не разрешается** (например, `midsld` для unknown payload), функция логирует ошибку и выполняет `instance_cutoff`.
|
||
|
||
> [!note] Проще говоря
|
||
> «Не разрешается» значит, что в данных нет места, на которое указывает маркер: у пакета неизвестного протокола нет имени хоста, а значит нет и его середины. Тогда функция пишет ошибку в лог и отключается (cutoff — см. [[#Фаза 3: cutoff|Фаза 3]]).
|
||
|
||
---
|
||
|
||
## Полный список аргументов
|
||
|
||
Формат вызова:
|
||
|
||
```
|
||
--lua-desync=oob[:arg1[=val1][:arg2[=val2]]...]
|
||
```
|
||
|
||
**Важно:** у oob **нет** фильтров `dir` и `payload`. Функция не может быть отфильтрована по payload, поскольку после начала модификации TCP handshake (сдвиг sequence) соскок невозможен -- иначе поедут sequence numbers. Направление обрабатывается автоматически: исходящие -- вставка OOB, входящие -- коррекция ack.
|
||
|
||
> [!note] Проще говоря
|
||
> Фильтр `dir` («в какую сторону») и фильтр `payload` («для какого типа данных») в других функциях описаны в [[Zapret2/desync/multisplit#B) Standard direction|B) Standard direction]] и [[Zapret2/desync/multisplit#C) Standard payload|C) Standard payload]] у multisplit. У `oob` их нет, потому что после сдвига seq на SYN она обязана довести соединение до конца. Направление она определяет сама: исходящие — вставка байта, входящие — правка ack. Отбирать соединения приходится профилем ([[Zapret2/filter|фильтры профиля]]), см. [[#4. Нет фильтрации по payload|нюанс 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`) или [[Zapret2/desync|маркеры]]
|
||
- **По умолчанию:** `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 прочитал ([[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]). L3 — сетевой уровень (заголовок IP), L4 — транспортный (заголовок TCP). Тот же набор параметров описан в [[Zapret2/desync/multisplit#D) Standard fooling|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).
|
||
|
||
> [!note] Проще говоря
|
||
> В других техниках порча безопасна, потому что портят фейк, а настоящие данные идут отдельно. В `oob` портить можно только единственный настоящий пакет с данными: если сервер его отбросит, запрос до него просто не дойдёт.
|
||
|
||
---
|
||
|
||
### C) Standard ipid
|
||
|
||
IP ID — числовой идентификатор пакета в IP-заголовке; аргументы решают, как его нумеровать. Тот же набор описан в [[Zapret2/desync/multisplit#E) Standard ipid|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. Тот же набор описан в [[Zapret2/desync/multisplit#F) Standard ipfrag|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 — сборка сырого пакета из полей перед отправкой. Тот же набор описан в [[Zapret2/desync/multisplit#G) Standard reconstruct|G) Standard reconstruct]] у multisplit.
|
||
|
||
| Параметр | Описание |
|
||
|:---------|:---------|
|
||
| `badsum` | Испортить L4 (TCP) checksum при реконструкции raw-пакета. Сервер отбросит такой пакет |
|
||
|
||
**Предупреждение:** `badsum` в oob сделает OOB-пакет невалидным -- сервер его отбросит, OOB-байт не будет вставлен, а sequence останутся сдвинутыми. TCP-соединение будет сломано. Не используйте `badsum` в oob в боевых профилях.
|
||
|
||
> [!note] Проще говоря
|
||
> Checksum — контрольная сумма, по которой получатель проверяет целость TCP-пакета; `badsum` портит её нарочно. В фейках это удобно: сервер отбрасывает обманку. У `oob` отдельного фейка нет, и порча суммы убила бы единственный настоящий пакет.
|
||
|
||
---
|
||
|
||
### F) Standard rawsend
|
||
|
||
Rawsend — отправка сформированного «сырого» пакета в сеть помимо обычной очереди перехваченных пакетов. Тот же набор описан в [[Zapret2/desync/multisplit#H) Standard 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 она не работает вовсе, а если инстанс подключился к уже установленному соединению, он сразу отключается.
|
||
|
||
> [!note] Проще говоря
|
||
> Общий скелет — одинаковый порядок действий у всех функций дурения; `oob` выполняет их не все и не так, как остальные. Вместо фильтра направления она смотрит позицию пакета, вместо штатных проверок (гвардов) — позицию и непустой payload. `desync.track` — запись о текущем соединении в системе отслеживания (conntrack), в ней `oob` держит своё состояние (`track.lua_state`). Стадии подробнее: [[Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff|1]], [[Zapret2/жизненный цикл desync-функции#Стадия 4. Выбор данных: blob → reasm → payload|4]], [[Zapret2/жизненный цикл desync-функции#Стадия 5. Три гварда|5]], [[Zapret2/жизненный цикл desync-функции#Стадия 6. Развилка перепроигрывания (replay)|6]], [[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|8]]; сводка отличий по всем функциям — [[Zapret2/жизненный цикл desync-функции#Карта отличий по всем функциям|Карта отличий по всем функциям]].
|
||
|
||
---
|
||
|
||
## Псевдокод алгоритма
|
||
|
||
Тело функции целиком, включая общие для всех техник стадии — они пронумерованы так же, как в [[жизненный цикл desync-функции]]:
|
||
|
||
```lua
|
||
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
|
||
|
||
> [!note] Проще говоря
|
||
> Reasm («повторная сборка») — целый запрос, собранный движком из нескольких пакетов; replay — прогон придержанных частей через функции; MSS — наибольший объём данных в одном TCP-сегменте. Если запрос не влез в один пакет, `oob` вставляет байт в собранное целое и сама режет результат на сегменты. Общая механика описана в [[Zapret2/desync/multisplit#Поведение при replay / reasm|Поведение при replay / reasm]] и [[Zapret2/desync/multisplit#Автосегментация по MSS|Автосегментация по 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-части.
|
||
|
||
> [!note] Проще говоря
|
||
> Флаг `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.
|
||
|
||
> [!note] Проще говоря
|
||
> `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-пакетов.
|
||
|
||
> [!note] Проще говоря
|
||
> `--in-range=-s1` пускает `oob` к самому началу соединения: без этого она не увидит SYN и откажется работать. Формат `--in-range` разобран в заметке [[Zapret2/out-range]]. `--wf-tcp-in` — параметр перехвата входящих TCP-портов на Windows ([[Zapret2/wf]]; сами режимы перехвата — в [[Zapret2/windivert|заметке про WinDivert]]).
|
||
|
||
### 2. Для server-first протоколов нужен --wf-tcp-in (Windows)
|
||
|
||
Для протоколов, где сервер отправляет данные **до** первого запроса клиента (например, SMTP, FTP, SSH), требуется разрешить **все** входящие пакеты до первого исходящего с данными. На Windows это означает обязательный `--wf-tcp-in`.
|
||
|
||
> [!note] Проще говоря
|
||
> Server-first протоколы — те, где первым говорит сервер: баннер SMTP, FTP или SSH приходит раньше любого запроса клиента. `oob` должна видеть все входящие пакеты до первого исходящего с данными, поэтому на Windows входящие приходится разрешать целиком через `--wf-tcp-in` ([[Zapret2/wf]]).
|
||
|
||
### 3. urp=b работает только на Linux-серверах
|
||
|
||
Значение `th_urp=0` невалидно по RFC 1122. Linux-серверы его обрабатывают корректно (удаляют OOB-байт), но Windows и BSD серверы **ломаются** -- соединение может разорваться или данные будут повреждены. Используйте `urp=b` только если уверены, что целевой сервер работает на Linux.
|
||
|
||
> [!note] Проще говоря
|
||
> Если не уверены, что сервер работает на Linux, `urp=b` не используйте; для не-Linux серверов в [[#2. Позиция urp=0 (нулевая, 0-based = первый байт)|примере 2]] безопаснее назван `urp=0`. О самих стандартах — [[#Два стандарта th_urp|Два стандарта th_urp]].
|
||
|
||
### 4. Нет фильтрации по payload
|
||
|
||
`oob` **не может** быть отфильтрован по типу payload (`payload=tls_client_hello` и т.д.). Причина: модификация начинается на SYN (до появления любого payload). После сдвига sequence "соскочить" невозможно -- если oob не вставит байт, sequence будут сдвинуты, и TCP-соединение сломается.
|
||
|
||
Фильтрация возможна только на уровне профиля (`--filter-tcp`, `--hostlist` через `--ipcache-hostname` и т.д.).
|
||
|
||
> [!note] Проще говоря
|
||
> Профиль — набор правил для определённого трафика ([[Zapret2/profile#Анатомия профиля|Анатомия профиля]]); отбирают трафик фильтрами вроде `--filter-tcp` ([[Zapret2/filter|фильтры профиля]]) и списками доменов ([[hostlist]]). Тип payload (`tls_client_hello`, `http_req` и другие) — то, как движок распознаёт протокол; подробнее в заметке [[payload]]. Причина запрета — сдвиг seq на SYN: [[#Фаза 1: перехват 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 **невозможно**.
|
||
|
||
> [!note] Проще говоря
|
||
> Соседние функции отправляют собственные копии данных: [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]] и [[Zapret2/desync/multidisorder#Зачем нужен multidisorder|multidisorder]] режут запрос на сегменты, [[Zapret2/desync/fakedsplit#Зачем нужен fakedsplit|fakedsplit]] и [[Zapret2/desync/fakeddisorder#Зачем нужен fakeddisorder|fakeddisorder]] подмешивают к разрезу фейки. Ни одна из них не знает о срочном байте и сдвиге seq, поэтому сервер получил бы дубли с рассогласованной нумерацией. С [[Zapret2/desync/fake#Зачем нужен fake|fake]] в разных профилях `oob` ужиться может: [[#10. Комбинация: fake + oob (в разных профилях)|пример 10]].
|
||
|
||
### 7. Длящаяся десинхронизация и переключение профилей
|
||
|
||
`oob` -- **длящаяся десинхронизация**: между SYN (сдвиг seq) и первым payload (вставка OOB) проходит несколько пакетов. Если за это время происходит переключение профиля (например, `--ipcache-hostname` разрешил домен и переключил на другой профиль), новый профиль **должен также содержать oob**. Иначе:
|
||
|
||
- Старый профиль сдвинул seq на SYN
|
||
- Новый профиль не знает об этом и не вставит OOB
|
||
- TCP-соединение сломано (sequence рассогласованы)
|
||
|
||
**Решение:** дублировать `oob` во всех профилях, которые могут получить управление в процессе handshake.
|
||
|
||
> [!note] Проще говоря
|
||
> Длящаяся десинхронизация — та, что растянута на несколько пакетов соединения: сначала SYN, потом данные. Профиль — набор правил для определённого трафика ([[Zapret2/profile#Анатомия профиля|Анатомия профиля]]); движок может сменить профиль соединения по ходу, например когда кэш `--ipcache-hostname` выдал имя сайта ([[Zapret2/filter#Как выбирается профиль|Как выбирается профиль]]). Если новый профиль не содержит `oob`, сдвиг seq, сделанный старым, никто не компенсирует. Поэтому строку `oob` дублируют во всех профилях, которые могут получить управление на рукопожатии; пример — [[#14. Дублирование oob при переключении профилей|14]].
|
||
|
||
### 8. Требуется conntrack (tracking)
|
||
|
||
`oob` использует `desync.track` для хранения состояния (видели ли SYN, текущая позиция). Без tracking функция немедленно возвращает `nil`.
|
||
|
||
> [!note] Проще говоря
|
||
> Tracking (conntrack) — система, которая запоминает состояние каждого соединения между пакетами. `oob` хранит там, видела ли она SYN и на каком пакете соединения сейчас находится. Без такой памяти функция сразу выходит и ничего не делает. Устройство записи `track` описано в [[Zapret2/структура desync и диссекта|структуре desync и диссекта]].
|
||
|
||
### 9. Соединение должно быть перехвачено с SYN
|
||
|
||
Если oob получает управление **после** SYN (например, после перескока профиля или при подключении к уже установленному соединению), она немедленно выполняет cutoff. Специальный флаг `lua_state[key.."_syn"]` гарантирует, что oob видела SYN самого первого пакета.
|
||
|
||
### 10. Фильтрация по хостлистам
|
||
|
||
Фильтрация через `--hostlist` возможна **только** при использовании `--ipcache-hostname`. Если в кэше хоста ещё нет, и профиль получает управление не с начала соединения -- срабатывает cutoff (см. п.9).
|
||
|
||
> [!note] Проще говоря
|
||
> Hostlist — файл со списком доменов ([[hostlist]]): профиль срабатывает только для сайтов из списка. Имя сайта из SNI или `Host:` появляется лишь в первом пакете с данными, то есть позже SYN, а `oob` должна начать раньше; поэтому список доменов с `oob` работает только вместе с кэшем `--ipcache-hostname` (в пресетах он включается строкой `--ipcache-hostname=1`, см. [[Zapret2/preset|пресет]]).
|
||
|
||
### 11. OOB-байт ровно 1
|
||
|
||
OOB-байт **обязательно** 1 байт. Попытка задать `char` длиной != 1 вызовет Lua error.
|
||
|
||
### 12. Пустой ACK не ломает поток
|
||
|
||
Если первый исходящий пакет с данными оказался пустым ACK (пустой payload), oob просто уменьшает seq и возвращает `VERDICT_MODIFY`. Вставка OOB произойдёт при появлении следующего непустого пакета.
|
||
|
||
---
|
||
|
||
## Практические примеры
|
||
|
||
Пятнадцать примеров для [[Zapret2/profile#Анатомия профиля|профиля]]. Во всех строках с `oob` есть `--in-range=-s1`: без него функция не включится ([[#1. Обязательно --in-range=-s1|нюанс 1]]). Если `urp` не указан, действует `b`. Порядок — от простого к комбинациям с другими опциями; самый популярный вариант — пример 4.
|
||
|
||
### 1. Минимальный: urp=b (по умолчанию), OOB=0x00
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob
|
||
```
|
||
|
||
Вставляет `0x00` перед первым байтом payload. Работает только на Linux-серверах.
|
||
|
||
### 2. Позиция urp=0 (нулевая, 0-based = первый байт)
|
||
|
||
```bash
|
||
--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|Два стандарта th_urp]].
|
||
|
||
### 3. Позиция urp=2 (после первого байта)
|
||
|
||
```bash
|
||
--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 (самый популярный вариант)
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob:urp=midsld
|
||
```
|
||
|
||
Для `example.com`: OOB вставляется посередине `example`. DPI видит `exa[OOB]mple.com`.
|
||
|
||
Маркер `midsld` разобран в [[Zapret2/desync/multisplit#Типы маркеров|Типы маркеров]]; схема этого варианта — в анимации в начале статьи ([[#Коротко и по-простому|Коротко и по-простому]]).
|
||
|
||
### 5. Произвольный OOB-символ
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob:char=Z:urp=midsld
|
||
```
|
||
|
||
OOB-байт = ASCII 'Z' вместо `0x00`.
|
||
|
||
Аргумент `char` — один символ; описание — в [[#A) Собственные аргументы oob|Собственные аргументы oob]].
|
||
|
||
### 6. OOB-байт по числовому значению
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob:byte=255:urp=midsld
|
||
```
|
||
|
||
OOB-байт = `0xFF`.
|
||
|
||
### 7. Для HTTPS (TLS) с фильтром по порту
|
||
|
||
```bash
|
||
--filter-tcp=443 --in-range=-s1 --lua-desync=oob:urp=midsld
|
||
```
|
||
|
||
Применяется только к HTTPS-соединениям.
|
||
|
||
`--filter-tcp=443` — фильтр профиля по порту: 443 — порт HTTPS ([[Zapret2/filter|фильтры профиля]]).
|
||
|
||
### 8. Для HTTP и HTTPS одновременно
|
||
|
||
```bash
|
||
--filter-tcp=80,443 --in-range=-s1 --lua-desync=oob:urp=midsld
|
||
```
|
||
|
||
Порты 80 и 443 — HTTP и HTTPS: `oob` вставляет байт и в HTTP-запрос, и в TLS ClientHello.
|
||
|
||
### 9. С хостлистом и ipcache
|
||
|
||
```bash
|
||
--filter-tcp=443 --hostlist=blocked.txt --ipcache-hostname \
|
||
--in-range=-s1 --lua-desync=oob:urp=midsld
|
||
```
|
||
|
||
Фильтрация по хостлисту. `--ipcache-hostname` обязателен для работы хостлиста с oob.
|
||
|
||
Проще говоря: [[hostlist|хостлист]] — файл со списком доменов; кэш `--ipcache-hostname` нужен, чтобы хостлист вообще работал с `oob` (нюанс [[#10. Фильтрация по хостлистам|10]]).
|
||
|
||
### 10. Комбинация: fake + oob (в разных профилях)
|
||
|
||
```bash
|
||
# Профиль 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, порча, чтобы сервер фейк отбросил ([[Zapret2/desync/fake#blob — источник фейковых данных|blob]], [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]); во втором профиле `oob` работает с настоящим запросом.
|
||
|
||
### 11. Перебор позиций urp (как в blockcheck2)
|
||
|
||
```bash
|
||
# 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`. Как честно проверять результат — [[Zapret2/verify-strategy#Короткий чек-лист честной проверки|чек-лист честной проверки]].
|
||
|
||
### 12. С IP-фрагментацией
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob:urp=midsld:ipfrag:ipfrag_pos_tcp=32
|
||
```
|
||
|
||
OOB-пакет дополнительно фрагментируется на IP-уровне.
|
||
|
||
### 13. С IPv6 extension headers
|
||
|
||
```bash
|
||
--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|B) Standard fooling]]).
|
||
|
||
### 14. Дублирование oob при переключении профилей
|
||
|
||
```bash
|
||
# Профиль "по умолчанию" (до разрешения 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. Длящаяся десинхронизация и переключение профилей|нюанс 7]]).
|
||
|
||
### 15. OOB в позиции host (начало hostname)
|
||
|
||
```bash
|
||
--in-range=-s1 --lua-desync=oob:urp=host:byte=0
|
||
```
|
||
|
||
Вставляет нулевой байт прямо перед hostname. DPI видит `\x00example.com` вместо `example.com`.
|
||
|
||
Маркер `host` — первый байт имени хоста; `byte=0` — то же, что значение по умолчанию (`0x00`).
|
||
|
||
---
|
||
|
||
## 📚 См. также
|
||
|
||
- [[Zapret2/desync|desync]] — обзор всех функций `--lua-desync` и общий контракт: кто вызывает функцию, что ей передаёт и как складываются вердикты
|
||
- [[Zapret2/жизненный цикл desync-функции|жизненный цикл desync-функции]] — восемь стадий, общих для всех техник дурения; отдельно разобрано, чем от них отличается `oob`
|
||
- [[Zapret2/структура desync и диссекта|структура desync и диссекта]] — поля `th_flags`, `th_urp` и работа с sequence numbers
|
||
- [[Zapret2/out-range|out-range]] — формат `--in-range` и `--out-range`, в том числе `-s1`
|
||
- [[multisplit]] · [[multidisorder]] · [[fakedsplit]] · [[fakeddisorder]] — функции отправки payload, с которыми `oob` в одном потоке несовместима
|
||
- [[syndata]] — данные в SYN, другая техника, работающая с рукопожатием
|
||
- [[tcpseg]] — отправка диапазона данных между двумя маркерами
|
||
- [[fake]] — отдельные фейковые пакеты; их можно держать в соседнем профиле
|
||
- [[payload]] — как распознаются типы данных и почему у `oob` нет фильтра `payload`
|
||
- [[Zapret2/filter|фильтры профиля]] · [[hostlist]] — как отбирать соединения на уровне профиля
|
||
- [[Zapret2/wf|WinDivert-фильтры]] — `--wf-tcp-in` для протоколов, где сервер говорит первым
|
||
- [[Zapret2/ts-and-fooling|ts-and-fooling]] — что такое fooling и почему в `oob` он почти не нужен
|
||
- [[Zapret2/verify-strategy|verify-strategy]] — как честно проверить подобранную позицию `urp`
|
||
- [[Zapret2/profile|profile]] · [[Zapret2/preset|preset]] — где `--lua-desync` живёт среди остальных настроек
|
||
- [[DPI/DPI|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.
|
||
|
||
---
|
||
|
||
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
|
||
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: [исходник этой заметки](https://git2.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/Zapret2/desync/oob.md) · [скачать весь репозиторий одним zip-архивом](https://git2.zapret.moe/zapretdiscordyoutube/todo/archive/main.zip).
|