О чём заметка

Это полный текст видео номер 7 из серии о том, как работает Zapret 2, разбитый по сценам, с анимированными схемами. Каждую сцену можно читать отдельно.

Как читать пометки

«В нашем тесте» значит настоящий запуск в лаборатории проекта: клиент, роутер, сервер. «По коду» значит, что мы прочитали исходники, но не запускали. «По общим сведениям» значит, что это вне нашего кода и лаборатории. О настоящем оборудовании ТСПУ мы ничего не утверждаем: наш «инспектор» это модель.

Подложное письмо: повтор и вопрос выпуска

Добро пожаловать в выпуск о подделках. Сначала короткий повтор, чтобы эту часть можно было смотреть отдельно от остальных.

В выпуске «Письмо в сортировочном центре» мы собрали модель наблюдателя: игрушечный инспектор стоит в роутере и читает пакеты. Это игрушка нашего проекта, а не настоящее оборудование. Слово «сработало» в нашей серии значит: игрушку обошли, и сервер получил настоящие байты.

В выпусках «multisplit: резать на части» и «Перемешать: disorder» программа работала с байтами самого запроса: резала их на куски и отправляла по порядку и не по порядку. В сети были настоящие байты запроса. Исключение одно: накладка seqovl, лишние нулевые байты перед первой частью. А в конце «Перемешать: disorder» был намёк: добавочный сегмент со сроком жизни один исчезал на роутере. Но и в нём лежали настоящие байты запроса.

Сегодня другой класс приёмов. Программа ничего не режет и не переставляет: настоящий пакет остаётся таким, как был, а рядом она кладёт лишний, с чужими данными. По-английски он называется fake, по-русски скажем «фейк».

Резка или фейк Сравнение двух классов приёмов: в прошлых выпусках программа резала настоящее приветствие в 250 байт на куски 104 и 146 и отправляла их по порядку и не по порядку, а в этом выпуске настоящий пакет остаётся целым и перед ним встаёт один лишний пакет с чужими байтами, фейк, тогда как добавочный сегмент со сроком жизни 1 из конца прошлого выпуска нёс настоящие байты и фейком ещё не был. прошлые выпуски: режем и переставляем настоящие байты начало: 104 байтаконец: 146 байтразрез: приветствие в 250 байт, пример из прошлых выпусков по порядку (multisplit): сначала начало1-й: начало · 1042-й: конец · 146 не по порядку (disorder): сначала конец1-й: конец · 1462-й: начало · 104 исключение одно: накладка seqovl, лишние нулевые байты перед первой частью этот выпуск: фейк (fake) настоящий пакет250 байт, без изменений фейк: лишний пакетвнутри чужие байты на провод ничего не режем: настоящий пакет остаётся как был, рядом кладём лишний намёк из конца выпуска «Перемешать: disorder» (образ, не данные этой сцены) клиентроутерсервер 1 · байты настоящий 1 · байты настоящий добавочный сегмент: срок жизни 1, внутри настоящие байты запроса; на роутере он исчезаетнастоящий пакет едет дальше до серверачужих байт в нём нет, поэтому это ещё не фейк: фейк несёт чужие байты, см. выше

Так же называется и стратегия, которая его отправляет: функция программы вместе с её аргументами, как в выпуске «Читаем командную строку zapret2».

Теперь слово из названия выпуска. Настройки, которые делают подделку непригодной для получателя, в программе называются fooling. Мы зовём их «условиями подделки», а коротко «условиями»: это намеренный дефект, без которого подделка получателю вредит. Не путайте их с условиями профиля из выпуска «Читаем командную строку zapret2»: те решают, какие пакеты достаются профилю, а эти решают, чем подделка отличается от настоящего пакета. В выпусках «multisplit: резать на части» и «Перемешать: disorder» то же самое называлось подменой полей, а в одном месте просто подделками. Это один механизм под тремя именами, и дальше мы говорим только «условие подделки», а коротко «условие».

Игрушка, пример выдуманный. На ленте идут письма. Сортировщик смотрит в окошко только первое письмо каждого адресата и по нему решает, пропускать ли всю переписку. Мы кладём на ленту перед настоящим письмом подложное, с безобидным адресом. Сортировщик читает его, успокаивается и дальше не смотрит.

Но у подложного письма два читателя. Если адресат его не получит или выбросит, всё хорошо. Если прочтёт как настоящее, он ответит на чужие слова, и переписка сломается. Весь выпуск о том, как сделать подделку, которая обманывает первого читателя и не обманывает второго.

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

Договорённости, они повторятся ещё не раз. «Работает» значит: работает против нашего игрушечного инспектора и нашего получателя, сервера на Linux. О настоящем оборудовании мы не знаем ничего. «В нашем тесте» значит настоящий запуск программы в нашей лаборатории. «По общим сведениям» значит знание не из нашего кода и не из лаборатории. «По счёту» значит арифметика на бумаге. «Прогон функций» значит, что мы запустили настоящие функции программы без сети и подали им числа.

Три имени, один термин Слово из названия выпуска: три прежних имени одного механизма, подмена полей, подделки и английское fooling, сходятся в один термин условие подделки, это намеренный дефект подделки, который отличается от условий профиля, решающих, какие пакеты достаются профилю, и внизу пять договорённостей серии: в нашем тесте, по общим сведениям, по счёту, прогон функций и «ТСПУ: неизвестно». один механизм под тремя именами подмена полейвыпуски «multisplit» и «disorder» подделкивыпуск «multisplit: резать на части» foolingтак называется в программе условие подделкикоротко: условиев коде: fooling не путатьусловия профилярешают, какие пакетыдостаются профилю условие подделки: намеренный дефект, без которого подделка получателю вредитпрофиль решает, какие пакеты брать; условие подделки решает, чем подделка отличается от настоящего договорённости выпуска, они повторятся в нашем тестенастоящий запускnfqws2 в нашейлаборатории по общимсведениямзнание не изнашего кода и неиз лаборатории по счётуарифметикана бумаге прогонфункцийфункции программыбез сети, на числах,которые мы подали ТСПУ:неизвестноо настоящемоборудовании мыне знаем ничего «Работает» значит: работает против нашего игрушечного инспектора и нашего сервера на Linux.Игрушечный инспектор это наша модель наблюдателя, а не настоящее оборудование.

Итак. Фейк это лишний пакет рядом с настоящим, а вопрос выпуска такой: чем подложный пакет должен отличаться от настоящего, чтобы его принял один читатель и отверг другой, и как программа этого добивается? Начнём с картинки на проводе, а потом разберём, что от такого пакета вообще требуется.

Картинка заранее: что уходит на провод и какой у нас инспектор

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

Клиент собирается отправить приветствие в двести пятьдесят байт, с именем www.example.com. Программа со стратегией fake ставит перед ним второй пакет: шестьсот восемьдесят байт, а имя в нём другое, www.microsoft.com. Номера у двух пакетов одинаковые: оба претендуют на первый байт потока. Значит, на провод уходят два пакета подряд, сначала подделка, за ней настоящий. В нашем тесте так и было.

Два пакета, один номер Две ленты байтов с одним номером 1: настоящее приветствие в 250 байт с именем www.example.com на смещениях 97–111 и подделка в 680 байт с именем www.microsoft.com на смещениях 125–141, и на провод они уходят двумя пакетами подряд, сначала подделка, за ней настоящее, и в нашем тесте так было в 110 запусках из 110. два пакета претендуют на первый байт потока подделка: 680 байт, имя www.microsoft.comимя: смещения 125–141номер 1680 настоящее: 250имя www.example.com: смещения 97–111номер 1250 ← тот же номер 1: оба претендуют на первый байт на провод уходят два пакета подряд 1-й: подделка · 680 байтPSH+ACK · номер 1 2-й: настоящее · 250PSH+ACK · номер 1 клиент в нашем тесте первой уходила подделка: в 110 запусках из 110за ней идёт настоящее приветствие с тем же номером; оба пакета видны на сетевой карте клиента

На входе в роутер стоит инспектор, и он здесь другой, чем в прошлых выпусках. Тот игрушечный инспектор смотрел каждый пакет отдельно и ничего не помнил. Этот ждёт первый пакет с данными, решает по нему один раз, и решение действует на всё соединение. Первым ему попадается подделка: имя в ней разрешённое, и настоящее приветствие он уже не читает. Как мы его построили, покажем в сцене про лабораторию.

Остаётся вопрос, ради которого весь выпуск: что сервер сделает с двумя пакетами на одном месте. Если примет подделку за данные, соединение сломано. Поэтому подделке нужны условия, о которых мы только что говорили: пусть она до сервера не доедет, или пусть сервер сам её выбросит.

Инспектор решает один раз Игрушечный инспектор этого выпуска, в отличие от прежнего, который смотрел каждый пакет отдельно и ничего не помнил, ждёт первый пакет с данными и решает один раз на всё соединение, поэтому подделка, встающая первой, получает зелёную галочку и замок, настоящее приветствие уже не читается, а дальше у подделки две возможности не навредить, не доехать до сервера или быть выброшенной самим сервером, и если сервер примет её за данные, соединение сломано. игрушечный инспектор на входе в роутер: наша модель, не оборудование прежний игрушечный инспекторсмотрит каждый пакет отдельно,ничего не помнит инспектор этого выпускаждёт первый пакет с данными, решает один раз,решение действует на всё соединение клиентсервер инспектору входа 1-й: подделка · 680имя разрешено ок решено 2-й: настоящеене читается первой ему попадается подделка: имя разрешённое, настоящее он уже не читает что дальше: чтобы подделка не вредила подделка не доедет до серверасервер сам её выбросит условия подделки:их разбор в следующих сценах если сервер примет подделку за данные: соединение сломано

И одна договорённость о слове «игрушка». Фразы вроде «Игрушка, выдуманная» или «Игрушка, пример выдуманный» открывают картинку для понимания, вроде письма на ленте: к лаборатории они отношения не имеют. А когда мы говорим просто «игрушка» про инспектора в роутере, это сокращение от «игрушечный инспектор», нашей модели наблюдателя.

Итак. С фейком на провод уходят два пакета, подделка и настоящий. Инспектор этого выпуска судит по первому пакету с данными, поэтому подделка встаёт первой. Дальше выясним, чем подделка должна отличаться от настоящего пакета.

Три задачи подделки: два читателя одного пакета

Вопрос сцены: что должно быть правдой о подложном пакете, чтобы он помог? Три вещи.

Игрушка, выдуманная. Подложное письмо летит по ленте и проходит двух читателей. Первый, сортировщик с окошком, должен принять его за настоящее. Второй, адресат, не должен.

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

Насколько глубоко должно быть это правдоподобие? Для нашей игрушки совсем неглубоко. В одном нашем опыте подделкой служил блоб, готовый набор байтов с именем, всего из десяти байт: заголовок записи TLS и пять произвольных. Наш инспектор, о нём в сцене про лабораторию, ищет в таком пакете запрещённое имя, не находит и смотрит только на первые два байта, и ему этого хватило: он разрешил пакет. В том опыте у подделки было ещё условие, о нём позже. Это свойство нашей игрушки, а не настоящего оборудования.

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

Третья задача: безвредность. Подделка не должна запутать соединение: настоящий пакет, идущий следом, должен дойти как обычно, и сервер должен ответить клиенту.

Два читателя, три задачи Выдуманный пример с подложным письмом, которое проходит двух читателей, сортировщика-инспектора, который должен принять его за настоящее, и адресата-получателя, который не должен, причём подложное письмо либо умирает по дороге, как письмо с просроченной маркой, либо доходит и отвергается, как письмо с неправильной печатью, а три задачи подделки, правдоподобие, отказ получателя и безвредность, описаны в таблице вместе с тем, что случится, если задача не выполнена. игрушка, выдуманный пример: подложное письмо и два читателя сортировщикс окошкомчитатель один: инспектор адресатоткрывает конвертчитатель два: получатель подложное письмо ок настоящее сортировщик смотрит только первое письмо, поэтому подложное кладут на ленту первым способ 1: умирает по дорогекак письмо с просроченной маркой:до адресата не доходит способ 2: доходит, но адресат отвергаеткак письмо с неправильной печатью:штамп «не принято» не принято первый способ разбираем в этой части выпуска, второй в следующих три задачи подделки задачакто читаетесли не вышло 1правдоподобиеинспекторсудит по настоящему пакету и блокирует 2отказ получателясерверпринимает подделку за данные, поток испорчен 3безвредностьсоединениесоединение путается измерено только против нашего игрушечного инспектора и нашего получателя на Linux

На игрушке. Если адресат вскроет подложное письмо вместе с настоящим, он перепутает страницы и ответит невпопад. Безвредность значит, что страницы не путаются, и переписка идёт дальше.

Заметим и порядок. Инспектор нашей игрушки судит по первому пакету с данными, поэтому подделка должна встать перед настоящим пакетом, а не после: если настоящий пойдёт первым, решение будет вынесено уже по нему. Как письмо, которое кладут на ленту первым, потому что сортировщик смотрит только на первое. Что программа так и делает, мы увидим на пакетах.

Откуда берётся содержимое подделки? Из блоба. Блоб, как мы разбирали в выпуске «Читаем командную строку zapret2», это именованный набор байтов: из файла, из текста или встроенный в программу. В программе уже есть встроенный образец приветствия, в коде он называется fake_default_tls. Это запись приветствия на шестьсот восемьдесят байт, и имя сайта в ней другое: www.microsoft.com.

Наше настоящее приветствие короче: двести пятьдесят байт, имя www.example.com. Значит, подделка длиннее настоящего пакета на четыреста тридцать байт. Откуда взялись байты образца и что в них лежит, разберём ближе к концу выпуска.

Десять байт, два размера Реальный пример глубины правдоподобия, блоб из десяти байт 16 03 01 00 05 aa bb cc dd ee, где наш инспектор смотрит только на первые два байта 16 03 и разрешает пакет, и два размера подделок в лаборатории, встроенный образец приветствия в 680 байт с именем www.microsoft.com на смещениях 125–141 и настоящее приветствие в 250 байт с именем www.example.com на смещениях 97–111, разница 430 байт. пример из лаборатории: подделка из десяти байт 1603010005aabbccddee заголовок записи TLS: 5 байтпять произвольных наш инспектор смотрит только на эти два байта: 16 03 итог опытаинспектор: разрешеносервер получил 250 байту подделки было ещёусловие MD5, о нём позже свойство нашего игрушечного инспектора, а не настоящего оборудования два размера подделки и настоящего: 680 и 250 байт встроенный образец: 680 байтимя www.microsoft.com: смещения 125–141начало: 16 03 01 02 a3, длина записи 675, всего 5 + 675 = 680 настоящее: 250имя www.example.com: смещения 97–111начало: 16 03 01 00 f5, длина записи 245, всего 5 + 245 = 250 подделка длиннее на 430 байт: 680 − 250 и то и другое: запись приветствия TLS, поэтому с первых двух байт 16 03 она выглядит как настоящее началооткуда взялись байты образца и что в них лежит, разберём ближе к концу выпуска

Проверь себя. Допустим, подделка умерла слишком рано: раньше, чем дошла до инспектора. Какая из трёх задач провалена?

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

Итак. У подделки три задачи: быть правдоподобной для инспектора, не быть принятой получателем и не навредить соединению. Дальше мы проверяем это не на словах, а на пакетах, и для этого нужна лаборатория.

Лаборатория и две точки отсчёта: игрушка запрещает настоящее приветствие, без игрушки всё проходит

Вопрос сцены: где и чем мы смотрим на подделки, и что бывает с настоящим приветствием, когда подделки нет совсем? Лабораторию опишем один раз, все опыты выпуска идут в ней.

Игрушка, выдуманная. Шлагбаум на въезде в город. Охранник смотрит на первую машину колонны и решает один раз: пропустить всю колонну или закрыть въезд.

Лаборатория знакома по выпуску «Письмо в сортировочном центре»: клиент, роутер, сервер и четыре точки, где мы записываем пакеты. Цэ-ноль это сетевая карта клиента после работы программы, эр-ноль вход в роутер, эр-один выход из него, эс-ноль сетевая карта сервера. Цэ-ноль стоит после программы, поэтому на ней видно всё, что программа отправила, в том числе подделки, которые потом не доедут до сервера. В этом выпуске новое одно: роутеров может быть два или три, и тогда добавляются точки эр-два и эр-три.

Срок жизни, TTL, мы помним. В выпуске «Что видит наблюдатель» мы называли его временем жизни, а в этом выпуске говорим «срок жизни». Клиент ставит шестьдесят четыре, каждый роутер вычитает единицу, а пакет с нулём роутер выбрасывает. В нашем тесте пакет с данными записан на четырёх точках со сроками шестьдесят четыре, шестьдесят четыре, шестьдесят три и шестьдесят три. Вход в роутер ещё не вычел единицу, выход уже вычел.

Теперь игрушечный инспектор этого выпуска. Это вторая копия программы nfqws2, мы поставили её на входе в первый роутер, до того как роутер проверит срок жизни. Подделок эта копия не делает: она запускает только наш короткий скрипт на Lua, который читает пакеты, записывает своё решение в журнал и выбрасывает запрещённые.

Она следит за одним соединением, от клиента к серверу, и решает один раз, по первому пакету с данными. Прежний игрушечный инспектор судил каждый пакет отдельно, а этот запоминает своё решение.

Внутри имя www.example.com: соединение запрещено, этот пакет и все следующие данные клиента выбрасываются. Запись приветствия TLS или строка запроса HTTP без имени: разрешено, дальше она пакеты не смотрит. Это наше допущение, а не знание о настоящем оборудовании.

Получатель один: сервер на Linux шестой серии. У него два слоя. Ядро принимает пакеты и может отвергнуть какие-то, записав это в свои счётчики. Приложение получает только то, что ядро приняло, и записывает, сколько байт пришло.

О счётчиках ядра, чтобы дальше не возвращаться. Ядро Linux, по общим сведениям, ведёт числовые счётчики: сколько пакетов оно приняло, сколько отвергло и по какой причине. Это не тот счётчик правила, что был в игрушечном инспекторе прошлых выпусков, а счётчики самого ядра. Мы снимали их у клиента и у сервера до каждого опыта и после, и разница показывает, какой из них сдвинулся. Имена счётчиков будут на экране, а по-русски мы читаем их как причину. Это наше чтение названий: прямо ядро причину не сообщает, доказано лишь, какой счётчик сдвинулся в каждом опыте.

Лаборатория и инспектор Лаборатория выпуска: клиент 10.1.0.1, от одного до трёх роутеров и сервер 10.2.0.2 с Linux 6.18, четыре точки съёмки c0, r0, r1 и s0 со сроками жизни 64, 64, 63 и 63 у пакета с данными, точки r2 и r3 на длинных цепочках, игрушечный инспектор первого пакета на входе в роутер 1, который решает один раз по первому пакету с данными, и счётчики ядра, например число повторных отправок клиента, выросшее с 0 до 5 в опыте с игрушкой. лаборатория: цепочка роутеров и четыре точки съёмки клиент10.1.0.1роутер 1роутер 2роутер 3сервер10.2.0.2 c0r0r1r2r3s0 игрушка на входе 64646363числа под точками: срок жизни пакета с данными; вход в роутер ещё не вычел единицу, выход уже вычел c0 это сетевая карта клиента после работы программы, r0 вход в роутер 1, r1 выход из него, s0 сетевая картасервера; r2 и r3 выходы из второго и третьего роутера, они есть на цепочках из двух и трёх роутеров игрушка первого пакета: наша модель наблюдателя вторая копия nfqws2 и один скрипт на Lua: читает, пишет решение в журнал, выбрасывает 1следит за одним соединением клиент → сервер и решает один раз, по первому пакету с данными 2имя www.example.com внутри: ЗАПРЕТ, этот пакет и все следующие данные клиента выбрасываются 3запись приветствия TLS или строка запроса HTTP без имени: РАЗРЕШЕНО, дальше пакеты не смотрятся стоит на входе в роутер 1 до проверки срока жизни; наше допущение, не знание об оборудовании получатель: сервер на Linux 6.18, два слоя ядропринимает пакеты, ведёт счётчики приложениеполучает принятое ядром, пишет сколько байт счётчики ядра клиентаTcp: RetransSegsдо: 0, после: 5разность 5, опыт с игрушкой имя читаем как причину: нашечтение, доказано лишь, что счётчиксдвинулся

Строка запуска, как в выпуске «Читаем командную строку zapret2»: фильтр вида данных, имя стратегии и её аргументы. Условий подделки в этой части поначалу нет.

Каждый опыт читаем одной таблицей: что ушло, где это видно, что получил сервер, что видит клиент и что решила игрушка.

Первая точка отсчёта. Стратегия «пустышка», по-латыни pass, из выпуска «Читаем командную строку zapret2»: она ничего не делает, только пишет в журнал, что её вызвали. Игрушка включена. Три пакета рукопожатия проходят: данных в них нет, и игрушка их не судит.

Потом клиент отправляет настоящее приветствие: двести пятьдесят байт, имя www.example.com. Игрушка читает этот первый пакет с данными, находит имя и решает: соединение запрещено. На выходе из роутера пакета нет.

Подтверждения клиент не получает и отправляет приветствие снова. В нашем тесте шесть отправок, паузы между ними растут, и все шесть есть на точках цэ-ноль и эр-ноль, а на точках эр-один и эс-ноль ни одной. Журнал игрушки отмечает первый пакет строкой «имя найдено», остальные пять строкой «соединение запрещено, данные выбрасываю». Счётчик повторов в ядре клиента вырос на пять.

Сервер не получил данных вовсе и закрывает молчащее соединение примерно через две секунды после первого приветствия. Шестое приветствие клиент отправил уже после закрытия. Итог: сервер получил ноль байт.

У нашей игрушки всё решает первый пакет с данными, что бы ни пришло дальше. Поэтому подделку нужно поставить перед настоящим приветствием, на первое место.

Проверь себя. Выключим игрушку и оставим пустышку. Что получит сервер?

Приветствие идёт одно, без повторов, и срок жизни у него падает с шестидесяти четырёх до шестидесяти трёх. Сервер отвечает подтверждением: жду двести пятьдесят первый байт. Следом идёт его ответ в четырнадцать байт. Сервер получил ровно двести пятьдесят байт, с именем www.example.com.

Назовём такой итог «чистым потоком»: сервер получил ровно настоящие байты клиента. Это наш эталон успеха. В таблицах будем писать «чисто», когда так, и «испорчен», когда сервер получил что-то другое. Подделка должна пройти две проверки сразу: игрушка не запрещает соединение, и поток остаётся чистым.

Две точки отсчёта Две точки отсчёта с пустышкой pass: при включённой игрушке настоящее приветствие в 250 байт запрещено, клиент шлёт его шесть раз на c0 и r0 и ни разу на r1 и s0, повторов в ядре клиента 5, и сервер получает 0 байт, а без игрушки приветствие проходит одним пакетом со сроками 64, 64, 63, 63, сервер подтверждает 251, отвечает 14 байтами, получает ровно 250 байт и поток чистый. строка запуска и два опыта с пустышкой pass --payload=tls_client_hello --lua-desync=passфильтр вида данных, затем стратегия «пустышка» опыт 1: игрушка включена 123456FIN серверашесть приветствий, паузы растут (один запуск) c0 и r0: шесть приветствийr1 и s0: ни одного, пакет выброшен на входе carries the blocked name: connectionBLOCKED, dropping this packet and later data+ 5 строк: TOY blocked connection: dropping data ядро клиента: RetransSegs 0 → 5 сервер0 байтклиентсоединение закрыто,ответа нет опыт 2: игрушки нет c0срок 64r0срок 64r1срок 63s0срок 63 одно приветствие в 250 байт доходит до сервера сервер: подтверждение, жду 251-й байтсервер: ответ в 14 байт, подтверждение 251сервер: подтверждение 252 вместе с FIN сервер250 байтклиентответ получен имя в приветствии: www.example.com две точки отсчёта для всех опытов выпуска игрушка без подделки: ЗАПРЕТ, сервер 0 байт без игрушки: сервер 250 байт, чисто «чисто»: сервер получил ровно настоящие байты клиента; «испорчен»: получил что-то другоеподделка должна пройти обе проверки сразу: игрушка не запрещает соединение, и поток остаётся чистым

Итак. В лаборатории от одного до трёх роутеров, игрушка на входе в первый и один получатель на Linux. С игрушкой и без подделки настоящее приветствие запрещено, сервер получает ноль байт. Без игрушки оно проходит, и поток чистый. Между этими двумя опытами и будет работать подделка.

Подделка без условий: пакет за пакетом

Вопрос сцены: что будет, если отправить подделку без единого условия? Это самый важный опыт части: он показывает, зачем нужны условия.

Игрушка, выдуманная. Адресат получает два письма с надписью «страница один». Первое кладёт в папку как первую страницу, второе считает дубликатом. Какое пришло первым, то и стало страницей.

Строка опыта: стратегия fake с аргументом blob, образец fake_default_tls, условий подделки нет, игрушка включена. Номера здесь относительные, как в выпусках «Что видит наблюдатель» и «multisplit: резать на части»: начало соединения принято за ноль, а первый байт данных клиента получает номер один. Идём по точке цэ-ноль пакет за пакетом. Первый пакет с данными несёт шестьсот восемьдесят байт, номер один. Это подделка, срок жизни у неё обычный.

Сразу за ней второй пакет: настоящее приветствие, двести пятьдесят байт, и номер тоже один. Два разных пакета встали на одно место потока.

И идентификатор IP у них одинаковый. Откуда он берётся, скажем в следующей сцене. Оба пакета видны на входе в роутер, на выходе и на сервере.

Игрушка на входе читает первый пакет с данными, то есть подделку. Заблокированного имени там нет, это запись приветствия TLS: разрешено, дальше не смотрим. Настоящее приветствие игрушка уже не читает. В нашем тесте первую задачу подделка выполнила: игрушка обманута.

Подделка без условий Опыт с фейком без условий и включённой игрушкой: на c0 первым уходит пакет подделки в 680 байт, за ним настоящее приветствие в 250 байт, оба с флагами PSH и ACK, номером 1, сроком жизни 64 и одним ip id 191, оба проходят r0, r1 и s0, игрушка читает только первый пакет с данными и по записи TLS без запрещённого имени разрешает соединение, поэтому первую задачу подделка выполнила, а на ленте номеров два пакета занимают одно и то же место потока. опыт: фейк без условий, игрушка включена --payload=tls_client_hello --lua-desync=fake:blob=fake_default_tlsусловий подделки нет номера относительные: начало соединения 0, первый байт данных клиента 1 c0r0r1s0 1подделка: 680 байтAP · номер 1 · ip id 191ttl 64ttl 64ttl 63ttl 63 2настоящее: 250 байтAP · номер 1 · ip id 191ttl 64ttl 64ttl 63ttl 63 игрушка len=680: TOY first data packet is a TLS handshake record without the blocked name:connection ALLOWED, not examining the restlen=250: TOY allowed connection: not examined игрушка читает первым подделку: имени нет, это запись TLS, разрешено в нашем тесте первую задачу подделка выполнила: игрушка обманута лента номеров: два пакета на одном месте потока подделка: номера 1 … 680настоящее 1 … 2501250680

Теперь сервер. Он принимает первый из двух пакетов, подделку, как данные с первого байта по шестьсот восьмидесятый. Подтверждение сервера: жду шестьсот восемьдесят первый байт. Следом он отправляет свой ответ в четырнадцать байт. Приложение сервера получило один кусок, и в нём имя www.microsoft.com.

Настоящее приветствие приходит после и целиком лежит в уже принятом месте. Сервер отвечает блоком SACK, и этот блок лежит ниже самого подтверждения. Так, мы помним по выпуску «Перемешать: disorder», получатель сообщает о дубликате.

А что видит клиент? Он отправил двести пятьдесят байт, и его следующий номер двести пятьдесят первый. Сервер же подтверждает шестьсот восемьдесят один: данные, которых клиент не отправлял.

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

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

