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>
890 lines
86 KiB
Markdown
890 lines
86 KiB
Markdown
---
|
||
date: 2026-02-17
|
||
tags:
|
||
- zapret
|
||
- zapret2
|
||
- nfqws2
|
||
- lua
|
||
- lua-desync
|
||
- antidpi
|
||
- tcp
|
||
- syndata
|
||
- tls_mod
|
||
aliases:
|
||
- syndata
|
||
description: "Справочник стратегии syndata в Zapret 2 (nfqws2): payload в TCP SYN, аргументы blob и tls_mod, отличия от fake, комбинации и миграция с nfqws1."
|
||
---
|
||
|
||
> [!mirror] Резервное зеркало
|
||
> Актуальная версия этой страницы — на основной вики: [wiki2.zapret.moe/Zapret2/desync/syndata](https://wiki2.zapret.moe/Zapret2/desync/syndata)
|
||
|
||
# `syndata` --- payload в TCP SYN (zapret2 / nfqws2)
|
||
|
||
## Коротко и по-простому
|
||
|
||
Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке `Host:` запроса. ТСПУ (Технические средства противодействия угрозам — оборудование [[DPI/DPI|DPI]], то есть глубокой инспекции трафика, которое стоит у провайдера) ищет это имя в первом пакете соединения с данными и по нему решает, что делать с соединением дальше.
|
||
|
||
Но ClientHello уходит не первым. Перед данными TCP устраивает «рукопожатие» (handshake) из трёх пакетов. Первый, SYN, клиент отправляет, чтобы попросить о соединении: в обычном виде в нём нет данных, только заголовки с флагом SYN. Сервер отвечает пакетом SYN-ACK, клиент подтверждает его пакетом ACK, и только после этого браузер отправляет ClientHello. Имени сайта в SYN нет, поэтому на этом этапе его не знает и сам Zapret: чем это грозит хостлистам, описано в разделе [[#Работа с хостлистами|Работа с хостлистами]].
|
||
|
||
> [!example] Рукопожатие как звонок
|
||
> SYN — «Алло, можем говорить?», SYN-ACK — «Да, говори», ACK — «Ок». Если начать рассказывать своё дело ещё до ответа «Да, говори», собеседник, скорее всего, пропустит эти слова мимо ушей. Примерно так обычно ведёт себя и сервер с данными, которые пришли в SYN.
|
||
|
||
Дальше данные идут кусками — сегментами. У каждого байта в соединении есть порядковый номер (sequence number, коротко seq), и сервер раскладывает принятые куски в свой буфер по этим номерам; почему это гарантирует именно TCP, разобрано в нюансе [[Zapret2/desync/multisplit#1. Работает только с TCP — и почему|«Работает только с TCP» в статье про multisplit]]. Фейк — это пакет-обманка с ложными данными, например с поддельным ClientHello. Обычно фейк «портят»: искажают его заголовки так, чтобы сервер пакет отбросил, а DPI прочитал. Такая порча называется fooling, подробно — в разделе [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]. У `syndata` с fooling своя особенность: портить приходится пакет, который сервер обязан принять (см. [[#2. Fooling может сломать handshake|нюанс 2]]).
|
||
|
||
`syndata` — функция «нулевой фазы»: она работает до рукопожатия, на самом первом пакете соединения. Движок Zapret 2 (`nfqws2`) перехватывает исходящий SYN, а `syndata` делает четыре шага. Сначала копирует перехваченный SYN, сам оригинал при этом не трогая. Потом дописывает к копии данные из `blob` — заготовленного куска данных с именем, например стандартного TLS-фейка `fake_default_tls` (про блобы — [[Zapret2/blob|Блобы]]); если `blob` не указан, берутся 16 нулевых байт. Затем при желании портит копию по правилам fooling и правит TLS-поля через `tls_mod`, например заменяет SNI в фейке. И наконец отправляет копию вместо оригинала, а оригинал выбрасывает. Как только по соединению приходит пакет без флага SYN, то есть рукопожатие прошло, `syndata` отключает себя для этого соединения.
|
||
|
||
Формально TCP разрешает данные в SYN (RFC 793), но большинство TCP-стеков завершают рукопожатие ответом SYN-ACK и данные из SYN игнорируют: соединение ещё не установлено, и payload отбрасывается. Настоящий ClientHello браузер отправит после рукопожатия обычным порядком, и сервер прочитает именно его. DPI может повести себя иначе: принять содержимое SYN за начало потока данных, записать ложные сведения о соединении (например, фейковое имя сайта) и пропустить настоящие данные, не проверив имя в них. Это «может»: как поведёт себя конкретная система, зависит от её устройства.
|
||
|
||
Команда из быстрого старта `syndata:blob=fake_default_tls` приклеивает к первому пакету соединения (SYN) фейковый ClientHello с именем `www.w3.org`. Его байты приходятся на те же позиции потока, что и будущий настоящий ClientHello. Большинство TCP-стеков отвечают SYN-ACK и данные из SYN не принимают, а ТСПУ может счесть их началом потока и не проверить имя в настоящем ClientHello, который уходит после рукопожатия. Как читать схему: строки — пакеты в порядке отправки, подписанные в тех же обозначениях, что и таблицы в этой статье; по горизонтали — место данных в TCP-потоке (номер последовательности, seq); в колонке справа — что сделал с пакетом сервер; внизу — буфер сервера, в который принятые куски встают по своим seq.
|
||
|
||
![[desync-anim-syndata.svg|Анимация syndata: фейковый ClientHello приклеен к SYN, сервер отвечает SYN-ACK и принимает только настоящий ClientHello]]
|
||
|
||
Самая короткая команда включает `syndata` без аргументов: в SYN уходят 16 нулевых байт. Строку `--lua-desync=…` пишут в [[Zapret2/profile#Анатомия профиля|профиль]] — набор правил Zapret 2 для определённого трафика:
|
||
|
||
```bash
|
||
--lua-desync=syndata
|
||
```
|
||
|
||
Вариант со схемы, `syndata:blob=fake_default_tls`, кладёт в SYN TLS-фейк. Остальные готовые команды собраны в разделах [[#Быстрый старт|Быстрый старт]] и [[#Практические примеры|Практические примеры]].
|
||
|
||
Функцию стоит пробовать, если DPI начинает следить за соединением уже с SYN и не реагирует на фейки после рукопожатия, а также когда нужно «отравить» DPI ложными данными до того, как пойдёт реальный трафик. Ограничения у неё такие. Данные в SYN должны поместиться в один пакет: разрезать их на TCP-сегменты на этом этапе нельзя ([[#1. Payload должен помещаться в один пакет|нюанс 1]]). Fooling здесь бьёт по SYN, который сервер должен принять, поэтому часть параметров, например `badsum` или `tcp_seq`, ломает рукопожатие. Хостлисты напрямую не работают: имени сайта ещё нет, и остаётся только `--ipcache-hostname`, а первое соединение к каждому IP пройдёт без `syndata` ([[#Работа с хостлистами|Работа с хостлистами]]). Ту же нулевую фазу использует [[Zapret2/desync|wssize]], который управляет размером окна TCP (сколько байт получатель готов принять) и заставляет сервер слать маленькие сегменты; почему из-за нулевой фазы `--wssize` считается крайней мерой, объяснено в [[Zapret/wssize#Почему wssize — плохо|отдельной заметке]].
|
||
|
||
Ближайший сосед `syndata` — [[Zapret2/desync/fake#Зачем нужен fake|fake]]. Он тоже подсовывает DPI ложные данные, но отдельным пакетом уже после рукопожатия, оригинал при этом уходит дальше, а fooling нужен, чтобы сервер фейк отбросил. `syndata` кладёт данные в сам SYN и заменяет им оригинал. Все различия сведены в таблицу в разделе [[#Отличия от fake|Отличия от fake]], а с точки зрения `fake` — в [[Zapret2/desync/fake#Сравнение с syndata и fakedsplit|Сравнение с syndata и fakedsplit]]. С техниками сегментации `syndata` обычно стоит в одной цепочке: он занимается SYN, а [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]] режет настоящий ClientHello уже после рукопожатия (см. [[#Комбинирование с другими функциями|Комбинирование с другими функциями]]). Остальные функции, которые работают с данными после рукопожатия, — [[Zapret2/desync/multidisorder#Обратный порядок отправки — суть disorder|multidisorder]], [[Zapret2/desync/fakedsplit#Зачем нужен fakedsplit|fakedsplit]] и [[Zapret2/desync/fakeddisorder#Зачем нужен fakeddisorder|fakeddisorder]]. Рабочую комбинацию для своего провайдера подбирают и проверяют по [[Zapret2/verify-strategy#Короткий чек-лист честной проверки|чек-листу честной проверки]].
|
||
|
||
---
|
||
|
||
## Оглавление
|
||
|
||
- [Коротко и по-простому](#коротко-и-по-простому)
|
||
- [Справка: где функция в коде и родственники](#справка-где-функция-в-коде-и-родственники)
|
||
- [Зачем нужен syndata](#зачем-нужен-syndata)
|
||
- [Быстрый старт](#быстрый-старт)
|
||
- [Принцип работы](#принцип-работы)
|
||
- [Что такое SYN с payload](#что-такое-syn-с-payload)
|
||
- [Нулевая фаза](#нулевая-фаза)
|
||
- [Логика обработки пакетов](#логика-обработки-пакетов)
|
||
- [Полный список аргументов](#полный-список-аргументов)
|
||
- [A) Собственные аргументы syndata](#a-собственные-аргументы-syndata)
|
||
- [B) Standard fooling](#b-standard-fooling)
|
||
- [C) Standard reconstruct](#c-standard-reconstruct)
|
||
- [D) Standard rawsend](#d-standard-rawsend)
|
||
- [E) Standard ipfrag](#e-standard-ipfrag)
|
||
- [tls_mod --- модификация TLS в syndata](#tls_mod-----модификация-tls-в-syndata)
|
||
- [Работающие модификации](#работающие-модификации)
|
||
- [dupsid и padencap --- молчаливое игнорирование](#dupsid-и-padencap-----молчаливое-игнорирование)
|
||
- [Почему dupsid и padencap не работают](#почему-dupsid-и-padencap-не-работают)
|
||
- [Место в общем скелете](#место-в-общем-скелете)
|
||
- [Псевдокод алгоритма](#псевдокод-алгоритма)
|
||
- [Отличия от fake](#отличия-от-fake)
|
||
- [Работа с хостлистами](#работа-с-хостлистами)
|
||
- [Комбинирование с другими функциями](#комбинирование-с-другими-функциями)
|
||
- [Нюансы и подводные камни](#нюансы-и-подводные-камни)
|
||
- [Миграция с nfqws1](#миграция-с-nfqws1)
|
||
- [Практические примеры](#практические-примеры)
|
||
- [📚 См. также](#-см-также)
|
||
|
||
---
|
||
|
||
## Справка: где функция в коде и родственники
|
||
|
||
Паспорт функции для тех, кто сверяется с исходниками zapret2: в каком файле и на какой строке лежит код, какой ключ первой версии zapret она заменяет и как объявлена.
|
||
|
||
**Файл:** `lua/zapret-antidpi.lua:395`
|
||
**nfqws1 эквивалент:** `--dpi-desync=syndata`
|
||
**Сигнатура:** `function syndata(ctx, desync)`
|
||
|
||
`syndata` --- стратегия "нулевой фазы" в zapret2. Она добавляет произвольный payload в TCP SYN-пакет, применяет модификации (fooling, tls_mod) и отправляет его **вместо** оригинального SYN. Оригинальный пакет дропается (`VERDICT_DROP`). Работает **до** установления TCP-соединения --- на этапе, когда клиент ещё только отправляет SYN.
|
||
|
||
Родственные/сопутствующие функции: [[fake]] (фейковый пакет после SYN), [[multisplit]] (TCP-сегментация), [[Zapret2/desync|wssize]] (управление размером окна), [[multidisorder]], [[fakedsplit]], [[fakeddisorder]].
|
||
|
||
Общий для всех техник дурения порядок работы — восемь стадий от отсева чужого транспорта до вердикта — разобран в [[жизненный цикл desync-функции]]; здесь описано только то, чем `syndata` от этого скелета отличается.
|
||
|
||
> [!note] Проще говоря
|
||
> «Нулевая фаза» — этап до завершения рукопожатия, когда у приложения ещё нет данных для отправки; подробнее в разделе [[#Нулевая фаза|Нулевая фаза]]. Fooling — намеренная порча заголовков пакета, разобранная в [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]. `VERDICT_DROP` — решение «перехваченный пакет не отправлять»: вместо оригинала уходит подделанная копия; как движок складывает такие решения от разных функций, описано в [[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадии 8 жизненного цикла]]. Блоб (`blob`) — заранее заготовленные данные, которые можно подсунуть в пакет; см. [[Zapret2/blob|Блобы]].
|
||
|
||
---
|
||
|
||
## Зачем нужен syndata
|
||
|
||
Некоторые DPI начинают анализировать соединение уже с первого пакета --- с SYN. Они запоминают IP и порт клиента, ждут данные и сопоставляют их с сигнатурами. `syndata` атакует этот механизм на самом раннем этапе:
|
||
|
||
1. **Ложные данные в SYN:** DPI видит SYN-пакет с payload (например, фейковый TLS ClientHello) и может принять его за начало реального соединения
|
||
2. **Сбой трекинга:** если DPI привязывает сессию к содержимому первого пакета, подменный SYN может направить трекинг по ложному пути
|
||
3. **Обход до handshake:** воздействие происходит до TCP handshake --- DPI ещё не видел реальных данных приложения
|
||
|
||
**Важно:** TCP SYN с payload --- это валидная TCP-операция (RFC 793 разрешает данные в SYN, хотя многие стеки их игнорируют до завершения handshake). Сервер примет SYN, но payload из него обычно отбросит --- DPI же может попытаться его проанализировать.
|
||
|
||
> [!note] Проще говоря
|
||
> Трекинг — слежение DPI за соединением: он запоминает IP и порт клиента, ждёт данные и сопоставляет их с сигнатурами, то есть заранее известными строками. `syndata` подсовывает ему данные раньше настоящих, ещё в SYN. Сервер их обычно не читает: разговор для него ещё не начался. Что такое рукопожатие (handshake) и почему данные в SYN обычно игнорируются, объяснено в начале статьи, в разделе [[#Коротко и по-простому|Коротко и по-простому]]. Слова «может» и «обычно» в тексте выше не случайны: как поведёт себя конкретный DPI или конкретный сервер, зависит от их устройства.
|
||
|
||
---
|
||
|
||
## Быстрый старт
|
||
|
||
Минимально (16 нулевых байт в SYN):
|
||
|
||
```bash
|
||
--lua-desync=syndata
|
||
```
|
||
|
||
С TLS-фейком:
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls
|
||
```
|
||
|
||
С TLS-фейком и модификацией SNI:
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls:tls_mod=rnd,rndsni
|
||
```
|
||
|
||
Типовая комбинация (wssize + syndata + multisplit):
|
||
|
||
```bash
|
||
--lua-desync=wssize:wsize=1:scale=6 \
|
||
--lua-desync=syndata \
|
||
--lua-desync=multisplit:pos=midsld
|
||
```
|
||
|
||
Четыре блока идут от простого к сложному: без аргументов, с TLS-фейком, с TLS-фейком и модификацией SNI и, наконец, цепочка из трёх функций. Аргументы `blob` и `tls_mod` разобраны в разделе [[#A) Собственные аргументы syndata|A) Собственные аргументы syndata]]. Цепочка описана в разделе [[#Типовая цепочка: wssize + syndata + multisplit|Типовая цепочка: wssize + syndata + multisplit]], там же объяснено, почему [[Zapret2/desync|wssize]] стоит первым. Строки `--lua-desync=…` пишут в [[Zapret2/profile#Анатомия профиля|профиль]]; порядок вызовов в цепочке важен, подробнее в [[последовательность аргументов]].
|
||
|
||
---
|
||
|
||
## Принцип работы
|
||
|
||
### Что такое SYN с payload
|
||
|
||
Обычный TCP SYN-пакет не содержит данных --- только заголовки с флагом SYN. `syndata` берёт этот пакет, добавляет в него payload (указанный в `blob` или 16 нулевых байт по умолчанию), применяет модификации и отправляет через raw socket вместо оригинала.
|
||
|
||
```
|
||
Обычный SYN:
|
||
[IP Header][TCP Header (SYN)]
|
||
|
||
SYN после syndata:
|
||
[IP Header][TCP Header (SYN)][PAYLOAD (blob или 16x 0x00)]
|
||
```
|
||
|
||
Сервер получит SYN с данными. Большинство TCP-стеков:
|
||
- Завершат handshake (SYN-ACK), **проигнорировав** payload в SYN
|
||
- Payload будет отброшен, потому что соединение ещё не установлено
|
||
|
||
DPI же может:
|
||
- Проанализировать payload из SYN как начало потока данных
|
||
- Записать ложную информацию о соединении (фейковый SNI, фейковый HTTP Host)
|
||
- Пропустить реальные данные, которые пойдут позже
|
||
|
||
> [!note] Проще говоря
|
||
> Payload — полезные данные пакета без сетевых заголовков. Обычный SYN — «конверт» без письма; `syndata` вкладывает в конверт письмо (содержимое `blob` или 16 нулей) и отправляет через raw socket, то есть сырым пакетом помимо очереди перехваченных пакетов; способы отправки разобраны в разделе [[Zapret2/жизненный цикл desync-функции#Четыре способа выпустить пакет|Четыре способа выпустить пакет]]. Сервер это письмо обычно не прочтёт, а DPI может прочесть.
|
||
|
||
### Нулевая фаза
|
||
|
||
`syndata` --- **стратегия нулевой фазы**. В терминологии zapret это означает:
|
||
|
||
| Фаза | Когда | Что доступно | Примеры функций |
|
||
|:-----|:------|:-------------|:----------------|
|
||
| **Фаза 0 (SYN)** | До TCP handshake | Только SYN-пакет. Нет payload от приложения | `syndata`, [[Zapret2/desync|wssize]] |
|
||
| **Фаза 1 (данные)** | После handshake, первые данные | Реальный payload (TLS ClientHello, HTTP request и т.д.) | [[fake]], [[multisplit]], [[fakedsplit]] |
|
||
|
||
Фаза здесь — этап жизни соединения. В фазе 0 идёт только рукопожатие, и у приложения ещё нет данных. В фазе 1 первые данные уже отправлены, и Zapret видит настоящий ClientHello или HTTP-запрос. Функции фазы 1 ([[fake]], [[multisplit]], [[fakedsplit]]) работают с этими данными, а функции фазы 0 их ещё не видят. К фазе 0 относится и [[Zapret2/desync|wssize]].
|
||
|
||
Следствия нулевой фазы:
|
||
|
||
- **Нет реального payload:** приложение ещё ничего не отправило, поэтому syndata работает только с blob-данными или нулями
|
||
- **Нет hostname:** на этом этапе zapret не знает, к какому домену обращается клиент --- хостлисты работают только через `--ipcache-hostname`
|
||
- **Нет payload filter:** параметры `--payload=...` и `payload=...` не применимы --- SYN-пакет не содержит данных приложения
|
||
- **Воздействие на все ретрансмиссии SYN:** если TCP-стек ретранслирует SYN (timeout), syndata обработает и его
|
||
|
||
> [!note] Проще говоря
|
||
> Пока идёт рукопожатие, Zapret не знает, какой сайт открывает клиент: имя появится только в ClientHello или в HTTP-запросе. Отсюда следствия. Нечего сверять с хостлистом по имени: остаётся кеш `--ipcache-hostname`, см. [[#Работа с хостлистами|Работа с хостлистами]]. Нечего фильтровать по типу данных: фильтр `payload` (см. [[Zapret2/payload|payload]]) для SYN не применим. Ретрансмиссия — повторная отправка SYN, если первый не дошёл или ответ потерялся; `syndata` обработает и её, подробнее в нюансе [[#4. Все ретрансмиссии SYN обрабатываются|4. Все ретрансмиссии SYN обрабатываются]].
|
||
|
||
### Логика обработки пакетов
|
||
|
||
![[wiki-anim-z2-syndata-flow.svg|Схема: логика обработки пакета в syndata]]
|
||
|
||
Ключевые моменты:
|
||
|
||
- Проверяется именно `TH_SYN` без `TH_ACK` --- то есть **только исходящий SYN** от клиента, не SYN+ACK от сервера
|
||
- `deepcopy` --- работа ведётся с **копией** dissect, оригинал не модифицируется
|
||
- Если пакет не SYN (например, ACK, PSH+ACK) --- это значит, что handshake уже прошёл, и syndata отключает себя (`instance_cutoff`)
|
||
- Для ICMP cutoff не выполняется --- ICMP может быть связан с SYN (например, ICMP Destination Unreachable)
|
||
|
||
> [!note] Проще говоря
|
||
> Dissect (диссект) — пакет, разобранный движком на поля: заголовки, флаги, данные; устройство этой таблицы описано в заметке [[Zapret2/структура desync и диссекта|структура desync и диссекта]]. `deepcopy` — полная копия такой таблицы: правки идут в копию, а оригинал остаётся нетронутым. `instance_cutoff` — «этот вызов функции больше не нужен для данного соединения»: после него движок перестаёт вызывать `syndata` для потока, см. [[Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff|стадию 1 жизненного цикла]]. `VERDICT_DROP` — «оригинальный пакет не отправлять». Логика в целом такая: пришёл исходящий SYN, значит подменяем его; пришёл любой другой TCP-пакет, значит рукопожатие уже прошло и функция отключается; ICMP-пакет сигналом к отключению не считается.
|
||
|
||
---
|
||
|
||
## Полный список аргументов
|
||
|
||
Формат вызова:
|
||
|
||
```
|
||
--lua-desync=syndata[:arg1[=val1][:arg2[=val2]]...]
|
||
```
|
||
|
||
Все `val` приходят в Lua как строки. Если `=val` не указан, значение = пустая строка `""` (в Lua это truthy), поэтому флаги пишутся просто как `:tcp_md5`, `:badsum`, `:ipfrag`.
|
||
|
||
**Важно:** syndata **не поддерживает** стандартные аргументы `direction`, `payload` (фильтр) и `ipid`. Это связано с тем, что syndata работает на фазе 0 (SYN), где нет данных приложения, нет понятия "payload type", и `apply_ip_id` не вызывается.
|
||
|
||
> [!note] Проще говоря
|
||
> `direction` — фильтр «какое направление пакетов обрабатывать», `payload` — фильтр по типу данных, `ipid` — управление полем IP ID в заголовке пакета. В SYN нет ни данных приложения, ни выбора направления, поэтому этих аргументов у `syndata` нет; причины разобраны в нюансе [[#3. Нет direction, payload filter, ipid|3. Нет direction, payload filter, ipid]]. Стандартные блоки B–E ниже общие для многих функций; их описание для другой функции можно сверить, например, с разделом [[Zapret2/desync/fake#Полный список аргументов|Полный список аргументов]] статьи про fake.
|
||
|
||
### A) Собственные аргументы syndata
|
||
|
||
#### `blob`
|
||
|
||
- **Формат:** `blob=<blobName>`
|
||
- **Тип:** имя blob-переменной
|
||
- **По умолчанию:** 16 нулевых байт (`\x00` x 16)
|
||
- **Описание:** Payload, который будет добавлен в SYN-пакет. Должен помещаться в один пакет --- сегментация **невозможна** (используется `rawsend_dissect_ipfrag`, а не `rawsend_payload_segmented`). Если blob не задан, отправляются 16 нулевых байт
|
||
- **Примеры:**
|
||
- `blob=fake_default_tls` --- стандартный TLS-фейк из zapret
|
||
- `blob=fake_default_http` --- стандартный HTTP-фейк
|
||
- `blob=0xDEADBEEF` --- inline hex
|
||
- `blob=my_custom_payload` --- предзагруженный blob
|
||
- без `blob=` --- 16 нулей (дефолт)
|
||
|
||
> [!note] Проще говоря
|
||
> Blob — заранее заготовленный кусок данных с именем. Какие бывают стандартные и как объявить свой, объяснено в заметке [[Zapret2/blob|Блобы]] и в разделе [[Zapret2/desync/fake#blob — источник фейковых данных|blob — источник фейковых данных]] статьи про fake. Inline hex — байты, записанные прямо в команде (`0xDEADBEEF`), без отдельного файла. Сегментация — разрезание данных на несколько TCP-пакетов, как в [[multisplit]]; на этапе SYN она невозможна, поэтому весь blob должен уйти одним пакетом ([[#1. Payload должен помещаться в один пакет|нюанс 1]]).
|
||
|
||
#### `tls_mod`
|
||
|
||
- **Формат:** `tls_mod=<mod1[,mod2,...]>`
|
||
- **Тип:** строка со списком модификаций через запятую
|
||
- **По умолчанию:** не задан
|
||
- **Описание:** Модификации TLS-данных в payload перед отправкой. Применяется **после** `apply_fooling`. Подробности --- в разделе [tls_mod](#tls_mod-----модификация-tls-в-syndata)
|
||
- **Работающие значения:** `rnd`, `rndsni`, `sni=<str>` (включая `sni=%var`)
|
||
- **Молча игнорируемые:** `dupsid`, `padencap`
|
||
- **Примеры:**
|
||
- `tls_mod=rnd` --- рандомизация TLS-полей
|
||
- `tls_mod=rndsni` --- рандомизация SNI
|
||
- `tls_mod=sni=google.com` --- замена SNI
|
||
- `tls_mod=rnd,rndsni,sni=google.com` --- комбинация
|
||
- `tls_mod=dupsid` --- **не даст ошибки, но молча проигнорируется**
|
||
|
||
> [!note] Проще говоря
|
||
> `tls_mod` правит поля TLS-фейка перед отправкой: например, подменяет имя сайта (SNI) или заменяет случайные значения `session_id` и `random`. Работает только часть значений, и почему именно эта, объяснено в разделе [[#tls_mod --- модификация TLS в syndata|tls_mod --- модификация TLS в syndata]]. Как `tls_mod` устроен в `fake`, где настоящий ClientHello уже есть, описано в разделах [[Zapret2/desync/fake#tls_mod — модификации TLS в фейке|tls_mod — модификации TLS в фейке]] и [[Zapret2/desync/fake#Опции tls_mod|Опции tls_mod]].
|
||
|
||
---
|
||
|
||
### B) Standard fooling
|
||
|
||
Модификации L3/L4 заголовков. В `syndata` применяются к **копии** dissect (через `apply_fooling` после `deepcopy`). Оригинальный пакет не модифицируется --- он дропается.
|
||
|
||
> [!note] Проще говоря
|
||
> Fooling — «порча» заголовков пакета (TTL, флагов, номеров seq и ack, контрольной суммы), из-за которой пакет либо не доходит до сервера, либо сервер его отбрасывает; идея разобрана в [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]], а способы — в [[Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов|каталоге способов]]. L3 и L4 в названии — это заголовки IP и TCP. Параметры `ip_*` и `ip6_*` относятся к IPv4 и IPv6, `tcp_*` — к TCP. TTL (у IPv6 Hop Limit) — предельное число хопов, то есть маршрутизаторов на пути, после которого пакет пропадает.
|
||
|
||
| Параметр | Описание | Пример |
|
||
|:---------|:---------|:-------|
|
||
| `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 в syndata имеет иной смысл, чем в [[fake]] или [[fakedsplit]]. В [[fake]] fooling нужен, чтобы сервер **отбросил** фейк. В syndata fooling модифицирует сам SYN-пакет, который сервер должен **принять** для установления соединения. Поэтому деструктивные fooling-параметры (`tcp_seq`, `tcp_ack`, `badsum`) **сломают** handshake --- сервер не получит валидный SYN. Безопасные варианты: `ip_ttl`/`ip6_ttl` (если TTL хватит до DPI, но не до сервера --- спорно для SYN), `tcp_md5` (сервер без MD5 проигнорирует опцию), IPv6 extension headers.
|
||
|
||
> [!note] Проще говоря
|
||
> В `fake` порча нужна, чтобы сервер выбросил фейк. В `syndata` пакет один, и он же должен дойти до сервера: это SYN, без которого не будет рукопожатия. Поэтому портить его приходится осторожно. Параметры `tcp_seq`, `tcp_ack` и `badsum` лишают сервер валидного SYN, а `tcp_md5` и IPv6 extension headers (дополнительные заголовки IPv6) в тексте выше названы безопасными. Про `ip_ttl` в заметке сказано «спорно для SYN», см. пример [[#С TTL fooling (осторожно!)|С TTL fooling (осторожно!)]]. Подробнее — в нюансе [[#2. Fooling может сломать handshake|2. Fooling может сломать handshake]].
|
||
|
||
---
|
||
|
||
### C) Standard reconstruct
|
||
|
||
| Параметр | Описание |
|
||
|:---------|:---------|
|
||
| `badsum` | Испортить L4 (TCP) checksum при реконструкции raw-пакета. Сервер отбросит такой пакет |
|
||
|
||
**Предупреждение:** `badsum` в syndata означает, что сервер **не получит SYN вообще** --- TCP handshake не состоится. Используйте только если хотите отправить "мусорный" SYN, за которым пойдёт ретрансмиссия.
|
||
|
||
> [!note] Проще говоря
|
||
> Reconstruct — сборка сырого пакета заново из полей перед отправкой. `badsum` портит контрольную сумму (checksum) TCP, по которой получатель проверяет пакет; с неверной суммой пакет отбрасывается, и сервер SYN просто не увидит. Про повторную отправку SYN — нюанс [[#4. Все ретрансмиссии SYN обрабатываются|4. Все ретрансмиссии SYN обрабатываются]].
|
||
|
||
---
|
||
|
||
### D) Standard rawsend
|
||
|
||
| Параметр | Описание |
|
||
|:---------|:---------|
|
||
| `repeats=N` | Отправить пакет N раз (идентичные повторы) |
|
||
| `ifout=<iface>` | Интерфейс для отправки (по умолчанию определяется автоматически) |
|
||
| `fwmark=N` | Firewall mark (только Linux, nftables/iptables) |
|
||
|
||
Rawsend — этап отправки готового пакета в сеть (см. [[Zapret2/жизненный цикл desync-функции#Четыре способа выпустить пакет|Четыре способа выпустить пакет]]). `repeats` отправляет копии подряд (пример — [[#Отладка: повторы отправки|Отладка: повторы отправки]]), `ifout` выбирает сетевой интерфейс, `fwmark` ставит метку пакета для фаервола Linux.
|
||
|
||
---
|
||
|
||
### E) Standard ipfrag
|
||
|
||
IP-фрагментация SYN-пакета. Каждый отправляемый SYN+payload фрагментируется на уровне IP.
|
||
|
||
| Параметр | Описание | По умолчанию |
|
||
|:---------|:---------|:-------------|
|
||
| `ipfrag[=func]` | Включить IP-фрагментацию. Если без значения --- `ipfrag2` | --- |
|
||
| `ipfrag_disorder` | Отправить IP-фрагменты в обратном порядке | --- |
|
||
| `ipfrag_pos_tcp=N` | Позиция фрагментации TCP (кратно 8) | `32` |
|
||
| `ipfrag_pos_udp=N` | Позиция фрагментации UDP (кратно 8). Для syndata бесполезно --- он только TCP | `8` |
|
||
| `ipfrag_next=N` | IPv6: next protocol во 2-м фрагменте (penetration атака на фаерволы) | --- |
|
||
|
||
IP-фрагментация — разбиение одного IP-пакета на несколько частей (фрагментов); получатель склеивает их обратно, а DPI, который фрагменты не собирает, не увидит пакет целиком. Это другой уровень, чем TCP-сегментация в [[multisplit]]: режется IP-пакет, а не поток данных TCP. Для `syndata` доступна только она, TCP-сегментации нет (нюанс [[#1. Payload должен помещаться в один пакет|1]]). Примеры: [[#С IP-фрагментацией|С IP-фрагментацией]] и [[#С IP-фрагментацией (кастомная позиция)|С IP-фрагментацией (кастомная позиция)]]; те же параметры для `fake` — в разделе [[Zapret2/desync/fake#F) Standard ipfrag|F) Standard ipfrag]].
|
||
|
||
---
|
||
|
||
## tls_mod --- модификация TLS в syndata
|
||
|
||
`tls_mod` позволяет модифицировать TLS-данные внутри payload перед отправкой. Вызывается **после** `apply_fooling`:
|
||
|
||
```lua
|
||
if desync.arg.tls_mod then
|
||
dis.payload = tls_mod_shim(desync, dis.payload, desync.arg.tls_mod, nil)
|
||
end
|
||
```
|
||
|
||
Четвёртый аргумент --- `nil` (реальный payload от приложения). Это ключевой момент, определяющий какие модификации работают, а какие --- нет.
|
||
|
||
> [!note] Проще говоря
|
||
> Вызов `tls_mod_shim` получает четвёртым аргументом «настоящие данные клиента». В `syndata` их нет: клиент ещё ничего не отправил, поэтому передаётся `nil`, то есть «ничего». Модификации, которые только правят поля самого blob (случайные значения, имя сайта), работают. Модификации, которым нужен настоящий ClientHello, пропускаются. Как то же устроено в `fake`, где настоящий ClientHello есть, описано в разделе [[Zapret2/desync/fake#Когда tls_mod применяется, а когда нет|Когда tls_mod применяется, а когда нет]].
|
||
|
||
### Работающие модификации
|
||
|
||
| Мод | Описание | Работает? | Причина |
|
||
|:----|:---------|:----------|:--------|
|
||
| `rnd` | Рандомизация TLS-полей (session_id, random) | Да | Код находится **вне** блока `if(payload)` |
|
||
| `rndsni` | Рандомизация SNI (замена на случайные символы) | Да | Код находится **вне** блока `if(payload)` |
|
||
| `sni=<str>` | Замена SNI на указанную строку. Поддерживает `sni=%var` | Да | Код находится **вне** блока `if(payload)` |
|
||
|
||
**Пример:** если blob содержит TLS ClientHello с SNI `www.example.com`, а вы указали `tls_mod=sni=google.com`, в отправленном SYN-пакете SNI будет заменён на `google.com`.
|
||
|
||
### dupsid и padencap --- молчаливое игнорирование
|
||
|
||
| Мод | Описание | Работает? | Причина |
|
||
|:----|:---------|:----------|:--------|
|
||
| `dupsid` | Дублирование session_id из реального ClientHello | Молча игнорируется | Требует реальный payload (4-й аргумент != NULL) |
|
||
| `padencap` | Упаковка в padding extension | Молча игнорируется | Требует реальный payload (4-й аргумент != NULL) |
|
||
|
||
Поведение при использовании dupsid/padencap:
|
||
|
||
| Аспект | Результат |
|
||
|:-------|:----------|
|
||
| Вызывают ошибку? | НЕТ |
|
||
| Выводят warning в лог? | НЕТ |
|
||
| Возвращают false? | НЕТ (возвращают true = "успех") |
|
||
| Применяются? | НЕТ --- код просто пропускается |
|
||
|
||
Проще говоря: `dupsid` копирует идентификатор сессии (session_id) из настоящего ClientHello клиента в фейк, а `padencap` упаковывает настоящие данные в расширение padding. Если написать `tls_mod=dupsid`, ничего не сломается и ничего не произойдёт: ни ошибки, ни предупреждения, функция просто сделает вид, что всё выполнила.
|
||
|
||
### Почему dupsid и padencap не работают
|
||
|
||
В C-коде (`protocol.c`) логика dupsid и padencap находится внутри блока `if (payload)`:
|
||
|
||
```c
|
||
// protocol.c
|
||
if (payload) // <-- syndata передаёт NULL сюда
|
||
{
|
||
if (tls_mod->mod & FAKE_TLS_MOD_DUP_SID)
|
||
// ... копирование session_id из реального ClientHello
|
||
// этот код НЕ выполнится
|
||
|
||
if (tls_mod->mod & FAKE_TLS_MOD_PADENCAP)
|
||
// ... упаковка реального payload в padding
|
||
// и этот тоже
|
||
}
|
||
return bRes; // возвращает true --- "всё ОК"
|
||
```
|
||
|
||
Причина: dupsid копирует session_id **из реального ClientHello клиента** в фейк. На фазе SYN реального ClientHello ещё не существует (клиент его не отправлял). Аналогично padencap упаковывает **реальный payload** в padding extension --- на фазе SYN упаковывать нечего.
|
||
|
||
Это дизайн-решение, а не баг. Но отсутствие warning в логе может ввести в заблуждение --- конфигурация с `tls_mod=dupsid` будет молча работать "без dupsid".
|
||
|
||
> [!note] Проще говоря
|
||
> В коде выше `if (payload)` значит «если есть настоящие данные». `syndata` передаёт туда `NULL`, то есть данных нет, и весь блок пропускается, а функция всё равно возвращает `true`, «успех». Поэтому предупреждения в логе нет. Хотите использовать `dupsid` или `padencap` — берите [[Zapret2/desync/fake#Опции tls_mod|fake]], где настоящий ClientHello есть.
|
||
|
||
---
|
||
|
||
## Место в общем скелете
|
||
|
||
Тело `syndata` построено по тому же шаблону, что и все остальные функции дурения: восемь стадий от отсева чужого транспорта до вердикта. Разобраны они один раз в [[жизненный цикл desync-функции]] — здесь только отклонения:
|
||
|
||
| Стадия общего скелета | Что делает `syndata` |
|
||
|:----------------------|:----------------|
|
||
| 1. Отсев транспорта | нужен TCP и именно SYN без ACK; после прохождения фазы рукопожатия делает `instance_cutoff` с пометкой «mission complete» |
|
||
| 2. Направление | **отклонение**: аргумента `dir` нет вообще — SYN по определению исходящий |
|
||
| 3. Аргументы | обязательных нет: без `blob` подставляются 16 нулевых байт |
|
||
| 4. Данные | **отклонение**: цепочка `blob → reasm → payload` не используется, берётся только `blob` |
|
||
| 5. Гварды | **отклонение**: штатных гвардов нет, единственный фильтр — флаги TCP |
|
||
| 6. replay | не используется: SYN всегда одиночный пакет |
|
||
| 7. **Своя техника** | подставить payload в SYN, применить fooling и `tls_mod`, отправить `rawsend_dissect_ipfrag` |
|
||
| 8. Вердикт | `VERDICT_DROP` при успешной отправке — оригинальный SYN заменяется подделанным |
|
||
|
||
Отклонений здесь много именно потому, что `syndata` работает до появления прикладных данных. Отсюда же ограничение, о котором легко забыть: payload должен помещаться в один пакет — сегментации на этапе SYN не существует.
|
||
|
||
> [!note] Проще говоря
|
||
> Расшифровка терминов из таблицы. Гвард — проверка в начале функции, которая отсеивает ненужные пакеты: у большинства функций это проверки размера данных, направления и типа payload ([[Zapret2/жизненный цикл desync-функции#Стадия 5. Три гварда|Стадия 5]]). Reasm — данные, которые движок собрал из нескольких пакетов, придержав их ([[Zapret2/жизненный цикл desync-функции#Стадия 4. Выбор данных: blob → reasm → payload|Стадия 4]]). Replay — повторный прогон собранных данных через desync-функции ([[Zapret2/жизненный цикл desync-функции#Стадия 6. Развилка перепроигрывания (replay)|Стадия 6]]). Вердикт — решение, что делать с перехваченным пакетом ([[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|Стадия 8]]). У SYN нет ни собранных данных, ни повторного прогона: он всегда одиночный пакет.
|
||
|
||
---
|
||
|
||
## Псевдокод алгоритма
|
||
|
||
Тело функции целиком, включая общие для всех техник стадии — они пронумерованы так же, как в [[жизненный цикл desync-функции]]:
|
||
|
||
```lua
|
||
function syndata(ctx, desync)
|
||
-- 1. Проверка: только TCP
|
||
if not desync.dis.tcp then
|
||
-- ICMP пропускаем (не делаем cutoff), остальное --- cutoff
|
||
if not desync.dis.icmp then instance_cutoff(ctx, desync) end
|
||
return
|
||
end
|
||
|
||
-- 2. Проверка: только SYN (не SYN+ACK)
|
||
if bitand(th_flags, TH_SYN + TH_ACK) == TH_SYN then
|
||
|
||
-- 3. Глубокая копия dissect (оригинал не трогаем)
|
||
dis = deepcopy(desync.dis)
|
||
|
||
-- 4. Установка payload: blob или 16 нулей
|
||
dis.payload = blob(desync, arg.blob, "\x00" * 16)
|
||
|
||
-- 5. Применение fooling к копии
|
||
apply_fooling(desync, dis)
|
||
|
||
-- 6. tls_mod (если задан)
|
||
if arg.tls_mod then
|
||
dis.payload = tls_mod_shim(desync, dis.payload, arg.tls_mod, nil)
|
||
-- ^^^ payload от приложения = nil
|
||
end
|
||
|
||
-- 7. Отправка и фрагментация
|
||
if rawsend_dissect_ipfrag(dis, desync_opts(desync)) then
|
||
return VERDICT_DROP -- оригинальный SYN дропается
|
||
end
|
||
|
||
else
|
||
-- 8. Не SYN --- миссия завершена
|
||
instance_cutoff(ctx, desync)
|
||
end
|
||
end
|
||
```
|
||
|
||
Ключевые отличия от функций сегментации ([[multisplit]], [[fakedsplit]] и т.д.):
|
||
|
||
- **Нет цикла по позициям** --- payload отправляется **одним** пакетом целиком
|
||
- **`rawsend_dissect_ipfrag`** вместо `rawsend_payload_segmented` --- нет TCP-сегментации, только IP-фрагментация
|
||
- **`deepcopy`** --- полная копия dissect, а не создание нового пакета
|
||
- **Нет replay/reasm логики** --- на фазе SYN нет многопакетных данных
|
||
|
||
> [!note] Проще говоря
|
||
> Псевдокод читается как рецепт. Проверить, что пакет TCP. Проверить, что это SYN без ACK: в комментарии к коду эта проверка так и названа, а выражение `bitand(th_flags, TH_SYN + TH_ACK) == TH_SYN` оставляет из флагов пакета только SYN и ACK и сравнивает результат с одним SYN. Скопировать пакет, вложить payload, применить fooling и `tls_mod`. Отправить копию и выбросить оригинал. Если пакет не SYN, отключить функцию для этого соединения. Сегментированную отправку (`rawsend_payload_segmented`) используют функции вроде [[multisplit]], там же есть цикл по позициям разреза: см. [[Zapret2/desync/multisplit#Стадия 7: нарезка и отправка|Стадия 7: нарезка и отправка]].
|
||
|
||
---
|
||
|
||
## Отличия от fake
|
||
|
||
Обе функции подсовывают DPI ложные данные, но в разное время и по-разному. В таблице слева названа строка сравнения, посередине стоит значение для `syndata`, справа для [[fake]].
|
||
|
||
| Аспект | `syndata` | [[fake]] |
|
||
|:-------|:----------|:---------|
|
||
| **Фаза** | 0 (SYN, до handshake) | 1 (после handshake, есть реальные данные) |
|
||
| **Что отправляет** | SYN-пакет с payload | Отдельный data-пакет (не SYN) |
|
||
| **Дефолтный blob** | 16 нулевых байт | **Обязательный** --- без blob ничего не отправится |
|
||
| **Сегментация** | Невозможна (один пакет) | Невозможна (один пакет) |
|
||
| **Заменяет оригинал** | Да (VERDICT_DROP на SYN) | Нет (оригинал проходит дальше) |
|
||
| **Fooling** | Модифицирует сам SYN --- осторожно! | Модифицирует фейк --- сервер должен его отбросить |
|
||
| **tls_mod=dupsid** | Молча игнорируется | Работает (есть реальный ClientHello) |
|
||
| **tls_mod=padencap** | Молча игнорируется | Работает (есть реальный payload) |
|
||
| **Хостлисты** | Только `--ipcache-hostname` | Работают напрямую (payload уже содержит hostname) |
|
||
| **Воздействие на ретрансмиссии** | Да --- каждый SYN обрабатывается | Нет --- работает с конкретным data-пакетом |
|
||
| **direction** | Не поддерживается | Поддерживается |
|
||
| **payload filter** | Не поддерживается | Поддерживается |
|
||
| **ipid** | Не поддерживается | Поддерживается |
|
||
|
||
> [!note] Проще говоря
|
||
> `fake` — отдельный дополнительный пакет после рукопожатия, который сервер должен отбросить, а оригинальные данные уходят дальше. `syndata` подменяет сам SYN, который сервер должен принять. Из этого следуют остальные строки таблицы: у `syndata` нет настоящих данных, поэтому `dupsid` и `padencap` не работают, нет hostname, нет фильтров. Подробнее о `fake` — в разделе [[Zapret2/desync/fake#Зачем нужен fake|Зачем нужен fake]], сравнение с его стороны — в [[Zapret2/desync/fake#Сравнение с syndata и fakedsplit|Сравнение с syndata и fakedsplit]].
|
||
|
||
**Когда использовать syndata вместо fake:**
|
||
|
||
- DPI начинает трекинг с SYN и не реагирует на фейки после handshake
|
||
- Нужно "отравить" DPI ложными данными **до** того, как пойдёт реальный трафик
|
||
- В комбинации с [[Zapret2/desync|wssize]] и [[multisplit]] для многоуровневой атаки
|
||
|
||
О связке с wssize и multisplit — раздел [[#Комбинирование с другими функциями|Комбинирование с другими функциями]], а пример с фейком после рукопожатия — [[#Боевая комбинация: syndata + fake + multisplit|Боевая комбинация: syndata + fake + multisplit]].
|
||
|
||
---
|
||
|
||
## Работа с хостлистами
|
||
|
||
На фазе 0 (SYN) zapret **не знает** hostname --- приложение ещё не отправило HTTP-запрос или TLS ClientHello. Поэтому стандартная фильтрация по хостлистам (`--hostlist=...`) не работает с syndata напрямую.
|
||
|
||
Единственный способ --- `--ipcache-hostname`:
|
||
|
||
```bash
|
||
nfqws2 --ipcache-hostname \
|
||
--hostlist=blocked.txt \
|
||
--lua-desync=syndata:blob=fake_default_tls
|
||
```
|
||
|
||
Как это работает:
|
||
|
||
1. Первое соединение к IP-адресу проходит **без** syndata (hostname ещё не известен)
|
||
2. Из первого соединения zapret узнаёт hostname (из TLS ClientHello или HTTP Host) и кеширует привязку IP -> hostname
|
||
3. Последующие SYN к этому IP уже проходят через syndata, потому что zapret знает hostname из кеша
|
||
|
||
**Следствие:** syndata с хостлистом не подействует на **первое** соединение к данному IP. Это может быть проблемой для сайтов с уникальными IP или CDN с ротацией адресов.
|
||
|
||
Та же проблема нулевой фазы у классического `--wssize` в nfqws — почему из-за неё wssize считается крайней мерой, разобрано в [[Zapret/wssize|отдельной заметке]].
|
||
|
||
> [!note] Проще говоря
|
||
> Хостлист — файл со списком доменов, к которым нужно применять технику (см. [[Zapret/hostlist|Хостлисты]]). Чтобы сверить соединение со списком, нужно знать имя сайта, а в SYN его нет. Выход — кеш: Zapret запоминает, какому имени соответствовал IP при прошлом соединении. Поэтому первое соединение к IP пройдёт без `syndata`, а следующие уже с ним. CDN (сеть серверов, раздающих сайт с разных адресов) с ротацией адресов для такой схемы неудобны: каждый новый IP для Zapret «первый». Ключ `--ipcache-hostname` описан в [[Zapret/Zapret flags|Zapret flags]], а хостлист вместе с ним в профиле показан в примере [[#Боевая комбинация: syndata + fake + multisplit|Боевая комбинация: syndata + fake + multisplit]].
|
||
|
||
---
|
||
|
||
## Комбинирование с другими функциями
|
||
|
||
`syndata` часто используется в цепочке с другими функциями. **Порядок инстансов важен!**
|
||
|
||
Инстанс — один вызов `--lua-desync=…` в профиле. Каждая строка в блоках ниже — отдельный инстанс, и порядок записи определяет порядок, в котором они увидят пакет; подробнее в [[последовательность аргументов]].
|
||
|
||
### Типовая цепочка: wssize + syndata + multisplit
|
||
|
||
```bash
|
||
--lua-desync=wssize:wsize=1:scale=6 \
|
||
--lua-desync=syndata \
|
||
--lua-desync=multisplit:pos=midsld
|
||
```
|
||
|
||
Что происходит:
|
||
|
||
1. **SYN-пакет приходит:**
|
||
- `wssize` модифицирует TCP Window Size в SYN (wsize=1, scale=6)
|
||
- `syndata` добавляет payload в SYN, дропает оригинал, отправляет модифицированный
|
||
- `multisplit` ничего не делает (SYN, нет данных для нарезки)
|
||
|
||
2. **Первый data-пакет (например, TLS ClientHello) приходит:**
|
||
- `wssize` --- миссия уже выполнена (cutoff или пропуск)
|
||
- `syndata` делает `instance_cutoff` (пакет не SYN)
|
||
- `multisplit` нарезает payload по позиции `midsld`
|
||
|
||
**Почему wssize перед syndata:** [[Zapret2/desync|wssize]] модифицирует TCP Window Size в SYN-пакете. Если поставить после syndata, wssize увидит `VERDICT_DROP` и не получит SYN. Если перед --- wssize модифицирует dissect, затем syndata использует `deepcopy` этого (уже модифицированного) dissect.
|
||
|
||
> [!note] Проще говоря
|
||
> Сначала wssize меняет размер окна в SYN (окно — сколько байт получатель готов принять, см. [[Zapret/wssize#Что делает wssize|Что делает wssize]]; в цепочке оно заставляет сервер слать маленькие сегменты), потом `syndata` копирует уже изменённый SYN, дописывает данные и отправляет вместо оригинала. Если поставить их наоборот, wssize увидит уже принятое решение «оригинал выбросить», и SYN до него не дойдёт. `midsld` — маркер «середина домена второго уровня», все маркеры перечислены в разделе [[Zapret2/desync/multisplit#Маркеры позиций (pos)|Маркеры позиций (pos)]].
|
||
|
||
### Цепочка: syndata + fake + multisplit
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls \
|
||
--lua-desync=fake:blob=fake_default_tls:tcp_md5 \
|
||
--lua-desync=multisplit:pos=1,midsld
|
||
```
|
||
|
||
Многоуровневая атака:
|
||
1. Фаза 0: SYN с фейковым TLS
|
||
2. Фаза 1: фейковый пакет с fooling (tcp_md5)
|
||
3. Фаза 1: реальный payload нарезан на сегменты
|
||
|
||
> [!note] Проще говоря
|
||
> Три функции работают в разное время. `syndata` действует на SYN. [[Zapret2/desync/fake#Зачем нужен fake|fake]] после рукопожатия отправляет отдельный пакет-обманку, испорченный fooling `tcp_md5` (см. [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]). [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]] режет настоящий ClientHello на сегменты по `pos=1,midsld`: после первого байта и посередине домена второго уровня. Оговорка про `tcp_md5`. В `fake` его берут, чтобы сервер отбросил фейк: так сказано в статье про fake, где серверы на Linux отбрасывают пакеты с MD5-опцией, если MD5-аутентификация у них не настроена. А в `syndata` тот же `tcp_md5` назван безопасным для SYN, потому что сервер без MD5 опцию игнорирует ([[#2. Fooling может сломать handshake|нюанс 2]]). Эти два описания поведения сервера в источниках стоят порознь и не сведены к одному; как поведёт себя конкретный сервер, здесь не утверждается, а подобранную комбинацию стоит проверять по [[Zapret2/verify-strategy#Короткий чек-лист честной проверки|чек-листу]].
|
||
|
||
---
|
||
|
||
## Нюансы и подводные камни
|
||
|
||
### 1. Payload должен помещаться в один пакет
|
||
|
||
syndata использует `rawsend_dissect_ipfrag`, а **не** `rawsend_payload_segmented`. Это означает, что TCP-сегментация **невозможна**. Если blob слишком большой и не влезает в один Ethernet frame (с учётом MTU), пакет может быть отброшен сетевым стеком или фрагментирован на IP-уровне (если включён `ipfrag`).
|
||
|
||
> [!note] Проще говоря
|
||
> SYN — один пакет, и разрезать его на TCP-сегменты нельзя. MTU — наибольший размер пакета, который проходит по каналу за один раз. Другие функции сегментации сами доводят куски до размера MSS (наибольший объём данных в одном TCP-сегменте, см. [[Zapret2/desync/multisplit#Автосегментация по MSS|Автосегментация по MSS]]); `syndata` отправляет через `rawsend_dissect_ipfrag`, где такого разбиения нет ([[Zapret2/жизненный цикл desync-функции#Четыре способа выпустить пакет|Четыре способа выпустить пакет]]).
|
||
|
||
### 2. Fooling может сломать handshake
|
||
|
||
В отличие от [[fake]], где fooling **должен** заставить сервер отбросить пакет, в syndata fooling применяется к пакету, который сервер **должен принять**. Деструктивные параметры:
|
||
|
||
- `tcp_seq=N` --- сервер не увидит SYN с правильным sequence
|
||
- `tcp_ack=N` --- невалидный ack в SYN
|
||
- `badsum` --- сервер отбросит пакет с плохой checksum
|
||
- `tcp_flags_unset=SYN` --- пакет перестанет быть SYN
|
||
|
||
Безопасные параметры:
|
||
- `tcp_md5` --- сервер без MD5 проигнорирует опцию (RFC 2385)
|
||
- `tcp_ts_up` --- перестановка timestamp option (не влияет на валидность)
|
||
- `tcp_nop_del` --- удаление NOP (не влияет на валидность)
|
||
- IPv6 extension headers --- для обхода DPI/фаерволов на пути
|
||
|
||
> [!note] Проще говоря
|
||
> Ломать нельзя то, без чего сервер не примет SYN: seq, ack, контрольную сумму, флаг SYN. Безопасными названы `tcp_md5`, `tcp_ts_up`, `tcp_nop_del` (NOP — пустая опция-заполнитель в заголовке TCP) и IPv6 extension headers. Про `tcp_md5` есть оговорка в [[#Цепочка: syndata + fake + multisplit|цепочке с fake]]. Сверьте с таблицей в разделе [[#B) Standard fooling|B) Standard fooling]]; каталог способов порчи — в [[Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов|ts-and-fooling]].
|
||
|
||
### 3. Нет direction, payload filter, ipid
|
||
|
||
syndata не вызывает `direction_cutoff_opposite`, `direction_check`, `payload_check` или `apply_ip_id`. Эти стандартные блоки просто отсутствуют в коде функции:
|
||
|
||
- **direction** --- SYN всегда исходящий, фильтрация по направлению бессмысленна
|
||
- **payload filter** --- в SYN нет данных приложения, фильтровать нечего
|
||
- **ipid** --- `apply_ip_id` не вызывается, IP ID не контролируется
|
||
|
||
> [!note] Проще говоря
|
||
> Это стандартные проверки, которые есть у остальных функций: см. [[Zapret2/жизненный цикл desync-функции#Стадия 2. Отсечение противоположного направления|Стадию 2]] и [[Zapret2/жизненный цикл desync-функции#Стадия 5. Три гварда|Стадию 5]] жизненного цикла. У `syndata` они не нужны, поэтому их в коде нет.
|
||
|
||
### 4. Все ретрансмиссии SYN обрабатываются
|
||
|
||
Если первый SYN не дошёл (или SYN+ACK потерялся), TCP-стек отправит ретрансмиссию SYN. syndata обработает **каждую** ретрансмиссию --- добавит payload, применит модификации, дропнет оригинал. Это полезно: DPI может обрабатывать только первый SYN, а ретрансмиссия с payload "перезапишет" данные в трекере.
|
||
|
||
> [!note] Проще говоря
|
||
> Ретрансмиссия — повторная отправка SYN средствами TCP-стека самой системы, если ответа нет. `syndata` подменяет каждый такой SYN, поэтому, если DPI обрабатывает только первый SYN, повторный тоже придёт с данными и может «перезаписать» то, что DPI уже запомнил. Трекер здесь — «память» DPI о соединении.
|
||
|
||
### 5. deepcopy защищает оригинал
|
||
|
||
syndata делает `deepcopy(desync.dis)` и работает с копией. Это значит, что `apply_fooling` и замена payload не затрагивают оригинальный dissect. Если после syndata стоят другие инстансы и SYN не дропнут (rawsend failed), они увидят **немодифицированный** пакет.
|
||
|
||
> [!note] Проще говоря
|
||
> Копия — черновик, оригинал — чистовик. Правки идут в копию, оригинал остаётся как был. Оригинал выбрасывается только после успешной отправки копии. Если отправка не удалась, оригинальный SYN остаётся и попадает к следующим инстансам без изменений.
|
||
|
||
### 6. instance_cutoff на не-SYN
|
||
|
||
Как только syndata видит пакет без флага SYN (ACK, PSH+ACK и т.д.), он делает `instance_cutoff` --- отключает себя для этого потока навсегда. Это правильно: миссия syndata --- только SYN-пакеты. Последующие данные должны обрабатываться другими функциями ([[multisplit]], [[fake]] и т.д.).
|
||
|
||
Как работает cutoff для всех функций сразу, описано в [[Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff|стадии 1 жизненного цикла]].
|
||
|
||
### 7. ICMP не вызывает cutoff
|
||
|
||
Если в цепочке инстансов через syndata проходит ICMP-пакет (например, ICMP Destination Unreachable в ответ на SYN), syndata **не** делает `instance_cutoff`. Это защита от преждевременного отключения --- ICMP может быть связан с SYN и не означает конец потока.
|
||
|
||
ICMP — служебный протокол сети, которым сообщают об ошибках доставки, например «получатель недоступен».
|
||
|
||
### 8. Дефолтный blob --- 16 нулей, а не обязательный параметр
|
||
|
||
В отличие от [[fake]], где blob обязателен, syndata имеет fallback --- 16 нулевых байт. `--lua-desync=syndata` без `blob=` отправит SYN с 16 байтами `0x00`. Это может быть достаточно, чтобы сбить DPI, который не ожидает payload в SYN.
|
||
|
||
Про обязательный blob у `fake` — нюанс [[Zapret2/desync/fake#5. blob обязателен — без optional будет Lua exception|5. blob обязателен — без optional будет Lua exception]] в статье про fake.
|
||
|
||
### 9. tls_mod без blob бессмыслен
|
||
|
||
Если вы указали `tls_mod=rnd,rndsni` без `blob=`, tls_mod попытается модифицировать 16 нулевых байт как TLS --- результат будет непредсказуемым. Всегда используйте `tls_mod` вместе с blob, содержащим валидный TLS ClientHello.
|
||
|
||
> [!note] Проще говоря
|
||
> `tls_mod` рассчитан на данные, которые выглядят как TLS ClientHello, а 16 нулей таким ClientHello не являются, поэтому результат непредсказуем. Стандартный blob с TLS-фейком, который подходит для пары `blob` и `tls_mod`, описан в заметке [[Zapret2/blob|Блобы]].
|
||
|
||
---
|
||
|
||
## Миграция с nfqws1
|
||
|
||
nfqws1 — движок первой версии zapret: там техники включаются флагами `--dpi-desync-*`, а в nfqws2 — вызовами `--lua-desync`. Ниже таблица соответствия для `syndata` и примеры перехода. Общая таблица всех флагов — в разделе [[Zapret2/desync#Соответствие флагов nfqws1 → nfqws2|Соответствие флагов nfqws1 → nfqws2]], аналогичный раздел для сегментации — [[Zapret2/desync/multisplit#Миграция с nfqws1|Миграция с nfqws1]] в статье про multisplit.
|
||
|
||
### Соответствие параметров
|
||
|
||
| nfqws1 | nfqws2 |
|
||
|:-------|:-------|
|
||
| `--dpi-desync=syndata` | `--lua-desync=syndata` |
|
||
| `--dpi-desync-fake-tls=<file>` | `--blob=name:@file` + `:blob=name` |
|
||
| `--dpi-desync-fake-tls-mod=rnd,rndsni` | `:tls_mod=rnd,rndsni` |
|
||
| `--dpi-desync-fooling=md5sig` | `:tcp_md5` |
|
||
| `--wssize 1:6` | `--lua-desync=wssize:wsize=1:scale=6` (отдельный инстанс, **перед** syndata) |
|
||
|
||
Проще говоря: в nfqws1 `--wssize` — глобальный флаг, а в nfqws2 это отдельный вызов `wssize`, который нужно поставить в цепочке перед `syndata`; причина описана в разделе [[#Типовая цепочка: wssize + syndata + multisplit|Типовая цепочка: wssize + syndata + multisplit]].
|
||
|
||
### Пример полной миграции
|
||
|
||
```bash
|
||
# nfqws1:
|
||
nfqws --dpi-desync=syndata,multisplit \
|
||
--dpi-desync-split-pos=midsld \
|
||
--wssize 1:6
|
||
|
||
# nfqws2 (порядок инстансов важен!):
|
||
nfqws2 \
|
||
--lua-desync=wssize:wsize=1:scale=6 \
|
||
--lua-desync=syndata \
|
||
--lua-desync=multisplit:pos=midsld
|
||
```
|
||
|
||
```bash
|
||
# nfqws1:
|
||
nfqws --dpi-desync=syndata \
|
||
--dpi-desync-fake-tls-mod=rnd,rndsni
|
||
|
||
# nfqws2:
|
||
nfqws2 \
|
||
--lua-desync=syndata:blob=fake_default_tls:tls_mod=rnd,rndsni
|
||
```
|
||
|
||
```bash
|
||
# nfqws1:
|
||
nfqws --dpi-desync=syndata \
|
||
--dpi-desync-fooling=md5sig
|
||
|
||
# nfqws2:
|
||
nfqws2 \
|
||
--lua-desync=syndata:tcp_md5
|
||
```
|
||
|
||
---
|
||
|
||
## Практические примеры
|
||
|
||
Примеры идут от простых к сложным. Каждый блок кода — строки для [[Zapret2/profile#Анатомия профиля|профиля]] Zapret 2; аргументы разобраны в разделе [[#Полный список аргументов|Полный список аргументов]].
|
||
|
||
### Минимальный (дефолт: 16 нулей в SYN)
|
||
|
||
```bash
|
||
--lua-desync=syndata
|
||
```
|
||
|
||
Отправляет SYN с 16 нулевыми байтами вместо обычного SYN. Простейший вариант --- может сбить DPI, не ожидающий payload в SYN.
|
||
|
||
### С TLS-фейком
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls
|
||
```
|
||
|
||
SYN содержит стандартный TLS ClientHello из zapret. DPI может принять это за начало TLS-сессии.
|
||
|
||
### С кастомным blob из файла
|
||
|
||
```bash
|
||
--blob=mysyndata:@custom_syn_payload.bin \
|
||
--lua-desync=syndata:blob=mysyndata
|
||
```
|
||
|
||
Загружает произвольный payload из файла и вставляет в SYN.
|
||
|
||
### С inline hex blob
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=0x160301000100
|
||
```
|
||
|
||
6 байт inline hex --- минимальный TLS record header.
|
||
|
||
Проще говоря: TLS record — «запись», в которую TLS упаковывает передаваемые данные, а её заголовок стоит в начале записи.
|
||
|
||
### С рандомизацией TLS
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls:tls_mod=rnd,rndsni
|
||
```
|
||
|
||
TLS-фейк с рандомизированными полями и SNI. Каждый SYN будет содержать уникальные random/session_id/SNI.
|
||
|
||
### С заменой SNI
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls:tls_mod=sni=google.com
|
||
```
|
||
|
||
SYN содержит TLS ClientHello с SNI `google.com`. DPI может записать в трекер "это соединение к google.com" и не блокировать.
|
||
|
||
### С TCP MD5
|
||
|
||
```bash
|
||
--lua-desync=syndata:tcp_md5
|
||
```
|
||
|
||
SYN с 16 нулями и TCP MD5 option. Сервер без MD5 проигнорирует опцию, DPI может быть сбит нестандартным TCP-заголовком.
|
||
|
||
### С TTL fooling (осторожно!)
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls:ip_ttl=5:ip6_ttl=5
|
||
```
|
||
|
||
SYN с TTL=5. Если DPI ближе 5 хопов --- увидит фейк. Если сервер дальше 5 хопов --- SYN не дойдёт, handshake не состоится. Ретрансмиссия SYN тоже получит TTL=5 и тоже не дойдёт. **Используйте только если точно знаете расстояние до DPI и сервера.**
|
||
|
||
Проще говоря: хоп — один переход пакета между маршрутизаторами. Пакет с TTL=5 успеет пройти пять хопов и дальше пропадёт. Про TTL как способ fooling и его слабые места — [[Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов|каталог способов]] в заметке про fooling.
|
||
|
||
### С IP-фрагментацией
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls:ipfrag:ipfrag_disorder
|
||
```
|
||
|
||
SYN с TLS-фейком, фрагментированный на IP-уровне в обратном порядке. DPI, не собирающий IP-фрагменты, не увидит полный пакет.
|
||
|
||
### С IP-фрагментацией (кастомная позиция)
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls:ipfrag:ipfrag_pos_tcp=24
|
||
```
|
||
|
||
Фрагментация по позиции 24 байта (кратно 8). Первый фрагмент содержит TCP-заголовок, второй --- payload.
|
||
|
||
### Типовая боевая комбинация: wssize + syndata + multisplit
|
||
|
||
```bash
|
||
--filter-tcp=443 \
|
||
--lua-desync=wssize:wsize=1:scale=6 \
|
||
--lua-desync=syndata \
|
||
--lua-desync=multisplit:pos=midsld
|
||
```
|
||
|
||
Трёхуровневая атака на DPI: маленькое окно (заставляет сервер слать маленькие сегменты) + payload в SYN + нарезка реального ClientHello.
|
||
|
||
### Боевая комбинация: syndata + fake + multisplit
|
||
|
||
```bash
|
||
--filter-tcp=443 --hostlist=blocked.txt --ipcache-hostname \
|
||
--lua-desync=syndata:blob=fake_default_tls:tls_mod=rnd,rndsni \
|
||
--lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \
|
||
--lua-desync=multisplit:pos=1,midsld
|
||
```
|
||
|
||
Максимальная комбинация: фейк в SYN, фейк после handshake (с dupsid --- здесь он работает, потому что это [[fake]], а не syndata), нарезка реального payload.
|
||
|
||
Проще говоря: `--hostlist=blocked.txt` ограничивает профиль доменами из списка, а `--ipcache-hostname` включает кеш имён, без которого `syndata` хостлист не увидит (см. [[#Работа с хостлистами|Работа с хостлистами]]).
|
||
|
||
### Отладка: повторы отправки
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls:repeats=3
|
||
```
|
||
|
||
Каждый SYN с payload отправляется 3 раза. Полезно для отладки или если DPI обрабатывает только N-й пакет.
|
||
|
||
### IPv6: extension headers
|
||
|
||
```bash
|
||
--lua-desync=syndata:blob=fake_default_tls:ip6_hopbyhop:ip6_destopt
|
||
```
|
||
|
||
SYN с hop-by-hop и destination options extension headers. Некоторые DPI/фаерволы не умеют парсить IPv6 extension headers и пропускают пакет.
|
||
|
||
---
|
||
|
||
## 📚 См. также
|
||
|
||
- [[desync]] — обзор всех функций `--lua-desync` и общий контракт: кто вызывает функцию, что ей передаёт и как складываются вердикты
|
||
- [[жизненный цикл desync-функции]] — восемь стадий, общих для всех техник дурения
|
||
- [[структура desync и диссекта]] — подробное устройство таблицы `desync` и диссекта пакета
|
||
- [[fake]] — отдельный фейковый пакет после рукопожатия; сравнение с `syndata`
|
||
- [[blob]] — как объявлять и передавать данные для аргумента `blob`
|
||
- [[payload]] — распознавание типов протоколов и фильтр `--payload`
|
||
- [[multisplit]] — нарезка настоящего ClientHello, с которой `syndata` обычно стоит в одной цепочке
|
||
- [[ts-and-fooling]] — что такое fooling и как им портят пакеты
|
||
- [[verify-strategy]] — как честно проверить, работает ли подобранная комбинация
|
||
- [[последовательность аргументов]] — как выстраивается цепочка инстансов в профиле
|
||
- [[Zapret/wssize|wssize в zapret]] — та же нулевая фаза и та же проблема хостлистов
|
||
- [[Zapret/hostlist|Хостлисты]] — списки доменов, с которыми связан `--ipcache-hostname`
|
||
- [[DPI/DPI|DPI и ТСПУ]] — как устроена инспекция трафика, против которой работает `syndata`
|
||
|
||
---
|
||
|
||
> **Источники:** `lua/zapret-antidpi.lua:385`, `lua/zapret-lib.lua`, `docs/manual.md:3944-3961` из репозитория zapret2.
|
||
|
||
---
|
||
|
||
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
|
||
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: [исходник этой заметки](https://git2.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/Zapret2/desync/syndata.md) · [скачать весь репозиторий одним zip-архивом](https://git2.zapret.moe/zapretdiscordyoutube/todo/archive/main.zip).
|