Виновата ли игрушка? Нет. В таком же опыте без игрушки сервер получил те же шестьсот восемьдесят байт, а клиент так же молчал. Беда не в инспекторе, а в том, что получатель принял подделку за данные.

Откуда настоящий пакет после подделки уходит как обычно? Функция fake отправляет подделку тем же отправщиком части, что и в выпуске «multisplit: резать на части», только вместо данных подставлен блоб. Функция кончается без возврата, программа читает «ничего» как «пропустить», и настоящий пакет идёт следом. Multisplit, напомним, возвращала «выбросить».

Взгляд сервера и клиента Чем кончается фейк без условий: сервер принимает подделку за данные и подтверждает 681, то есть принято 680 байт, а клиент отправил только 250 и отвечает пустыми пакетами с номером 251 и подтверждением 1, так что ответа сервера он не получает, перед каждым из шести повторов приветствия встаёт своя подделка с ip id 191, 193, 194, 195, 197 и 199, а в коде фейк кончается без возврата, и программа читает это как пропустить, тогда как multisplit возвращала выбросить. в нашем тесте: что видят сервер и клиент сервер, точка s0клиент, точка c0 подтверждение после подделки: 681то есть жду 681-й байт отправил 250 байт, следующий номер 251 ответ в 14 байт, подтверждение всё ещё 681 получил подтверждение 681данных, которых не отправлял после настоящего: 681 и SACK [1,251)блок ниже подтверждения: это дубликат пустые пакеты: номер 251, подтверждение 1байты ответа сервера не приняты приложение: один кусок, 680 байтв нём имя www.microsoft.com ответ в 14 байт повторяется, ответа неттишина 4 секунды ядро клиента: TCPChallengeACK 5, TCPACKSkippedChallenge 20читаем как «недопустимые подтверждения»: это наше прочтение названий арифметика подтверждения сервер ждёт 681принял 681 − 1 = 680 байтнастоящих байт 250все принятые байты: подделкабез подделки ждал бы 251тогда 251 − 1 = 250 перед каждым из шести повторов приветствия своя подделка 680250ip id 191680250ip id 193680250ip id 194680250ip id 195680250ip id 197680250ip id 199 везде номер 1; стратегия не помнит, что фейк уже был, и вызывается на каждом подходящем пакете без игрушки тот же итог: сервер получил 680 байт, клиент молчит; беда не в инспекторе в коде: почему настоящий пакет идёт следом zapret-antidpi.lua:468rawsend_payload_segmented(desync,fake_payload)zapret-antidpi.lua:473end -- конец fake, return нетnfq2/desync.c:846// absence of the return value or nil mean VERDICT_PASSzapret-antidpi.lua:527return desync.arg.nodrop and VERDICT_PASS or VERDICT_DROP -- это multisplit

Проверь себя. Сервер ответил: жду шестьсот восемьдесят первый байт. Сколько байт он принял и чьи они?

Шестьсот восемьдесят. Настоящих байт у клиента всего двести пятьдесят, значит, все принятые байты это подделка, и цифра в подтверждении её выдаёт. Без подделки сервер ждал бы двести пятьдесят первый.

Итак. Подделка без условий обманула игрушку и сломала соединение: сервер принял её за данные, клиент это подтверждение не принял, переписка встала. Значит, нужно условие, при котором получатель подделку не примет. Начнём с самой простой: пусть она умрёт по дороге.

Анатомия подделки: что у неё общее с настоящим пакетом, в каком порядке она уходит и почему очередь её не видит

Вопрос сцены: из чего сделана подделка и чем она отличается от настоящего пакета? Ответ даст проба: положить два пакета рядом.

Берём пакеты из опыта прошлой сцены. Первая группа одинаковых полей: адреса и порты, идентификатор IP, порядковый номер и номер подтверждения. Значения совпадают до последней цифры. Вторая группа: флаги, окно, параметры TCP с отметкой времени и срок жизни. Даже отметка времени у подделки та же, что у настоящего пакета.

Различаются четыре вещи, и все они связаны с тем, что данные другие. Данные: у подделки шестьсот восемьдесят байт. У настоящего пакета двести пятьдесят. Длина в заголовке IP, потому что данные другие. И обе контрольные суммы, IP и TCP: программа пересчитала их, обе верны, это просто суммы разных пакетов.

Подделка и настоящее приветствие Таблица сравнения подделки и настоящего приветствия из нашего опыта на выходе клиента: девять полей совпадают до последней цифры (идентификатор IP 191, адреса и порты, порядковый номер, номер подтверждения, флаги PSH и ACK, окно 63, параметры TCP с отметкой времени, заголовок TCP 32 байта, срок жизни 64), а различаются четыре вещи, связанные с другими данными: данные 680 против 250 байт, длина в заголовке IP 732 против 302, контрольная сумма IP 0x2358 против 0x2506 и контрольная сумма TCP 0x8028 против 0x7016, причём обе суммы верны. Два пакета рядом: подделка и настоящее приветствие пример: один опыт, выход клиента подделкапервый пакет с данныминастоящее приветствиевторой пакет с данными совпадает идентификатор IP191191= адреса и порты10.1.0.1:46204 -> 10.2.0.2:44310.1.0.1:46204 -> 10.2.0.2:443= порядковый номер (сырой)335373287 (относит. 1)335373287 (относит. 1)= номер подтверждения (сырой)40858206114085820611= флагиPSH+ACK (AP)PSH+ACK (AP)= окно6363= параметры TCPNOP NOP TS=1629753311/65330837NOP NOP TS=1629753311/65330837= заголовок TCP, байт3232= срок жизни6464= различается данные, байт680250≠ отпечаток данных (sha256)11d33b3d926b100c2aa4206d≠ длина в заголовке IP732 = 20 + 32 + 680302 = 20 + 32 + 250≠ контрольная сумма IP0x2358 (верна)0x2506 (верна)≠ контрольная сумма TCP0x8028 (верна)0x7016 (верна)≠ Четыре различия, и все они следуют из того, что данные другие: сами данные, длина в заголовке IPи две контрольные суммы. Суммы программа пересчитала: обе верны, это просто суммы двух разных пакетов. Всё, что не пересчитывается заново, подделка наследует от настоящего пакета без изменений.

Откуда эта копия? Отправщик части, знакомый нам по выпуску «multisplit: резать на части», копирует таблицу текущего пакета и вместо данных ставит блоб. Потом применялка условий, по-латыни apply_fooling, делает ещё одну копию и применяет к ней условия. Итого две операции: копия с блобом вместо данных и условия на этой копии. У fake условия касаются только отправляемой копии: настоящий пакет они не трогают.

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

Идентификатор IP у подделки и у настоящего пакета один: функция берёт его из текущего пакета, а следующий свой пакет нумерует на единицу больше.

Шесть отправок приветствия на точке цэ-ноль дают шесть пар. В каждой паре подделка стоит перед настоящим пакетом, и у пары один идентификатор.

Порядок. В наших запусках, где подделка шла на пакете с данными, первым с данными на точке цэ-ноль стоял фейк, настоящее приветствие следом. Подделка уходит в ядро прямо внутри вызова функции, а настоящий пакет программа отпускает только после того, как вернулись все стратегии. Устройство главного цикла разбирает выпуск «Внутри программы».

Почему программа не ловит свою подделку снова? Ядро отдаёт ей пакеты через очередь. Свои пакеты программа помечает особой отметкой, по общим сведениям это число, которое ядро хранит рядом с пакетом, и правило ядра помеченные пакеты в очередь не отдаёт; подробности в выпуске «Внутри программы». Нам достаточно журнала: среди пакетов, которые получила программа, ни одной подделки нет.

Копия, порядок и очередь Откуда берётся подделка, в каком порядке она уходит и почему не возвращается в очередь: функции копируют таблицу текущего пакета (строка 1284 файла lua/zapret-lib.lua) и подменяют данные блобом (1285), потом делают вторую копию (1235) и применяют условия к ней (1237), идентификатор IP первый свой пакет берёт у настоящего, а следующий больше на единицу (1098-1099); в опыте с шестью приветствиями подделка 680 байт стоит перед настоящим пакетом 250 байт в каждой из шести пар и у пары один идентификатор, все 24 строки журнала очереди несут отметку 00000000, а шесть отправленных подделок несут особую отметку 40000000, поэтому в очередь программа получила шесть пакетов, а на проводе клиента было двенадцать. Откуда копия, в каком порядке уходит и почему не возвращается в очередь две операции над копией rawsend_payload_segmentedlua/zapret-lib.lua:1282-1290 1284local dis = (payload or seq and seq~=0) and deepcopy(desync.dis) or desync.dis↳ сначала копия таблицы текущего пакета 1285if payload then dis.payload = payload end↳ в копии вместо данных блоб подделки: 680 байт rawsend_dissect_segmentedlua/zapret-lib.lua:1233-1279 1235local discopy = deepcopy(dis)↳ вторая копия: для условий 1237apply_fooling(desync, discopy, options and options.fooling)↳ у fake условия применяются к этой копии, настоящий пакет не трогают apply_ip_idlua/zapret-lib.lua:1086-1107 1098dis.ip.ip_id = desync.ip_id or dis.ip.ip_id 1099desync.ip_id = dis.ip.ip_id + 1↳ первый свой пакет берёт номер настоящего, следующий на единицу больше порядок на проводе: клиент, точка c0 (не в масштабе времени) 680 250id 191 680 250id 193 680 250id 194 680 250id 195 680 250id 197 680 250id 199 подделка: уходит в ядро прямо внутри вызова функции, поэтому перваянастоящий пакет: программа отпускает после возврата всех стратегий (цикл с verdict_aggregate,nfq2/desync.c:1087) журнал программы в том же опыте: что пришло из очереди и что она отправила сама из очереди: строки с данными клиентаpacket: id=4 len=302 mark=00000000packet: id=10 len=302 mark=00000000packet: id=14 len=302 mark=00000000packet: id=18 len=302 mark=00000000packet: id=21 len=302 mark=00000000packet: id=23 len=302 mark=00000000 отправила сама: rawsend_dissectrawsend_dissect ... size=732 ... fwmark=40000000rawsend_dissect ... size=732 ... fwmark=40000000rawsend_dissect ... size=732 ... fwmark=40000000rawsend_dissect ... size=732 ... fwmark=40000000rawsend_dissect ... size=732 ... fwmark=40000000rawsend_dissect ... size=732 ... fwmark=40000000 Отметка 40000000 в шестнадцатеричной записи: особая отметка собственных пакетов программы, правило ядрапомеченные пакеты в очередь не отдаёт. Во всех 24 строках очереди этого опыта стоит mark=00000000: подделоксреди них нет.глосса: очередь и отметка, устройство в выпуске «Внутри программы», он выйдет позже Проверь себяПриветствие отправлено один раз, fake сработал: сколько пакетов с данными получит программа и сколькоуйдёт в сеть? Из очереди один (настоящее приветствие), в сеть два (подделка и настоящее).В опыте на шесть приветствий так и вышло: шесть пакетов в очереди и двенадцать на проводе.

Проверь себя. Клиент отправил приветствие один раз, и стратегия fake сработала. Сколько пакетов с данными получит программа из очереди и сколько уйдёт в сеть?

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

Итак. Подделка это копия заголовков настоящего пакета с другими данными и пересчитанными длиной и суммами. Она уходит первой, с тем же идентификатором, и в очередь не возвращается. Всё, что мы захотим изменить в заголовках, делают условия.

Письмо с просроченной маркой: срок жизни один на одном роутере

Вопрос сцены: как заставить подделку исчезнуть по дороге, и что при этом видят все участники?

Игрушка, выдуманная. На письме марка с числом штрихов. Сортировочный центр, получив письмо, смотрит на марку. Остался один штрих: письмо выбрасывают, а отправителю шлют записку «срок вышел». Штрихов больше: центр стирает один и отправляет письмо дальше. Марка на два штриха и три центра подряд: первый стирает штрих, второй видит последний и выбрасывает письмо. До третьего оно не доедет.

В сети роль марки играет срок жизни, TTL: каждый роутер вычитает единицу, и пакет, у которого срок вышел, роутер выбрасывает. Условие, которым мы его задаём, называется «срок жизни подделки», по-латыни ip_ttl. Число записывается в заголовок IP подделки, и только подделки; настоящий пакет уходит со своим сроком.

Опыт. Один роутер, ip_ttl равно один. На точке цэ-ноль сначала подделка: шестьсот восемьдесят байт, срок жизни один. Следом настоящее приветствие со сроком шестьдесят четыре.

На входе в роутер видны оба пакета. Игрушка читает первый пакет с данными, то есть подделку, и в её журнале срок жизни пакета один. Заблокированного имени там нет: разрешено.

На выходе из роутера только настоящее приветствие, и срок у него уже шестьдесят три. Подделку роутер выбросил: срок у неё вышел, и дальше она не пошла. Это видно по точкам: на эр-один и на эс-ноль её нет.

Марка со штрихами и опыт Игрушка со штрихами на марке и настоящий опыт с одним роутером и сроком жизни подделки 1: в игрушке центр стирает один штрих или, увидев последний, выбрасывает письмо и шлёт отправителю записку «срок вышел», а в опыте подделка со сроком 1 видна на выходе клиента c0 и на входе в роутер r0, где стоит игрушечный инспектор и читает её в журнале как ttl=1 и разрешает соединение, после роутера на r1 и у сервера на s0 её уже нет, там только настоящее приветствие со сроком 63, а клиент получает ICMP 10.1.0.2 типа 11 кода 0. Срок жизни подделки: игрушка и опыт с одним роутером игрушка: марка со штрихами выдуманный пример отправитель центр 1 центр 2 центр 3 получатель 2 штриха 1 штрих записка отправителю: «срок вышел» два штриха: стираетодин, письмо идётдальше один штрих: письмовыбрасывают до третьего письмоне доехало настоящий опыт: срок жизни подделки 1, один роутер c0выход клиента r0вход в роутер 1 r1выход роутера 1 s0сервер подделка, 680 байтid 19710, PSH+ACK ttl 1 ttl 1 нет нет настоящее, 250 байтid 19710, PSH+ACK ttl 64 ttl 64 ttl 63 ttl 63 ICMP «время истекло»10.1.0.2 -> 10.1.0.1, тип 11 виден виден нет нет игрушка стоит на входе в роутер 1, до вычитания единицы: поэтому видит подделку со сроком 1это допущение нашей модели; где стоит настоящий инспектор, неизвестно журнал игрушки: первый пакет с данными TOY first data packet is a TLS handshake record without the blocked name:connection ALLOWED, not examining the rest seq=136239465 len=680 ttl=1 Имени в первом пакете нет: игрушка разрешает всё соединение, и настоящее приветствие она уже не разбирает.

Но роутер не молчит. По общим сведениям, так сети сообщают об ошибках: он отправляет обратно отправителю пакета служебное сообщение «время истекло», ICMP, с адреса самого роутера. В нём роутер приводит начало выброшенного пакета, поэтому клиент получает цитату собственной подделки. Размеры покажем на экране.

Итог опыта. Сервер подтвердил двести пятьдесят первый байт, ответил четырнадцатью байтами и закрыл соединение. Он получил чистые двести пятьдесят байт, и клиент получил ответ. Повторов не было: на точке цэ-ноль ровно два пакета с данными, подделка и настоящий. В нашем тесте, против нашей игрушки и нашего получателя, сработали все три задачи сразу.

Цитата ICMP и срок 2 Что клиент получает в ответ на подделку со сроком жизни 1 и чем кончается опыт: ICMP «время истекло» длиной 576 байт состоит из 20 байт заголовка IP, 8 байт ICMP и цитаты в 548 байт, а цитата это 20 байт заголовка IP подделки и 528 байт её сегмента TCP (32 байта заголовка и первые 496 из 680 байт данных), поэтому 184 байта подделки в цитату не вошли; в итоге сервер получил чистые 250 байт, клиент получил ответ, игрушка разрешила соединение, на выходе клиента было ровно два пакета с данными, срок жизни записывает строка 1064 файла lua/zapret-lib.lua, а при сроке 2 подделка доходит до сервера, который отвечает подтверждением 681 с блоком SACK и принимает 680 байт. Сообщение «время истекло», итог опыта и срок 2 что клиент получает от роутера ICMP «время истекло»: 576 байт = 20 (IP) + 8 (ICMP) + 548 (цитата) цитата: начало подделки, 548 байтIP 20ICMP 8 сама подделка на проводе: 732 байта = 20 + 32 + 680 данные: первые 496 из 680 байтне вошлиIP 20TCP 32 обрезано: 184 байта 548 = 20 (заголовок IP подделки: срок жизни 1, длина 732) + 528 (весь её сегмент TCP, который влез: 32 байтазаголовка и первые 496 из 680 байт данных). Клиент получает цитату собственной подделки. общее правило, не из кода: ICMP версии четыре укладывается в 576 байт итог опыта сервер получил250 байт, www.example.com сервер ответилack 251, 14 байт, ack 252 клиентответ получен игрушкаРАЗРЕШЕНО на выходе клиента c0два пакета с данными повторовни одного Против нашей игрушки и нашего получателя три задачи подделки сработали сразу. где срок записывается: lua/zapret-lib.lua 1062if dis.ip then1063 local ttl = ttl_discover(fooling_options.ip_ttl,fooling_options.ip_autottl)10641065end if ttl then dis.ip.ip_ttl = ttl end<- число пишется в заголовок копии проверь себя: тот же роутер, срок жизни подделки 2 c0 r0 r1 s0 подделкаttl на точке ttl 2 ttl 2 ttl 1 ttl 1 сервер ответилack 681 и SACK [1,251) приложение сервера680 байт, www.microsoft.com клиентмолчит: нет ответа за 4 с Роутер выбрасывает пакеты со сроком, который вышел, а получатель пакет со сроком 1 принял (по общимсведениям): те же симптомы, что без условия. Срок 2 на одном роутере уже не работает.

И важная оговорка о нашей модели. Игрушка стоит на входе в первый роутер, до того как роутер вычтет единицу. Поэтому она видит подделку со сроком один. Если бы инспектор стоял за роутером, он такую подделку не увидел бы. Где стоит настоящее оборудование относительно места, где умирает пакет, мы не знаем.

Проверь себя. Та же схема, один роутер, но срок жизни подделки два. Дойдёт ли она до сервера?

Дойдёт. Роутер сотрёт один штрих, на выходе срок один, и по общим сведениям получатель такой пакет принимает: выбрасывают роутеры, а не получатель. В нашем тесте сервер ответил подтверждением с номером шестьсот восемьдесят один, то есть принял шестьсот восемьдесят байт. Приложение получило подделку, а клиент молчал: те же симптомы, что без условия.

Итак. Срок один на одном роутере: подделка доходит до игрушки на входе, на выходе её нет, клиент получает цитату, а сервер чистый поток. Срок два на одном роутере уже не работает: подделка доходит до сервера.

Какое число ставить: два и три роутера, и где умирает каждая подделка

Вопрос сцены: какое число ставить, если между клиентом и сервером несколько роутеров, и где умрёт подделка при каждом числе?

Игрушка со штрихами из прошлой сцены. Марка на два штриха и три центра подряд: письмо умирает на втором. Где оно пропало, зависит только от числа штрихов.

Теперь настоящие пакеты. Два роутера, срок один. Подделка видна на точках цэ-ноль и эр-ноль, дальше её нет, а служебное сообщение «время истекло» пришло с адреса первого роутера. Сервер получил чистые двести пятьдесят байт.

Срок два на тех же двух роутерах. Подделка проходит первый, на его выходе срок уже один. На втором роутере она умирает, и ответ «время истекло» приходит с адреса второго. Настоящее приветствие доходит до сервера со сроком шестьдесят два, и поток чистый.

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

Теперь три роутера. Срок три: подделка видна на всех точках до выхода из второго роутера, умирает на третьем, ответ приходит с адреса третьего, сервер получает чистый поток. Срок четыре доходит до сервера и портит поток.

Правило по счёту. Подделка достигает сервера, если срок не меньше числа роутеров плюс один. Срок, равный числу роутеров, убивает её на последнем роутере, перед самым сервером. Срок меньше умирает раньше. Наши запуски на трёх путях согласны с правилом.

Где умирает подделка Шесть опытов показывают, где умирает подделка при сроке жизни от 1 до 4 на путях из одного, двух и трёх роутеров: срок, равный числу роутеров или меньше, убивает её на роутере с номером срока и клиент получает ICMP «время истекло» с адреса этого роутера, а срок не меньше числа роутеров плюс один доводит подделку до сервера, и тогда приложение сервера получает 680 байт подделки вместо чистых 250 байт; правило доходит тогда и только тогда, когда срок не меньше числа роутеров плюс один, и срок, равный числу роутеров, убивает подделку на последнем роутере перед самым сервером. Где умирает подделка: путь из одного, двух и трёх роутеров путь и сроксрок подделки на отрезкечто увидели 1 роутер, срок 11 < 1 + 1: умирает клиент1сервер1 ICMP от 10.1.0.2 (роутер 1)сервер: 250 байт, чисто 2 роутера, срок 11 < 2 + 1: умирает клиент12сервер1 ICMP от 10.1.0.2 (роутер 1)сервер: 250 байт, чисто 2 роутера, срок 22 < 2 + 1: умирает клиент12сервер21 ICMP от 10.9.1.2 (роутер 2)сервер: 250 байт, чисто 2 роутера, срок 33 ≥ 2 + 1: доходит клиент12сервер321 ICMP нет: подделка не умерласервер: 680 байт, испорчено 3 роутера, срок 33 < 3 + 1: умирает клиент123сервер321 ICMP от 10.9.2.2 (роутер 3)сервер: 250 байт, чисто 3 роутера, срок 44 ≥ 3 + 1: доходит клиент123сервер4321 ICMP нет: подделка не умерласервер: 680 байт, испорчено Игрушка стоит на входе в роутер 1 и видит подделку во всех шести опытах: вердикт «РАЗРЕШЕНО». Где стоитнастоящий инспектор, мы не знаем. подделка доходит до сервера ⇔ срок ≥ роутеров + 1срок равен числу роутеров: подделка умирает на последнем роутере, перед самым серверомсрок меньше: умирает раньше

Крайнее значение: ноль. Оно допустимо, подделка отправляется и умирает на первом роутере.

Теперь заметим, что в цитате внутри служебного сообщения срок такой, с каким подделка пришла на этот роутер. У нас это единица, а при исходном нуле в цитате ноль. По общим сведениям, роутер выбрасывает такой пакет и цитирует его в том виде, как получил.

Зачем целиться в последний роутер, если хватило бы и единицы? В нашей лаборатории игрушка стоит на входе, и ей достаточно единицы. Если бы инспектор стоял дальше по пути, подделка с единицей до него не дошла бы, и срок пришлось бы подбирать ближе к концу пути. Это рассуждение, мы его не проверяли.

Другое крайнее значение: двести пятьдесят шесть. Программа его отвергает. В нашем тесте это ошибка внутри Lua, подделка не отправляется вовсе, игрушка запрещает настоящее приветствие, и сервер получает ноль байт.

Прогон функций, напомним, это запуск настоящих функций без сети. Двести пятьдесят пять допустимо и записывается как есть. Минус один и слово вместо числа дают ту же ошибку. Допустимо число от нуля до двухсот пятидесяти пяти.

Проверь себя. Три роутера, срок жизни два. Где умрёт подделка?

На втором роутере. На выходе из первого срок один, и второй роутер её выбрасывает. Это не запуск, а арифметика по нашему правилу: запуска со сроком два на трёх роутерах у нас нет. Два меньше трёх плюс один, значит, до сервера она не дойдёт.

Ноль, 256 и счёт на бумаге Крайние значения срока жизни подделки и счёт на бумаге: срок 0 допустим, подделка уходит и умирает на первом роутере, а в цитате ICMP стоит срок, с каким она пришла на роутер (1 в опытах со сроками 1, 2 и 3, и 0 при исходном нуле), срок 256 программа отвергает ошибкой в строке 964 файла lua/zapret-lib.lua, подделка не отправляется, игрушка запрещает настоящее приветствие и сервер получает 0 байт, допустимы числа от 0 до 255, а на трёх роутерах при сроке 2 подделка по правилу умрёт на втором роутере. Крайние значения срока жизни: ноль, 256 и счёт на бумаге срок 0 допустим клиент01сервер подделка со сроком 0 видна на c0 и r0,на r1 её нет; роутер её выбросилICMP от 10.1.0.2, в цитате ip_ttl=0сервер: 250 байт, чисто ICMP «время истекло»: откуда и что в цитате 1 роутер, срок 110.1.0.2ip_ttl=1 2 роутера, срок 210.9.1.2ip_ttl=1 3 роутера, срок 310.9.2.2ip_ttl=1 1 роутер, срок 010.1.0.2ip_ttl=0 В цитате стоит срок, с каким подделка пришла на этот роутер: при исходных 1, 2 и 3 это 1, при исходном 0 это 0.По общим сведениям, роутер выбрасывает такой пакет и цитирует его в том виде, как получил. срок 256 недопустим LUA ERROR: ...zapret-lib.lua:964: apply_fooling: ip_ttl and ip6_ttl require valid value на c0 за 6 отправоктолько настоящие подделкане отправлена вовсе игрушкаЗАПРЕТ сервер и клиент0 байт, ответа нет Это ошибка пользователя, а не часть правила: подделка не уходит, и стратегия не защищает приветствие. допустимые значения: от 0 до 255 962ttl = tonumber(arg_ttl)963if not ttl or ttl<0 or ttl>255 then964 error("apply_fooling: ip_ttl and ip6_ttl require valid value") Проверка выполняется только когда автоматический подбор срока ничегоне дал: о нём в конце части. прогон функций без сети ip_ttl=0срок 0 ip_ttl=1срок 1 ip_ttl=255срок 255 ip_ttl=256ошибка ip_ttl=-1ошибка ip_ttl=abcошибка проверь себя: три роутера, срок 2, где умрёт подделка срок 23 роутера клиент123сервер21 умрёт на втором роутере:на выходе из первого срок 1 Это не запуск, а счёт по правилу: 2 < 3 + 1, значит, до сервера подделка не дойдёт. Запуска со сроком 2 на трёхроутерах у нас нет.

Итак. Подделка умирает на том роутере, где срок кончается, и до сервера доходит, только если срок не меньше числа роутеров плюс один. Срок ноль допустим, а число вне границ ломает стратегию. А откуда программа знает число роутеров? Об этом в конце части.

IPv6: предельное число переходов, и условие не той версии, которое молча не делает ничего

Вопрос сцены: что меняется для IPv6, и что будет, если написать условие не той версии?

Лаборатория умеет ходить и по IPv6: это шестая версия протокола IP, мы упоминали её в выпуске «Что видит наблюдатель». По общим сведениям, адреса там другие и заголовок устроен немного иначе, но клиент, роутер, сервер и те же приветствия остаются. До сих пор мы работали по IPv4, четвёртой версии, как в прошлых выпусках.

Игрушка, выдуманная. Фабрика с двумя конвейерами, у каждого свой переключатель скорости, на каждом подпись. Если повернуть переключатель второго конвейера, первый не заметит ничего, и никто об этом не скажет. Нас будет интересовать, какой переключатель мы повернули.

В IPv6 нет поля «срок жизни» под этим названием. Поле того же назначения называется предельным числом переходов, по-английски hop limit, и по общим сведениям роутер вычитает из него единицу так же, как из срока жизни. Условие, которым мы его задаём, это «предельное число переходов подделки», по-латыни ip6_ttl. Это то же правило под другим именем.

Опыт на одном роутере, ip6_ttl равно один. Подделка видна на точках цэ-ноль и эр-ноль и дальше не идёт. Роутер отвечает служебным сообщением «время истекло» для IPv6, с адреса самого роутера, и в цитате значение один. Сервер получил чистые двести пятьдесят байт.

Два роутера, ip6_ttl равно два. Подделка проходит первый роутер и умирает на втором, ответ приходит с адреса второго, поток чистый. Правило то же, что для IPv4: умирает там, где значение кончается.

Есть разница в цитате: в IPv6 она в нашем тесте длиннее, подделка процитирована целиком. В IPv4, помните, цитата была обрезана.

IPv6: то же правило IPv6 даёт то же правило под другим именем: в IPv4 подделку убивает срок жизни ip_ttl, в IPv6 предельное число переходов ip6_ttl (поле ip6_hlim), и в опытах при ip6_ttl равном 1 на одном роутере и 2 на двух подделка умирает там, где значение кончается, клиент получает ICMPv6 «время истекло» типа 3 с адресов fd01::2 и fd09:1::2, а сервер получает чистые 250 байт; цитата в IPv6 длиной 752 байта (40 + 712) содержит подделку целиком, а в IPv4 цитата обрезана до 548 из 732 байт; игрушка с фабрикой и двумя переключателями показывает, что поворот не того переключателя ничего не меняет и никто об этом не сообщает. IPv6: то же правило под другим именем IPv6: шестая версия протокола IP; до сих пор мы работали по IPv4, четвёртой версии игрушка: фабрика с двумя конвейерами выдуманный пример конвейер 1 переключатель 1медл.быстро конвейер 2 переключатель 2медл.быстро Переключатель второго конвейера повернули: второй конвейер пошёл быстрее, а первый не заметил ничего.И никто об этом не сказал. Важно, какой переключатель мы повернули. два имени одного правила (по общим сведениям) IPv4: срок жизни, TTLполе заголовка ip_ttlусловие подделки: ip_ttl IPv6: предельное число переходовhop limit, поле ip6_hlimусловие подделки: ip6_ttl = Роутер вычитает единицу из того и другого. Это то же правило под другим именем. опыты по IPv6 1 роутер, ip6_ttl=1предел переходов 1 клиент1сервер1 ICMPv6 (тип 3, код 0) от fd01::2цитата: hlim=1 nh=6 plen=712сервер: 250 байт, чисто 2 роутера, ip6_ttl=2предел переходов 2 клиент12сервер21 ICMPv6 (тип 3, код 0) от fd09:1::2цитата: hlim=1 nh=6 plen=712сервер: 250 байт, чисто цитата внутри ICMP: подделка процитирована IPv4, срок 1цитата 548 из 732 байтобрезана IPv6, предел 1цитата 752 = 40 + 712: целикомцеликом ICMPv6 целиком: 800 байт = 40 + 8 + 752. В IPv4 цитата была обрезана, в IPv6 в нашем тесте нет.

Теперь ошибка, которую легко сделать. Клиент идёт по IPv6, а в строке стоит условие ip_ttl, для IPv4. В нашем тесте на проводе у подделки предельное число переходов шестьдесят четыре, как у любого обычного пакета: условие ничего не изменило. Подделка дошла до сервера, приложение приняло шестьсот восемьдесят байт, клиент молчит. И ни одной строки об ошибке.

Условие не той версии молчит Условие не той версии молча ничего не делает: в опыте с потоком IPv6 и единственным условием ip_ttl=1 подделка в 680 байт идёт с предельным числом переходов 64, 64, 63 и 63 на точках c0, r0, r1 и s0 как обычный пакет, сервер получает 680 байт с именем www.microsoft.com, клиент молчит 4 секунды, игрушка разрешает соединение и в журнале нет ни одной строки об ошибке, потому что код в строках 1062-1069 файла lua/zapret-lib.lua имеет две независимые ветви, IPv4 читает ip_ttl и ip_autottl, а IPv6 читает ip6_ttl и ip6_autottl; прогон функций без сети подтверждает все четыре сочетания: ip_ttl=1 даёт срок 1 только на IPv4, ip6_ttl=1 даёт предел 1 только на IPv6, а чужое условие оставляет 64. Условие не той версии: ip_ttl на потоке IPv6 опыт: клиент идёт по IPv6, в строке только ip_ttl=1 --payload=tls_client_hello --lua-desync=fake:blob=fake_default_tls:ip_ttl=1 c0выход клиентаr0вход в роутер 1r1выход роутера 1s0сервер подделка, 680 байтPSH+ACK, IPv6hlim 64hlim 64hlim 63hlim 63 настоящее, 250 байтPSH+ACK, IPv6hlim 64hlim 64hlim 63hlim 63 ICMPv6 «время истекло»ни одного пакетанетнетнетнет Подделка никуда не пропала: условия на ней нет, поэтому она дошла до сервера, как обычный пакет. игрушкаРАЗРЕШЕНОв журнале ttl=64 приложение сервера680 байтимя www.microsoft.com клиентмолчитнет ответа за 4 с журнал программытишинастрок об ошибке нет в коде: одно место, две ветви 1062if dis.ip then1063 local ttl = ttl_discover(fooling_options.ip_ttl,fooling_options.ip_autottl)1064 if ttl then dis.ip.ip_ttl = ttl end1065end 1066if dis.ip6 then1067 local ttl = ttl_discover(fooling_options.ip6_ttl,fooling_options.ip6_autottl)1068 if ttl then dis.ip6.ip6_hlim = ttl end1069end ветвь для IPv4:читает ip_ttlи ip_autottl ветвь для IPv6:читает ip6_ttlи ip6_autottl пропущена выполняется У пакета заголовок IPv6, поэтому выполняется вторая ветвь. Условия ip6_ttl в строке нет, срок не записывается, аip_ttl=1 эта ветвь не читает вообще. Для программы это не ошибка: условие записано верно, просто для другойверсии. прогон функций без сети: копия настоящего приветствия, одно условие за раз условие ip_ttl=1условие ip6_ttl=1 пакет IPv4срок 1записаносрок 64не изменилось пакет IPv6предел 64не изменилосьпредел 1записано Если стратегия должна работать на обеих версиях, нужны оба условия. Версия без своего условия остаётсяподделкой без условия. Сначала смотрим, по какой версии идёт соединение, и только потом выбираем имяусловия. проверь себяВ строке только ip_ttl=1, а клиент идёт по IPv6. Что получит сервер? подделку: 680 байтУсловие не той версии молча пропущено, стратегия ведёт себя как без условия. Это показал наш запуск.

Почему так, видно в коде. Одно место, две ветви. Если у пакета заголовок IPv4, функция читает ip_ttl и пишет в срок жизни. Если у пакета заголовок IPv6, она читает ip6_ttl и пишет в предельное число переходов. Чужую пару условий ветвь не читает вообще. Программа не считает это ошибкой: условие записано верно, просто для другой версии.

Прогон функций показывает все четыре сочетания. Для IPv6 условие ip6_ttl равно один даёт значение один, а ip_ttl равно один оставляет шестьдесят четыре. Для IPv4 наоборот.

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

Проверь себя. В строке только ip_ttl равно один, а клиент идёт по IPv6. Что получит сервер?

Подделку: шестьсот восемьдесят байт. Условие не той версии молча пропущено, и стратегия ведёт себя, как без условия. Именно это показал наш запуск.

Итак. В IPv6 то же правило с другим названием поля: ip6_ttl задаёт предельное число переходов, и подделка умирает там, где оно кончается. Условие чужой версии не делает ничего и не жалуется, поэтому про обе версии надо помнить отдельно.

Автоматический срок жизни: как программа узнаёт длину пути из пакета, который пришёл обратно

Вопрос сцены: программа должна сама узнать длину пути. Откуда она знает, сколько роутеров между клиентом и сервером? Ответ приходит из пакета, который идёт в обратную сторону.

Игрушка, выдуманная. Ответное письмо приходит с остатком марки. Допустим, наш сервер всегда ставит марку шестьдесят четыре, а пришло письмо с отметкой шестьдесят два. Шестьдесят четыре минус шестьдесят два, два центра по дороге. Это верно, если обратный путь такой же, как прямой.

Какой пакет от сервера нужен? В нашей лаборатории первый, ответ на открытие соединения, SYN-ACK. На точке цэ-ноль его срок жизни в трёх наших запусках: один роутер, шестьдесят три. Два роутера, шестьдесят два. Три роутера, шестьдесят один.

Программа записывает это число в журнал: «incoming TTL», входящий срок. По счёту: шестьдесят четыре минус шестьдесят три, один роутер. Минус шестьдесят два, два роутера. Минус шестьдесят один, три. Столько, сколько мы и поставили.

Длина пути из SYN-ACK Программа узнаёт длину пути из пакета, пришедшего от сервера: в игрушке письмо уходит с маркой 64 и приходит с маркой 62, 64 минус 62 даёт два центра по дороге, а в трёх опытах первый пакет от сервера, SYN-ACK на точке c0, имел срок жизни 63, 62 и 61, журнал программы записал incoming TTL 63, 62 и 61, и счёт 64 минус пришедшее даёт 1, 2 и 3 роутера, как мы и ставили; число записывается в строках 1332-1337 файла nfq2/desync.c один раз, пока в записи соединения нет входящего срока, по коду его ещё хранит кэш по адресу сервера (строки 1339 и 1342), в лаборатории это не проверяли, а руководство для FreeBSD и OpenBSD отдельно оговаривает, что входящие пакеты SYN+ACK должны попадать в программу, иначе режим autottl недоступен. Откуда программа знает длину пути: срок жизни пакета от сервера игрушка: ответное письмо с остатком марки выдуманный пример клиентцентрцентрнаш сервер марка 64 марка 63 пришла 62 64 63 62 64 − 62 = 2два центра по дороге, если обратный путь такой же, как прямой настоящие опыты: первый пакет от сервера на точке c0, это SYN-ACK путьсрок SYN-ACKжурнал программысчёт 1 роутерttl 63incoming TTL 6364 − 63 = 1 2 роутераttl 62incoming TTL 6264 − 62 = 2 3 роутераttl 61incoming TTL 6164 − 61 = 3 Столько роутеров мы и поставили. Программа пишет это число в журнал строкой incoming TTL. в коде: где число запоминается (nfq2/desync.c, строки 1332-1337) 1332if (ps->bReverseFixed)1333{1334 if (ps->ctrack && !ps->ctrack->incoming_ttl)1335 {1336 ps->ctrack->incoming_ttl = ttl46(dis->ip, dis->ip6);1337 DLOG("incoming TTL %u\n", ps->ctrack->incoming_ttl); это пакет от сервера в записи соединения числа ещё нет записать срок этого пакета (ttl46) строка в журнал запись соединения: память о каждом соединении (её устройство разберёт выпуск «Память о соединении») ключсчётчики...входящий срок (новое поле) по коду есть ещё кэш по адресу сервера по коду: в лаборатории не проверяли 1339ipcache_update_ttl(ps->ctrack, ...)для пакета от сервера: запомнить срок в кэшефункция в строках 791-821 1342ipcache_get_ttl(ps->ctrack, ...)для исходящего: если срока нет, взять из кэшафункция в строках 822-834 В журналах лаборатории есть строка ipcache lifetime 7200s: срок жизни записи кэша, два часа. Повторноеиспользование кэша не проверялось. руководство: входящие пакеты должны доходить до программы manual.en.md:499 (раздел FreeBSD)...only SYN+ACK, FIN, and RST packets for incoming TCP traffic....allows the use of autottl mode...manual.en.md:541 (раздел OpenBSD)...intercepting only SYN+ACK, FIN, and RST for inbound TCP.This allows for the use of autottl mode... Если правила ядра не отдают программе входящие пакеты, числа не будет. В нашей лаборатории отдают: правилоочереди берёт первые 12 пакетов в каждом направлении.

Где число запоминается? В записи соединения: так называется память программы о каждом соединении, её устройство разбирает выпуск «Память о соединении». Программа, обработав пакет от сервера, смотрит, есть ли в записи входящий срок. Если нет, записывает срок этого пакета.

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

Если правила ядра не отдают программе входящие пакеты, числа не будет. В нашей лаборатории отдают.

Но число шестьдесят два само не длина пути. Программа должна угадать, с какого числа начинал ответ. Она считает, что сервер начинает с одного из трёх круглых чисел: двести пятьдесят пять, сто двадцать восемь или шестьдесят четыре, и что путь короче тридцати двух переходов. Тогда полосы не пересекаются.

Больше двухсот двадцати трёх: начало двести пятьдесят пять.

От девяносто седьмого до ста двадцать восьмого: начало сто двадцать восемь.

От тридцать третьего до шестьдесят четвёртого: начало шестьдесят четыре. Всё, что попало между полосами, это «не знаю», и числа нет. Если догадка есть, переходов столько, сколько начальное значение минус пришедшее.

Три полосы и счёт переходов Из срока жизни пришедшего пакета программа угадывает начальное значение по трём полосам ленты от 0 до 255, значения выше 223 дают начало 255, от 97 до 128 дают 128, от 33 до 64 дают 64, а значения между полосами и до 32 не дают догадки и числа нет, число переходов равно начальному значению минус пришедшее, так 62, 126 и 253 дают по 2 перехода (код в строках 873-883 файла lua/zapret-lib.lua); прогон функций без сети проверил 34 значения от 1 до 255 и согласился с кодом, для значения 117 по счёту выходит 128 минус 117 равно 11 переходов, запуска с таким числом нет, и за всем этим стоят два допущения из руководства: сервер начинал с 255, 128 или 64, а обратный путь по длине такой же, как прямой, причём в лаборатории он симметричный по построению. Догадка о начальном значении: три полосы на ленте 0..255 начало 255224..255 начало 12897..128 начало 6433..64 нет1..32 нет65..96 догадки нет129..223 Каждая полоса шириной 32: путь короче 32 переходов, поэтому полосы не пересекаются. 62: 64 − 62 = 2 126: 128 − 126 = 2 253: 255 − 253 = 2 лента значений срока жизни; первый пакет от сервера приходит с одним из них в коде: функция hop_count_guess (lua/zapret-lib.lua, строки 873-883) пришло 253: больше 223,начало 255, 255 − 253 = 2 пришло 126: от 97 до 128,начало 128, 128 − 126 = 2 пришло 62: от 33 до 64,начало 64, 64 − 62 = 2 873if incoming_ttl>223 then874 orig=255875elseif incoming_ttl<=128 and incoming_ttl>96 then876 orig=128877elseif incoming_ttl<=64 and incoming_ttl>32 then878 orig=64879else880 return nil881end883return orig-incoming_ttl Если пришедшее число попало междуполосами, догадки нет: функция возвращаетnil, числа нет.Если догадка есть, переходов столько, скольконачальное значение минус пришедшее (строка883). прогон функций без сети: проверено 34 значения до 321 → нет2 → нет10 → нет30 → нет31 → нет32 → нет полоса 6433 → 3134 → 3050 → 1460 → 462 → 263 → 164 → 0 между 64 и 9765 → нет66 → нет80 → нет96 → нет полоса 12897 → 3198 → 30100 → 28120 → 8126 → 2127 → 1128 → 0 между 128 и 224129 → нет130 → нет200 → нет223 → нет полоса 255224 → 31225 → 30250 → 5253 → 2254 → 1255 → 0 Границы полос взяты из кода; проверены только эти значения. проверь себя: входящий срок 117, сколько переходов 117в полосе 97..128начало 128128 − 117 = 11 11 переходов. Это счёт по правилу из кода: запуска с таким числом у нас нет. два допущения за всем этим сервер начинал с 255, 128или 64три круглых начальныхзначения обратный путь по длинетакой же, как прямойтак говорит руководство,manual.en.md:3390-3391 в лаборатории путьсимметричный по построениюэто не доказательство длянастоящей сети

Проверим на числах. Прогон функций: шестьдесят два дают два перехода, сто двадцать шесть тоже два, двести пятьдесят три тоже два. Для проверенных чисел между полосами догадки нет; границы самих полос взяты из кода.

Проверь себя. Входящий срок сто семнадцать. Сколько переходов?

Сто семнадцать попадает в полосу от девяносто седьмого до ста двадцать восьмого, начало сто двадцать восемь, переходов одиннадцать. Это по счёту, правило из кода, запуска с таким числом у нас нет.

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

Итак. Программа читает срок первого пакета от сервера, угадывает начальное значение из трёх возможных и вычитает: так она узнаёт длину пути. Если догадки нет, числа нет. Но длина пути ещё не срок подделки: как из неё получается «на один роутер раньше» или «ровно перед сервером», разберём в следующей части.

Что мы знаем к этому месту: подделка, срок жизни и длина пути

Подведём итог первой части.

Первое: у подделки три задачи. Она должна быть правдоподобной для инспектора, не быть принятой получателем и не навредить соединению. Всё это мы проверяли только против нашей игрушки на входе в первый роутер и нашего получателя, сервера на Linux.

Второе: подделка без условия обманывает игрушку, но ломает соединение. Сервер принимает её за данные и отвечает подтверждением с номером шестьсот восемьдесят один, то есть принял шестьсот восемьдесят байт. Клиент отправил только двести пятьдесят и этого подтверждения не принимает, ответа нет. Перед каждым повтором приветствия стоит своя подделка, потому что стратегия не помнит прошлых.

Третье: подделка это копия заголовков настоящего пакета с другими данными. Тот же идентификатор IP, тот же номер, те же флаги и окно. Она уходит первой и помечена как своя, поэтому очередь её не видит. У fake условия применяются к этой копии и не трогают настоящий пакет.

Четвёртое: срок жизни. Подделка умирает на том роутере, где срок кончается, и доходит до сервера, только если срок не меньше числа роутеров плюс один. Роутер сообщает о смерти служебным сообщением «время истекло» с цитатой.

В IPv6 то же делает ip6_ttl, а условие чужой версии молча ничего не делает. Число вне границ от нуля до двухсот пятидесяти пяти программа отвергает: подделка не отправляется, и игрушка запрещает настоящее приветствие.

Пятое: длину пути программа узнаёт из пакета, который пришёл обратно. Она читает срок первого пакета от сервера, угадывает начальное значение из трёх и вычитает. Если догадки нет, числа нет.

Проверь себя. Два роутера. Какой срок поставить, чтобы подделка умерла на последнем роутере?

Два. В нашем тесте подделка со сроком два была видна на точках цэ-ноль, эр-ноль и эр-один, на втором роутере умерла, и ответ «время истекло» пришёл с его адреса. Сервер получил чистый поток.

Два роутера: срок 2 Ответ на вопрос о двух роутерах: подделка со сроком жизни 2 видна на точках записи у клиента, на входе роутера 1 и на его выходе со сроком 1, на втором роутере срок кончается и оттуда приходит ICMP тип 11 с адреса 10.9.1.2, до сервера подделка не доходит, игрушечный инспектор на входе роутера 1 видит её длиной 680 со сроком 2 и разрешает соединение, приложение сервера получает чистые 250 байт, а клиент получает ответ. Проверь себя: два роутера, срок подделки 2 клиентроутер 1роутер 2серверигрушка-инспектор c0r0r1r2s0 подделканастоящее приветствие срок 2срок 1 срок 0 ICMP тип 11 «время истекло» с адреса 10.9.1.2 c0 · клиентподделка 680 Б, срок 2настоящее 250 Б, срок 64 r0 · вход роутера 1игрушка: РАЗРЕШЕНОlen=680, срок 2 r1 · выход роутера 1подделка со сроком 1настоящее со сроком 63 r2 и s0 · дальшеподделки нетнастоящее: срок 62 приложение сервера: 250 байт, чисто клиент: ответ получен срок 2: после роутера 1 остаётся 1, роутер 2 вычитает ещё 1 и получает 0 поэтому подделка умирает на последнем роутере, а игрушка стоит раньше и успевает её увидеть ответ: срок равен числу роутеров, то есть два

Как читать любой опыт выпуска, мы теперь знаем: что видно на точках записи, что получил сервер, что видит клиент и что решила игрушка. Так мы будем смотреть и на следующие условия.

Теперь о границах. Мы проверяли одного получателя, Linux шестой серии. Мы не проверяли другие системы, роутер, который не отвечает служебными сообщениями или меняет срок, и пути, у которых обратная дорога не равна прямой. А настоящий инспектор нам неизвестен: наша игрушка просто стоит там, где ей удобно.

Итог первой части: пять пунктов Итог первой части выпуска в пяти строках: у подделки три задачи; без условия игрушка обманута, но сервер принимает подделку за данные, клиент отправил 250 байт, а сервер подтвердил 681; подделка копирует заголовки настоящего пакета, у обоих один ip id и один номер 1, и идёт первой; срок жизни кончается на роутере, где он равен нулю, и подделка доходит до сервера только при сроке не меньше числа роутеров плюс один; длину пути программа берёт из срока первого ответа сервера, 64 минус 62 дают 2 перехода, а проверяли это только на нашей игрушке и нашем сервере с Linux, и всё, что не проверяли, перечислено серым. Что помнить из первой части выпуска 1Три задачи подделкиправдоподобие, отказ получателя, безвредностьпроверено против игрушки на входе в роутер 1и против одного получателя: сервера на Linux 2Без условия подделка ломает соединениеигрушка обманута, сервер принял подделку за данныеклиент отправил 250 байт, сервер подтвердил 681то есть принял 680; ответа клиент не получил 3Подделка копирует заголовки настоящегоидёт первой, помечена как своя, очередь не видитодин ip id (191), один номер 1, одно окно 63отличаются только данные: 680 байт вместо 250 4Срок жизни кончается на роутередоходит, если срок не меньше роутеров плюс 1роутер шлёт ICMP тип 11 «время истекло»значение вне 0..255: ошибка, подделки нет 5Длину пути берём из ответа серверастарт 255, 128 или 64 угадываем и вычитаемпришёл срок 62, старт 64: 64 − 62 = 2 переходанет догадки, нет и числа не проверяли: граница знаний· получатель, кроме Linux 6.18· настоящий инспектор: игрушка лишь модель· асимметричные пути: туда и обратно по-разному· роутер без ICMP или с подменой срока жизни о настоящем ТСПУ здесь ничего не утверждается: это опыты на игрушке и на нашем сервере

Что дальше. В следующей части мы превратим длину пути в срок подделки: дельта, диапазон, правило знака, тихие отказы и ошибки. А потом перейдём к условиям другого рода, при которых подделку отвергает уже сам получатель: подпись MD5, неверная сумма, неверные номера и отметка времени. Лаборатория и способ чтения опытов останутся те же.

Итак. Подделка это лишний пакет рядом с настоящим. Без условия он ломает соединение, со сроком жизни умирает по дороге, а длину пути программа узнаёт сама, из пакета, пришедшего обратно. И всё это проверено против нашей игрушки и нашего сервера, а не против настоящего оборудования.

Повтор, а затем первая цифра подбора: срок жизни равен числу переходов плюс дельта

Это вторая часть выпуска «Фейк и его условия», её можно смотреть отдельно, поэтому начнём с короткого повтора. Фейк, он же подделка, это один лишний пакет: программа копирует заголовки настоящего пакета с данными, кладёт в копию чужую нагрузку и отправляет её перед настоящим. Нагрузка в наших опытах готовая: поддельное приветствие на шестьсот восемьдесят байт.

В прошлых выпусках наблюдатель был правилом в роутере, которое смотрело каждый пакет отдельно. Здесь он другой: вторая копия нашей программы в роутере ждёт первый пакет с данными и раз и навсегда решает судьбу соединения. Это игрушечный инспектор, модель, а не настоящее оборудование. О настоящем ТСПУ мы ничего не знаем, и слово «работает» дальше значит: работает против этой модели и против нашего получателя, сервера под Linux.

Без условий подделка вредит: в нашем тесте получатель принимает её за данные, и соединение встаёт. Поэтому подделке нужны условия подделки, по-английски fooling; в выпусках про резку мы называли это подменой полей. В этой части две темы: подбор срока жизни, у которого много тихих отказов, и шесть условий, после которых получатель сам выбрасывает подделку. Причину мы читаем по счётчикам ядра получателя, их значения мы снимали до и после каждого опыта.

Срок жизни, TTL, падает на единицу на каждом роутере. Подделка, у которой срок равен числу роутеров, умирает на последнем, и оттуда приходит сообщение «время истекло». Получатель её не видит, а инспектор нашей модели стоит раньше и видит. Число роутеров программа выводит из срока первого ответного пакета сервера: у нас он пришёл со значением шестьдесят два, и если сервер стартовал с шестидесяти четырёх, то переходов, они же хопы, два.

Какой срок поставить подделке, если до сервера два перехода?

Игрушка, выдуманная. На конверте стоит счётчик, каждое отделение связи вычитает единицу, а кто получил ноль, возвращает письмо отправителю. Отделений на пути два. Поставим два: первое вычтет единицу, второе получит ноль и вернёт письмо. Поставим один: письмо вернётся уже с первого. Поставим три: оно проедет оба и попадёт в город.

Этот аргумент по-русски называется «автоматический срок жизни», коротко «автоподбор», в строке стратегии он записан ip_autottl. Его значение читается так: ноль, запятая, один, дефис, двадцать. Первое число называется дельтой, остальное диапазоном, его разберём в следующей сцене.

Дельта, по-английски delta, это число со знаком. Программа берёт число переходов и прибавляет дельту. Ноль значит «ровно по числу переходов», минус один «на переход короче», плюс один «на переход длиннее». Ошибиться в сторону «короче» безопасно: подделка умрёт раньше. Ошибиться в сторону «длиннее» опасно: она дойдёт до сервера.

Первый опыт, дельта ноль. В журнале входящий срок шестьдесят два и «найден автоподбор, два». Подделка ушла со сроком два, на втором роутере срок кончился, и клиенту пришло «время истекло» с адреса второго роутера. Инспектор стоит в первом роутере, до проверки срока: в его журнале пакет в шестьсот восемьдесят байт со сроком два, соединение разрешено. Получатель получил чистые двести пятьдесят байт, и это результат нашего теста.

Дельта минус один даёт срок один: подделка умирает на первом роутере, инспектор всё равно её видит, получатель получает чистые двести пятьдесят байт.

Дельта плюс один даёт три: подделка проходит оба роутера и доезжает до сервера, получатель принимает её за данные, клиент напрасно ждёт ответа. Это не ошибка подбора, мы сами попросили на переход дальше.

Дельта: три опыта Три опыта с дельтой на дороге из двух роутеров, где первый ответ сервера пришёл со сроком 62, то есть переходов два: при дельте минус один срок подделки 1 и она умирает на роутере 1 с ICMP от 10.1.0.2, при дельте 0 срок 2 и она умирает на роутере 2 с ICMP от 10.9.1.2, при дельте плюс один срок 3 и она доезжает до сервера, который принимает 680 байт, а клиент ждёт ответа; игрушечный инспектор на входе роутера 1 видит подделку во всех трёх опытах и разрешает соединение. Срок подделки = число переходов + дельта клиентроутер 1 + игрушкароутер 2сервер первый ответ сервера пришёл со сроком 62, старт 64: переходов 2 игрушка на входе роутера 1 видит подделку в каждом опыте: РАЗРЕШЕНО дельта −1переходы 2 + (−1) = 1, срок подделки 1 срок 1 умирает на роутере 1, ICMP с 10.1.0.2сервер: 250 Б, чисто · клиент: ответ получен дельта 0переходы 2 + 0 = 2, срок подделки 2 срок 2срок 1 умирает на роутере 2, ICMP с 10.9.1.2сервер: 250 Б, чисто · клиент: ответ получен дельта +1переходы 2 + 1 = 3, срок подделки 3 срок 3срок 2срок 1 доезжает до сервера при сроке 3сервер: 680 Б · клиент ждёт 4 с и сдаётся шпаргалка: −1 первый из двух роутеров, 0 последний, +1 до сервера

Теперь код. Расчёт делает маленькая функция, назовём её оценщиком, в библиотеке это часть функции autottl. Её первая строка: d равно hop_count плюс attl точка delta, то есть переходы плюс дельта. Остальные строки про диапазон и знак, они в следующей сцене.

Автоподбор: первый опыт Первый опыт с автоподбором на дороге из двух роутеров: в строке стратегии ip_autottl=0,1-20 дельта равна 0, диапазон от 1 до 20, первый ответ сервера пришёл со сроком 62, программа считает 64 минус 62 дают 2 перехода, строка 889 библиотеки d = hop_count + attl.delta даёт 2, подделка 680 байт уходит со сроком 2 и тем же ip id 35247, что и настоящее приветствие на 250 байт со сроком 64, потом приходит ICMP тип 11 с адреса 10.9.1.2, инспектор разрешает, сервер получает чистые 250 байт, а клиент получает ответ. Строка стратегии, расчёт и первый опыт --lua-desync=fake:blob=fake_default_tls:ip_autottl=0,1-20ip_autottl01-20 ip_autottl: автоподбор срока жизнидельтадиапазон: в следующей сцене расчёт: zapret-lib.lua, строки 886-898 889d = hop_count + attl.delta891-894диапазон: следующая сцена896проверка знака: следующая сцена897return fake переходы: 64 − 62 = 2d = 2 + 0 = 2, в диапазоне 1-20срок подделки 2 пакеты клиента и ответы, первый опыт ответ сервера SYN-ACKсрок 62 (журнал: incoming TTL 62) подделка PA, 680 Бсрок 2 · ip id 35247 настоящее PA, 250 Бсрок 64 · ip id 35247 ICMP тип 11 код 0с адреса 10.9.1.2: срок кончился журнал: apply_fooling: discovered autottl 2 игрушка: РАЗРЕШЕНО, len=680, срок 2 сервер: 250 Б, чисто · клиент: ответ получен

Проверь себя. Дорога из трёх роутеров, ответ сервера пришёл со сроком шестьдесят один, дельта ноль. Какой срок у подделки и где она умрёт?

Переходов три: шестьдесят четыре минус шестьдесят один. Срок три, подделка умирает на третьем, последнем роутере. В нашем тесте сообщение «время истекло» пришло с адреса третьего роутера, а получатель получил чистые двести пятьдесят байт.

Три роутера: срок 3 Проверка на дороге из трёх роутеров при дельте 0: ответ сервера пришёл со сроком 61, значит переходов 64 минус 61 равно 3, срок подделки 3 плюс 0 даёт 3, подделка видна на точках c0, r0, r1 и r2, умирает на третьем роутере с ICMP типа 11 с адреса 10.9.2.2, а приложение сервера получает чистые 250 байт. Проверь себя: три роутера, ответ со сроком 61, дельта 0 клиентроутер 1роутер 2роутер 3сервер срок 3срок 2срок 1срок 0: умирает здесь ICMP тип 11 с адреса 10.9.2.2 64 − 61 = 3 перехода, 3 + 0 = 3, срок подделки 3 видна на c0 r0 r1 r2на сервере её нет приложение сервера250 байт, чисто

Итак. Срок подделки равен числу переходов плюс дельта. На дороге из двух роутеров минус один убивает подделку на первом, ноль на последнем, плюс один пропускает до сервера. В нашем тесте это работает против нашей модели и нашего получателя, и только если число переходов угадано верно.

Диапазон и проверка знака: когда подбор отказывается

Остались два числа в аргументе: диапазон. Что он делает и когда подбор отказывается отвечать?

Игрушка, выдуманная. Вы просите у термостата двадцать пять градусов, а в паспорте написано, что прибор держит от десяти до двадцати двух. Он поставит двадцать два. Диапазон делает со сроком жизни то же: что ниже нижней границы, поднимает до неё, что выше верхней, опускает до неё.

Опыт, два роутера: дельта пять, диапазон от одного до трёх. Два плюс пять дают семь, это выше верхней границы, берётся три. Подделка со сроком три проезжает оба роутера, получатель принимает её за данные, клиент ждёт ответа. Подбор сработал как написано, просто результат нам не годится.

Теперь отказ. Два роутера, дельта минус три, диапазон от двух до девяти. Два минус три дают минус один, диапазон поднимает до двух. Мы просили срок короче пути, а вышло два, и это не короче: путь тоже два. Подбор отказывает. Журнал пишет «не удалось найти автоподбор» на каждую отправку приветствия, шесть раз за опыт: клиент завис и повторяет приветствие, а перед каждым повтором программа делает свою подделку.

Что тогда с подделкой? В нашем тесте её срок остался шестьдесят четыре, она доехала до сервера, получатель принял шестьсот восемьдесят чужих байт, клиент завис. Отказ не отключает подделку, он лишает её условия.

Обратный случай: дельта ноль, диапазон от пяти до девяти. Два плюс ноль дают два, диапазон поднимает до пяти. Просили не короче пути, вышло пять, проверка довольна. Срок пять, и подделка доезжает до сервера. Проверка смотрит на направление, а не на цель.

Чтобы не строить дорогу из пяти роутеров, мы запустили настоящие функции библиотеки на выдуманных числах, без сети. Мы называем это «прогоном функций». Для пяти переходов дельта пять с диапазоном от одного до трёх даёт отказ: три меньше пяти, а дельта неотрицательная. А дельта минус три с диапазоном от одного до двадцати даёт два: пять минус три, и знак сохранён.

Диапазон и знак: три опыта Диапазон и проверка знака на дороге из двух роутеров с двумя переходами: в игрушечном примере с термостатом просят 25, а прибор держит от 10 до 22 и ставит 22; при ip_autottl=5,1-3 два плюс пять дают 7, диапазон срезает до 3, знак верный, срок 3 и подделка доезжает до сервера, который принимает 680 байт; при -3,2-9 два минус три дают минус один, диапазон поднимает до 2, но просили короче пути, а вышло не короче, подбор отказывает, срок остаётся 64 и подделка тоже доезжает до сервера; при 0,5-9 два плюс ноль дают 2, диапазон поднимает до 5, проверка довольна, срок 5 и подделка доезжает до сервера, знак сохранён, а цель потеряна; прогон функций на выдуманных числах для пяти переходов даёт для 5,1-3 отказ, для 0,5-9 число 5, для -3,1-20 число 2. Диапазон срезает результат, проверка знака может отказать игрушка (выдуманный пример): термостат102225паспорт: от 10 до 22просим 25, прибор ставит 22:выше границы опускает до неётак же диапазон делает со сроком 1ip_autottl=5,1-3discovered autottl 3 переходы 2 + дельта 5 = 7, выше границы 3: берём 3знак: дельта не меньше 0, результат 3 не меньше пути 2: верно срок на проводе 3до сервера дошласервер: 680 Бклиент ждёт 2ip_autottl=-3,2-9could not discover autottl, 6 раз переходы 2 + дельта −3 = −1, ниже границы 2: берём 2знак: дельта меньше 0, а 2 не короче пути 2: отказ срок остался 64до сервера дошласервер: 680 Бклиент ждёт 3ip_autottl=0,5-9знак сохранён, цель потеряна переходы 2 + дельта 0 = 2, ниже границы 5: берём 5знак: дельта не меньше 0, результат 5 не меньше пути 2: верно срок на проводе 5до сервера дошласервер: 680 Бклиент ждёт прогон функций: настоящие функции библиотеки на выдуманных числах, без сетипять переходов, входящий срок 59:5,1-3результат: нет0,5-9результат: 5-3,1-20результат: 2 проверка смотрит на направление дельты, а не на то, попали ли мы в цель

Теперь код оценщика. Первая строка знакомая: d равно числу переходов плюс дельта. Дальше диапазон: если d меньше нижней границы, результат равен ей, если больше верхней, то верхней, иначе просто d.

И последняя строка, проверка знака, два условия через «или». Просили короче пути, а вышло не короче. Или просили не короче, а вышло короче. Если верно хоть одно, функция возвращает nil, то есть «ничего».

Проверь себя. Дорога из одного роутера, дельта минус один, диапазон от одного до двадцати. Что вернёт подбор?

Один минус один дают ноль, диапазон поднимает до одного. Просили короче пути, а вышло один, и путь тоже один: отказ. Опыт показал то же: срок остался шестьдесят четыре. На дороге из одного роутера «на переход раньше» просто некуда.

Оценщик по строкам Функция autottl_eval из zapret-lib.lua по строкам с разбором ответа на вопрос про один роутер, дельту минус 1 и диапазон от 1 до 20: строка 889 считает d как переходы плюс дельта, то есть 1 минус 1 равно 0, строки 891-894 поднимают результат до нижней границы 1, строка 896 отказывает, если просили короче пути, а вышло не короче, или если просили не короче, а вышло короче, здесь дельта меньше 0 и 1 не меньше 1, поэтому возвращается nil; в опыте срок подделки остался 64, она доехала до сервера, сервер принял 680 байт, клиент ждёт. Оценщик срока: zapret-lib.lua, строки 886-898 пример: один роутер, дельта −1, диапазон 1-20 889d = hop_count + attl.delta переходов 1, поэтому d = 1 + (−1) = 0 891if d<attl.min then fake=attl.min892elseif d>attl.max then fake=attl.max893else fake=d894end 0 ниже нижней границы 1, берём 1 896if attl.delta<0 and fake>=hop_countor attl.delta>=0 and fake<hop_countthen return nil end дельта меньше 0, а 1 ≥ 1: условие верно 897return fake до этой строки дело не дошло возвращается nil: подбора нет ответ со сроком 63: переходов 1 (64 − 63) а: просили короче пути (дельта меньше 0),а вышло не короче: fake не меньше hop_count б: просили не короче (дельта не меньше 0),а вышло короче: fake меньше hop_count хоть одно верно: функция возвращает nil, то есть «ничего» опыт: подбор отказал, срок подделки остался 64, она доехала до сервера: 680 Б в коде строка 885 говорит про 0, но возвращается nil

Итак. Диапазон подтягивает результат к границам, проверка знака отказывает, если результат противоречит знаку дельты. В нашем тесте отказ значит срок шестьдесят четыре и подделку, которая доезжает до сервера. А верный знак цель ещё не гарантирует.

Когда подбор молчит или ошибается: запасные пути, неверные догадки, память

Что получает подделка, если подбор не нашёл числа и что будет, если догадка неверна?

Если подбор ничего не вернул, у подделки остаётся срок жизни настоящего пакета, шестьдесят четыре. Но к подбору можно дописать запасное число, аргумент ip_ttl: оно включается только тогда, когда подбор молчит. Руководство описывает это так же.

Если число найдено, оно главнее. Мы записали автоподбор «ноль, от одного до двадцати» и запасное число один: подбор нашёл два, и подделка ушла со сроком два.

Опыт без ответов. Правила ядра у клиента мы настроили так, чтобы программа получала только исходящие пакеты: ядро отдаёт ей лишь то, что выбрали правила, это разбирает выпуск «Внутри программы». Ответа сервера программа не видит, и в первом соединении срок ответа взять неоткуда. Без запасного числа подделка уходит со сроком шестьдесят четыре, и получатель принимает её за данные. С запасным числом один она умирает на первом роутере, а получатель видит чистые двести пятьдесят байт.

Откуда программа берёт срок ответа? Из записи соединения: в неё она кладёт срок первого ответного пакета, подробности в выпуске «Память о соединении». Если записи нет, например когда запись соединений выключена отдельным ключом, ветка автоподбора пропускается молча, и берётся запасное число. В нашем тесте с этим ключом подделка ушла со сроком один.

По коду программа запоминает этот срок ещё и по адресу сервера, по умолчанию на два часа, и может взять его из прошлого соединения. У нас в каждом опыте одно соединение, поэтому это мы не проверяли.

Теперь опечатка: вместо дельты и диапазона написано просто abc. Если срок ответа известен, разбор значения падает с ошибкой. Ошибка в функции значит, что стратегия в этот раз не сработала и пакет идёт как есть, мы видели это в выпуске «multisplit: резать на части». Так же падает разбор, если нижняя граница больше верхней.

Подделки нет совсем, и игрушечный инспектор читает настоящее приветствие с запрещённым именем и блокирует соединение.

А если срока ответа нет, то же abc не вызывает ошибки: разбор до него не доходит. Подделка уходит со сроком шестьдесят четыре, получатель принимает её за данные. Одна опечатка, два разных тихих отказа.

Проверь себя. Ответов сервера программа не видит, в строке автоподбор «ноль, от одного до двадцати» и запасной ip_ttl, равный одному. Какой срок будет у подделки?

Один: автоподбор числа не нашёл, берётся запасное. В нашем тесте подделка умерла на первом роутере, получатель получил чистые двести пятьдесят байт.

Что получает подделка: семь опытов Таблица из семи опытов о том, какой срок получает подделка, когда автоподбор молчит или ломается: при отказе без запасного числа срок 64 и сервер принимает 680 байт; когда число найдено, оно главнее запасного ip_ttl=1 и срок 2; без ответов сервера в правилах клиента срок 64, а с запасным числом 1 подделка умирает на первом роутере и сервер получает чистые 250 байт, так же при выключенной записи соединений; опечатка abc при известном сроке ответа даёт ошибку в функции, подделки нет, игрушка блокирует соединение и сервер получает 0 байт, а при неизвестном сроке ответа ошибки нет, срок 64 и сервер принимает 680 байт. Что получает подделка, когда подбор молчит или ломается опытсроквидна наитог 1подбор отказал, запасного числа нетip_autottl=-1,1-20 · 1 роутерсрок 64c0 r0 r1 s0сервер: 680 Бклиент ждёт 2найдено число и есть запасноеip_autottl=0,1-20:ip_ttl=1срок 2c0 r0 r1сервер: 250 Б, чистоклиент: ответ получен 3в правилах клиента нет ответов сервераip_autottl=0,1-20 · 6 отказов в журналесрок 64c0 r0 r1 r2 s0сервер: 680 Бклиент ждёт 4то же, но есть запасное числоip_autottl=0,1-20:ip_ttl=1срок 1c0 r0сервер: 250 Б, чистоклиент: ответ получен 5запись соединений выключена ключом--ctrack-disable, …:ip_ttl=1срок 1c0 r0сервер: 250 Б, чистоклиент: ответ получен 6опечатка abc, срок ответа известенip_autottl=abc · LUA ERROR, 6 разнетнигдеигрушка блокируетсервер: 0 Б, ответа нет 7опечатка abc, срока ответа нетip_autottl=abc · ошибки нетсрок 64c0 r0 r1 r2 s0сервер: 680 Бклиент ждёт у каждого пункта одна и та же подделка 680 байт; опыты в нашей лаборатории, о настоящих сетях выводов нет

Теперь про догадку. Напомним из начала выпуска: число переходов это стартовое число сервера минус пришедшее, а стартовое программа угадывает по трём полосам, до шестидесяти четырёх, до ста двадцати восьми и до двухсот пятидесяти пяти. Со стартами сто двадцать восемь и двести пятьдесят пять подбор в нашем тесте посчитал верно, два перехода.

Стартовые числа тридцать два и восемьдесят мы выдумали, чтобы потрогать границы, и заставили сервер ставить их в ответы. Пришли тридцать и семьдесят восемь, ни в одну полосу они не попали, догадки нет, подбор отказал, подделка доехала до сервера.

Хуже неверная догадка. Старт сто, пришло девяносто восемь: это вторая полоса, и подбор уверенно считает сто двадцать восемь минус девяносто восемь, тридцать переходов, а на самом деле их два. В нашем тесте при дельте ноль и диапазоне до двадцати результат срезан до двадцати, проверка знака отказала, и срок остался шестьдесят четыре.

При диапазоне пошире вышел бы срок тридцать, подделка дошла бы до сервера и стала данными: это по счёту, такого запуска не было. Неверная догадка даёт или тихий отказ, или неверный срок, и оба молчат.

Какие стартовые числа у настоящих серверов, мы не знаем. И руководство называет второе допущение: подбор считает путь обратно таким же длинным, как туда. Наша лаборатория симметрична по построению, настоящая сеть не обязана.

Теперь память. Удачное число запоминается на время соединения, отдельно для каждой записи lua-desync в строке: два фейка в строке помнят каждый своё. В опыте журнал пишет «найдено три», а потом пять раз «использую запомненное»: клиент завис и повторяет приветствие.

Отказ не запоминается: подбор пробует снова на каждом пакете, это показал прогон функций.

Догадка о старте и память Как автоподбор угадывает стартовый срок сервера и что запоминает: по коду zapret-lib.lua полосы входящего срока 33..64, 97..128 и 224..255 дают старт 64, 128 и 255, а между ними догадки нет; в опытах на двух роутерах ответ 126 при старте 128 и ответ 253 при старте 255 дают 2 перехода и срок подделки 2, а выдуманные старты 32 и 80 дают ответы 30 и 78 без догадки, срок остаётся 64 и подделка доезжает до сервера; при выдуманном старте 100 ответ 98 попадает во вторую полосу, 128 минус 98 дают 30 переходов вместо 2, диапазон 1-20 срезает до 20, проверка знака отказывает, срок 64, а по счёту при диапазоне 0,1-64 вышел бы срок 30 и подделка дошла бы до сервера, такого запуска не было; удачное число запоминается на соединение, в журнале один раз найдено 3 и пять раз использовано запомненное, а отказ не запоминается и подбор пробует заново на каждой отправке. Догадка о старте сервера и память подбора догадки нетдогадки нетдогадки нет 33..64: старт 64строка 87797..128: старт 128строка 875224..255: старт 255строка 873иначе nil: строка 879 307898126253 числа над шкалой: входящий срок первого ответа старт 128, пришло 126полоса 97..128: 128 − 126 = 2 переходасрок 2250 Б чисто старт 255, пришло 253полоса 224..255: 255 − 253 = 2 переходасрок 2250 Б чисто старт 32*, пришло 30в провале: догадки нет, подбор отказалсрок 64680 Б старт 80*, пришло 78в провале: догадки нет, подбор отказалсрок 64680 Б старт 100*, пришло 98вторая полоса: 128 − 98 = 30 переходов вместо 20 + 30 = 30, выше 20: берём 20; 20 меньше 30: отказсрок 64680 Б * значения 32, 80 и 100 выдуманы, чтобы потрогать границы: сервер заставили ставить их в ответы по счёту, запуска не было: диапазон 0,1-64 дал бы d = 30, проверка знака довольна,срок 30 при двух переходах, и подделка дошла бы до сервера руководство (docs/manual.en.md:3390): расчёт предполагает симметричные пути и старты 64, 128, 255 наша лаборатория симметрична по построению, настоящая сеть не обязана память: удача запоминается на соединение, отказ нет ip_autottl=5,1-3: найдено 3, дальше пять раз берётся запомненное отправка 1найдено 3отправка 2память: 3отправка 3память: 3отправка 4память: 3отправка 5память: 3отправка 6память: 3 ip_autottl=-3,2-9: отказ шесть отправок подряд, ничего не запоминается отправка 1не найденоотправка 2не найденоотправка 3не найденоотправка 4не найденоотправка 5не найденоотправка 6не найдено два fake в одной строке помнят каждый своё; в прогоне функций после успеха с ответом 62 ответ 50 снова даёт 2 после отказа и ответа 62 в том же прогоне подбор находит 2: отказ запомнен не был

Итак. Если подбор молчит и запасного числа нет, подделка остаётся со сроком шестьдесят четыре, а найденное число главнее запасного. Опечатка даёт два разных тихих отказа. Неверная догадка о старте сервера даёт тихий отказ или неверный срок. Удача запоминается на соединение, отказ нет. Всё проверено только на нашей симметричной лаборатории.

Опция MD5: подпись, ключа от которой у получателя нет

Начинаем шесть условий для получателя. Первое: опция эм-дэ-пять. Как заставить наш получатель выбросить пакет, который ни один роутер на пути не заметит? Дальше опыты идут на дороге из одного роутера.

Игрушка, выдуманная. Письмо запечатано печатью, которую получатель не признаёт. Сортировочный центр печатей не проверяет, получатель видит незнакомую печать и выбрасывает письмо.

В TCP есть опция для подписи пакетов между сторонами, которые заранее договорились о ключе; по общим сведениям, её используют, например, между роутерами. Если подпись пришла, а ключа нет, наш получатель пакет отбрасывает.

Причину мы видим по счётчику ядра получателя. Его имя, оно на экране, мы читаем как «подпись, которой не ждали». Это наше чтение: доказано лишь, что в этом опыте сдвинулся именно он.

Опыт: аргумент эм-дэ-пять без значения, в записи tcp_md5. Подделка уходит со сроком шестьдесят четыре, с теми же флагами и номером, что у настоящего пакета. Заголовок TCP вырос с тридцати двух байт до пятидесяти двух. Тридцать два это двадцать обязательных и двенадцать опций: две пустые опции NOP и метка времени. Добавилась опция: вид девятнадцать, длина восемнадцать, шестнадцать случайных байт подписи и два нулевых байта выравнивания.

Подделку видно в четырёх точках записи: у клиента, на входе и на выходе первого роутера и у сервера. Роутер опцию не проверяет, инспектор принимает подделку за первый пакет. А получатель её выбросил: счётчик вырос на единицу, прикладная программа получила чистые двести пятьдесят байт, клиент получил ответ.

В нашем тесте содержимое подписи не важно: с шестнадцатью байтами, заданными вручную, результат тот же. Важно, что опция правильного вида и длины есть.

Опция MD5: дорога и счётчик Подделка с опцией MD5 на дороге из одного роутера со сроком 64: она видна у клиента, на входе и выходе роутера и у сервера, игрушечный инспектор на входе роутера видит её длиной 680 и разрешает, роутер опцию не проверяет, а ядро сервера выбрасывает подделку, и счётчик TcpExt TCPMD5Unexpected растёт с 0 до 1, который мы читаем как подпись, которой не ждали; приложение сервера получает чистые 250 байт, клиент получает ответ, с шестнадцатью байтами, заданными вручную, результат тот же; в коде zapret-lib.lua строки 1021-1025 добавляют опцию MD5 в список опций, если её там ещё нет, а длину не проверяют. Опция MD5 на дороге из одного роутера подделканастоящее приветствие клиентроутер 1серверигрушка-инспектор c0r0r1s0 срок 64срок 63 MD5 ядро выбросилоподделку c0 · клиентподделка видна, срок 64с MD5, заголовок 52 Б r0 · вход роутера 1игрушка: РАЗРЕШЕНОlen=680, срок 64 r1 · выход роутера 1подделка видна, срок 63роутер MD5 не проверяет s0 · серверподделка видна, срок 63видна, но не принята счётчик ядра сервераTcpExt TCPMD5Unexpected: 0 → 1читаем как «подпись, которой не ждали»: наше чтение приложение сервера: 250 Б, чистоклиент: ответ полученс 16 байтами 00 11 … ff вручную: то же код: lua/zapret-lib.lua, строки 1021-1025 1021if fooling_options.tcp_md5 then1022 if find_tcp_option(dis.tcp.options,TCP_KIND_MD5) then1023 DLOG("apply_fooling: md5 option already present")1024 else1025 table.insert(dis.tcp.options,{kind=TCP_KIND_MD5, data=prepare_bin(fooling_options.tcp_md5,brandom(16))}) такой опции ещё нет: в список опций добавляется новая, данные берутся из значения или 16 случайных байт длина не проверяется: что записано в значении, то и попадает в опцию

Теперь код. Если аргумент задан и такой опции в пакете ещё нет, в список опций добавляется новая: вид эм-дэ-пять и данные. Данные это шестнадцатеричная строка из аргумента или, если значение пустое, шестнадцать случайных байт.

И ловушка: код не проверяет длину. Мы написали ноль-ноль-один-один, это два байта. Опция получилась длиной четыре, заголовок вырос до тридцати шести, счётчик не сдвинулся, получатель принял подделку, клиент завис. Нам кажется, что это для получателя уже не подпись, но проверено лишь то, что он подделку принял.

И про место: на опции в заголовке TCP отведено не больше сорока байт. В прогоне функций мы взяли пакет, у которого опции уже занимали почти всё это место: подпись не поместилась, сборка пакета выдала ошибку, и стратегия в этот раз не сработала.

Проверь себя. Есть ключ «убрать пустые опции», в записи tcp_nop_del. Метка времени вместе с двумя пустыми опциями занимает двенадцать байт, ключ убирает эти две. Какой тогда будет длина заголовка с подписью?

Метка времени без пустых опций занимает десять байт. Десять плюс восемнадцать дают двадцать восемь, плюс двадцать байт основного заголовка, всего сорок восемь. Так и было на проводе: двадцать восемь уже делится на четыре, и нулей выравнивания нет. Получатель подделку отверг.

Заголовок TCP с опцией MD5 Заголовок TCP в байтах: у настоящего приветствия 32 байта, то есть 20 обязательных и 12 байт опций из двух NOP и метки времени, у подделки с tcp_md5 52 байта, потому что добавлена опция MD5 с видом 19, длиной 18 и шестнадцатью случайными байтами и два нулевых байта выравнивания; с ключом tcp_nop_del заголовок 48 байт, потому что 10 плюс 18 дают 28, а 20 плюс 28 дают 48, и нули не нужны; при tcp_md5=0011 опция получается длиной 4 и заголовок 36 байт, получатель такую подделку принимает; в прогоне функций на синтетическом пакете опции NOP NOP SACK и метка времени уже занимают 38 байт из 40 допустимых, подпись на 18 байт не помещается и стратегия не срабатывает. Заголовок TCP по байтам: обязательная часть и опции настоящее приветствиезаголовок 32 = 20 + 1220 обязательныхметка времени, 1001 01 | 08 0a 2b 24 f8 d4 1d 18 f7 6f два NOP: 01 01 подделка с tcp_md5заголовок 52 = 20 + 12 + 18 + 220 обязательныхметка времени, 10MD5: вид 19, длина 1801 01 | 08 0a 2b 24 f8 d4 1d 18 f7 6f | 13 12 9e bd d9 7f d5 ad 91 33 5e 09 fc 4f 42 ed 7f 1f | 00 00 нули выравнивания: 00 00 то же с tcp_nop_del: два NOP убранызаголовок 48 = 20 + 10 + 1820 обязательныхметка времени, 10MD5: вид 19, длина 1808 0a 21 c0 20 fb 91 ed 42 08 | 13 12 ca 9d cc ac 12 3b 34 8c 9d fa 18 97 3a 49 26 65 28 делится на 4, нули не нужны ловушка: tcp_md5=0011, два байта вместо 16заголовок 36 = 20 + 12 + 420 обязательныхметка времени, 1001 01 | 08 0a 35 e5 46 e8 4b 0c 4d 37 | 13 04 00 11вид 19, длина 4, данные 00 11 код длину не проверяет; получатель такую подделку принял: 680 Б, клиент завис место под опции: не больше 40 байт (прогон функций, синтетический пакет)38 + 18 = 56 больше 40SACK: 3 блока, 26 байтметка, 10MD5, 18предел 40 байтNOP NOP + SACK + метка = 38 байтсборка пакета даёт ошибку, и стратегия в этот раз не сработала содержимое подписи не важно, длина обязана быть настоящей, места должно хватить

Итак. Опция эм-дэ-пять это подпись без ключа: роутеры её пропускают, наш получатель отбрасывает пакет, и счётчик «подпись, которой не ждали» растёт. Содержимое неважно, длина обязана быть настоящей, места должно хватить. Всё это проверено только на нашем получателе и нашей модели.

Испорченная контрольная сумма

Второе условие для получателя: испорченная контрольная сумма. Что это за число и почему порчу не замечает никто на пути?

Игрушка, выдуманная. На посылке написан вес. Сортировочный центр её не взвешивает, а получатель взвешивает при вскрытии. Вес не сошёлся, посылка возвращается.

Контрольная сумма TCP это число в заголовке. Отправитель считает его по адресам, заголовку и данным, а получатель пересчитывает и сверяет. Не сошлось, значит пакет испорчен в пути, и получатель его выбрасывает.

Роутерам на пути эту сумму проверять незачем: по общим сведениям, им хватает заголовка IP. В нашем тесте так и вышло: подделка с неверной суммой прошла роутер и дошла до сервера.

Опыт: аргумент «испортить сумму», в записи badsum, без значения. Мы разобрали подделку собственным разборщиком и пересчитали сумму сами: в заголовке стоит одно число, пересчёт даёт другое, и так во всех четырёх точках записи. Сами числа на экране. У настоящего приветствия следом число сходится.

Получатель подделку не принял. В счётчиках ядра сдвинулись два, по единице: ошибки TCP вообще и ошибки контрольной суммы. Прикладная программа получила чистые двести пятьдесят байт, клиент получил ответ. Игрушечный инспектор суммы не проверяет и принял подделку за первый пакет.

Испорченная контрольная сумма на проводе и у получателя Подделка с испорченной контрольной суммой TCP из одного опыта: в заголовке стоит число 0x96c4, а пересчёт по адресам, заголовку и данным даёт 0xbb31, у настоящего приветствия сумма сходится, подделка с неверной суммой видна во всех четырёх точках записи, получатель её выбросил, и у него выросли счётчики Tcp:InErrs и Tcp:InCsumErrors с 0 до 1, а игрушечный инспектор суммы не смотрит и разрешил соединение. второе условие: контрольная сумма TCP не сходится сумма считается по трём частям, результат лежит в заголовке TCP (16 бит)адреса отправителя и получателязаголовок TCPданныесумма получатель пересчитывает сумму и сверяет; не сошлось: пакет испорчен, его выбрасывают.Роутерам на пути, по общим сведениям, хватает заголовка IP, суммы TCP они не проверяют. два пакета из одного опыта (числа этого запуска) подделка: PA, номер 1, длина 680сумма на проводе:0x96c4 (38596)пересчёт по пакету:0xbb31 (47921)не сходится: пакет испорчен настоящее: PA, номер 1, длина 250тот же ip id 4559 и тот же номерсумма на проводе сходится с пересчётомсходится: пакет принят где видна подделка с неверной суммой клиентсумма BAD вход роутерасумма BAD выход роутерасумма BAD серверсумма BAD проверка пакетов у каждой точки записи: подделка прошла и роутер, значит, выбросил её именно получатель что произошло у получателя счётчики ядра сервераTcp:InErrs0 → 1Tcp:InCsumErrors0 → 1 приложение сервера: 250 байт, чистоклиент получил ответ сервераигрушка (инспектор): РАЗРЕШЕНО, сумм не смотрит всё это проверено только на нашем получателе и нашей модели

Где эта порча делается? Не в подмене полей: там badsum нет. Подмена меняет копию пакета, а настоящие байты из копии собирает упаковщик пакета, в коде он называется reconstruct; как он устроен, рассказывает выпуск «От байтов к таблице». Сначала упаковщик считает правильную сумму для нового заголовка, поэтому у подделок из прошлых сцен суммы верные. И только потом, если задан badsum, портит её.

Порча это одна строка на языке Си. Исключающее «или» двух чисел переворачивает те биты первого, где у второго стоят единицы, а остальные оставляет как есть. Контрольная сумма меняется исключающим «или» с числом: единица плюс остаток от деления случайного числа на шестьдесят пять тысяч пятьсот тридцать пять. Это число от единицы и выше, нулём оно не бывает. А исключающее «или» с ненулевым числом всегда меняет значение.

Само значение аргумента не важно. Мы записали badsum равно ноль, и журнал отправки всё равно показывает «badsum равно один»: код включает порчу для любого заданного значения. Счётчик ошибок суммы сдвинулся так же.

Можно ждать, что аппаратный подсчёт сумм на сетевой карте скроет порчу. Мы оставили настройки виртуальных карт по умолчанию, и получатель подделку всё равно выбросил: счётчик ошибок суммы вырос на единицу. Почему, мы не выясняли.

Где портится контрольная сумма Сумма портится не в подмене полей, а позже, в упаковщике пакета nfq2/lua.c:2611-2612, где сначала считается верная сумма, а затем она меняется исключающим «или» с числом от 1 до 65535, поэтому значение всегда другое; аргумент badsum включает порчу при любом заданном значении, в том числе badsum=0, а счётчик ошибок суммы у получателя вырос и при настройках виртуальных карт по умолчанию. где портится сумма: после подмены полей, при сборке пакета подмена полейapply_foolingработает с копией, badsum в ней нет упаковщик пакетав коде reconstructиз копии собирает настоящие байты проводпакет уходит nfq2/lua.c, функция lua_reconstruct_dissect 2611tcp_fix_checksum(tcp,l-l3,ip,ip6); сначала верная сумма для нового заголовка 2612if (badsum) tcp->th_sum ^= 1 + (random() % 0xFFFF); потом, если задан badsum, портит её random() % 0xFFFF0..65534 1 + …1..65535, не ноль ^=исключающее «или» исключающее «или»: где у второго единицы, там биты первого переворачиваются 1100первое число 1010второе, единицы в рамке^ 0110результат то же на нашей подделке (из одного опыта)верная сумма0xbb31маска порчи0x2df5на проводе0x96c4не сходится значение аргумента не важномы записали badsum=0, журнал: badsum=1любое заданное значение, nfq2/lua.c:1897-1902 настройки виртуальных карт по умолчаниюTcp:InCsumErrors снова 1, сервер 250 чистопорчу подсчёт не скрыл, почему: не выясняли у подделок с коротким сроком жизни и с MD5 сумма верна: портит её только badsum

Проверь себя. У подделок из прошлых сцен, с коротким сроком жизни и с опцией эм-дэ-пять, какая контрольная сумма?

Верная. Подмена полей сумму пересчитывает, портит её только отдельный аргумент. Так и было в наших прогонах для этой части: везде, кроме опыта с badsum, сумма у подделки сходится, мы проверили это разбором пакетов.

Итак. Неверную контрольную сумму видит наш получатель, а в нашем тесте роутеры не видят. Её портит упаковщик пакета после расчёта верной суммы, и маска порчи не бывает нулём. Счётчик получателя называет причину: ошибка суммы. Всё это проверено только на нашем получателе и нашей модели.

Сдвиг порядкового номера: на сколько, чтобы подделку отбросили

Третье условие для получателя: сдвинуть порядковый номер подделки. На сколько, чтобы получатель её отбросил, а не принял за данные?

Игрушка, выдуманная. Театр, места пронумерованы. Билет на место, которое уже пройдено контролёром, не впустит. А билет на соседнее место может сработать, и зритель сядет не туда.

Напомним расчёт из выпуска «multisplit: резать на части»: номера считаются от начала данных клиента. Настоящее приветствие занимает номера с первого по двести пятидесятый, получатель ждёт номер один. Подделка в шестьсот восемьдесят байт копирует номер настоящего пакета и тоже начинается с единицы. Аргумент «сдвиг номера», в записи tcp_seq, прибавляет к нему своё число.

Минус шестьсот восемьдесят. Подделка занимает номера с минус шестьсот семьдесят девятого по нулевой и кончается ровно перед байтом один. Все её байты старые. Получатель их выбросил и ответил блоком SACK на шестьсот восемьдесят байт, дублирующим SACK, как в выпуске «Перемешать: disorder». У получателя вырос счётчик, который мы читаем как «дубликат о старых данных». Прикладная программа получила чистые двести пятьдесят байт.

Минус шестьсот семьдесят девять, на единицу меньше. Теперь подделка занимает номера с минус шестьсот семьдесят восьмого по первый, и её последний байт попадает на байт один, который получатель как раз ждёт. Он его принимает. Прикладная программа получила двести пятьдесят байт, но первый из них последний байт подделки, а остальные двести сорок девять настоящие. Первый байт приветствия подменён, и в потоке имя сайта уже не найти.

Минус один: подделка занимает номера от нулевого до шестьсот семьдесят девятого. Нулевой старый, остальные шестьсот семьдесят девять новые, и получатель принял их все. Клиент завис.

Значит, подделка должна лежать в прошлом целиком: сдвиг не больше минус её длины. Для наших шестисот восьмидесяти байт это минус шестьсот восемьдесят и меньше. Минус десять тысяч лежит с запасом и тоже работает.

Сдвиг номера назад на ленте номеров Сдвиг порядкового номера назад на ленте относительных номеров: подделка в 680 байт при сдвиге -680 занимает номера от -679 до 0 и вся лежит в прошлом, получатель выбросил её и ответил блоком SACK на 680 байт, при -679 её последний байт попадает на ожидаемый байт 1 и получатель его принял, при -1 принято 679 новых байт и клиент завис, поэтому подделка должна лежать целиком в прошлом, то есть сдвиг не больше минус её длины. третье условие: сдвиг порядкового номера, шаг назад (относительные номера) получатель ждёт номер 1-700-3503507000 сдвиг -680настоящее 1…250 подделка: -679…0, все байты старые выброшена: ответ ACK 1 с блоком SACK на 680 байт, TCPDSACKOldSent +1приложение сервера: 250 байт, чисто сдвиг -679настоящее 1…250 подделка: -678…1 принят один байт, последний байт подделки лёг на байт 1: ответ ACK 2сервер получил 250 байт, но первый чужой: имя сайта не найти сдвиг -1настоящее 1…250 подделка: 0…679 новых байтов 679 (номера 1…679): ответ ACK 680сервер получил 679 чужих байт, клиент завис правило нашего теста: подделка целиком в прошлом, сдвиг не больше минус её длины для 680 байт это -680 и меньше; -10000 лежит с запасом и тоже работает проверь себя: подделка 300 байт: 1 + сдвиг + 299 не больше 0, значит -300 и меньше.При -299 останется один новый байт. Это счёт по правилу, запуска не было.

Теперь вперёд. Плюс один оставляет дыру в один байт, настоящее приветствие её закрывает, и к нему дописывается хвост подделки: двести пятьдесят настоящих байт и ещё четыреста тридцать один чужой, всего шестьсот восемьдесят один. Клиент завис. Как очередь собирает такой поток, показывал выпуск «Перемешать: disorder».

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

Плюс сто тысяч уже за окном приёма. Получатель её не принял: вырос счётчик, который мы читаем как «пакет за окном», а клиенту ушло пустое подтверждение «жду номер один», без блока SACK. Приложение получило чистые двести пятьдесят байт.

В коде это сложение: новый номер равен старому плюс заданное число по модулю два в тридцать второй степени. Отрицательное число переходит через ноль на другой край счёта, как в выпуске «multisplit: резать на части». Если вместо числа написана ерунда, будет ошибка, и стратегия в этот раз не сработает.

Сдвиг номера вперёд и строка кода Сдвиг порядкового номера вперёд: при +1 остаётся дыра в один байт, настоящее приветствие её закрывает и сервер получает 681 байт, при +10000 подделка внутри окна приёма ложится в очередь вне порядка и счётчик TCPOFOQueue растёт на 1, при +100000 она за окном приёма, сервер отвечает пустым ACK 1 без блока SACK и растёт счётчик BeyondWindow, а в коде сдвиг прибавляется к номеру по модулю 2 в степени 32 в строке 984 файла lua/zapret-lib.lua. шаг вперёд: подделка правее ожидаемого номера (схемы не в масштабе) сдвиг +1подделка 2…681 сдвиг +10000подделка 10001…10680 сдвиг +100000подделка 100001…100680 1подделканастоящее дыра в один байт (номер 1)ACK 1 с SACK на 680 байт,после настоящего: ACK 682сервер: 250 + 431 = 681 байтклиент завис 1…250· · ·в очереди внутри окна приёма:подделка ждёт вне порядкаSACK 10001…10681TcpExt:TCPOFOQueue +1сервер: 250 байт, чисто 1…250край окназа окном за окном приёма: не принятапустой ACK 1, без блока SACKTcpExt:BeyondWindow 0 → 1потом ACK 251 на настоящеесервер: 250 байт, чисто границу окна не измеряли: проверены только 10000 внутри и 100000 снаружи lua/zapret-lib.lua: сдвиг прибавляется к номеру подделки 982if fooling_options.tcp_seq then 984dis.tcp.th_seq = u32add(dis.tcp.th_seq, fooling_options.tcp_seq) 986error("apply_fooling: tcp_seq requires increment parameter. there's no default value.") u32add (nfq2/lua.c:359-372): сумма по модулю 2^32, минус переходит через ноль на другой край счётаесли вместо числа ерунда, будет ошибка, и стратегия в этот раз не сработает

Проверь себя. Подделка длиной в триста байт. Какой сдвиг нужен, чтобы она лежала в прошлом целиком?

Минус триста и меньше: последний байт подделки, то есть единица плюс сдвиг плюс двести девяносто девять, должен быть не больше нуля. Минус двести девяносто девять оставит один новый байт. Это счёт по правилу, запуска с такой подделкой у нас не было.

Итак. В нашем тесте подделка должна целиком лежать в прошлом: сдвиг не больше минус её длины. Назад на единицу меньше длины, и получатель принимает один байт подделки и портит поток. Вперёд в окне подделка ждёт в очереди, за окном получатель её не принимает.

Сдвиг подтверждения: ответ-вызов и граница в пять тысяч байт

Четвёртое условие: подтверждение. В каждом пакете есть число, которым отправитель говорит, до какого байта он всё получил. Что будет, если подделка это число соврёт?

Игрушка, выдуманная. Две стороны переписываются. Чем больше писем собеседник вам прислал и вы подтвердили, тем более старую расписку он стерпит. Если вы ему ничего не слали, терпеть ему нечего.

Аргумент называется «сдвиг подтверждения», в записи tcp_ack, и работает так же, как сдвиг номера из прошлой сцены, только на другом поле. Опыт: восемь значений сдвига подтверждения, от минус шестидесяти шести тысяч до плюс шестидесяти шести тысяч, среди них минус один и плюс один. Результат один: подделка отвергнута, у сервера сдвинулся один и тот же счётчик, прикладная программа получила чистые двести пятьдесят байт.

Возьмём минус один. Подделка ушла с подтверждением ноль, а настоящее приветствие за ней с подтверждением один. Сервер сразу ответил пустым пакетом, повторяющим старое подтверждение. Такой ответ называют ответом-вызовом, по-английски challenge ACK, так называется и счётчик. По общим сведениям, это способ не верить подозрительному подтверждению и переспросить; мы видели только ответ и счётчик.

Код тот же, что у номера: к подтверждению прибавляется заданное число по модулю, отрицательное уходит через ноль. Меняется только поле.

Ответ-вызов и восемь значений сдвига подтверждения Сдвиг подтверждения меняет то же поле, что настоящий пакет несёт в заголовке: подделка со сдвигом -1 уходит с подтверждением 0, настоящее приветствие с подтверждением 1, сервер сразу отвечает пустым ответом-вызовом и у него растёт счётчик TCPChallengeACK, а во всех восьми опытах от -66000 до +66000 результат один: подделка отвергнута, приложение сервера получило 250 байт, и код на строке 991 файла lua/zapret-lib.lua отличается от сдвига номера только полем. четвёртое условие: сдвиг подтверждения (tcp_ack) номер (seq): сдвиг tcp_seq, прошлая сцена подтверждение (ack): сдвиг tcp_ack, эта сцена клиентсервер подделка PA, длина 680номер 1, подтверждение 0 настоящее PA, длина 250номер 1, подтверждение 1 A, длина 0: ответ-вызовномер 1, подтверждение 1 TcpExt:TCPChallengeACK 0 → 1сервер переспросил, подделки нет эхо метки времени в ответе-вызоверавно метке предыдущего пакета клиента по общим сведениям, так переспрашиваютпри подозрительном подтверждении;мы видели только ответ и счётчик сдвигackвызовсервер -100 → 1250 чисто -1000-9990 → 1250 чисто -64000-639990 → 1250 чисто -65000-649990 → 1250 чисто -66000-659990 → 1250 чисто +120 → 1250 чисто +100010010 → 1250 чисто +66000660010 → 1250 чисто ack отсчитан от начала потока сервера;минус уходит через ноль на другой край счёта(вызов: счётчик TCPChallengeACK у сервера) результат один: отвергнута, сервер 250 чисто lua/zapret-lib.lua: то же сложение, другое поле 989if fooling_options.tcp_ack then 991dis.tcp.th_ack = u32add(dis.tcp.th_ack, fooling_options.tcp_ack) отрицательное число уходит через ноль, как у номера; если вместо числа ерунда, будет ошибка

Но почему отвергнут даже минус один? Возможно, потому что сервер к этому моменту ничего не отправлял. Проверим на сервере, который говорит первым: сразу после соединения он отправляет клиенту пять тысяч байт. Клиент их читает, ждёт три десятых секунды, чтобы подтверждения ушли, и только потом отправляет приветствие.

Минус один, минус тысяча и минус пять тысяч: подделка принята, получатель получил шестьсот восемьдесят чужих байт, клиент завис.

Минус пять тысяч один отвергнут: счётчик ответа-вызова вырос, поток чистый.

Дальше отвергнуты все: минус шесть тысяч, минус шестьдесят шесть тысяч и даже плюс тысяча. Граница проходит ровно между минус пятью тысячами и минус пятью тысячами одним.

Сервер отправил байты с первого по пятитысячный, клиент подтвердил всё: подтверждение пять тысяч один. Подделка со сдвигом минус пять тысяч несёт подтверждение один, то есть «из потока сервера не получено ничего». Со сдвигом минус пять тысяч один она несёт ноль, и это уже до начала потока сервера. Это счёт по пакетам, он показывает границу, но не объясняет правило ядра.

В нашем тесте терпимая «старость» подтверждения равна тому, что сервер сам отправил и получил подтверждённым. Когда клиент говорит первым, как с приветствием TLS, сервер ничего не отправлял, терпеть нечего, и отвергается даже минус один. По общим сведениям, правило ядра берёт меньшее из двух чисел: окна клиента, примерно шестидесяти четырёх тысяч байт, и подтверждённых байтов. Мы проверили только пять тысяч, сервер, отправивший больше окна, не пробовали.

Теперь спор. Руководство говорит: Linux отбрасывает пакет с верным номером и неверным подтверждением, только когда метка времени идёт в списке опций первой. Для этого есть аргумент «метка первой», в записи tcp_ts_up. Мы повторили с ним четыре опыта из восьми: результат тот же, без него подделку отвергали тоже. В нашем тесте аргумент не понадобился, почему, мы не выясняли.

Сервер говорит первым: граница сдвига подтверждения Сервер, который говорит первым, сразу после соединения отправил клиенту 5000 байт и получил подтверждение 5001, поэтому подделки со сдвигом подтверждения -1, -1000 и -5000 (подтверждения 5000, 4001 и 1) приняты и сервер получил 680 чужих байт, а при -5001 (подтверждение 0), -5002, -6000, -66000 и +1000 они отвергнуты ответом-вызовом, так что граница лежит ровно между -5000 и -5001. почему отвергнут даже -1: сервер, который говорит первым сервер: 5000 байт1448, 1448, 1448, 656 клиент подтверждаетACK 1449…5001 пауза 0,3 сACK дошли приветствиеподделка и настоящее сдвиглента подтверждений сервера: 0…5001ackитог настоящее 5001 до потокане слали -15000принята-10004001принята-50001принята -50010отвергнута -5002-1отвергнута-6000-999отвергнута-66000-60999отвергнута+10006001отвергнута принятые: сервер получил 680 байтклиент завис, ждёт ответа отвергнутые: TCPChallengeACK +1приложение сервера: 250 байт, чисто счёт по пакетам: 5001 - 5000 = 1, то есть «из потока сервера не получено ничего»;5001 - 5001 = 0, это уже до начала потока. Граница ровно между -5000 и -5001:счёт показывает границу, но правило ядра не объясняет. обычный случай: клиент говорит первым (TLS)сервер ничего не слал: отвергнут и -1 по общим сведениям, ядро берёт меньшееиз окна клиента и подтверждённых байт окно клиента около 64 тысяч байт; мы проверили только 5000, сервер с большим объёмом не пробовали спор с руководством: «метка первой» (tcp_ts_up) меняет порядок опций, и четыре опыта с ней дали тот же отказ без аргумента опции NOP NOP TS, с ним TS NOP NOP; нам аргумент не понадобился, почему, не выясняли

Проверь себя. Тот же сервер с пятью тысячами байт, сдвиг минус четыре тысячи: принята подделка или нет?

По границе принята: минус четыре тысячи ближе к нулю, чем минус пять тысяч, а минус пять тысяч принимался. Такого запуска у нас не было, это вывод из границы, а не измерение.

Итак. В нашем тесте сдвиг подтверждения отвергался с ответом-вызовом, пока сервер ничего не отправил; по общим сведениям, так бывает, когда первым говорит клиент, как в TLS и HTTP. Если сервер уже отправлял байты, малый сдвиг принимался. Условие «метка первой» мы не воспроизвели. Проверено на одном получателе и на пяти тысячах байт.

Снятый флаг ACK, и почему PSH условием не является

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

Игрушка, выдуманная. В начатой переписке каждое письмо обязано нести отметку «я прочитал ваше предыдущее». Письмо без отметки получатель выбрасывает. В TCP такая отметка есть: это флаг ACK.

У приветствия клиента два флага. ACK значит «в пакете есть подтверждение», PSH значит «передайте данные приложению сразу». Каждому флагу соответствует свой разряд: у PSH он равен восьми, у ACK шестнадцати, вместе двадцать четыре. Остальные разряды: у FIN один, у SYN два, у RST четыре.

Аргумент «снять флаги», в записи tcp_flags_unset, принимает список имён. Код превращает имена в число-маску, переворачивает её биты и накладывает на флаги подделки логическое «и»: выбранные разряды гаснут. Например, ACK это шестнадцать; перевернули, наложили на двадцать четыре, осталось восемь, это PSH. Неизвестное имя флага вызывает ошибку.

Разряды флагов и маска снятия флага Флаги TCP это разряды одного поля: FIN 1, SYN 2, RST 4, PSH 8, ACK 16, у приветствия клиента стоят PSH и ACK, вместе 24, а аргумент tcp_flags_unset в строке 997 файла lua/zapret-lib.lua превращает имена в маску, переворачивает её биты и накладывает логическое «и», например ACK это 10000, после переворота 01111, и наложенная на 11000 она оставляет 01000, то есть один PSH. пятое условие: флаги TCP, у каждого флага свой разряд ACKPSHRSTSYNFIN 11000 168421 у приветствия клиента два флага: PSH и ACKPSH значит «передай данные приложению сразу»ACK значит «в пакете есть подтверждение» разряды: FIN 1, SYN 2, RST 4, PSH 8, ACK 16; вместе у приветствия 8 + 16 = 24 lua/zapret-lib.lua: снять флаги 996if fooling_options.tcp_flags_unset then 997dis.tcp.th_flags = bitand(dis.tcp.th_flags, bitnot(parse_tcp_flags(fooling_options.tcp_flags_unset))) пример: tcp_flags_unset=ack 10000имя ack, число 16: маска 01111перевернули биты маски 11000флаги подделки, 24 01000логическое «и»: осталось 8, один PSH проверь себя: tcp_flags_unset=ack,psh даёт 24 - 24 = 0, поле флагов пустоенеизвестное имя флага вызывает ошибку: tcp flag 'BOGUS' is invalid (lua/zapret-lib.lua:656)обратный аргумент tcp_flags_set только включает флаги; на проводе не запускался

Опыт: снимаем ACK. Остаётся один PSH. Подделка уходит с флагом PSH без ACK, видна во всех четырёх точках записи, и ни один из нужных нам счётчиков получателя не двигается, лишнего ответа тоже нет: пакет выброшен молча. Прикладная программа получила чистые двести пятьдесят байт, а игрушечный инспектор на флаги не смотрит и принял подделку за первый пакет.

Где подделку выбросили? Она видна у клиента, на входе и выходе роутера и у самого сервера, значит, роутеры флаги не проверяют, и выбросил её именно получатель.

Почему получатель молчит? По общим сведениям, пакет без флага ACK в уже установленном соединении ядро отбрасывает без ответа. Мы видели только, что счётчики не двинулись, а правило ядра сами не проверяли.

Опыт: снимаем PSH. Двадцать четыре минус восемь, остаётся шестнадцать: один ACK. Подделка уходит с флагом ACK без PSH, и в нашем тесте получатель её принимает: шестьсот восемьдесят чужих байт, клиент завис. Пакет с данными без PSH это обычный пакет с данными, PSH лишь просит передать их приложению быстрее. По общим сведениям, ACK несёт каждый пакет установленного соединения, а PSH нужен не всякому, поэтому снятие PSH условием не является.

Проверь себя. Снимаем сразу и ACK, и PSH. Чему равны флаги и что сделает получатель?

Двадцать четыре минус двадцать четыре, ноль: флагов нет совсем, поле флагов у подделки пустое. В нашем опыте получатель выбросил такой пакет молча, сервер получил чистые двести пятьдесят байт.

Три опыта со снятием флагов Три опыта со снятием флагов у подделки в 680 байт: без ACK остаётся один PSH, подделка видна во всех четырёх точках записи, получатель выбрасывает её молча, счётчики не двигаются и сервер получает чистые 250 байт, без PSH остаётся один ACK и сервер принимает 680 чужих байт, поэтому PSH условием не является, а без ACK и без PSH флагов нет совсем и подделку тоже выбрасывают молча. что делает получатель с подделкой без ACK, без PSH и без обоих флагов tcp_flags_unset=ack24 - 16 = 8: остался PSH01000 видна в четырёх точках записи:PклиентPвходPвыходPсервервход и выход: две точки у роутера выброшена молчасчётчики не двигались, ответа нетсервер: 250 байт, чистоклиент получил ответигрушка: РАЗРЕШЕНО, не смотрит tcp_flags_unset=psh24 - 8 = 16: остался ACK10000 видна в четырёх точках записи:AклиентAвходAвыходAсервервход и выход: две точки у роутера принятасервер получил 680 чужих байтклиент зависпакет с данными без PSH обычный,PSH лишь просит передать быстрее tcp_flags_unset=ack,psh24 - 24 = 0: флагов нет совсем00000 видна в четырёх точках записи:пустоклиентпустовходпустовыходпустосервервход и выход: две точки у роутера выброшена молча, как без ACKсервер: 250 байт, чистосчётчики не двигалисьпакет без флагов выбросили тоже по общим сведениям, пакет без ACK в установленном соединении ядро отбрасывает без ответа;мы видели только молчание счётчиков; проверено на нашем получателе и нашей модели

Есть и обратный аргумент, «поднять флаги», в записи tcp_flags_set: он включает флаги. На проводе нашей лаборатории мы его не запускали: по общим сведениям, пакет с флагом сброса мог бы оборвать наше соединение. Прогон функций показал лишь, что в байтах пакета флаги меняются.

Итак. Подделка без флага ACK в нашем тесте выбрасывается получателем молча. Подделка без PSH принимается: PSH условием не является. Расчёт простой: у каждого флага свой разряд, а аргумент гасит выбранные. Это проверено только на нашем получателе и нашей модели.

Старая метка времени: минус один терпится, минус два нет, будущее вредит

Шестое условие для получателя: старая метка времени. Что это за метка и почему получатель сравнивает её с прошлыми?

В пакетах TCP бывает опция метки времени с двумя числами. Первое, «часы отправителя», это показание часов в момент отправки. Второе, «эхо», это последнее значение часов собеседника, которое отправитель видел. Их латинские имена на экране. Опция есть, только если обе стороны договорились о ней при установлении соединения.

По общим сведениям, получатель запоминает последнюю метку собеседника и отбрасывает пакеты с более старой меткой: это защита от запоздавших копий. Игрушка, выдуманная: пришло письмо от четвёртого числа, а следом письмо от третьего, и третье выбрасывается как устаревшее.

Аргумент «сдвиг метки времени», в записи tcp_ts, прибавляет число только к первым четырём байтам данных опции, то есть к часам отправителя. Вторая половина остаётся как была. Если опции нет или данные не восемь байт, код пишет в журнал, что метки нет, и ничего не меняет.

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

Минус два: подделка отстаёт на две единицы, и получатель её отбросил. Сдвинулся счётчик, имя которого мы читаем как «старая метка в установленном соединении», поток чистый. Минус шестьсот тысяч тоже отброшена.

Что такое «единица» метки? В нашем тесте метка тикает раз в миллисекунду: между двумя повторами приветствия с разницей в двести миллисекунд она выросла примерно на двести единиц. Значит, «минус два» это отставание на две миллисекунды. Предыдущий пакет клиента ушёл в ту же миллисекунду, что и приветствие, поэтому граница оказалась здесь. Если между ними пройдёт больше времени, граница сдвинется: мы этого не мерили, и надёжнее брать большой сдвиг, например минус шестьсот тысяч.

Опция метки времени: байты, код и единица Опция метки времени TCP в байтах настоящих пакетов нашего опыта состоит из двух чисел, TSval (часы отправителя) и TSecr (последнее значение часов собеседника), аргумент tcp_ts в строке 1013 файла lua/zapret-lib.lua прибавляет число только к первым четырём байтам, так что при -1 последний байт TSval у подделки 06 вместо 07, а в нашем тесте метка растёт примерно на единицу за миллисекунду: за 207,8 мс она выросла на 208. шестое условие: метка времени TCP, байты опции из одного опыта TSvalчасы отправителяTSecrчасы собеседникавид и длинаNOP NOP 08 0a предыдущий пакет клиента0101080a5aa7bc076f0c7b95 настоящее приветствие0101080a5aa7bc076f0c7b95 подделка, tcp_ts=-10101080a5aa7bc066f0c7b95 последний байт TSval стал 06 вместо 07, TSecr остался как был lua/zapret-lib.lua: сдвиг метки времени 1009if fooling_options.tcp_ts then 1012if idx and (dis.tcp.options[idx].data and #dis.tcp.options[idx].data or 0)==8 then 1013dis.tcp.options[idx].data = bu32(u32add(u32(dis.tcp.options[idx].data), fooling_options.tcp_ts))..string.sub(dis.tcp.options[idx].data,5) 1015DLOG("apply_fooling: timestamp tcp option not present or invalid") bu32(…) .. string.sub(data,5): первые четыре байта (TSval) меняются, с пятого (TSecr) остаютсястрока 1012: в опции должно быть 8 байт; иначе код пишет в журнал и ничего не меняет единица метки в нашем тесте: повторы одного приветствия время 920,9 мсTSval ...311 время 1128,8 мсTSval ...519 время 1336,5 мсTSval ...727 за 207,8 мс: плюс 208единица около 1 мс «минус два» значит отставание на две миллисекунды (для нашего клиента)предыдущий пакет клиента ушёл в ту же миллисекунду, что и приветствие;если пройдёт больше времени, граница сдвинется: это мы не мерили

Теперь метка из будущего, плюс шестьсот тысяч. Получатель подделку принял, и это только половина беды. Похоже, он запомнил метку из будущего, и собственные пакеты клиента с обычной меткой стали казаться ему старыми: по счётчикам сервера, правило отбросило пакеты клиента ещё семь раз. Метка из будущего портит само соединение.

А если метки времени вообще не договорены? Сервер с выключенными метками: заголовок клиента двадцать байт, без опций. Код пишет «метки нет» и подделку не меняет, получатель её принимает, шестьсот восемьдесят байт. А опция эм-дэ-пять и сдвиг подтверждения при выключенных метках по-прежнему отвергаются: эти условия на метках не держатся.

Четыре сдвига метки времени и сервер без меток Четыре опыта со сдвигом метки времени у подделки: -1 принят и сервер получил 680 чужих байт, -2 и -600000 отвергнуты со сдвигом счётчика PAWSEstab на 1, а +600000 принят и поломал соединение, потому что счётчики сервера дошли до PAWSEstab 7 и PAWSOldAck 5, при выключенных метках на сервере опции нет, код только пишет в журнал, подделка принята, а MD5 и сдвиг подтверждения от меток не зависят. шестое условие: четыре опыта со сдвигом метки времени (TSval пакетов клиента) сдвигпред. пакетприветствиеподделкачто получил сервер -1152094208715209420871520942086принята: сервер получил 680 байтклиент завис; PAWS не двигались -2180744723818074472381807447236отвергнута: PAWSEstab +1сервер 250 байт, чисто -600000195216033319521603341951560334отвергнута: PAWSEstab +1сервер 250 байт, чисто +600000126310826912631082691263708269принята: 680 байт, клиент зависPAWSEstab 0 → 7, PAWSOldAck 0 → 5 граница в нашем тесте: -1 терпится, -2 уже нет; единица метки около одной миллисекунды+600000: метка из будущего, похоже, запомнена: обычные пакеты клиента стали «старыми»,а ответы сервера несут эхо этой метки (1263708269) метки времени не договорены: сервер с выключенными метками tcp_ts=-1 при выключенных меткахв заголовке клиента 20 байт, опций нетжурнал: timestamp tcp option not present or invalidподделка не изменена: принята, 680 байт при тех же выключенных меткахMD5: TCPMD5Unexpected +1, сервер 250 чистосдвиг подтверждения: TCPChallengeACK +1, чистоэти условия на метках не держатся проверь себя: метки не договорены, tcp_ts=-5: код не найдёт опцию, подделка уйдёт как есть, её примутпо общим сведениям, получатель помнит последнюю метку собеседника и отбрасывает старые пакеты:это защита от запоздавших копий; имена счётчиков PAWS читаем по названию;доказано лишь, что счётчики сдвинулись; всё на одном получателе и нашей модели

Проверь себя. Метки времени в соединении не договорены, мы задали сдвиг метки времени минус пять. Что случится?

Ничего не изменится: код не найдёт опцию и напишет об этом в журнал. Подделка уйдёт как есть, и получатель её примет. Так и было в опыте с выключенными метками.

Итак. В нашем тесте получатель отбрасывает подделку с меткой, отстающей не меньше чем на две единицы, при нашем темпе часов в одну миллисекунду. Минус один терпится, метка из будущего портит соединение, а без договорённости о метках условие ничего не делает. Всё это на одном получателе и нашей модели.

Что мы знаем к этому месту: шесть условий и подбор срока жизни

Остановимся и сложим то, что узнали: один способ работает на дороге, шесть на получателе.

Семь условий: где остаётся подделка Семь условий подделки в одной таблице: срок жизни убивает её на роутере, а шесть остальных (MD5, контрольная сумма, номер, подтверждение, снятый ACK, старая метка времени) пускают её до получателя, который отбрасывает её сам и в пяти случаях оставляет след в счётчике ядра, а при снятом ACK молчит, причём всё измерено на одном получателе, Linux 6.18, и игрушечном инспекторе. семь условий подделки: где она остаётся и что делает получатель условиегде остаётсяполучательсчётчик ядра на дороге срок жизниip_ttl, ip_autottlумирает на роутере,до сервера не доходитничего не видитнет; клиенту приходитICMP «время истекло» у получателя: подделка доходит, он выбрасывает её сам MD5tcp_md5доходит до сервераотбрасываетTCPMD5Unexpected контрольная суммаbadsumдоходит до сервераотбрасываетTcp:InCsumErrors номерtcp_seq ≤ −680доходит до сервераотбрасывает как староеTCPDSACKOldSent подтверждениеtcp_ackдоходит до сервераотвечает ответом-вызовомTCPChallengeACK флаг ACK снятtcp_flags_unset=ackдоходит до серверамолча отбрасываетнет метка времениtcp_ts ≤ −2доходит до сервераотбрасываетPAWSEstab номер вперёд: в окне приёма TCPOFOQueue, за окном BeyondWindow и пустое подтверждение наша игрушка стоит в первом роутере, до проверки срока жизни, и видит подделку в обоих случаяхизмерено на одном получателе (Linux 6.18) и на игрушке проекта; о настоящем ТСПУ неизвестно

Одно условие работает на дороге: срок жизни. Подделка умирает на роутере, до получателя не доходит, а у клиента приходит «время истекло». Число берётся как переходы плюс дельта и держится на двух догадках: о стартовом сроке сервера и о длине обратного пути. Если подбор отказывает и запасного числа нет, подделка остаётся со сроком шестьдесят четыре и доезжает.

Шесть условий работают на получателе: подделка доходит до него, а он выбрасывает её сам. Опция эм-дэ-пять: счётчик «подпись, которой не ждали». Испорченная сумма: счётчик ошибок суммы. Сдвиг номера в прошлое на длину подделки и больше: счётчик старых данных. Сдвиг подтверждения: ответ-вызов. Снятый ACK: молчаливый выброс, счётчиков нет. Метка, отстающая на две единицы и больше: счётчик старой метки.

Счётчики это следы, а не объяснения: мы знаем, что в опыте сдвинулся именно этот счётчик, а название читаем как причину. У номера вперёд свои следы: в окне приёма растёт счётчик очереди, за окном счётчик выхода за окно и приходит пустое подтверждение.

Заметим, чем различаются два вида условий. Срок жизни не пускает подделку даже до сервера, и получатель её не видит. Шесть условий пускают её до самого получателя, и там она отвергается. Инспектор нашей модели видит подделку в обоих случаях: он стоит в первом роутере, до проверки срока.

Где подделку принимают Девять случаев из опытов, где условие подделки не сработало и подделка стала данными: номер, оставляющий хоть один новый байт, опция MD5 неверной длины, малый сдвиг подтверждения при сервере, который уже говорил, снятый только PSH, метка времени на единицу старее и метка из будущего, метки выключены на сервере и отказ автоподбора срока жизни без запасного числа, и почти везде сервер получил 680 байт подделки, а клиент завис. где условие не сработало и подделку приняли вместо настоящих данных условие в опытесервер получилклиент tcp_seq=−679последний байт подделки на месте первого250 B, из них 1 чужойответ есть, поток испорчен tcp_seq=−1 и +1оставляют новые байты679 B и 681 Bзавис tcp_md5=0011опция неверной длины680 Bзавис tcp_ack малого сдвигасервер уже говорил, до −5000680 Bзавис tcp_flags_unset=pshснят только PSH, ACK на месте680 Bзавис tcp_ts=−1метка на единицу старее680 Bзавис tcp_ts=+600000метка из будущего; PAWSEstab=7680 Bзавис tcp_ts=−600000метки времени выключены на сервере680 Bзавис ip_autottl без ip_ttlподбор отказал, срок остался 64680 Bзавис в большинстве случаев соединение виснет; всё измерено в нашем тесте, Linux 6.18

Есть и места, где условие не срабатывает и подделка становится данными. Номер, оставляющий хоть один новый байт. Опция неверной длины. Малый сдвиг подтверждения, когда сервер уже говорил. Снятый PSH. Метка на единицу старее и метка из будущего. И отказ подбора срока. В большинстве этих случаев соединение виснет, а при сдвиге номера минус шестьсот семьдесят девять ответ приходит, но поток испорчен.

Проверь себя. Подделка ушла со сдвигом подтверждения минус тысяча, а сервер до этого ничего не отправлял. Дойдёт ли подделка до сервера и какой счётчик двинется?

Дойдёт: в нашем тесте роутеры сдвиг подтверждения не проверяют. А получатель её отвергнет, и двинется счётчик ответа-вызова. Это условие из таблицы, мы видели его в опыте: приложение получило чистые двести пятьдесят байт.

Слово «работает» в этой таблице значит одно: получатель отбросил подделку, а простой игрушечный инспектор принял её за первый пакет. Всё измерено на одном получателе, Linux, и на нашем инспекторе. О настоящем ТСПУ и других получателях мы не знаем ничего.

Дальше в выпуске два вопроса: что будет, если игрушечный инспектор строже и проверяет то же, что получатель, и что будет с другим получателем. Там же увидим всю таблицу на одном экране.

Итак. Подбор срока останавливает подделку на дороге, шесть условий заставляют получателя выбросить её самому. У каждого условия своя причина, видная в счётчиках, и свои ловушки, где подделка превращается в данные. Всё это относится к нашему получателю и нашей модели.

Повтор и вопрос: чьё поведение мы измеряли

Коротко повторим, что было раньше в этом выпуске, чтобы эту часть можно было смотреть отдельно.

Подделка, в коде fake, это подложное письмо: программа отправляет рядом с настоящим пакетом ещё один, с теми же заголовками и другими байтами. У подделки нет вердикта, поэтому настоящий пакет идёт следом без изменений.

Подделка должна показаться нашей игрушечной модели инспектора настоящим первым пакетом с данными, быть отброшенной получателем и ничего больше не испортить. В нашем тесте подделка без условий принимается сервером вместо настоящих данных, и клиент зависает. Для этого есть условия: одни убивают подделку в пути, это срок жизни, другие портят пакет, чтобы получатель его отбросил: сумма, MD5, номера, ACK, метка времени.

Слово «сработало» у нас значит только «обошло нашу игрушку и нашего получателя». Настоящий ТСПУ мы не видели.

Вопрос сцены: чьё поведение мы на самом деле измеряли, и что изменится, если получатель будет другим?

Игрушка, пример выдуманный. Два почтовых отделения и одна и та же посылка с неверно написанным весом. Первое отделение такие посылки возвращает, второе доставляет не глядя.

Наше отделение это сервер на Linux версии шесть точка восемнадцать с настройками по умолчанию. Кроме одной: расчёт контрольной суммы силами интерфейса, по общим сведениям он называется offload, мы отключили. Что сервер сделал с подделкой, видно по счётчикам его ядра до и после опыта.

Счётчики ядра получателя Наш получатель, сервер на Linux 6.18 с настройками по умолчанию и выключенным offload, в трёх опытах отбросил подделку и сдвинул свой счётчик на единицу: при опции MD5 TCPMD5Unexpected, при испорченной контрольной сумме Tcp:InErrs и Tcp:InCsumErrors, при метке времени на две единицы старее PAWSEstab, а приложение каждый раз получило чистые 250 байт настоящего приветствия. чьё поведение измеряли: счётчики ядра сервера до и после опыта наш получатель: сервер на Linux 6.18 (сборка WSL2), настройки по умолчанию,кроме одной: расчёт контрольной суммы силами интерфейса (offload) выключен tcp_md5подделка 680 B, заголовок TCP 52 B:NOP NOP TS MD5(16 B) END; настоящий 250 B, заголовок 32 Bсчётчик получателяTcpExt:TCPMD5Unexpected +1 250 B чисто badsumконтрольная сумма подделки невернаво всех четырёх точках съёмасчётчик получателяTcp:InErrs +1Tcp:InCsumErrors +1 250 B чисто tcp_ts=−2метка времени TSval подделкина 2 меньше положеннойсчётчик получателяTcpExt:PAWSEstab +1 250 B чисто остальные следы разобраны в таблице условий tcp_seqTCPDSACKOldSent tcp_ackTCPChallengeACK без ACK— срок жизни 1— у срока жизни 1 и у снятого ACK следа нет: подделка не дошла или отброшена молча названия счётчиков читаем как причины по названию: проверено лишь то, что они сдвигаются

Первый опыт, подделка с опцией MD5. В нашем тесте у сервера вырос на единицу счётчик, по названию он про опцию MD5, которой получатель не ждал.

Второй, подделка с испорченной контрольной суммой: вырос счётчик ошибок суммы, снова на единицу.

Третий, со старой меткой времени, на две единицы меньше положенной: вырос счётчик с именем PAWS. По общим сведениям, это проверка меток времени, которая отбрасывает старые пакеты. Во всех трёх опытах приложение сервера получило чистые двести пятьдесят байт настоящего приветствия. Названия счётчиков мы читаем как причины только по названию: проверено лишь то, что они сдвигаются в этих опытах.

Две вариации получателя Две проверенные вариации получателя: при выключенных на сервере метках времени условие старой метки ни за что не цепляется, программа пишет в журнал, что опции нет, подделка принята и приложение получает 680 байт, а условие MD5 работает как прежде, а при включённом offload плохая контрольная сумма всё равно отброшена, причина не выяснена, и отдельно перечислено, чего мы не проверяли: другие системы, балансировщики и промежуточные устройства. две вариации получателя, проверенные нарочно 1. на сервере выключены метки времениtcp_timestamps=0 заголовок TCP клиента20 Bнет опции времениобычно 32 B: 20 и 12 байт опций условие tcp_ts=−600000: в журнале программыapply_fooling: timestamp tcp option not present or invalid подделка принята: приложение получило 680 B, клиент завис (4 с без ответа) проверь: тот же сервер, условие tcp_md5TCPMD5Unexpected +1, приложение получило чистые 250 B 2. offload интерфейсов оставлен включённым ожидали: плохая сумма не подействует в нашем тесте подействовала: Tcp:InCsumErrors +1, приложение получило чистые 250 B почему так, не выясняли не проверяли Windows BSD macOS встроенные системы серверы за балансировщиками промежуточные устройства предположения, а не результаты:другой стек может не отбросить опцию MD5, для которой у него нет ключа, или не проверять метки временизаданный вручную ip_ttl зависит от пути, а не от стека; на других стеках мы это не проверялиавтоподбор опирается на начальный срок жизни сервера, у систем он, по общим сведениям, разный

Теперь две вариации получателя, которые мы проверили нарочно. Первая: на сервере выключены метки времени. Клиентские пакеты тогда не несут эту опцию, и условию «старая метка» не за что зацепиться. Программа пишет в журнал, что опции нет, и ничего не меняет. Подделка принята: приложение получило шестьсот восемьдесят байт, а клиент завис.

Проверь себя. Метки времени у сервера всё ещё выключены, а условие другое: опция MD5. Что будет с подделкой?

Её отбросят: MD5 с метками времени не связана. В нашем тесте счётчик MD5 снова вырос на единицу, а приложение получило чистые двести пятьдесят байт.

Вторая вариация: в одном опыте мы оставили offload включённым. Мы ожидали, что плохая сумма тогда не подействует. В нашем тесте подействовала: счётчик ошибок суммы вырос на единицу, приложение получило чистое приветствие. Почему, мы не выясняли.

Всё остальное мы не проверяли: Windows, BSD, macOS, встроенные системы, серверы за балансировщиками и промежуточные устройства. По общим сведениям, другой стек может не отбросить опцию MD5, для которой у него нет ключа, или не проверять метки времени; это предположения, а не результаты. Заданный вручную срок жизни от стека получателя не зависит, он зависит от пути, но на других стеках мы это не проверяли. А автоподбор срока опирается на начальный срок жизни сервера, и у разных систем он, по общим сведениям, разный.

Итак. Всё измеренное относится к одной связке: игрушечный инспектор и Linux шесть точка восемнадцать. У большинства условий в счётчиках получателя остаётся след, но не у всех: подделка со сроком жизни один до сервера не доходит, а подделка без флага ACK выбрасывается молча. Выключенные метки времени отключают одно условие, но не остальные.

Игрушки построже: условие, причину которого видно, не работает

Сначала честная плашка. Инспектор в этой сцене игрушка нашего проекта, мы построили её сами, и слово «сработало» значит «обошло игрушку».

Вопрос сцены: что будет с условиями, если игрушка станет внимательнее?

Игрушка, пример выдуманный. Сортировщик видит на подложной посылке неверно написанный вес. Он знает, что адресат такую посылку вернёт, поэтому откладывает её, не читая, и берёт следующую, настоящую. Подделка не сработала: причина, по которой посылку не примут, видна и сортировщику.

Игрушка: первый пакет решает Игрушечный инспектор проекта стоит на входе в первый роутер, раньше проверки срока жизни, поэтому видит и пакеты со сроком жизни 1, и первый пакет с данными, который он принял во внимание, решает за всё соединение: блокируемое имя внутри значит блокировку, приветствие без имени значит разрешение, а игнорированный пакет заставляет ждать следующий; опорные опыты: без подделки игрушка блокирует и сервер получает 0 байт, без игрушки сервер получает 250 байт. игрушка проекта: первый принятый пакет с данными решает за всё соединение клиент сервер роутер 1 входхук PREROUTINGдо проверки срока проверкасрока жизни выходк серверу игрушка: вторая nfqws2 на очереди 201;видит и пакеты со сроком жизни 1 что игрушка решает по первому принятому пакету с данными блокируемое имя внутриэтот пакет и все следующие данные клиента выбрасываются: BLOCKED приветствие TLS или запрос HTTP без именисоединение разрешено, дальше игрушка не смотрит: ALLOWED пакет игнорирован вариантом игрушкив расчёт не взят, игрушка ждёт следующий пакет опорные опыты: стратегия pass, подделки нет с игрушкойимя найдено в первом пакете (250 B):BLOCKED, сервер получил 0 Bклиент: closed, no reply без игрушкитот же запрос доходит:сервер получил 250 Bответ получен

Теперь наша игрушка. Она стоит на входе в первый роутер, раньше проверки срока жизни, и видит даже пакеты, которым осталось жить один шаг. Она следит за потоком клиента к серверу. Первый пакет с данными, который она приняла во внимание, решает за всё соединение. Если внутри блокируемое имя, этот пакет и все следующие данные клиента выбрасываются. Если приветствие без имени, соединение разрешено, и дальше игрушка не смотрит.

Опорные опыты. Без подделки игрушка находит имя в первом пакете и выбрасывает его вместе с повторами: сервер получил ноль байт. Без игрушки тот же запрос доходит: двести пятьдесят байт и ответ.

Матрица условий и игрушек Матрица из шести условий подделки и пяти вариантов игрушечного инспектора: красные клетки, где сервер получил 0 байт, стоят только там, где игрушка видит причину условия, то есть сумму, MD5, номер и снятый ACK, зелёные клетки с 250 байтами означают, что игрушка приняла подделку за первый пакет, а серые клетки в нашем тесте не запускались. условие против игрушки: сколько байт получил сервер простаяигрушкапроверяющийсуммуотсеивающийMD5следящийза номеромтребующийACK срок жизни 1ip_ttl=1250 Bразрешено250 Bразрешено250 Bразрешено250 Bразрешено250 Bразрешено MD5tcp_md5250 Bразрешено250 Bразрешено0 Bблокне запускалине запускали плохая суммаbadsum250 Bразрешено0 Bблок250 Bразрешеноне запускалине запускали номер −10000tcp_seq=-10000250 Bразрешеноне запускалине запускали0 Bблокне запускали без ACKtcp_flags_unset=ack250 Bразрешеноне запускалине запускалине запускали0 Bблок старая меткаtcp_ts=-600000250 Bразрешеноне запускалине запускали250 Bразрешеноне запускали в журнале игрушки: TOY csum: ignoring packet with BAD tcp checksumигнорирует подделку, берёт настоящее приветствие, находит имя, блокирует игрушки построены нами, каждая видит ровно одну причину: правило верно по построениюкаков настоящий инспектор, мы не знаем

У игрушки есть ещё четыре строгих варианта. Каждый не принимает во внимание пакеты определённого вида. Первый, «проверяющий сумму», игнорирует пакеты с неверной контрольной суммой. Против подделки с плохой суммой в его журнале запись «пакет с неверной суммой не учтён». Первым пакетом с данными для него стало настоящее приветствие, он нашёл в нём имя и выбросил. Сервер получил ноль байт.

Второй, «отсеивающий MD5», игнорирует пакеты с опцией MD5. Против подделки с MD5 исход тот же: подделка не учтена, имя найдено, ноль байт.

Третий, «следящий за номером», ждёт определённый номер первого байта. У подделки номер на десять тысяч меньше, игрушка её игнорирует, исход тот же.

Четвёртый, «требующий ACK», игнорирует пакеты без флага ACK. Подделка без этого флага игнорирована, сервер получил ноль байт.

Проверим, что дело в совпадении причины. MD5 против проверяющего сумму: игрушка принимает подделку за первый пакет и разрешает соединение, сервер получил чистые двести пятьдесят байт. Плохая сумма против отсеивающего MD5: то же. Старая метка времени против следящего за номером: то же. И срок жизни один против всех четырёх: все четыре приняли такую подделку за первый пакет.

Игрушки построены нами, каждую мы научили видеть ровно одну причину. Поэтому правило верно по построению: это не открытие о настоящих инспекторах, а проверка простой мысли, что условие помогает там, где причину не видят. Старая метка времени прошла следящего за номером; остальных трёх мы с ней не пробовали, по устройству игрушек она должна пройти и их, но это счёт, а не запуск. Чтобы видеть срок жизни или метку времени, игрушке нужно знать длину пути или состояние меток у получателя.

Проверь себя. Игрушка требует ACK, а у подделки испорчена контрольная сумма. Что решит игрушка?

Флаг ACK у подделки на месте, игрушка примет её за первый пакет, имени в нём нет, и она разрешит соединение. А плохую сумму отбросит сервер. Это счёт по устройству игрушки: такую пару мы не запускали.

Итак. Условие, причину которого видит игрушка, против неё не работает: сумму видит проверяющий сумму, MD5 отсеивающий MD5, номер следящий за номером, ACK требующий ACK. Срок жизни приняли все четыре. Каков настоящий инспектор, мы не знаем.

Матрица условий: семь строк, четыре графы

Соберём всё в одну таблицу, по строке на условие.

Вопрос сцены: что мы знаем про каждое условие? Для каждого четыре ответа. Что меняется в пакете. Где подделка останавливается. Что получил сервер и что показали его счётчики. И какая из наших игрушек умеет такую подделку отсеять.

Игрушка, пример выдуманный: анкета с четырьмя графами. Заполняем её по строке.

Где подделка остановилась Матрица условий, первая половина: по семи строкам видно, что меняется в пакете подделки и в каких из четырёх точек съёма она появилась, срок жизни 1 даёт подделку только на клиенте и на входе в роутер и роутер возвращает сообщение об ошибке, а остальные шесть условий доезжают до сервера, и при каждом из них сервер получил чистые 250 байт. по строке на условие: что меняется и где подделка остановилась клиентвходроутеравыходроутерасерверсервер получил умирает в пути срок жизниip_ttl=1: в заголовке IP срок жизни 1250 B чистороутер вернул ICMP доезжает и умирает у получателя MD5заголовок TCP вырос с 32 до 52 байт250 B чисто плохая сумманеверно одно поле, остальное в порядке250 B чисто плохой номерtcp_seq=−10000: подделка вся в прошлом250 B чисто плохое подтверждениеtcp_ack=−1000: номер ACK на 1000 назад250 B чисто снят ACKфлаги подделки: остался один PSH250 B чисто старая меткаtcp_ts=−600000: TSval на 600000 меньше250 B чисто точки съёма: клиент, вход роутера, выход роутера, серверсервер Linux 6.18; приложение везде получило чистое приветствие

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

Строка вторая, опция MD5. Заголовок TCP вырос с тридцати двух до пятидесяти двух байт: добавилась опция с шестнадцатью случайными байтами и выравнивание нулями. Подделка доехала до сервера и там отброшена, вырос счётчик MD5. Отсеять её умеет игрушка, отсеивающая MD5.

Строка третья, плохая контрольная сумма. Испорчено одно поле, остальное в порядке. Подделка доехала, сервер отбросил, вырос счётчик ошибок суммы. Отсеять её умеет проверяющий сумму.

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

Строка пятая, плохое подтверждение. Номер ACK сдвинут на тысячу назад. В нашем тесте сервер отбросил подделку, вырос счётчик проверочных подтверждений. Игрушки для этого условия нет: ни одна не проверяет номер подтверждения. И оговорка: так, пока сервер сам ничего не отправил. Когда сервер отправил клиенту пять тысяч байт, а клиент их подтвердил, сдвиг на тысячу подделку уже не отбрасывал.

Строка шестая, снятый флаг ACK. Остался один PSH. Сервер отбросил молча, счётчик не вырос, приложение чисто. Отсеять умеет требующий ACK. А снять только PSH не помогает: такая подделка принята.

Строка седьмая, старая метка времени. От значения отнято шестьсот тысяч. Вырос счётчик PAWS, приложение чисто. Из игрушек проверена одна, следящий за номером, и подделку она не отсеяла. Две оговорки: минус один сервер принял, а метка из будущего принимается и портит соединение.

След, игрушка и оговорка Матрица условий, вторая половина: для каждого из семи условий показаны след в счётчиках сервера, игрушка, которая умеет отсеять такую подделку, и главная оговорка, и видно, что пять из шести условий у получателя оставляют след, снятый ACK не оставляет, игрушка есть против MD5, суммы, номера и снятого ACK, а против срока жизни, подтверждения и метки времени такой игрушки нет. у каждого условия: след у получателя, игрушка, оговорка условиеслед у получателякто отсеиваетоговорка срок жизнинет следа,ICMP клиентуни одна: все четырепринимают её за первыйнужна длина пути: доходитпри TTL не меньше роутеры + 1 MD5TCPMD5Unexpectedотсеивающий MD5опция неверной длиныtcp_md5=0011: принята, 680 B суммаTcp:InCsumErrorsпроверяющий суммуодно испорченное поле номерTCPDSACKOldSentпо названию: дубликатследящий за номеромсдвиг не меньше 680: при −679в потоке 1 байт подделки подтверждениеTCPChallengeACKникакая: номер ACKне проверяет ни однасервер уже говорил: −1000принято, граница 5000 байт снят ACKнет следаотброшена молчатребующий ACKснять только PSH непомогает: принято, 680 B метка времениPAWSEstabиз игрушек проверенаодна: номер не отсеял−1 принято; +600000 принятои портит соединение пять из шести условий у получателя оставляют след, снятый ACK не оставляетесть игрушка против четырёх условий: MD5, суммы, номера, снятого ACKпротив срока жизни, подтверждения и метки времени игрушки нетвсе выводы относятся к игрушке проекта и Linux 6.18

Как читать таблицу. Подделка либо умирает в пути, это срок жизни, либо доезжает и умирает у получателя, это остальные шесть. Первой группе нужно знать расстояние, второй нужен определённый получатель.

Проверь себя. Допустим, сдвиг номера минус сто. Сколько байт подделки ляжет на номера настоящего приветствия?

Пятьсот восемьдесят: шестьсот восемьдесят минус сто. Мы видели два края: при минус шестьсот семьдесят девять на настоящие номера ложится один байт, при минус один шестьсот семьдесят девять. Середина это счёт по этим краям, такого запуска мы не делали.

Итак. Семь условий, две группы: одна подделка умирает на роутере, шесть умирают у получателя. Пять из шести у получателя оставляют след в счётчиках сервера, снятый ACK не оставляет никакого. Против четырёх условий у нас есть игрушка, которая их отсеивает, а для срока жизни, подтверждения и метки времени такой игрушки нет.

Ловушки: условие бьёт не туда, не по той версии или не срабатывает вовсе

Условия подделки могут и навредить. Вопрос сцены: где условие бьёт не по подделке, а по настоящему пакету, или вообще ничего не делает?

Вспомним игрушку из выпуска «multisplit: резать на части»: курьер, которому велели ставить на каждую посылку штамп «доставить до первого поста». Он поставил, и ни одна посылка не дошла.

Ловушки: срок 1 бьёт по настоящим Две ловушки со сроком жизни 1: в multisplit условие достаётся настоящим частям, все двенадцать клиентских пакетов уходят со сроком 1 и ничего не доходит до сервера, а в pktmod условие правит сам проходящий пакет, поэтому все шесть пакетов по 250 байт тоже умирают на роутере, и в обоих случаях приложение получает 0 байт, тогда как fake отправляет и правит только копию. условие бьёт по настоящим пакетам: срок жизни 1 в нашем тесте multisplit:pos=midsld:ip_ttl=1у multisplit нет своих поддельных аргументов 10414610414610414610414610414610414612 пакетов, у всех срок 1 выход роутера: пакетов нетсервер: пакетов нет игрушка приняла часть 104 B без имени и разрешила соединение (ALLOWED) игрушку обошли, а соединение мертво: приложение получило 0 B, клиент closed, no reply pktmod:ip_ttl=1правит сам проходящий пакет, не копию 250250250250250250оригинал и пять повторов, срок 1 выход роутера: пакетов нетсервер: пакетов нет игрушка до проверки срока увидела имя в первом пакете и заблокировала (BLOCKED) без игрушки пакеты тоже не прошли бы роутер: приложение получило 0 B почему так: условие ложится на копию или на живой пакет fake: lua/zapret-lib.lua 1235local discopy = deepcopy(dis)fake отправляет копию 1237apply_fooling(desync, discopy, options and options.fooling)условие только на копии pktmod: lua/zapret-antidpi.lua 118apply_fooling(desync)условие на живой пакет 121return VERDICT_MODIFYпроходящий пакет изменён

Первая ловушка, та же самая. У multisplit нет своих поддельных аргументов, и всё, что вы пишете, получают настоящие части. В нашем тесте разрез midsld и срок жизни один: на клиенте двенадцать пакетов, шесть пар из ста четырёх и ста сорока шести байт, и у всех срок жизни один. За роутером не появилось ничего, приложение получило ноль байт.

Новое здесь одно. Игрушка приняла первую часть, в ней нет имени, и разрешила соединение. Игрушку обошли, а соединение всё равно мертво. Журнал чистый, ошибок нет.

Вторая ловушка. В программе есть простая стратегия, которая правит не копию, а сам проходящий пакет; назовём её правкой на месте, в коде это pktmod. Для неё срок жизни один значит, что настоящее приветствие уходит со сроком один. В нашем тесте на клиенте шесть пакетов по двести пятьдесят байт, оригинал и пять повторов, у всех срок жизни один, ни один не прошёл роутер, и без игрушки не прошёл бы. Игрушка стоит до проверки срока жизни, увидела имя и заблокировала. Приложение получило ноль байт.

У самой fake такой беды нет: она отправляет только копию, и условия ложатся только на неё.

Ловушки: IPv6 и неверное значение Ещё две ловушки условий: срок жизни 1 на потоке IPv6 не применяется, потому что для IPv6 аргумент другой, ip6_ttl, подделка доезжает, и приложение получает 680 байт, а срок жизни 256 не помещается в байт, поэтому программа пишет ошибку и пропускает пакет без изменений, подделки нет, игрушка блокирует, сервер получает 0 байт, и условие, которое молчит, отличается от условия с громкой ошибкой только по журналу. ещё две ловушки: не та версия протокола и неверное значение ip_ttl=1 на потоке IPv6условие не той версии молча не применяется hop limit: 64ip_ttl=1 не трогает подделка доехала до сервера приложение 680 B, клиент завис для IPv6 аргумент другой: ip6_ttl ip_ttl=256не помещается в байт: допустимо от 0 до 255 ошибка в журнале, шесть раз пакет пропущен без изменений подделки на проводе нет игрушка заблокировала: сервер 0 B журнал программы при ip_ttl=256 LUA ERROR: ...zapret-lib.lua:964: apply_fooling: ip_ttl and ip6_ttl require valid valuedesync ERROR. passing packet unmodified. проверка значения: lua/zapret-lib.lua, строки 961-966963if not ttl or ttl<0 or ttl>255 then 964error("apply_fooling: ip_ttl and ip6_ttl require valid value") громко: ошибка в значениистратегия для этого пакета отключена,подделка не отправлена,настоящий пакет уходит как есть тихо: пропуск без ошибкизаготовки с таким именем нет,а у вызова стоит флаг optional:blob nosuch not found. skipped применилось ли условие, видно только по журналу или по захвату пакетов

Третью ловушку мы разбирали раньше в этом выпуске: условие не той версии протокола молча не применяется. В нашем тесте срок жизни один на потоке IPv6 оставил предел пересылок шестьдесят четыре, подделка доехала, приложение приняло шестьсот восемьдесят байт, а клиент завис. Для IPv6 аргумент другой, ip6_ttl.

Четвёртая тоже знакома: неверное значение. Срок жизни двести пятьдесят шесть не помещается в байт. В нашем тесте программа написала ошибку, потом написала, что пакет пропускается без изменений. Подделки нет, настоящее приветствие ушло целым, игрушка его заблокировала, сервер получил ноль байт. Ошибка повторилась шесть раз, по разу на каждую отправку приветствия клиентом: оригинал и повторы.

Заметьте разницу. Ошибка в значении отключает стратегию для этого пакета: подделка не отправляется, настоящий пакет идёт как есть. Тихо пропускается только один случай: имя заготовки неизвестно, а у вызова стоит флаг optional; об этом в сцене про заготовки. И проверить, что условие вообще применилось, можно только по журналу или по захвату пакетов.

Проверь себя. Строка fake с заготовкой по умолчанию и сроком жизни минус один. Что будет с подделкой и с настоящим пакетом?

Допустимые значения от нуля до двухсот пятидесяти пяти, так что минус один это ошибка, как с двумястами пятьюдесятью шестью: подделки нет, настоящий пакет уходит целым, игрушка блокирует. Это по коду, живьём минус единицу мы не запускали.

Итак. Условие в multisplit и в pktmod бьёт по настоящим пакетам, условие не для той версии протокола молчит, а неверное значение отключает стратегию, и обычно мы узнаём об этом только по журналу.

Фейк по умолчанию: шестьсот восемьдесят байт на бланке приветствия

Вопрос сцены: что лежит в шестистах восьмидесяти байтах подделки по умолчанию и чем она отличается от настоящего приветствия? Это тот самый образец из первой части, он называется fake_default_tls: блоб, готовый набор байтов с именем, и он всегда есть в программе. Слово «заготовка» в этом выпуске значит то же, что «образец»: блоб, из которого берутся байты подделки.

Игрушка, пример выдуманный. Чтобы подложное письмо не вызвало вопросов, оно должно быть на таком же бланке, с теми же графами. Графы бланка приветствия мы проходили в выпуске «Что видит наблюдатель», а как по ним ходит программа, в выпуске «Где резать: метки»: по длинам. Пройдём так же. Позиции считаем, как в том выпуске: от нуля, то есть байт номер сорок три это сорок четвёртый по счёту.

Первые пять байт, заголовок записи, говорят: дальше шестьсот семьдесят пять. Пять плюс шестьсот семьдесят пять и есть вся подделка, шестьсот восемьдесят. Дальше по длинам до идентификатора сессии: он на байте номер сорок три, длина тридцать два, ровно как у настоящего.

Первое отличие здесь: список наборов шифров. У настоящего приветствия он занимает шесть байт, это три набора. У подделки тридцать четыре байта, семнадцать наборов. Поэтому всё дальше сдвинуто на двадцать восемь байт.

На байте номер сто четырнадцать длина всех расширений: пятьсот шестьдесят четыре. Это метка extlen. Сто шестнадцать плюс пятьсот шестьдесят четыре: шестьсот восемьдесят. Длины сошлись до последнего байта.

Первое расширение, как у настоящего, имя сервера. Его данные начинаются на байте сто двадцать, это метка sniext. Внутри длина списка двадцать, тип имени ноль и длина имени семнадцать. Само имя занимает байты со сто двадцать пятого по сто сорок первый: www.microsoft.com. Это метка host.

Сравним с настоящим приветствием из наших опытов. У него метки восемьдесят шесть, девяносто два и девяносто семь, а имя www.example.com. У подделки сто четырнадцать, сто двадцать и сто двадцать пять: на двадцать восемь больше, и это целиком из-за списка шифров. А вся подделка длиннее настоящего приветствия на четыреста тридцать байт: двадцать восемь из них в шифрах, остальные четыреста два в расширениях. Расширения подделки занимают пятьсот шестьдесят четыре байта, у настоящего приветствия сто шестьдесят два.

Бланк подделки по длинам Бланк подделки по умолчанию на 680 байт пройден по длинам от нуля: 5 + 675 = 680, 4 + 671 = 675, 43 + 1 + 32 = 76, 76 + 2 + 34 = 112, 112 + 2 = 114 и 116 + 564 = 680, метки extlen, sniext и host стоят на 114, 120 и 125 против 86, 92 и 97 у настоящего приветствия в 250 байт, на 28 дальше из-за списка шифров в 34 байта вместо 6, а имя www.microsoft.com лежит на байтах с 125 по 141. бланк подделки по умолчанию: длины сходятся до последнего байта игрушка, пример выдуманный: подложное письмо должно быть на таком же бланке, с теми же графами настоящее приветствие из наших опытов: 250 байт шифры 6 байт, расширения 162 подделка fake_default_tls: 680 байт шифры 34 байта, 17 набороврасширения 564 байта 0..75: те же ходьба по длинам, позиции от нуля 10..4заголовок записи: длина 02 a3 = 6755 + 675 = 680 25..8заголовок сообщения: длина 6714 + 671 = 675 343длина идентификатора сессии = 32, как у настоящего43 + 1 + 32 = 76 476..77длина списка шифров = 34: 17 наборов, у настоящего 6 и три76 + 2 + 34 = 112 5112..113способы сжатия112 + 2 = 114 6114..115длина расширений = 564, метка extlen = 114116 + 564 = 680сошлось до последнего байта 7116..141первое расширение, имя сервера: sniext = 120, host = 125125 + 17 = 142www.microsoft.com на 125..141, дальше со 142 второе расширение три метки на двух лентах: у настоящего и у подделки extlen86→114разница на метке: 28 sniext92→120разница на метке: 28 host97→125разница на метке: 28 28 = 34 − 6: весь сдвиг меток идёт из списка шифров

После имени идут ещё шестнадцать расширений. Самое длинное, двести восемьдесят один байт, названо по типу encrypted client hello. В подделке это просто байты на своём месте, а что они значили бы для настоящего оборудования, мы не знаем.

Сама заготовка всегда одна и та же. Она записана в программе готовым массивом и создаётся раньше, чем программа читает вашу командную строку. В нашем тесте в двух отдельных запусках содержимое совпало до байта. Руководство называет её приветствием Firefox без Kyber, то есть, по общим сведениям, без постквантового ключа; с настоящим захватом Firefox мы её не сверяли.

Проверь себя. Допустим, имя в подделке длиннее на два байта: девятнадцать вместо семнадцати. Сколько байт во всей подделке?

Шестьсот восемьдесят два: поле длины каждого вложенного куска вырастет на два, и вся подделка тоже. В нашем тесте опция, которая подставляет своё имя, дала именно такую подделку; что ещё она умеет, разберёт выпуск «Другие слои».

Расширения подделки и сама заготовка Подделка по умолчанию длиннее настоящего приветствия на 430 байт, это 28 в списке шифров плюс 402 в расширениях, где 564 против 162, её 17 расширений от server_name на байте 116 до encrypted_client_hello на байте 395 в 281 байт лежат в массиве nfq2/params.c, который программа кладёт в блоб fake_default_tls раньше разбора командной строки, а подделка с именем на 2 байта длиннее, 19 вместо 17, весит 682 байта, потому что все шесть полей длины растут на 2. откуда лишние 430 байт и что это за массив подделка 680 байт против настоящего 250: 680 − 250 = 430 расширения: 402 шифры: 28 34 − 6 = 28564 − 162 = 40228 + 402 = 430 расширения подделки: 17 блоков, ширина по длине, всего 564 байта key_share: 107encrypted_client_hello: 281 server_name на байте 116 самый широкий: данные 281 байт начало каждого расширения, байт номер N 116server_name142extended_master_secret146renegotiation_info151supported_groups169ec_point_formats175session_ticket179ALPN197status_request206delegated_credentials220signed_cert_timestamp224key_share335supported_versions344signature_algorithms372psk_modes378record_size_limit384compress_certificate395encrypted_client_hello в подделке это просто байты на своих местах; что они значат для настоящего оборудования, неизвестно сама заготовка: один и тот же готовый массив массив в программеnfq2/params.c:28-87fake_tls_clienthello_default[680]комментарий в коде:SNI - www.microsoft.com создаётся первойnfq2/nfqws.c:1969-1978вызов в nfqws.c:2281, до строкижурнал: size=680 alloc=808808 = 680 + 128 запас в нашем тестедва отдельных запускаSHA-256 подделки:11d33b3d926b…равен SHA-256 массива params.c по руководству это приветствие Firefox без Kyber (docs/manual.en.md:1704)с настоящим захватом Firefox мы его не сверяли проверь себя: имя на 2 байта длиннее, 19 вместо 17 запись675 → 677сообщение671 → 673расширения564 → 566server_name22 → 24список20 → 22имя17 → 19 шесть полей длины растут на 2: 680 + 2 = 682; в опыте с tls_mod получилось 682

Итак. Подделка по умолчанию это один аккуратный бланк приветствия на шестьсот восемьдесят байт: длины сходятся до последнего байта, имя www.microsoft.com лежит на байте сто двадцать пять. Годится ли она против настоящего инспектора, мы не знаем.

Другие заготовки и откуда берутся байты: HTTP, QUIC, файл, запись в строке

Вопрос сцены: какие ещё заготовки есть и как подставить свою?

Игрушка, пример выдуманный. У каждого учреждения свой бланк: подложное письмо должно быть на бланке того, куда оно идёт. Так и здесь: для HTTP нужна заготовка-запрос, для QUIC заготовка-датаграмма.

Вторая заготовка, fake_default_http: запрос GET к корню сайта, с заголовком Host www.iana.org и строкой браузера Firefox, всего двести шестьдесят три байта.

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

Третья заготовка, fake_default_quic: шестьсот двадцать байт, первый байт четыре-ноль в шестнадцатеричной записи, остальные нули. Настоящим начальным пакетом QUIC это не является, а что о нём подумает настоящий инспектор, мы не знаем.

QUIC идёт по UDP, а в UDP нет ни номеров, ни подтверждений. Условия уровня TCP к нему не применяются: код проверяет, есть ли TCP, и без него молчит. В нашем тесте MD5 на QUIC ничего не сделал, подделка дошла до приложения. Без условий, с двумя повторами подделки, сервер получил две по шестьсот двадцать байт и настоящий пакет в тысячу двести: у UDP нет потока, доставляется каждая датаграмма.

Подействовали два условия. Срок жизни один убил обе подделки на роутере, и до сервера дошёл только настоящий пакет. Плохая контрольная сумма: сервер подделку до приложения не донёс. Условия не про TCP, например заголовки расширения IPv6, по коду к UDP применяются; мы их здесь не проверяли, это выпуск «Другие слои».

Заготовки HTTP и QUIC и их опыты Две другие встроенные заготовки: fake_default_http на 263 байта с Host www.iana.org, которая с условием tcp_md5 дала серверу чистые 81 байт настоящего запроса, а без условия сервер принял 263 байта подделки и клиент завис, и fake_default_quic на 620 байт с первым байтом 40 и нулями, где условие MD5 ничего не сделало, срок жизни 1 убил обе подделки на роутере, а плохая контрольная сумма не дошла до приложения сервера. другие встроенные заготовки: запрос HTTP и датаграмма QUIC игрушка, пример выдуманный: письмо должно быть на бланке того учреждения, куда оно идёт GET / HTTP/1.1Host: www.iana.orgUser-Agent: Mozilla/5.0 (Windows NT 10.0; …) Firefox/109.0Accept: text/html,application/xhtml+xml,…Accept-Encoding: gzip, deflate, br(пустая строка) fake_default_http263 байта, строки сокращеныnfq2/params.c:23-26настоящий запрос в опыте:81 байт, порт 80 с условием tcp_md526381оба: номер 1ip id 35162в подделке: опция MD5сервер получил 81 байт, чистоклиент: ответ полученсчётчик сервера TCPMD5Unexpected=1 без условия26381оба: номер 1ip id 2075опций у подделки нет, заголовок как у настоящегосервер принял 263 байта подделкиклиент завис: ответа нет 4 с заготовка QUIC: 620 байт, не в масштабе 40619 байт, все нули nfq2/nfqws.c:1974-1977: массив на 620, memset нулями, первый байт 0x40настоящим начальным пакетом QUIC это не является; что подумает настоящий инспектор, неизвестно QUIC идёт по UDP: что дошло в наших опытах lua/zapret-lib.lua:981if dis.tcp thenтолько поля tcp_*; срок жизни и badsum сюда не входят условиеу клиента на проводеу сервераприложение сервера tcp_md5620 ; 1200620 ; 1200620(40) 1200(c3)MD5 к UDP не применился: подделка дошла repeats=2, без условий620 ; 620 ; 1200620 ; 620 ; 1200620 620 1200у UDP нет потока: доходит каждая датаграмма repeats=2, ip_ttl=1620 ttl1 ; 620 ttl1 ; 120012001200(c3)срок жизни 1 убил обе подделки на роутере badsum620 BAD ; 1200620 BAD ; 12001200(c3)сервер подделку до приложения не донёс заголовки расширения IPv6 по коду к UDP применяются; здесь их не проверяли: это выпуск «Другие слои»

Теперь откуда берутся байты. Второй путь, свой файл: при запуске программа читает его в блоб. В нашем тесте файл с приветствием для iana.org на пятьсот семнадцать байт: подделка ушла в пятьсот семнадцать байт, сервер получил чистое приветствие.

Третий путь, байты прямо в строке: запись, которая начинается с нуля и буквы икс. В нашем тесте десять байт: начало записи TLS и ещё пять произвольных. Игрушка по журналу приняла их за приветствие и разрешила соединение. Четвёртый путь, строка, объявленная в файле Lua: в нашем тесте тринадцать байт, и исход тот же, сервер получил чистые двести пятьдесят байт.

Порядок поиска такой. Сначала запись с нулём и иксом, потом поле таблицы с данными о пакете, потом глобальная переменная Lua, и если нигде нет, то ошибка. Запись с нулём и иксом программа считает существующей всегда, даже с ошибкой в цифрах, поэтому флаг optional на неё не действует. Встроенное имя занять нельзя: если объявить fake_default_tls заново, программа не запустится и напишет, что имя повторяется. В нашем тесте игрушка после этого заблокировала настоящее приветствие.

Теперь про ошибки. Без аргумента blob и с неизвестным именем это ошибка, пакет уходит целым, игрушка блокирует, сервер получил ноль байт. С флагом optional и неизвестным именем программа пишет «блоб не найден, пропускаю» и тоже ничего не отправляет: в нашем тесте тот же исход. Флаг optional прячет ошибку, но блоб не заменяет.

Проверь себя. Блоб записан с нуля и икса и трёх цифр, да ещё стоит флаг optional. Что будет?

Ошибка про неверную шестнадцатеричную строку. Такая запись считается существующей всегда, поэтому optional не сработает, а три цифры нечётны. Это по коду и по прогону функций, с флагом мы не запускали.

Источники блоба и ошибки Блоб для fake берётся из четырёх источников, встроенного, файла в 517 байт, записи 0x в строке на 10 байт и строки Lua на 13 байт, и ищется по порядку 0x, поле desync, глобальная переменная, затем ошибка blob unavailable, при этом повторное имя fake_default_tls не даёт программе запуститься, а ошибки без blob и с неизвестным именем, даже с флагом optional, оставляют серверу 0 байт, потому что игрушка блокирует целое приветствие. откуда fake берёт байты: четыре источника, порядок поиска, ошибки 1. готоваяfake_default_tls, _http, _quicв программе всегда: 680, 263 и 620 байтсоздаётся раньше командной строки 2. файл--blob=iana:@…/tls_clienthello_iana_org.binжурнал: size=517; подделка ушла в 517 байтигрушка пропустила, сервер: 250 чисто 3. запись в строкеblob=0x1603010005aabbccddee10 байт: начало записи TLS и пять произвольныхигрушка пропустила, сервер: 250 чисто 4. строка Luablob=my_fake_from_luaстрока объявлена файлом --lua-init, 13 байтигрушка пропустила, сервер: 250 чисто порядок поиска имениlua/zapret-lib.lua:534-560 0x…запись в строкеlib:543 desync[имя]поле таблицы пакетаlib:549 _G[имя]глобальная строкаlib:552 errorblob unavailablelib:554 запись с 0x программа считает существующей всегда: blob_exist, lib:530 встроенное имя занять нельзя --blob=fake_default_tls:0x1603010005aabbccddeeжурнал: duplicate blob name fake_default_tls, программа не запустилась (nfq2/nfqws.c:1155-1160)игрушка заблокировала настоящее приветствие: сервер получил 0 байт ошибки: во всех трёх опытах игрушка блокирует, сервер получил 0 байт fake:tcp_md5LUA ERROR: zapret-antidpi.lua:455: fake: blob arg requireddesync ERROR, пакет уходит целым blob=nosuchLUA ERROR: zapret-lib.lua:554: blob nosuch unavailabledesync ERROR, пакет уходит целым blob=nosuch:optionalLUA: fake: blob nosuch not found. skippedбез ошибки, но подделки нет optional прячет ошибку, но блоб не заменяет проверь себяblob=0x160 (три цифры) и флаг optional: что будет? ошибка invalid hex string (lib:546): запись с 0x существует всегда (lib:530), optional не срабатываеттак по коду и по прогону функций; живой запуск с optional мы не делали

Итак. Заготовки три: TLS, HTTP и QUIC; на QUIC в нашем тесте подействовали срок жизни и плохая сумма. Свою заготовку можно взять из файла, из записи в строке или из строки Lua.

Большой фейк, повторы и длинное приветствие: куски, счёт и первый кусок

Вопрос сцены: что будет, если заготовка не помещается в один пакет, если попросить повторы, и если настоящее приветствие длиннее пакета?

Игрушка, пример выдуманный. Длинное письмо не лезет в один конверт, его раскладывают по нескольким, и на каждый ставят одну и ту же печать. Так будет и с условиями.

В нашем тесте заготовка на три тысячи байт, загруженная из файла, и условие MD5. Подделка ушла тремя пакетами: тысяча четыреста двадцать восемь байт, ещё столько же и сто сорок четыре. Проверим счёт. Два куска по тысяче четыреста двадцать восемь вместе дают две тысячи восемьсот пятьдесят шесть. Всего было три тысячи. Остаток сто сорок четыре, это третий кусок.

Откуда тысяча четыреста двадцать восемь. Размер сегмента в нашей лаборатории тысяча четыреста шестьдесят: это меньшее из двух чисел, которые стороны назвали друг другу в рукопожатии, как в выпуске «multisplit: резать на части». Из него вычитаются байты опций в заголовке: двенадцать на время и двадцать на MD5 с выравниванием, всего тридцать два. Тысяча четыреста шестьдесят минус тридцать два: тысяча четыреста двадцать восемь.

Номера идут подряд. Первый кусок с единицы. Второй с номера тысяча четыреста двадцать девять: единица плюс длина первого куска. Третий с номера две тысячи восемьсот пятьдесят семь: тысяча четыреста двадцать девять плюс тысяча четыреста двадцать восемь. У трёх пакетов одна и та же случайная печать MD5, потому что она выбирается один раз на вызов. Идентификаторы пакетов идут подряд, а у настоящего приветствия идентификатор первого куска подделки.

Код нарезки знаком по выпуску «multisplit: резать на части»: условия применяются один раз, до нарезки. Без условий, по прогону функций, те же три тысячи байт ложатся иначе: два куска по тысяче четыреста сорок восемь и остаток сто четыре.

Большая заготовка режется на куски Заготовка в 3000 байт из файла с условием MD5 ушла тремя пакетами по 1428, 1428 и 144 байта с номерами 1, 1429 и 2857 и одной подписью MD5 во всех трёх, потому что размер сегмента 1460 минус 32 байта опций даёт 1428, а настоящее приветствие в 250 байт пошло следом с номером 1 и идентификатором первого куска. большая заготовка: куски по размеру сегмента минус опции игрушка, пример выдуманный: длинное письмо идёт в нескольких конвертах с одной печатью заготовка из файла: 3000 байт, условие tcp_md5 14281428144 кусок 1: номер 1ip id 20508 кусок 2: номер 1429ip id 20509 кусок 3: номер 2857ip id 20510 одна подпись MD5(0fd84829d92aba0df7b56ad6f478a6dd) во всех трёх: выбирается один раз на вызов потом настоящее приветствие: номер 1, 250 байт, ip id 20508, заголовок 32 байта откуда 1428 и номера размер сегмента1460меньшее из двух чисел, названных в рукопожатии опции подделки12 + 20 = 32NOP NOP и время, MD5 18 байт и 2 нуля кусок1460 − 32 = 1428 номера1 + 1428 = 1429 1429 + 1428 = 2857 остаток3000 − 2 × 1428 = 144 код нарезкиlua/zapret-lib.lua 1237apply_fooling(desync, discopy, …) 1243local max_data = mss - extra_len 1250while pos <= #payload do 1265apply_ip_id(desync, discopy, …) 1270discopy.tcp.th_seq = u32add(…, len) один вызов: одна подпись, по id на кусок как размер кусков зависит от опций без условий, прогон функций1460 − 12 = 14481448 + 1448 + 104 tcp_md5, в нашем тесте1460 − 32 = 14281428 + 1428 + 144 tcp_md5 и tcp_nop_del, в нашем тесте1460 − 28 = 14321432 + 1432 + 136

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

И наконец настоящее приветствие длиннее пакета. В нашем тесте тысяча четыреста восемьдесят четыре байта: ядро клиента отдаёт их двумя сегментами, а программа задерживает оба, собирает и проигрывает заново. Подробно это выпуск «Сборка и повторный проигрыш», нам важно одно: функция fake вызывается на каждый проигрываемый кусок, но действует только на первом. На втором в журнале запись «не действую на дальнейшие куски».

На проводе сначала подделка, шестьсот восемьдесят байт, потом первый кусок настоящего приветствия, тысяча четыреста сорок восемь, потом второй, тридцать шесть. У подделки только флаг ACK, без PSH: она копирует флаги первого куска, а у того PSH нет. Сервер получил тысячу четыреста восемьдесят четыре чистых байта, кусками тысяча четыреста сорок восемь и тридцать шесть. Подделка по размеру не подгоняется под то, что изображает.

Проверь себя. Заготовка в тысячу пятьсот байт, из условий только срок жизни один. Сколько пакетов и какого размера?

Два: тысяча четыреста сорок восемь и пятьдесят два. Срок жизни опций TCP не добавляет, значит, в заголовке только время, двенадцать байт. Это счёт по коду и по прогону функций, живой запуск мы не делали.

Повторы и проигранное заново приветствие Повторы и длинное приветствие: при repeats=3 одна запись журнала даёт на проводе три побайтно одинаковых подделки по 680 байт с одним id 58426 и одной подписью MD5 и затем настоящие 250 байт, а настоящее приветствие в 1484 байта программа задерживает и проигрывает заново двумя кусками 1448 и 36, при этом подделка в 680 байт с флагом ACK встаёт только перед первым куском, на втором функция пишет, что не действует. повторы и приветствие длиннее пакета в нашем тесте, repeats=3: одна строка в журнале, на проводе четыре пакета rawsend_dissect repeats=3 size=752 680680680250 три побайтно одинаковых: номер 1, ip id 58426, одна подпись MD5(fd4f59cd…)настоящее: id 58426 в нашем тесте: приветствие 1484 байта, два сегмента, задержаны и проиграны заново DELAY desync until reasm is completeпроигрыш: на первом куске fake действует, на втором нет 6801448 подделка, флаг Aномер 1, id 6942, MD5, заголовок 52 кусок 1: номер 1, ip id 6942флаг A, заголовок 32 кусок 2: номер 1449, 36 байтфлаги AP, ip id 6943 у подделки нет PSH: она копирует флагипервого куска, а у него PSH нетсервер: 1484 чистых байта, кусками1448 и 36 на втором куске в журнале: fake: not acting on further replay piecesantidpi.lua:453 replay_first, 469-471; lib:1361-1364подделка по размеру не подгоняется под то, что изображает: 680 против 1448 проверь себя: заготовка 1500 байт, из условий только ip_ttl=1 пакетов два: 1448 и 521500 − 1448 = 52в заголовке только время: 12 байт опцийсрок жизни опций TCP не добавляетсчёт по коду и прогону функций,живой запуск мы не делали

Итак. Большая заготовка режется по размеру сегмента минус опции, условия лежат на каждом куске, повторы дают одинаковые копии, а перед проигранным заново приветствием подделка стоит только перед первым куском.

В коде: функция fake строка за строкой

Мы видели, что fake делает на проводе. Теперь прочитаем код: около двадцати пяти строк в файле стратегий. Из выпуска «Читаем командную строку zapret2» мы помним, что стратегия это функция на Lua, найденная по имени. Она получает два аргумента: контекст, ручку для разговора с программой, и таблицу desync, где лежит всё о текущем пакете и аргументы вашего вызова. Устройство таблицы разбирает выпуск «Lua внутри программы».

Вопрос сцены: какие проверки проходит пакет, прежде чем fake отправит подделку?

Игрушка, пример выдуманный. Охранник на проходной с бланком из четырёх клеток: пропускает, только если отмечены все.

Функция fake строка за строкой Функция fake из lua/zapret-antidpi.lua строки 449-473 по шагам: выключатель встречного направления, ворота из четырёх клеток TCP или UDP, исходящий пакет, известный вид данных и первый проигрываемый кусок, обязательный blob, флаг optional, подстановка заготовки, ветка tls_mod, отправка копии rawsend_payload_segmented и конец функции без return, который движок читает как пропустить оригинал. функция fake строка за строкойlua/zapret-antidpi.lua:449-473, строки сокращены игрушка, пример выдуманный: охранник пропускает, только если отмечены все четыре клетки ✓ TCP или UDP✓ исходящий✓ вид данных известный✓ первый кусок проигрыша 449function fake(ctx, desync)450direction_cutoff_opposite(ctx, desync)452if (desync.dis.tcp or desync.dis.udp)and direction_check(desync)and payload_check(desync) then453if replay_first(desync) then454if not desync.arg.blob then455error("fake: 'blob' arg required")456end457if desync.arg.optional and not blob_exist(…) then458DLOG("fake: blob … not found. skipped")459return460end461local fake_payload = blob(desync, desync.arg.blob)462if desync.reasm_data and desync.arg.tls_mod then463-465… tls_mod_shim(…) … end467if b_debug then DLOG(…) end468rawsend_payload_segmented(desync,fake_payload)469else470DLOG("fake: not acting on further replay pieces")471end472end473end 1 2 3 4 5 6 7 8 9 1выключатель встречногоdir=out по умолчанию: входящиеотключены, журнал: in=1 out=0 2ворота: три клеткиTCP или UDP, исходящий пакет,вид данных известный 3четвёртая клеткапервый проигрываемый кусок;иначе в журнал: не действую 4blob обязателеннет аргумента: ошибка, пакетушёл целым, игрушка блокирует 5флаг optionalнет блоба: запись в журнал ивыход; проверка после blob 6байты заготовкиblob() берёт их по порядкупоиска имени 7ветка для tls_modна TCP данные есть, у UDP нет;сами опции: «Другие слои» 8главная строкакопия, заготовка вместо данных,условия, нарезка, отправка 9end без returnнет результата: пропустить,оригинал идёт следом строка 452 в файле одна, здесь разбита на три для ширины

Первая строка тела. Назовём её выключателем встречного направления, в коде это direction_cutoff_opposite. По умолчанию fake работает только с исходящими пакетами: это задаёт аргумент dir, его значения out, in или any. Поэтому для этого соединения функция сразу отключает свой экземпляр для входящих. В журнале нашего теста это строка «экземпляр отключён для входящих».

Дальше одно условие из трёх клеток. Протокол TCP или UDP. Исходящий пакет. И вид данных известный, то есть не «пусто» и не «неизвестно»: так мы говорили в выпуске «Читаем командную строку zapret2», а в самой программе это тип нагрузки. Как программа его определяет, разбирает выпуск «Тип нагрузки и имя сайта». Чистый ACK и SYN под это не подходят, так что перед ними подделки нет.

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

Следующие три строки: аргумент blob обязателен, без него ошибка. В нашем тесте настоящий пакет ушёл целым, и игрушка заблокировала его.

Потом флаг optional. Если он стоит и блоба с таким именем нет, функция пишет в журнал и выходит без ошибки. Порядок строк важен: проверка обязательного blob идёт раньше, поэтому optional без blob всё равно ошибка.

Дальше функция blob достаёт байты по правилам прошлой сцены. Следующие четыре строки нужны опции tls_mod: она подменяет имя в заготовке. В нашем тесте опция сработала и в потоке HTTP, заготовка TLS стала на два байта длиннее, а для UDP не изменилась. Сами опции разбирает выпуск «Другие слои».

Главная строка, отправка копии с подставленными данными, в коде rawsend_payload_segmented. Она берёт копию разобранного пакета, подставляет заготовку вместо данных, применяет условия к копии и отправляет, нарезая по размеру сегмента, если нужно. Оригинал в таблице при этом не меняется.

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

Проверь себя. Строка fake с заготовкой по умолчанию и параметром dir равным in. Приходит исходящее приветствие. Сколько подделок?

Ни одной: клетка «исходящий пакет» не отмечена, функция даже не дойдёт до блоба. Это прогон функций, живой запуск мы не делали.

Подделка и настоящий пакет на проводе Подделка и настоящее приветствие на проводе клиента совпадают по ip id 52873, адресам, портам, номерам, флагам AP, окну и метке времени и отличаются только опциями, заголовком в 52 против 32 байт и данными в 680 против 250 байт, подделка идёт первой в 110 запусках из 110, потому что fake отдаёт копию ядру внутри вызова и возвращается без результата, а оригинал движок отпускает после, а с аргументом dir=in на исходящем приветствии подделок ни одной. в нашем тесте: подделка и настоящий пакет друг рядом с другом совпадает у обоих ✓ ip id 52873✓ TTL 64✓ адреса и порты✓ номер и ack✓ флаги AP✓ окно 63✓ метка времени✓ обе суммы верны отличается подделканастоящий опции TCPNOP NOP TS MD5NOP NOP TS заголовок TCP52 байта32 байта данные680 байт250 байт SHA-256 данных11d33b3d926b100c2aa4206d почему подделка идёт первой 1fake отдаёт копию ядрупрямо внутри вызова 2функция кончаетсябез результата 3движок отпускает оригинал,когда вернулись все экземпляры так устроен движок: nfq2/desync.c:846-868 110 из 110запусков: подделка перед приветствием100 по IPv4 и 10 по IPv6 в 100 запусках IPv4 у настоящего пакетатот же ip id; в IPv6 такого поля нет проверь себя fake:blob=fake_default_tls:dir=in и исходящее приветствие: сколько подделок? ни одной: клетка «исходящий» не отмечена, функция до блоба даже не доходиттак по прогону функций; живой запуск с dir=in мы не делали

Итак. Fake отключает встречное направление, проходит ворота, берёт заготовку, отправляет копию с условиями и возвращается ни с чем, поэтому настоящий пакет идёт следом.

В коде: apply_fooling по порядку, что происходит ниже и карта автоподбора

Последняя строка fake отправляет копию с условиями. Вопрос сцены: как аргументы строки превращаются в изменения полей копии? Это делает функция, которую можно назвать применялкой условий, в коде apply_fooling: около ста пятидесяти строк, пройдём их по порядку.

Игрушка, пример выдуманный. Закройщик с карточкой заказа. Пункты он выполняет строго по порядку: сначала «спороть старые пуговицы», потом «пришить новые». Поменяй порядок, и новые спорол бы вместе со старыми.

Функция получает три вещи: таблицу desync, разобранную копию пакета и условия. Нет копии, она возьмёт живой пакет. Нет условий, возьмёт аргументы вызова. Все значения приходят текстом, а голое имя без значения означает «да», как в выпуске «Читаем командную строку zapret2».

Порядок зашит в код. Сначала всё для TCP: сдвиг номера последовательности, сдвиг номера подтверждения, какие флаги снять, какие добавить, удаление пустышек NOP, сдвиг метки времени, опция MD5 и перенос метки времени вперёд. Потом заголовки IPv6, потом срок жизни, и последней, если вы её назвали, ваша собственная функция. А badsum в этом списке нет: контрольную сумму портит уже сборка пакета.

Разберём одно условие целиком: tcp_seq. Если значение число, номер в копии складывается с ним по кругу, потому что хранится в четырёх байтах; если нет, ошибка. В нашем тесте минус десять тысяч дал на проводе подделку с номером на десять тысяч раньше настоящего.

Второе, tcp_md5. Если опция уже есть, только запись в журнал. Иначе в список добавляется запись «тип девятнадцать, данные»: ваши шестнадцатеричные цифры или шестнадцать случайных байт. Длина не проверяется. В нашем тесте записанные нами четыре цифры, ноль-ноль-один-один, дали опцию в четыре байта, сервер её не отбросил и принял подделку.

Порядок важен в трёх местах. Удаление пустышек NOP стоит перед MD5: они исчезают ещё до добавления опции, и заголовок получается короче, сорок восемь байт вместо пятидесяти двух. Перенос времени вперёд стоит после MD5, поэтому время встаёт перед всеми. У клиента Linux перед временем стоят две пустышки NOP, и они остаются на месте. А срок жизни и собственная функция последние: она видит уже изменённую копию и может изменить всё.

Что происходит ниже, уже не в этой функции. Как программа разбирает пакет на поля и собирает обратно, показывает выпуск «От байтов к таблице». Нам важно одно: копию собирает функция на Си, пересчитывает длины и суммы и пишет опции из списка, не больше сорока байт всего, с выравниванием нулями. Бывает, что опции не помещаются: две пустышки, три блока SACK, время и MD5 вместе дают пятьдесят шесть байт. SACK, по общим сведениям, опция, которой получатель сообщает о принятых кусках. По прогону функций сборка тогда отказывает, а стратегия не работает.

И последнее, badsum. Когда сумма уже посчитана верно, её меняют маской из случайного числа, которое не бывает нулём, поэтому испорченная сумма не окажется верной случайно. Готовый пакет получает отметку программы. Ядро отдаёт программе пакеты через очередь, и правило берёт только пакеты без отметки, поэтому собственные пакеты программы к ней не возвращаются. Как это устроено, расскажет выпуск «Внутри программы».

Проверь себя. Условия tcp_md5 и tcp_ts_up вместе. В каком порядке опции окажутся в заголовке?

Сначала время, потом две пустышки NOP, потом MD5, а в конце нули выравнивания: перенос стоит после добавления MD5. Это прогон функций, живой запуск мы не делали.

Применялка условий по порядку Функция apply_fooling из lua/zapret-lib.lua строки 928-1079 берёт таблицу desync, копию пакета и условия и применяет их в зашитом порядке: сдвиг номера, сдвиг подтверждения, снятые и добавленные флаги, удаление NOP, метка времени, MD5, перенос времени вверх, заголовки IPv6, срок жизни и своя функция, при этом tcp_seq минус 10000 дал на проводе подделку 680@-10000 при настоящем 250@+0, tcp_md5 пишет запись 13 12 и 16 случайных байт и делает заголовок 52 байта, а запись из четырёх цифр 0011 дала опцию 13 04 00 11 в заголовке 36 байт, которую сервер не отбросил, и от порядка зависит заголовок: 52 байта без tcp_nop_del, 48 с ним и порядок TS NOP NOP MD5 с tcp_ts_up. apply_fooling: условия идут по зашитому порядку и меняют только копию игрушка, пример выдуманный: закройщик идёт по карточке заказа строго по порядку desyncвсё о пакете и аргументынет условий: аргументы, 978 disразобранная копия пакетанет копии: живой пакет, 980 fooling_optionsусловия: имена и значениятекстом; голое имя значит «да» порядок в кодеlua/zapret-lib.lua 1tcp_seqстроки 982-988 2tcp_ackстроки 989-995 3tcp_flags_unsetстроки 996-998 4tcp_flags_setстроки 999-1001 5tcp_nop_delстроки 1002-1008 6tcp_tsстроки 1009-1020 7tcp_md5строки 1021-1027 8tcp_ts_upстроки 1028-1030 9заголовки IPv6строки 1032-1061 10срок жизнистроки 1062-1069 11foolстроки 1071-1078 badsum: нетв этом списке шаг 1tcp_seqth_seq = u32add(dis.tcp.th_seq, fooling_options.tcp_seq) номер хранится в четырёх байтах: X + (−10000) считается по кругу, по модулю 2³²в нашем тесте на проводе: подделка 680@−10000, настоящее 250@+0 шаг 7tcp_md5table.insert(dis.tcp.options, {kind=19, data=prepare_bin(…, brandom(16))}) по умолчанию, 20 байт опции: 131216 случайных байт0000 заголовок 20 + 12 + 20 = 52 байта tcp_md5=0011, четыре цифры: 13040011 длина не проверяется: заголовок 36 байт,сервер не отбросил, приложение: 680 байт мусора порядок важен: заголовок с MD5 обычный: NOP NOP TS MD5 + 2 нуляNNTSMD5052 байта с tcp_nop_del: NOP убраны до MD5TSMD548 байт с tcp_ts_up: время вперёд после MD5TSNNMD5052 байта N: NOP, 0: нуль выравнивания; срок жизни и своя функция идут последними: она видит уже изменённую копию проверь себяtcp_md5 и tcp_ts_up вместе: в каком порядке опции? сначала время, потом два NOP, потом MD5, в конце нули: перенос стоит после MD5по прогону функций; живой запуск этой пары мы не делали

Автоподбор срока жизни мы разбирали раньше в этом выпуске, здесь только карта. Входящий срок жизни записывает движок на Си из первого пакета от сервера. Дальше в библиотеке четыре части: угадыватель начального числа, вычислитель со сдвигом и коридором, разборщик строки «сдвиг, от, до» и поиск числа внутри apply_fooling. Если подбор числа не дал, потому что входящего срока нет или правило знака сказало «пусто», берётся запасной ip_ttl, если он записан; если дал, подбор побеждает ip_ttl. Комбинацию «подбор не справился плюс ip_ttl» живьём мы не запускали, это по коду.

Руководство в разделах про BSD пишет, что правила, отдающие программе из входящих только SYN-ACK, FIN и RST, позволяют работать автоподбору. Есть и кэш по адресу сервера: он может подставить число из прошлого соединения. В коде он есть, а в нашей лаборатории мы его не гоняли.

Что ниже применялки и карта автоподбора Ниже функции apply_fooling функция на Си пересчитывает суммы и пишет опции не больше 40 байт, так что NOP NOP SACK с тремя блоками и время вместе с MD5 дают 56 байт и сборка отказывает, портит сумму маской от 1 до 65535 и ставит отметку 0x40000000, а карта автоподбора срока жизни ведёт от входящего срока 62 через начальное 64 и два перехода к сроку 2 по пяти частям кода desync.c и zapret-lib.lua, причём запасной ip_ttl берётся, если подбор не дал числа. ниже apply_fooling: пересборка, испорченная сумма, отметка пересборка копии на Сиnfq2/lua.c:2171-2248пересчитывает длины и суммы, пишет опции: не больше 40 байт, потом нули до кратного 4 NNSACK, 3 блокавремяMD5предел 40 байт2 + 26 + 10 + 18 = 56 байт56 > 40 прогон функций: reconstruct_dissect: bad tcp, затем Lua: invalid dissect data; стратегия не работает SACK по общим сведениям: получатель сообщает о принятых кусках badsumnfq2/lua.c:2612if (badsum) tcp->th_sum ^= 1 + (random() % 0xFFFF);сумма уже посчитана верно, маска от 1 до 65535, не нуль: испорченная сумма не станет верной случайно отметка программыnfq2/lua.c:2943, 2960в журнале: rawsend_dissect repeats=1 size=752 badsum=0 ifout=c0 fwmark=40000000правило берёт из очереди только пакеты без отметки, поэтому свои пакеты программа заново не получает автоподбор срока жизни: карта, разбор был раньше входящий 62начало 64переходов 2сдвиг 0, коридор 1-20срок 2 в нашем тесте, два роутера: подделка умерла на втором, сервер получил чистые 250 байт 1входящий срокnfq2/desync.c:1332-1343записывает движок из первогопакета от сервера 2угадывательlua/zapret-lib.lua:865-884начальное 255, 128 или 64,переходы: начальное − входящее 3вычислительlua/zapret-lib.lua:886-898сдвиг и коридор;правило знака даёт «пусто» 4разборщик строкиlua/zapret-lib.lua:848-861значение вида сдвиг,от-до 5поиск числаlua/zapret-lib.lua:934-969внутри apply_fooling; не дал:запасной ip_ttl (961-966) только по кодуподбор сказал «пусто» + ip_ttl;кэш по адресу сервера не гоняли:nfq2/desync.c:791-834 в нашем тесте без входящего срока запасной ip_ttl=1 сработал: подделка умерла на первом роутереруководство, разделы про BSD: docs/manual.en.md:499, 541; кэш по адресу сервера мы не гоняли

Итак. Применялка условий идёт по фиксированному порядку и меняет только копию. Ниже функция на Си пересчитывает суммы и пишет опции, и там же появляется испорченная сумма.

Итог выпуска: путь фейка, семь условий, граница знаний и что дальше

Подведём итог выпуска «Фейк и его условия».

Вот путь одной строки. Мы записали fake с заготовкой по умолчанию и условием. Функция отключила встречное направление, прошла четыре клетки, взяла заготовку в шестьсот восемьдесят байт и сделала копию пакета. В копию подставила заготовку, применила условия по порядку, и дальше функция на Си пересчитала суммы и отправила копию. А сама функция вернулась ни с чем, так что настоящий пакет пошёл следом.

Путь одной строки fake Путь одной строки fake:blob=fake_default_tls:tcp_md5 в шесть блоков: ворота и отключение встречного направления, заготовка в 680 байт, копия пакета с условиями по порядку, пересборка с суммами и отметкой на Си, отправка и возврат без результата, после чего на проводе подделка 680@+0 с флагами AP, заголовком 52 и опцией MD5 идёт перед настоящим пакетом 250@+0 с заголовком 32, а у сервера растёт счётчик TCPMD5Unexpected на 1. путь одной строки: от аргументов до провода 1строка и воротаотключить встречноенаправление, четыре клеткиlua/zapret-antidpi.lua:450-453 2заготовкаfake_default_tls:680 байтlua/zapret-antidpi.lua:454-461 3копия и условияв копию заготовка,условия по порядкуlua/zapret-lib.lua:928-1079 4пересборка на Сидлины, суммы, опции,отметка программыnfq2/lua.c:2171-2248 5отправкаrawsend_payload_segmentedlua/zapret-antidpi.lua:468 6нет результатадвижок пропускает,оригинал идёт следомnfq2/desync.c:846-868 пример: строка и что получилось на проводе fake:blob=fake_default_tls:tcp_md5 подделка680@+0 AP, заголовок 52, MD5 настоящий пакет250@+0 AP, заголовок 32 у сервераTCPMD5Unexpected растёт на 1 в нашем тесте: сервер получил чистые 250 байт, игрушка пропустила подделку как первый пакетподделка идёт первой, настоящий пакет следом, ip id у них одинаковый

Семь условий, которые мы разобрали. Первое, срок жизни: подделка умирает на роутере, нужное число зависит от пути, а автоподбор считает его по первому пакету от сервера и может промолчать. Второе, опция MD5: отбрасывает получатель, видит отсеивающий MD5. Третье, плохая контрольная сумма: отбрасывает получатель, видит проверяющий сумму. Четвёртое, номер: сдвиг назад не меньше длины подделки.

Пятое, номер подтверждения: отбрасывается, пока сервер сам ничего не отправил; игрушки для него у нас нет. Шестое, снятый ACK: отбрасывается молча, а снять один PSH мало. Седьмое, старая метка времени: не меньше чем на две единицы, метка из будущего портит соединение, а при выключенных метках условие не работает.

Три предупреждения. В multisplit и в pktmod условия бьют по настоящим пакетам. Ошибка в значении отключает стратегию, а оригинал уходит целым. А на QUIC из проверенного подействовали только срок жизни и плохая сумма; условия TCP код молча не применяет.

Теперь о том, что мы показали. Код, который читали строка за строкой, и провод: в нашем тесте, на одном получателе, Linux шесть точка восемнадцать, и против игрушечных инспекторов проекта. Пять условий из семи оставляли след в счётчиках сервера; срок жизни до сервера не доходил, а снятый ACK сервер сбрасывал молча. И игрушка, видящая причину, на такую подделку не поддавалась.

И о том, чего мы не знаем. Про настоящий ТСПУ мы не знаем ничего. Мы не проверяли Windows, BSD, macOS, встроенные системы и промежуточные устройства перед сервером. Не проверяли несимметричные пути для автоподбора и память о сроке жизни сервера между соединениями. Плохое подтверждение пробовали только с пятью тысячами уже отправленных сервером байт. Не разбирали, что делают опции tls_mod и заголовки расширения IPv6: это выпуск «Другие слои». И роутер, который не отвечает или переписывает срок жизни, мы тоже не пробовали.

Что дальше. В этом выпуске подделка стояла одна, перед целым приветствием. В выпуске «Резать и подделывать вместе» подделки встают между настоящими кусками разрезанного приветствия: функции fakedsplit, fakeddisorder и hostfakesplit. Мы помним ловушку из этого выпуска: условия падали на настоящие части. По коду этих функций настоящим частям достаётся из условий только перенос метки времени вперёд. Как это устроено и что получается на проводе, разберём там.

Семь условий и граница знаний Итог выпуска одной таблицей и границей знаний: семь условий подделки с местом, где она умирает, следом у получателя, игрушкой, которая её отсеивает, и оговоркой, три ловушки про multisplit и pktmod, ошибку в значении и QUIC, то, что показали код и провод с одним получателем Linux 6.18 и игрушечными инспекторами, серый список непроверенного вроде настоящего ТСПУ, других систем и несимметричных путей, и мост к выпуску про резку вместе с подделкой, где настоящим частям из условий достаётся только tcp_ts_up. что помнить: семь условий условиеумираетследигрушкаоговорка срок жизни (TTL)роутерICMP от роутерани одна из четырёхчисло зависит от пути,автоподбор может промолчать tcp_md5серверTCPMD5Unexpectedmd5 отсеиваетнужны 16 байт; при другойдлине принят (tcp_md5=0011) badsumсерверInCsumErrorscsum отсеиваетoffload не помешалв нашем тесте tcp_seqсерверTCPDSACKOldSentseq отсеиваетсдвиг не меньше длиныподделки (680) tcp_ackсерверTCPChallengeACKигрушки нетсервер молчит или сдвиг большеотправленного (у нас 5000) tcp_flags_unset=ackсервермолча, счётчика нетack отсеиваетснять один PSHне условие tcp_tsсерверPAWSEstabseq не отсеиваетминус 2 и больше; будущеепортит; без меток не работает у срока жизни и снятого ACK следа в счётчиках сервера нет три ловушки multisplit и pktmod:условия бьют понастоящим пакетам ошибка в значении:стратегия отключена,оригинал уходит целым QUIC: подействовали срокжизни и badsum; условияTCP код молча не применяет что мы показали, и чего не знаем что показала лаборатория• код, прочитанный строка за строкой• провод: один получатель, Linux 6.18• игрушечные инспекторы проекта• пять условий из семи: след в счётчиках сервера• срок жизни до сервера не дошёл• снятый ACK сервер сбросил молча• игрушка, видящая причину, подделку отсеивает не проверяли• настоящий ТСПУ и DPI: ничего не известно• Windows, BSD, macOS, встроенные системы• промежуточные устройства перед сервером• несимметричные пути для автоподбора• память о сроке жизни между соединениями• плохой ACK при отправленном больше окна(пробовали 5000 байт)• опции tls_mod, заголовки IPv6: «Другие слои»• роутер без ICMP или с переписыванием срока дальше: подделка между настоящими кусками кусок 104 байтаподделкакусок 146 байт следующий выпуск: «Резать и подделывать вместе»fooling = {tcp_ts_up = desync.arg.tcp_ts_up}antidpi.lua:727, 850, 955у этих функций настоящим частям из условий достаётся только tcp_ts_up

Итак. Fake это один подложный пакет с условием: он должен выглядеть настоящим для инспектора, умереть до сервера или быть отброшенным им и ничего не испортить. Условий семь, у каждого свой след, кроме двух, и своя оговорка. И всё это мы проверили на нашей игрушке и на нашем Linux, а не на настоящем оборудовании.

См. также

  • Перемешать: disorder — предыдущее видео серии
  • fake — справочная статья о функции
  • ts-and-fooling — подмена полей пакета и условия
  • blob — как объявлять данные для подделки

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

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