477 KiB
Содержание
- Содержание
- Введение
- Структура проекта
- Схема обработки трафика
- Перехват трафика из ядра ОС
- nfqws2
- C интерфейс nfqws2
- Базовые константы
- Стандартные блобы
- Переменные окружения
- C функции
- Логгинг
- Конвертация IP
- Битовые операции
- Операции с беззнаковыми числами
- Целочисленное деление
- Генерация случайных данных
- Парсинг
- Криптография
- Компрессия
- Системные функции
- Опции по работе с пакетами
- Диссекция и реконструкция
- conntrack
- Получение ip адресов
- Прием и отсылка пакетов
- Работа с пейлоадами
- Управление выполнением инстансов
- Библиотека базовых функций zapret-lib.lua
- Базовые desync функции
- Служебные функции
- Работа со строками
- Обслуживание raw string
- Обслуживание tcp sequence numbers
- Обслуживание позиций
- Диссекция
- Работа с элементами L3 и L4 протоколов
- Работа с именами хостов
- Операции с именами файлов и путями
- Чтение и запись файлов
- Компрессия данных
- autottl
- Операции с диссектами
- IP адреса и интерфейсы
- Отсылка
- Стандартные фильтры direction и payload
- Работа с многопакетными пейлоадами
- Оркестрация
- Библиотека программ атаки на DPI zapret-antidpi.lua
- Библиотека программ автоматизации и оркестрации zapret-auto.lua
- Вспомогательные программы
- blockcheck2
- Скрипты запуска
- Другие прошивки
- Особенности Windows
Введение
zapret2 является пакетным манипулятором, основная задача которого - совершение различных автономных атак на DPI в реальном времени с целью преодоления ограничений (блокировок) ресурсов или сетевых протоколов. Однако, этим возможности zapret2 не ограничиваются. Архитектура позволяет выполнять и другие виды пакетных манипуляций. Например, двусторонняя (клиент+сервер) обфускация протоколов с целью их сокрытия от DPI. Возможны и иные применения.
Структура проекта
Главный компонент zapret2 - программа nfqws2 (dvtws2 на BSD, winws2 на Windows), написанная на C, которая и является пакетным манипулятором. Содержит функции по перехвату пакетов, базовой фильтрации, распознаванию основных протоколов и пейлоадов, поддержке хост и IP листов, автоматических хостлистов с распознаванием блокировок, систему множественных профилей (стратегий), возможности по отсылке raw пакетов и другие сервисные функции. Однако, там нет никаких возможностей собственно для воздействия на трафик. Это вынесено в код на языке Lua, вызываемый из nfqws2.
Поэтому следующая по важности часть проекта - Lua код. В базовый комплект входит библиотека функций-хелперов zapret-lib.lua, библиотека программ автономных атак на DPI zapret-antidpi.lua, библиотека функций принятия динамических решений (оркестрации) zapret-auto.lua.
Дополнительно присутствует набор тестов C функций zapret-tests.lua, средства обфускации трафика zapret-obfs.lua и средство записи дампа пакетов в cap файлы zapret-pcap.lua.
Проект может работать с LuaJIT-2.1+ или с PUC Lua 5.3+. Более старые версии могут иметь несовместимости, не тестируются и не поддерживаются.
Функции перенаправления трафика из ядра в Linux возложены на iptables и nftables, в FreeBSD - на ipfw, в OpenBSD - на pf, в Windows - встроены в сам процесс winws2 посредством драйвера WinDivert. Схема перехвата трафика из ядра, nfqws2 и Lua код составляют минимально рабочее ядро проекта. Все остальное является дополнительным, второстепенным и опциональным.
Из второстепенных компонент - скрипты запуска под Linux - init.d, common, ipset, install_easy.sh, uninstall_easy.sh и средство автоматизации тестирования стратегий blockcheck2.
Цель скриптов запуска - согласовать процесс поднятия таблиц и запуск инстансов nfqws2, учесть особенности интеграции в различные дистрибутивы (openwrt, systemd, openrc).
Дополнительная функция - обеспечить поддержку и согласованное обновление различных листов и загрузку IP листов в пространство ядра - ipset.
Все это можно сделать при желании и собственными средствами, если так удобнее или функционал скриптов запуска не подходит.
Скрипты запуска выносят все настройки в файл config, лежащий в корне проекта. Этот конфиг относится только к ним, nfqws2 ничего о нем не знает.
Для обработки листов предусмотрены 2 программы, написанные на C. mdig - многопоточный ресолвер хостлистов неограниченного объема. ip2net - программа для группировки отдельных IP адресов в подсети с целью сокращения их объема. Эти программы используются в скриптах запуска и в blockcheck2.
Скрипты запуска и инсталлятор поддерживают установку на любые классические дистрибутивы Linux с systemd или openrc, из прошивок - на openwrt. Если система не удовлетворяет указанным требованиям - возможна самостоятельная "доприкрутка" к системе.
macOS не поддерживается по причине отсутствия подходящего средства перехвата и управления пакетами. Стандартное для BSD средство ipdivert было убрано из ядра производителем.
Схема обработки трафика
Сети работают с IP пакетами. Поэтому единицей обработки являются именно они. Приемом и отправкой пакетов занимается сетевая подсистема в ядре ОС. nfqws2 работает не в ядре (kernel mode), а является процессом пространства пользователя (user mode). Поэтому первая часть процесса обработки состоит в передаче пакетов из ядра ОС в процесс nfqws2. Все 4 средства перехвата обладают некоторыми возможностями фильтрации пакетов. Максимальные возможности - в Linux. Чем больше на этом этапе будет отсечено ненужного трафика, тем меньше будет нагрузка на процессор, поскольку передача пакетов из ядра в user mode и обратно сопряжена со значительными накладными расходами.
Пакет пришел в nfqws2. Первое, что он делает, это разбирает его по уровням OSI модели - выделяет ip , ipv6, tcp, udp заголовки и поле данных. Это называется диссекцией. Результатом диссекции является диссект - представление пакета в виде структур, которые можно адресовать по полям.
Далее задействуется встроенная в nfqws2 подсистема conntrack - система отслеживания потоков поверх отдельно взятых пакетов. Ищется уже имеющаяся запись о потоке на основании данных L3/L4 пакета. Если ее нет - создается. Старые записи, по которым давно нет активности, удаляются. conntrack отслеживает логическое направление пакетов в потоке (входящее/исходящее), ведет подсчет количества прошедших пакетов и байт в обе стороны, следит за sequence numbers для tcp. Он же используется в случае необходимости для сборки сообщений, передаваемых несколькими пакетами.
Сигнатурно определяется тип пейлоада - содержимого отдельно взятого пакета или группы пакетов. На основании типа пейлоада определяется тип протокола всего потока, который сохраняется за потоком до конца его существования. В рамках одного потока могут проходить разные типы пейлоадов. Например, протокол потока xmpp обычно несет несколько видов специфических для xmpp сообщений и сообщения, связанные с tls. Тип протокола потока xmpp остается, но последующие пакеты получают различные типы пейлоада - как известные, так и неизвестные. Неизвестные пейлоады определяются как тип "unknown".
Если для конкретного пейлоада и типа протокола потока выясняется необходимость реконструкции сообщения из нескольких пакетов, nfqws2 начинает их накапливать в связи с записью в conntrack и запрещает их немедленную отправку. После приема всех пакетов сообщения происходит реконструкция и при необходимости дешифровка составного пейлоада. Дальнейшие решения принимаются уже на базе полностью собранного пейлоада - reasm или результата сборки и дешифровки - decrypt.
Когда необходимая информация о пейлоаде получена, наступает очередь системы классификации по профилям. Профили содержат систему фильтров и команды действия внутри профиля. Профили фильтруются по L3 - версия IP протокола, номер IP протокола, ipset-ы - списки IP адресов, L4 - порты tcp или udp, type/code для icmp, L6/L7 - тип протокола потока, списки доменов (хостлисты). Профили сканируются строго в порядке от первого к последнему. При первом совпадении условий фильтра профиля выбирается этот профиль, а сканирование прекращается. Если ни одно из условий не выполнено, выбирается профиль по умолчанию, в котором нет никаких действий.
Все дальнейшие действия выполняются уже в рамках выбранного профиля. Выбранный профиль кэшируется в записи conntrack, поэтому для каждого пакета поиск заново не выполняется. Повторный поиск выполняется в случае изменения исходных данных - при обнаружении L7 протокола и при обнаружении имени хоста. В последних случаях производится повторный поиск и при необходимости переключение профиля. Таких переключений может быть за время существования потока до двух, поскольку есть лишь 2 изменяемых параметра.
Профиль выбран. Из чего состоит его содержание, отвечающее за действия ?
За действия отвечают Lua функции. В профиле их может быть произвольное количество.
Каждый вызов Lua функции из профиля называется инстансом. Функция может быть одна, вызовов несколько - с разными параметрами.
Поэтому и применяется понятие инстанса - экземпляра вызываемой функции, который идентифицируется номером профиля и порядковым номером вызова внутри профиля.
Инстансы вызываются через параметры --lua-desync. Каждый инстанс получает набор произвольных параметров, задаваемых в --lua-desync.
Порядок вызовов имеет принципиальное значение для логики стратегии и выполняется строго в порядке задания параметров --lua-desync.
Присутствуют и внутрипрофильные фильтры. Их 3 типа - фильтр --payload - список принимаемых инстансом пейлоадов, и 2 диапазонных фильтра --in-range и --out-range,
позволяющих задать диапазон позиций внутри потока, который интересен для инстанса. Внутрипрофильные фильтры после их определения действуют на все последующие инстансы
до их переопределения. Главный смысл наличия внутрипрофильных фильтров - сократить число относительно медленных вызовов Lua , принимая максимум решений на стороне C кода.
Пакет пришел в Lua инстанс. Функция имеет 2 параметра - ctx и desync. ctx - это контекст для связи с некоторыми функциями на стороне C кода.
desync - таблица, содержащая множество параметров обрабатываемого пакета. Прежде всего это диссект - подтаблица dis.
Информация из записи conntrack - подтаблица track. Еще целый ряд параметров, который можно увидеть, выполнив var_debug(desync) или просто вызвав готовый инстанс pktdebug.
Если идет перепроигрывание задержанных пакетов (replay), Lua инстанс получает информацию о номере части, количестве частей исходного сообщения, позиции текущей части, reasm или decrypt при наличии.
Lua код может использовать глобальное пространство переменных для хранения данных, не относящихся к конкретному обрабатываемому пакету. Ему доступна таблица desync.track.lua_state, в которой он может хранить любую информацию, связанную с записью conntrack. При каждом новом пакете потока в Lua выдается одна и та же таблица. Таблицу desync можно использовать для генерации и хранения временных данных, актуальных в цепочке обработки текущего пакета. Следующие Lua инстансы получают ту же таблицу desync и тем самым могут принимать данные от предыдущих инстансов.
Lua инстанс может создавать копии текущего диссекта, вносить в них изменения, генерировать собственные диссекты, отправлять их через вызовы C кода. Итогом работы каждого инстанса является вердикт - VERDICT_PASS - не делать ничего с текущим диссектом, VERDICT_MODIFY - в конце всей цепочки отослать модифицированное содержимое диссекта, VERDICT_DROP - дропнуть текущий диссект. Вердикты всех инстансов агрегируются - MODIFY замещает PASS, а DROP замещает и PASS, и MODIFY.
Lua инстанс может сам себя отключить от получения дальнейших пакетов потока по направлению in/out - это называется instance cutoff. Может отключить направление in/out текущего потока от всей Lua обработки - lua cutoff. Может запросить отмену всей дальнейшей цепочки вызовов Lua инстансов по текущему диссекту. Инстанс, принимающий такое решение, берет на себя функцию координации дальнейших действий. Такой инстанс называется оркестратором. Он получает от C кода план дальнейшего выполнения со всеми фильтрами профиля и параметрами вызова всех оставшихся инстансов и сам принимает решения когда и при каких условиях их вызывать или не вызывать, менять их параметры. Так реализуются динамические сценарии без модификации основных составляющих кода стратегии. Например, определение блокировки ресурса и смена стратегии, если предыдущая не сработала.
Если все инстансы текущего профиля вошли в состояние cutoff по текущему потоку, либо текущая позиция потока находится за верхней границей range фильтров, значит по этому потоку больше не будет Lua вызовов. C код помечает такие потоки специальным признаком "lua cutoff", который проверяется максимально быстро без вызовов кода Lua. Тем самым экономятся ресурсы процессора.
После выполнения всей цепочки инстансов профиля C код получает итоговый вердикт - что делать с текущим диссектом. Отправить как есть, отправить модифицированный вариант или дропнуть.
В конце nfqws2 переходит к ожиданию следующего пакета, и цикл повторяется вновь.
Перехват трафика из ядра ОС
Перехват трафика в ядре Linux
Осуществляется при помощи iptables или nftables с использованием механизма очередей NFQUEUE. nftables - предпочтительны, потому что позволяют работать с трафиком после NAT, а iptables - нет. Это важно при обработке проходящего (forwarded) трафика. На iptables перехват после NAT невозможен, поэтому некоторые воздействия, ломающие NAT, на iptables на проходящем трафике нереализуемы. У nftables есть один важный недостаток - чрезмерное требование к памяти при загрузке больших set-ов. Например, чтобы загрузить 100K IP адресов, требуется от 256-320 Mb, что для роутеров часто оказывается за пределом возможностей. ipset от iptables такое может провернуть даже на 64 Mb RAM.
Если стоит выбор между iptables или nftables, то однозначно выбирайте nftables. Поддержка nftables в скриптах запуска сделана более качественно, а сама технология nftables гораздо более уживчива к "соседям" - правилам разных программ, поскольку использует отдельные таблицы. В iptables все намешано в кучу, и одни программы , задающие iptables, могут попортить правила других программ. iptables можно рассматривать как legacy вариант для совместимости , когда иных вариантов нет. В современном дистрибутиве Linux точно не стоит выбирать iptables. Но если Linux более старый, ядро старее 5.15 или nft старее 1.0.1, а возможности обновить их нет, тогда лучше iptables. Со старыми ядрами и старым nft будут проблемы.
Приведенные далее тестовые примеры предназначены для своей системы запуска или запуска вручную. Скрипты запуска zapret сами генерируют необходимые правила, никаких iptables/nftables самому писать не нужно.
Перехват трафика с помощью nftables
Тестовая таблица для POSTNAT схемы. Обеспечивает перехват первых входящих и исходящих пакетов по потоку после NAT, если таковой присутствует. Из-за NAT IP адреса клиентов теряются, замещаясь IP wan интерфейса. Количество первых пакетов регулируется согласно вашей стратегии. Лишний перехват - дополнительная нагрузка на CPU. Перехват RST и FIN желателен для максимально корректной работы conntrack.
Фильтр по mark необходим для предотвращения кольца. Без этого возможны зависания и неправильная работа.
notrack нужен, чтобы NAT не ломал техники, которые не совместимы с NAT. Генерируемые nfqws2 пакеты не должны проходить проверки на валидность с точки зрения NAT и дропаться стандартными правилами таблиц. Подстановка IP адресов NAT не требуется, поскольку попадающий на nfqws2 пакет уже прошел NAT и имеет корректные адрес и порт источника для wan.
IFACE_WAN=wan
MAX_PKT_IN=15
MAX_PKT_OUT=15
FWMARK=0x40000000
PORTS_TCP=80,443
PORTS_UDP=443
QNUM=200
nft create table inet ztest
nft add chain inet ztest postnat "{type filter hook postrouting priority srcnat+1;}"
nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and $FWMARK == 0 udp dport "{$PORTS_UDP}" ct original packets 1-$MAX_PKT_OUT queue num $QNUM bypass
nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and $FWMARK == 0 tcp dport "{$PORTS_TCP}" ct original packets 1-$MAX_PKT_OUT queue num $QNUM bypass
nft add rule inet ztest postnat oifname $IFACE_WAN meta mark and $FWMARK == 0 tcp dport "{$PORTS_TCP}" tcp flags fin,rst queue num $QNUM bypass
nft add chain inet ztest pre "{type filter hook prerouting priority filter;}"
nft add rule inet ztest pre iifname $IFACE_WAN udp sport "{$PORTS_UDP}" ct reply packets 1-$MAX_PKT_IN queue num $QNUM bypass
nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{$PORTS_TCP}" ct reply packets 1-$MAX_PKT_IN queue num $QNUM bypass
nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{$PORTS_TCP}" "tcp flags & (syn | ack) == (syn | ack)" queue num $QNUM bypass
nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{$PORTS_TCP}" tcp flags fin,rst queue num $QNUM bypass
nft add chain inet ztest predefrag "{type filter hook output priority -401;}"
nft add rule inet ztest predefrag "mark & $FWMARK != 0x00000000 notrack"
Удаление тестовой таблицы :
nft delete table inet ztest
Перехват трафика с помощью iptables
Caution
Начиная с ядер Linux 6.17 присутствует параметр конфигурации ядра CONFIG_NETFILTER_XTABLES_LEGACY, который по умолчанию в дистрибутиве может быть "not set". Отсутствие этой настройки выключает iptables-legacy. Это часть процесса депрекации iptables. Тем не менее iptables-nft будут работать, поскольку используют backend nftables.
Тестовые правила для PRENAT схемы. Обеспечивают перехват первых входящих и исходящих пакетов по потоку до NAT, если таковой присутствует. Адреса и порты источника внутренней сети сохраняются. Атаки на проходящий трафик, ломающие NAT, невозможны, но возможны с самой системы.
IFACE_WAN=wan
MAX_PKT_IN=15
MAX_PKT_OUT=15
FWMARK=0x40000000
PORTS_TCP=80,443
PORTS_UDP=443
QNUM=200
JNFQ="-j NFQUEUE --queue-num $QNUM --queue-bypass"
CHECKMARK="-m mark ! --mark $FWMARK/$FWMARK"
CB_ORIG="-m connbytes --connbytes-dir=original --connbytes-mode=packets"
CB_REPLY="-m connbytes --connbytes-dir=reply --connbytes-mode=packets"
for tables in iptables ip6tables; do
$tables -t mangle -N ztest_post 2>/dev/null
$tables -t mangle -F ztest_post
$tables -t mangle -C POSTROUTING -j ztest_post 2>/dev/null || $tables -t mangle -A POSTROUTING -j ztest_post
$tables -t mangle -N ztest_pre 2>/dev/null
$tables -t mangle -F ztest_pre
$tables -t mangle -C PREROUTING -j ztest_pre 2>/dev/null || $tables -t mangle -A PREROUTING -j ztest_pre
$tables -t mangle -I ztest_post -o $IFACE_WAN $CHECKMARK -p udp -m multiport --dports $PORTS_UDP $CB_ORIG --connbytes 1:$MAX_PKT_OUT $JNFQ
$tables -t mangle -I ztest_post -o $IFACE_WAN $CHECKMARK -p tcp -m multiport --dports $PORTS_TCP $CB_ORIG --connbytes 1:$MAX_PKT_OUT $JNFQ
$tables -t mangle -I ztest_post -o $IFACE_WAN $CHECKMARK -p tcp -m multiport --dports $PORTS_TCP --tcp-flags fin fin $JNFQ
$tables -t mangle -I ztest_post -o $IFACE_WAN $CHECKMARK -p tcp -m multiport --dports $PORTS_TCP --tcp-flags rst rst $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p udp -m multiport --sports $PORTS_UDP $CB_REPLY --connbytes 1:$MAX_PKT_IN $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p tcp -m multiport --sports $PORTS_TCP $CB_REPLY --connbytes 1:$MAX_PKT_IN $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p tcp -m multiport --sports $PORTS_TCP --tcp-flags syn,ack syn,ack $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p tcp -m multiport --sports $PORTS_TCP --tcp-flags fin fin $JNFQ
$tables -t mangle -I ztest_pre -i $IFACE_WAN -p tcp -m multiport --sports $PORTS_TCP --tcp-flags rst rst $JNFQ
done
Удаление тестовых правил zapret :
for tables in iptables ip6tables; do
$tables -t mangle -D POSTROUTING -j ztest_post
$tables -t mangle -D PREROUTING -j ztest_pre
$tables -t mangle -F ztest_post
$tables -t mangle -X ztest_post
$tables -t mangle -F ztest_pre
$tables -t mangle -X ztest_pre
done
Удаление всех правил из таблицы mangle, включая и иные правила :
iptables -F -t mangle
ip6tables -F -t mangle
Перехват трафика в ядре FreeBSD
Основная боль при перехвате трафика на системах, отличных от Linux, - невозможность перехватить первые пакеты потока. Можно перехватить только весь поток целиком по направлению. В BSD с этим дела хуже всего - нет даже возможностей фильтрации raw payload, то есть по содержимому пакета. Поэтому первый набор правил - это перехват всех исходящих по портам и перехват только SYN+ACK,FIN,RST для TCP, чтобы без нагрузки на процессор можно было задействовать режим autottl и максимально корректно работал conntrack. Однако, в таком варианте не будет работать ничего, что требует иного входящего трафика.
RULE=100
IFACE_WAN=vmx0
PORTS_TCP=80,443
PORTS_UDP=443
PORT_DIVERT=989
ipfw delete $RULE
ipfw add $RULE divert $PORT_DIVERT tcp from any to any $PORTS_TCP out not diverted xmit $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT udp from any to any $PORTS_UDP out not diverted xmit $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT tcp from any $PORTS_TCP to any tcpflags syn,ack in not diverted recv $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT tcp from any $PORTS_TCP to any tcpflags fin in not diverted recv $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT tcp from any $PORTS_TCP to any tcpflags rst in not diverted recv $IFACE_WAN
Вариант с перехватом потока в обе стороны. Особо сильно загружает процессор. Все скачиваемые гигабайты пойдут через dvtws2. Из них обычно нужно всего 1-2 пакета, все остальное - впустую расходует CPU, но средства ipfw не предоставляют иных возможностей.
RULE=100
IFACE_WAN=vmx0
PORTS_TCP=80,443
PORTS_UDP=443
PORT_DIVERT=989
ipfw delete $RULE
ipfw add $RULE divert $PORT_DIVERT tcp from any to any $PORTS_TCP out not diverted xmit $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT udp from any to any $PORTS_UDP out not diverted xmit $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT tcp from any $PORTS_TCP to any in not diverted recv $IFACE_WAN
ipfw add $RULE divert $PORT_DIVERT udp from any $PORTS_UDP to any in not diverted recv $IFACE_WAN
Теоретически возможен перехват через pf divert-to, но на практике механизм предотвращения зацикливания сломан, поэтому использовать pf нереально. На pfsense и opnsense требуются дополнительные меры для задействования pf и ipfw одновременно. Часто с этим бывают проблемы, конфликты и глюки.
Перехват трафика в ядре OpenBSD
В OpenBSD есть только pf. С механизмом предотвращения зацикливания divert все в порядке.
Первый вариант - перехват всех исходящих по портам и перехват только SYN+ACK,FIN,RST для TCP, чтобы без нагрузки на процессор можно было задействовать режим autottl и максимально корректно работал conntrack. Однако, в таком варианте не будет работать ничего, что требует иного входящего трафика.
pf требует файлов с правилами. Вы пишите pf файл (обычно используется /etc/pf.conf), потом его применяете через
pfctl -f /etc/pf.conf. pfctl -e включает pf, pfctl -d - выключает.
Возможно использование якорей (anchors) с отдельными файлами правил, читайте документацию по pf.
Трюки с no state нужны, чтобы предотвратить автоматический перехват и входящих пакетов по потоку.
IFACE_WAN = "em0"
PORTS_TCP = "80,443"
PORTS_UDP = "443"
PORT_DIVERT = "989"
pass in quick on $IFACE_WAN proto tcp from port { $PORTS_TCP } flags SA/SA divert-packet port $PORT_DIVERT no state
pass in quick on $IFACE_WAN proto tcp from port { $PORTS_TCP } flags R/R divert-packet port $PORT_DIVERT no state
pass in quick on $IFACE_WAN proto tcp from port { $PORTS_TCP } flags F/F divert-packet port $PORT_DIVERT no state
pass in quick on $IFACE_WAN proto tcp from port { $PORTS_TCP } no state
pass out quick on $IFACE_WAN proto tcp to port { $PORTS_TCP } divert-packet port $PORT_DIVERT no state
pass out quick on $IFACE_WAN proto udp to port { $PORTS_UDP } divert-packet port $PORT_DIVERT no state
Вариант с перехватом потока в обе стороны. Особо сильно загружает процессор. Все скачиваемые гигабайты пойдут через dvtws2. Из них обычно нужно всего 1-2 пакета, все остальное - впустую расходует CPU, но средства pf не предоставляют иных возможностей.
Перехват входящих по тем же портам обеспечивается автоматически за счет state.
IFACE_WAN = "em0"
PORTS_TCP = "80,443"
PORTS_UDP = "443"
PORT_DIVERT = "989"
pass out quick on $IFACE_WAN proto tcp to port { $PORTS_TCP } divert-packet port $PORT_DIVERT
pass out quick on $IFACE_WAN proto udp to port { $PORTS_UDP } divert-packet port $PORT_DIVERT
Caution
В FreeBSD другая версия pf и немного другой синтаксис. Однако, фактически pf в FreeBSD сломан, поскольку не работает предотвращение зацикливания. В macOS хотя и используется pf, ipdivert из ядра убран, правила работать не будут.
Перехват трафика в ядре Windows
В Windows нет встроенных средств для перехвата трафика. Используется стороннее решение - драйвер WinDivert. Управление интегрируется в сам процесс winws2.
WinDivert принимает текстовые фильтры, похожие на фильтры wireshark и tcpdump. В них есть возможности фильтрации по ip (без ipset), портам и raw пейлоадам. Нет побитовых логических операций и сдвигов. Нет отслеживания потоков и ограничения по первым пакетам.
Драйвер WinDivert больше не разрабатывается, однако имеются подписанные варианты драйвера, совместимые со всеми современными Windows, но только для архитектуры x86_64. На arm64 есть неподписанный драйвер, требующий тестового режима подписи драйверов. При использовании winws2 на Windows 11 arm64 приходится пользоваться x86_64 версией, поскольку winws2 написан под cygwin, а его нет для arm. Драйвер .sys при этом заменяется на неподписанную arm64 версию. Запуск на Windows 10 arm64 теоретически возможен, но только с 32-битной версией winws2 x86, поскольку эмуляция x64 в Windows 10 не предусмотрена.
WinDivert является частой целью для нападок антивирусов. Это хакерский инструмент, но вирусом он не является. Правильнее воспринимать его как замену iptables для Windows. Иногда случаются конфликты со сторонним ПО, использующим драйвера режима ядра - прежде всего антивирусы и фаерволы, вплоть до синих экранов. Практически исправить это нереально хотя бы из-за подписи драйверов, которую получить простому смертному без стоящих за ним корпораций очень непросто и накладно.
WinDivert не может обеспечить корректного перехвата проходящего трафика при раздаче сети средствами Windows и как следствие использовании NAT, поэтому возможности работы по проходящему трафику не реализованы. Единственный доступный вариант - установить proxy server.
winws2 может принимать полные raw фильтры - вы пишите фильтр сами и указываете его в параметре --wf-raw=<filter> или --wf-raw=@<filter_file>.
Но это обычно не очень удобно, поэтому существует встроенный конструктор фильтров.
--wf-tcp-out, --wf-tcp-in, --wf-udp-out, --wf-udp-in берут список портов (80,443) или диапазонов портов (80,443,500-1000)
и включают полный перехват портов по указанному направлению.
--wf-icmp-out, --wf-icmp-in берут список icmp_type или icmp_type+icmp_code.
--wf-ipp-in, --wf-ipp-out берут список raw IP протоколов (для ipv6 не поддерживается выявление истинного протокола при наличии extension headers).
--wf-raw-part принимает частичные WinDivert фильтры. Синтаксис аналогичен --wf-raw. --wf-raw-part может быть несколько.
Частичные фильтры встраиваются конструктором в итоговый фильтр по принципу OR. Или указанные порты, или указанные icmp, или указанные ipp, или ваш фильтр1 или ваш фильтр2.
--wf-raw-filter - частичный WinDivert фильтр, объединяемый по AND с итоговым полным фильтром. Может быть только один. Типичное применение - фильтрация по небольшому списку IP адресов.
Например, задача - сделать icmp тоннель до сервера. Перехват всего icmp определенного типа слишком расточителен по процессору и бессмысленен.
Намного лучше к --wf-icmp-in=0 будет добавить --wf-raw-filter="ip.SrcAddr==1.2.3.4".
--wf-save=<filter_file> записывает созданный конструктором фильтр в файл для последующего анализа и модификации.
Конструктор фильтров автоматически перехватывает входящие tcp с флагами SYN+ACK,FIN,RST. Писать специально правила не нужно.
--wf-filter-lan (по умолчанию включен) отфильтровывает пакеты на не глобальные IP адреса, такие как 192.168.0.0/16.
--wf-tcp-empty (по умолчанию выключен) включает перехват пустых tcp пакетов без флагов SYN,FIN,RST.
Если параметр не включен, перехват пустых ACK пакетов не производится, что позволяет существенно сэкономить на процессоре.
Но для некоторых стратегий эти пакеты могут понадобиться. Только вы можете знать нужны они вам или нет.
Если есть перехват любого tcp порта, автоматически включается перехват всех http редиректов, чтобы работал autohostlist. Перехват http redirect работает по payload сигнатуре, то есть проверяются байты в определенных позициях содержимого пакета.
Предпочтительно по udp протоколам, особенно по тем, где порт не определен, использовать свои --wf-raw-part фильтры,
чтобы сэкономить на процессоре. Для tcp тоже возможны фильтры, но надо учитывать потребности conntrack. Он нуждается
как минимум в SYN пакете, а желательно еще и FIN, RST. Если нужна фильтрация по сообщениям , занимающим более одного tcp сегмента,
такое отфильтровать средствами WinDivert невозможно - требуется полный перехват порта по направлению.
nfqws2
Общие принципы задания параметров
Все параметры nfqws2 передаются в командной строке, либо загружаются из файла в том же самом формате.
nfqws2 использует стандартный парсер getopt_long_only.
Опции имеют формат --name[=value]. Некоторые опции не требуют параметров, другие требуют, а третьи могут их брать опционально.
Парсер getopt позволяет задавать значение через знак = или через пробел. Лишние значения через пробел могут игнорироваться,
поэтому казалось бы ошибочные параметры могут не вызвать ошибку. Лучше всегда писать значения через знак =.
Чтение параметров из файла реализовано через задание единственной опции @config_file.
Все остальные параметры командной строки будут проигнорированы.
Опции будут прочитаны из файла, как будто бы вы ввели его содержимое в командной строке.
Возможность не поддерживается в Android и OpenBSD версиях.
Полный список опций
Общие параметры для всех версий - nfqws2, dvtws2, winws2.
@<config_file> ; чтение опций командной строки из файла. все остальные опции из командной строки игнорируются.
--debug=0|1|syslog|android|@<filename> ; писать дебаг лог. 0 - нет , 1 - на консоль, syslog - в unix syslog, android - системный log android, @<filename> - в файл
--version ; вывести версию и выйти
--dry-run ; проверить валидность параметров командной строки и наличие файлов. не проверяет корректность скриптов Lua !
--comment=any_text ; любой текст. игнорируется
--intercept=0|1 ; разрешить перехват. 0 - нет, 1 - да. при 0 выполняются lua-init скрипты и процесс завершается, перехват не включается, очередь NFQUEUE не инициализируется
--daemon ; отключиться от консоли (демонизироваться)
--pidfile=<filename> ; запись PID в файл
--ctrack-timeouts=S:E:F[:U] ; таймауты conntrack для стадий tcp SYN, ESTABLISHED, FIN и для udp
--ctrack-disable=[0|1] ; 1 отключает conntrack
--payload-disable=[type[,type]] ; отключить распознавание указанных типов пейлоадов. без аргумента - отключить все.
--server=[0|1] ; серверный режим. для обслуживания listener-ов меняются многие аспекты выбора направления и ip/port источника/приемника
--ipcache-lifetime=<int> ; время жизни записей кэша IP в секундах. 0 - без ограничений.
--ipcache-hostname=[0|1] ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы
--reasm-disable=[type[,type]] ; отключить сборку фрагментов для списка пейлоадов : tls_client_hello quic_initial . без аргумента - отключить reasm для всего.
DESYNC ENGINE INIT:
--writeable[=<dir_name>] ; создать директорию для Lua с разрешением записи и поместить путь к ней в переменную env "WRITEABLE" (только одна директория)
--blob=<item_name>:[+ofs]@<filename>|0xHEX ; загрузить бинарный файл или hex строку в переменную Lua <item_name>. +ofs задает смещение от начала файла
--lua-init=@<filename>|<lua_text> ; однократно при старте выполнить Lua код из строки или из файла. поддерживаются сжатые gzip файлы. автоматически проверяется "<filename>.gz"
--lua-gc=<int> ; интервал вызова сборщика мусора Lua в секундах. 0 отключает периодический вызов.
MULTI-STRATEGY:
--new[=name] ; начало нового профиля. опционально установка имени профиля
--skip ; игнорировать профиль
--name=<name> ; установить имя профиля
--template[=<name>] ; использовать профиль как шаблон, задать имя
--cookie[=<string>] ; установить значение Lua переменной "desync.cookie", передаваемое каждому инстансу данного профиля
--import=<name> ; копировать настройки из шаблона в текущий профиль с полным замещением
--filter-l3=ipv4|ipv6 ; фильтр профиля : версия ip протокола
--filter-tcp=[~]port1[-port2]|* ; фильтр профиля : порты tcp или диапазоны портов через запятую
--filter-udp=[~]port1[-port2]|* ; фильтр профиля : порты udp или диапазоны портов через запятую
--filter-icmp=type[:code]|* ; фильтр профиля : icmp type и code через запятую. если code не задан - любой code.
--filter-ipp=proto|* ; фильтр профиля : номера raw ip протоколов через запятую
--filter-l7=proto[,proto] ; фильтр профиля : список протоколов потока через запятую
--ipset=<filename> ; фильтр профиля : включающий список ip адресов или подсетей из файла. может быть смешанным ipv4+ipv6.
--ipset-ip=<ip_list> ; фильтр профиля : включающий фиксированный список ip адресов или подсетей через запятую
--ipset-exclude=<filename> ; фильтр профиля : исключающий список ip адресов или подсетей из файла. может быть смешанным ipv4+ipv6.
--ipset-exclude-ip=<ip_list> ; фильтр профиля : исключающий фиксированный список ip адресов или подсетей через запятую
--hostlist=<filename> ; фильтр профиля : включающий список доменов из файла
--hostlist-domains=<domain_list> ; фильтр профиля : включающий фиксированный список доменов через запятую
--hostlist-exclude=<filename> ; фильтр профиля : исключающий список доменов из файла
--hostlist-exclude-domains=<domain_list> ; фильтр профиля : исключающий фиксированный список доменов через запятую
--hostlist-auto=<filename> ; фильтр профиля : автоматически пополняемый по обратной связи включающий фильтр доменов
--hostlist-auto-fail-threshold=<int> ; параметр автолиста : количество неудач подряд для занесения в лист. по умолчанию 3
--hostlist-auto-fail-time=<int> ; параметр автолиста : максимальное время между неудачами без сброса счетчика. по умолчанию 60 секунд
--hostlist-auto-retrans-threshold=<int> ; параметр автолиста : количество tcp ретрансмиссий в одном сеансе для фиксации неудачи. по умолчанию 3
--hostlist-auto-retrans-reset=[0|1] ; параметр автолиста : посылать RST ретрансмиттеру, чтобы прекратить долгое ожидание. по умолчанию 1
--hostlist-auto-retrans-maxseq=<int> ; параметр автолиста : исходящий relative sequence, после которого детект неудачи прекращается. по умолчанию 32768
--hostlist-auto-incoming-maxseq=<int> ; параметр автолиста : входящий relative sequence, после которого детект неудачи прекращается, а счетчик сбрасывается. по умолчанию 4096
--hostlist-auto-udp-out=<int> ; параметр автолиста : условие неудачи udp : количество исходящих пакетов больше или равно значению. по умолчанию 4
--hostlist-auto-udp-in=<int> ; параметр автолиста : условие неудачи udp : количество входящих пакетов меньше или равно значению. по умолчанию 1
--hostlist-auto-debug=<logfile> ; дебаг лог автолиста
LUA PACKET PASS MODE:
--payload=type[,type] ; внутрипрофильный фильтр : фильтр пейлоада для последующих инстансов внутри профиля
--out-range=[(n|a|d|s|p)<int>](-|<)[(n|a|d|s|p)<int>] ; внутрипрофильный фильтр : диапазон счетчиков conntrack для последующих инстансов внутри профиля - исходящее направление
--in-range=[(n|a|d|s|p)<int>](-|<)[(n|a|d|s|p)<int>] ; внутрипрофильный фильтр : диапазон счетчиков conntrack для последующих инстансов внутри профиля - входящее направление
LUA DESYNC ACTION:
--lua-desync=<function>[:param1=val1[:param2=val2]] ; при обработке профиля вызвать LUA инстанс с указанными параметрами, если соблюдены условия внутрипрофильных фильтров
Специфические параметры nfqws2 :
--qnum=<nfqueue_number> ; номер очереди NFQUEUE в Linux
--user=<username> ; сменить uid/gid на те, что связаны с указанным именем пользователя
--uid=uid[:gid1,gid2,...] ; сменить uid/gid на указанные числовые значения
--bind-fix4 ; лечение проблемы ухода генерированных пакетов на Linux с неверного интерфейса при использовании PBR (ipv4)
--bind-fix6 ; аналогично для ipv6
--fwmark=<int|0xHEX> ; бит в mark для предотвращения зацикливания. default = 0x40000000
--filter-ssid=ssid1[,ssid2,ssid3,...] ; фильтр профиля : имя wifi сети
Специфические параметры dvtws2 :
--port=<port> ; номер divert порта
--user=<username> ; сменить uid/gid на те, что связаны с указанным именем пользователя
--uid=uid[:gid1,gid2,...] ; сменить uid/gid на указанные числовые значения
Специфические параметры winws2 :
--wf-iface=<int>[.<int>] ; WinDivert конструктор : номер сетевого интерфейса
--wf-l3=ipv4|ipv6 ; WinDivert конструктор : версия ip
--wf-tcp-in=[~]port1[-port2] ; WinDivert конструктор : tcp порты или диапазоны портов для перехвата по входящему направлению. список через запятую
--wf-tcp-out=[~]port1[-port2] ; WinDivert конструктор : tcp порты или диапазоны портов для перехвата по исходящему направлению. список через запятую
--wf-udp-in=[~]port1[-port2] ; WinDivert конструктор : udp порты или диапазоны портов для перехвата по входящему направлению. список через запятую
--wf-udp-out=[~]port1[-port2] ; WinDivert конструктор : udp порты или диапазоны портов для перехвата по исходящему направлению. список через запятую
--wf-tcp-empty=[~]port1[-port2] ; WinDivert конструктор : перехватывать пустые tcp пакеты ACK. по умолчанию - нет.
--wf-icmp-in=type[:code] ; WinDivert конструктор : icmp type и code по входящему направлению. если code не задан - любой code. список через запятую
--wf-icmp-out=type[:code] ; WinDivert конструктор : icmp type и code по исходящему направлению. если code не задан - любой code. список через запятую
--wf-ipp-in=type[:code] ; WinDivert конструктор : номера raw ip протоколов по входящему направлению. список через запятую
--wf-ipp-out=type[:code] ; WinDivert конструктор : номера raw ip протоколов по исходящему направлению. список через запятую
--wf-raw-part=<filter>|@<filename> ; WinDivert конструктор : частичный WinDivert raw фильтр. объединяется по принципу ИЛИ. может быть несколько
--wf-raw-filter=<filter>|@<filename> ; WinDivert конструктор : частичный WinDivert raw фильтр. объединяется по принципу И. может быть только один
--wf-filter-lan=0|1 ; WinDivert конструктор : отфильтровывать неглобальные IP адреса. по умолчанию - да.
--wf-raw=<filter>|@<filename> ; полный WinDivert фильтр. замещает конструктор.
--wf-dup-check[=0|1] ; управление проверкой на повторный запуск с теми же параметрами фильтра --wf ( по умолчанию 1 )
--wf-save=<filename> ; сохранить полный итоговый WinDivert фильтр в файл
LOGICAL NETWORK FILTER:
--ssid-filter=ssid1[,ssid2,ssid3,...] ; список сетей wifi, при наличии подключения к которым перехват включается, а иначе не включается.
--nlm-filter=net1[,net2,net3,...] ; список сетей Network List Manager, при наличии подключения к которым перехват включается, а иначе не включается.
--nlm-list[=all] ; вывести список подключенных NLM сетей. all - список всех NLM сетей
Распознавание протоколов
nfqws2 сигнатурно распознает типы пейлоадов отдельно взятых пакетов или групп пакетов. Все пустые пакеты имеют пейлоад empty, неизвестные - unknown. Протокол потока присваивается после получения первого известного пейлоада и остается с потоком до конца его существования. При этом последующие пейлоады могут иметь как известный тип, так и неизвестный. В фильтрах по пейлоаду и протоколу потока доступны специальные значения - all и known. All означает любой, known - не empty и не unknown.
Таблица распознаваемых типов пейлоада и протоколов потока
| Протокол потока | L4 | Пейлоады |
|---|---|---|
| http | tcp | http_req http_reply |
| tls | tcp | tls_client_hello tls_server_hello |
| xmpp | tcp | xmpp_stream xmpp_starttls xmpp_proceed xmpp_features |
| mtproto | tcp | mtproto_initial |
| quic | udp | quic_initial |
| wireguard | udp | wireguard_initiation wireguard_response wireguard_cookie wireguard_keepalive |
| dht | udp | dht |
| discord | udp | discord_ip_discovery |
| stun | udp | stun |
| dns | udp | dns_query dns_response |
| dtls | udp | dtls_client_hello dtls_server_hello |
| любой | icmp | ipv4 ipv6 icmp |
Особые типы пейлоада - ipv4,ipv6,icmp. ipv4 и ipv6 генерируются, когда icmp содержит прикрепленный пакет. В остальных случаях icmp имеет тип пейлоада "icmp".
Использование множественных профилей
Профили существуют, чтобы в зависимости от указанных условий фильтра выбрать ту или иную стратегию воздействия на трафик. Общая схема использования профилей следующая :
nfqws2 <глобальные_параметры>
<фильтр 1> <стратегия 1> --new
<фильтр 2> <стратегия 2> --new
...............
<фильтр N> <стратегия N>
Когда на вход поступает пакет, и для него еще нет записи в conntrack, происходит выбор профиля. Фильтры профиля проверяются от первого до последнего - с начала в конец - слева направо, и никак иначе. Всегда выигрывает только один профиль - по первому совпадению условий фильтра, а все остальные не задействуются. Если не сработал ни один фильтр, выбирается пустой профиль с номером 0, не предполагающий никаких действий с трафиком.
Все условия, кроме --filter-l7 и хостлистов, являются однозначными и известными с момента начала обработки потока (с начала соединения).
В начале, как правило, еще неизвестнен протокол потока и имя хоста, выделяемое из сообщений потока.
Когда эти величины становятся известны, происходит поиск профиля заново. Если выбирается другой профиль - происходит перескок.
Таких перескоков может быть до двух, поскольку есть только 2 величины, влияющие на выбор, неизвестные с самого начала.
Для протоколов потока tls, http, quic обычно бывает только 1 перескок, поскольку определение протокола и имени хоста
происходит по одному пакету или группе пакетов. Для XMPP это 2 перескока - сначала определяется xmpp как таковой,
затем ловится переход на TLS, и только в нем выделяется имя хоста.
При написании стратегий следует их продумывать с учетом логики перескоков.
Если нужно, чтобы стратегия начала работу с самого первого пакета и продолжила работать дальше после изменения профиля,
нужно дублировать вызовы во всех профилях, по которым может пройти поток.
4 группы фильтров - tcp, udp, icmp и ipp объединяются по принципу OR. Если нет ни одного фильтра из этих групп, то считается разрешенным все. Если задается хотя бы один, остальные группы блокируются, если не определены.
Фильтр ipp не работает с протоколами tcp, udp и icmp. Проверка пойдет по группам фильтра, соответствующим протоколу. Например, если задано только --filter-ipp=6,
это не значит, что будут пропущены все tcp. Наоборот, это блокирует все, включая и сам tcp, потому что не задан --filter-tcp. Правильно будет указать --filter-tcp=*.
icmp автоматически подразумевает и icmpv6 - они обрабатываются единым образом. Однако, icmp types для icmp и icmpv6 отличаются. Это нужно учитывать.
Шаблоны профилей
Когда имеется много сложных и повторяющихся стратегий или групп одинаковых параметров, может быть удобно использовать шаблоны.
Шаблон - это такой же профиль, только он не идет в работу, а попадает в отдельный список шаблонов.
Шаблоном профиль становится через задание параметра --template=<name>.
Далее он может быть импортирован (--import=<name>) в другой профиль или другой шаблон.
Простые параметры - число, строка, bool - импортируются только, если они были заданы в импортируемом шаблоне. При импорте шаблона в другой шаблон в шаблоне назначения они также считаются заданными.
Списочные параметры добавляются в конец списка. Такими параметрами является все, что может принимать список значений. Например, хостлисты или --filter-tcp.
Номер и имя шаблона не копируются.
Директив --import может быть сколько угодно в любом месте. Предыдущие простые заданные параметры затираются новыми импортированными или заданными в текущем профиле.
Особый запрет - замещение autohostlist. Шаблон может иметь autohostlist, но при импорте куда-то, где уже есть autohostlist, будет ошибка.
nfqws2 <глобальные_параметры>
--template=tpl1 <базовые параметры 1> --new
--template=tpl2 <базовые параметры 2> --new
--template --import tpl2 --name tpl3 <базовые параметры 3> --new
--import tpl1 --name prof1 <дополнительные параметры 1> --new
--import tpl3 --name prof2 <дополнительные параметры 2> --new
--name prof3 <параметры 3>
В примере имеется 3 рабочих профиля и 3 шаблона, 1 из которых импортирует настройки другого.
- Профиль prof1 получает объединение
<базовые параметры 1>и<дополнительные параметры 1>. - Профиль prof2 получает объединение
<базовые параметры 2>,<базовые параметры 3>и<дополнительные параметры 2> - Профиль prof3 получает
<параметры 3>. Он не импортирует шаблоны.
В шаблонах допустимы любые параметры, относящиеся к профилям, включая и фильтры. Глобальные параметры не являются частью профилей и шаблонов.
Фильтрация по ipset
- В случае tcp и udp сравнивается адрес сервера в клиентском режиме и адрес клиента в серверном.
- related icmp проходят по профилю исходного пакета, поиск профиля не выполняется.
- Для unrelated icmp и raw ip проверяются адреса источника и приемника, совпадение фиксируется при совпадении любого из адресов.
Фильтрация по листам
Если имеются фильтры по хостлистам, и есть хотя бы один домен в любом хостлисте или указан автохостлист, то профиль никогда не будет выбран при отсутствующем имени хоста. Случай, когда нет автохостлиста, а все файлы листов пустые, приравнивается к отсутствию фильтра по хостлисту.
Если нет автохостлиста, но есть записи в обычных хостлистах, то профиль выбирается только если текущий хост проходит по любому из включающих хостлистов, но не проходит ни по одному из исключающих.
Если есть автохостлист, то при наличии имени хоста профиль выбирается всегда вне зависимости от его вхождения в какие-либо листы этого профиля. Действия зависят от вхождения в листы.
- Если хост входит в исключающие листы, не происходит никаких действий и не происходит попытки выяснить работает ли ресурс.
- Если хост не входит в исключающие листы, но входит во включающие - происходит применение стратегии без попыток выяснить работает ли ресурс с ней.
- Если хост не входит ни в исключающие листы, ни во включающие - стратегия не применяется, происходит обнаружение
неудачи доступа к ресурсу. Если случилась неудача, увеличивается счетчик неудач. Если случается удача или превышается
интервал времени между неудачами
--hostlist-auto-fail-time- счетчик сбрасывается. Когда счетчик достигает--hostlist-auto-fail-threshold, происходит занесение хоста в автолист. При следующем запросе будет считаться, что хост входит во включающий лист. - Файлы хостлистов и ipset-ов перечитываются автоматически при изменении - перезапуск nfqws2 не нужен.
- Сигнал SIGHUP помечает все листы для принудительной перечитки при обработки следующего пакета
- Каждая запись о домене, IP адресе или подсети идет на новой строке.
- Хостлисты и ipset-ы поддерживают комментарии. Пустые строки и строки, начинающиеся с
#, игнорируются. - В хостлистах поддомены учитываются автоматически.
*не поддерживается. Если в начале идет символ^, автоматический учет поддоменов отменяется для этого домена. - ipset-ы могут включать адреса и подсети как ipv4, так и ipv6.
- В статическом варианте
--ipset-ipи--hostlist-domainsдомены идут через запятую. В статическом хостлисте#и^так же поддерживаются. - Для листов поддерживается сжатие gzip.
Детектор неудач автохостлистов
Детектор срабатывает только при наличии имени хоста. Неудачей считается :
- tcp : происходит не менее
--hostlist-auto-retrans-thresholdретрансмиссий в пределах исходящего relative sequence--hostlist-auto-retrans-maxseq. если--hostlist-auto-retrans-reset=1, ретрансмиттеру шлется RST, чтобы он прекратил попытки достучаться (это может быть очень долго). - tcp : приходит RST в пределах входящего relative sequence от 1 до
--hostlist-auto-incoming-maxseq - tcp : принят пейлоад
http_replyи http ответ является переадресацией 302 или 307 на абсолютный URL с доменом 2 уровня, не совпадающим с доменом 2 уровня хоста. - udp : ушло не менее
--hostlist-auto-udp-outпакетов, пришло не более--hostlist-auto-udp-inпакетов. Эта ситуация означает, что клиент шлет запросы, а сервер на них не отвечает или отвечает меньше, чем должен по протоколу.
Удачей считается :
- tcp : превышение исходящего relative sequence
--hostlist-auto-retrans-maxseq. Клиент смог отослать достаточно много, что вряд ли бы случилось в случае реакции DPI. - tcp : превышение входящего relative sequence
--hostlist-auto-incoming-maxseq. Сервер прислал достаточно много, чтобы это не было похоже на ответ DPI. - udp : превышение количества пришедших пакетов
--hostlist-auto-udp-in. Сервер ответил достаточно много.
При неудаче, если с прошлой неудачи прошло не более --hostlist-auto-fail-time секунд, счетчик неудач увеличивается.
Если прошло больше времени - счетчик сбрасывается, счет идет заново.
При удаче счетчик сбрасывается. Считается, что ресурс работает, а сбой был временным и не связанным с блокировкой.
При достижении счетчиком --hostlist-auto-fail-threshold происходит занесение хоста в лист.
Большинство критериев удачи или неудачи требует анализа входящего и исходящего трафика, поэтому необходим их перехват в достаточном объеме для возможности срабатывания критериев.
Фильтр по наличию сетей
Если, допустим, вам нужно применить одну стратегию для wifi, другую для провода, это делается через фильтр, основанный на имени интерфейса. Но что делать, если вы подключаетесь к разным wifi сетям или подключаете провод в разных локациях ? В случае провода решение есть только на Windows, на других системах - нет. Для wifi есть решение на Linux и Windows, на BSD - нет.
Фильтр по wifi берет список SSID через запятую, однако он реализован по-разному в Linux и Windows.
В Linux используется фильтр профиля --filter-ssid. Если он задан, nfqws2 пытается получить SSID на интерфейсе, куда этот пакет уходит
или откуда он приходит. Если ему это удается - происходит проверка по списку сетей, если нет - условие фильтра не соблюдается, профиль не выбирается.
Такая концепция позволяет работать даже если вы подключаетесь к нескольким wifi сетям на разных адаптерах.
В Windows концепция иная. Мониторится наличие указанных wifi сетей на всех wifi адаптерах, и если на любом из них SSID есть,
перехват WinDivert включается, а иначе выключается. Чтобы обслужить wifi сети с разными стратегиями нужно запускать несколько инстансов winws2.
Один будет включаться, остальные - отключаться. Список SSID задается параметром --ssid-filter.
Другой способ решить вопрос, и не только с wifi, - использование фильтра NLM - Network List Manager.
--nlm-list[=all] вернет список GUID подключенных или всех сетей, если указано значение параметра "all".
Далее вписываете список GUID через запятую в --nlm-filter.
Сеть NLM - это результат детекта системой подключения к конкретной сети. Вы можете подключиться к роутеру по wifi или проводу, но сеть будет одна. Для различения сетей система обычно смотрит на MAC шлюза. Технология NLM интересная и полезная, но , увы , адекватное управление ей было только в Windows 7. В остальных системах нужно копаться в powershell или лезть в реестр, чтобы раскидать подключения по нужным GUID, если вдруг они раскидались системой неправильно. Но можно и не бороться, а просто внести список GUID, назначенных системой автоматически.
Серверный режим
Некоторые виды воздействий можно осуществлять не только со стороны клиента, но и сервера. nfqws2 создавался с учетом полноценной работы как по входящему, так и по исходящему трафику, как на клиентской стороне, так и на стороне сервера.
Понятие направления в сети во многом условно. Для адресатов пакетов все ясно - что-то отсылается, что-то принимается. Но для роутера это неясно совсем. Есть только входящий интерфейс и исходящий. По сути 2 направления равнозначны, если рассматривать только уровень L3 - уровень отдельных IP пакетов.
Поэтому направление в nfqws2 учитывается за счет слежения за потоками. Поток - это либо tcp соединение, либо последовательность udp пакетов. udp не предполагает понятия соединения, поэтому оставлено общее название - поток.
Поток характеризуется 4 величинами : ip1:port1-ip2:port2. Их совокупность (tuple) определяет принадлежность пакета к конкретному потоку.
За потоками в nfqws2 следит conntrack. Тот, кто первым послал SYN (tcp) или первый UDP пакет, считается клиентом, а противоположный конец - сервером. Если создание записи о потоке в conntrack произошло по SYN,ACK пакету (tcp), то этот конец считается сервером, а противоположный - клиентом. Таким образом conntrack определяет роли в установлении соединения и хранит по каждой роли отдельный набор счетчиков - сколько пакетов прошло, сколько пакетов с данными, сколько байт передано и тд.
В клиентском режиме исходящим направлением считается направление от клиента,
в серверном (--server) - от сервера. Входящим считается противоположное направление.
При указании --server направления инвертируются.
--in-range, --out-range, а так же признак desync.outgoing в Lua функциях
меняются местами, чтобы соответствовать фактически отсылаемым или принимаемым данным
со стороны сервера. Клиент шлет запросы (http_req) и принимает ответы (http_reply).
Сервер шлет ответы (http_reply) и принимает запросы (http_req).
Для обоих режимов - клиентского и серверного - поиск в ipset осуществляется по IP адресу назначения для исходящего направления и IP адресу источника для входящего направления.
В клиентском режиме фильтры портов проверяют порт назначения для исходящего направления и порт источника для входящего направления.
В серверном режиме фильтры портов проверяют порт назначения для входящего направления и порт источника для исходящего направления.
Это сделано потому, что в фильтрации реальный смысл имеет только порт сервера. Порт клиента выбирается как правило случайно из некоторого диапазона и не подходит для осмысленной фильтрации.
Таким образом, сервер фильтрует по ipset ip клиентов, а клиент - ip серверов. Но и сервер, и клиент фильтруют порт сервера.
Linux conntrack имеет похожий способ определения направлений. Для клиента исходящими будут пакеты original, входящими - reply. Для сервера - наоборот. Это нужно учитывать при написании правил перехвата, полагающихся на направление conntrack.
Можно определять направление и по именам интерфейсов. При стандартной конфигурации lan-wan входящими считаются пакеты, полученные с wan , а исходящими - отправленные через wan. Именно wan, поскольку обычно используется NAT, а для nfqws2 важно перехватывать исходящие пакеты после NAT и принимать входящие до NAT.
Если роутер работает без NAT (типично для ipv6), не имеет значения на каком этапе перехватываются пакеты. Все IP адреса равнозначны. Вы можете подключаться к интернету, интернет - к вам. Вы - его составная часть с прямой IP адресацией. Но на практике все же к вам подключаться скорее всего не будут, а вы защититесь от таких подключений фаерволом. Поэтому направление все равно по интерфейсу определяется достаточно однозначно. Но если у вас все же есть внутренний сервер, вы можете для него запустить отдельный инстанс nfqws2 в серверном режиме, а для клиентского трафика - в обычном режиме.
В BSD правила ipfw или pf обычно так и пишутся - "xmit wan", "recv wan" + добавляются фильтры по номерам портов назначения для xmit и портов источника для recv (или наоборот для серверного режима). Это достаточно надежно идентифицирует необходимый к перехвату трафик по направлению.
Кэш IP
ipcache представляет собой структуру в памяти процесса, позволяющую по ключу IP адреса и имени интерфейса запоминать некоторую информацию, которую впоследствии можно извлечь и использовать как недостающие данные. На текущий момент это применяются в следующих ситуациях :
-
IP,interface => incoming ttl . Кэшируется TTL/HL первого входящего пакета для последующего применения в Lua функциях (autottl) прямо с первого пакета, когда еще ответа не было.
-
IP => hostname . Кэшируется имя хоста, вне привязки к интерфейсу, для последующего поиска профиля с фильтрами по хостлистам и передачи в Lua функции, когда имя хоста еще неизвестно. Режим отключен по умолчанию и включается через параметр
--ipcache-hostname. Данная техника является экспериментальной. Ее проблема в том, что как такового нет однозначного соответствия между доменом и IP. Множество доменов могут ссылаться на тот же IP адрес. При коллизии происходит замещение имени хоста на последний вариант. Домен может скакать по разным IP на CDN. Сейчас один адрес, через час - другой. Эта проблема решается через время жизни записей кэша :--ipcache-lifetime. По умолчанию 2 часа. Однако, может случиться и так, что в вашем случае применение техники несет больше пользы, чем проблем. Будьте готовы к непонятному на первый взгляд поведению, которое может быть исследовано только через--debugлог.
Имена хостов для кэширования берутся из анализа L7 протоколов и из DNS ответов (не DoH).
Извлечение DNS работает только по udp протоколу и требует перенаправления пакетов с порта 53. Нужны только ответы DNS, имеющие source port 53.
На Windows это будет --wf-udp-in=53. Скорее всего будет обращение к DNS внутри локальной сети, поэтому нужен параметр --wf-filter-lan=0.
Если на Windows крутится какой-то dns proxy, а DNS настроен на localhost, потребуется еще и --wf-filter-loopback=0.
На роутере, если используется шифрованный DNS, надо ловить запросы от клиента еще до шифрования - на LAN интерфейсе.
Если шифрование не используется, можно ловить и на WAN.
Для LAN интерфейса они будут исходящими, для интерфейса WAN - входящими, по направлению conntrack - reply.
При использовании скриптов запуска в Linux для перехвата DNS ответов существует custom script 80-dns-intercept. Его достаточно скопировать в "custom.d".
Сигналы
- SIGHUP принудительно перечитывает все файлы хостлистов и ipset
- SIGUSR1 выводит содержимое пула conntrack
- SIGUSR2 выводит счетчики autohostlist и содержимое пула ipcache
Отладка
Параметр --debug включает вывод отладочных сообщений.
--debug=0выключает вывод--debug,--debug=1вывод на консоль--debug=@<filename>вывод в файл. размер файла ничем не ограничивается, но файл может быть удален в любой момент, и запись продолжится с чистого листа--debug=syslogвывод в syslog. чтение зависит от syslog daemon. rsyslog пишет файлы в /var/log. busybox logd читается через logread.--debug=androidвывод в android log. чтение через logcat. доступно только в версиях, собранных в Android NDK
Умение обращаться с --debug логом совершенно необходимо для отладки настроек и тем более для написания собственного Lua кода.
Все сообщения об ошибках (DLOG_ERR) и особо важные сообщения (DLOG_CONDUP) дублируются на консоли вне зависимости от log target. Сообщения об ошибках выводятся в stderr.
Параметр --dry-run позволяет протестировать корректность опций командной строки и доступность используемых файлов под сброшенными привилегиями.
--dry-run не инициализирует движок Lua, и поэтому не может обнаружить синтаксические ошибки Lua.
Виртуальные машины
Большинство десктопных гипервизоров ломают техники обхода при подключении сети через встроенный в гипервизор NAT. Это настройка по умолчанию. Необходимо подключение через мост (bridge). Проблема точно есть в vmware и virtualbox.
Песочница
В целях безопасности nfqws2 после инициализации сбрасывает свои привилегии. Весь Lua код выполняется только после сброса привилегий. Он никогда не получает исходные права.
BSD :
- Меняется UID/GID на указанные в параметрах
--user,--uid. По умолчанию на 0x7FFFFFFF.
Linux :
- Меняется UID/GID на указанные в параметрах
--user,--uid. По умолчанию на 0x7FFFFFFF. - Сбрасываются capabilities до cap_net_raw, cap_net_admin (требуется для NFQUEUE). Сбрасывается bounding set до нуля.
- Выставляется признак NO_NEW_PRIVS, чтобы не работали suid биты и caps на файлах. Если ядро старее 3.5, NO_NEW_PRIVS не поддерживается. В этом случае выводится предупреждение, выполнение не прекращается.
- Включается seccomp фильтр, запрещающий exec и ряд файловых операций - чтение содержимого каталогов, создание/удаление каталогов, создание специальных файлов (линки, девайсы), chmod, chown, посылание сигналов (kill), ptrace. В случае нарушения процесс аварийно завершается. Если ядро не поддерживает seccomp, выводится предупреждение, но выполнение не прекращается.
Windows :
- Хотя драйвер WinDivert требует привилегий администратора, после его инициализации процесс winws2 ставит себе low mandatory level. Это предотвращает доступ на запись практически ко всем файлам и объектам, защищенным security descriptor. Процесс больше не может управлять службами и осуществлять привилегированные действия. Однако, группа Administrators остается в токене процесса, поэтому ничто не предотвращает чтение большинства файлов, если на них есть доступ для Administrators. Lua не имеет встроенных средств чтения содержимого каталогов, поэтому обнаружение интересующих файлов для злоумышленника затруднено.
- Безвозвратно убираются все Se* привилегии из токена, кроме SeChangeNotifyPrivilege.
- С помощью Job запрещается создание дочерних процессов и ограничивается взаимодействие с десктопом - clipboard, change desktop, change display settings и тд
Есть простой способ передать Lua коду каталог, доступный на запись - параметр --writeable[=<dirname>].
nfqws2 создает каталог, назначает на него такие права, чтобы Lua код смог писать туда файлы, передает имя директории в переменной env WRITEABLE.
Если dirname не задан, на Windows создается каталог внутри %USERPROFILE%/AppData/LocalLow
Со стороны Lua убираются опасные функции - os.execute, io.popen, package.loadlib и модуль debug. На github исполняемые файлы nfqws2 собираются с вариантом luajit без FFI.
Вызов Lua кода
Lua код вызывается в 2 этапа.
- Однократно при запуске программы через
--lua-init=code|@file. Если значение параметра начинается с@, выполняется файл, иначе значение параметра является Lua кодом. Поддерживается сжатие файлов gzip. Сначала проверяется "file", потом "file.gz". - При обработке профиля через
--lua-desync=function_name:arg1[=val1]:arg2[=val2]:argN[=valN]. Сначала идет имя функции, затем через двоеточия аргументы и их значения. Все значения являются строками. Если значение не задано, оно равно пустой строке. Реализовано 2 типа автоматических подстановок на стороне C кода.%varподставляет значение переменнойdesync.varилиvar, если первая отсутствует.#varподставляет длину переменнойdesync.varилиvar, если первая отсутствует. Двоеточия и знаки%,#в начале могут быть эскейпнуты через\.
И --lua-init, и --lua-desync может быть несколько. Выполнение производится строго в порядке указания.
Передача блобов
Блоб - это двоичный блок данных любого размера, который может быть загружен в переменную Lua средствами C кода при старте программы.
--blob=<item_name>:[+ofs]@<filename>|0xHEX
item_name- имя переменной Lua.[+ofs]@<filename>- загрузка из файла со смещения ofs.0xHEX- загрузка из HEX строки
Прямые операции с файлами из кода Lua не рекомендованы без необходимости. Lua код выполняется с ограниченными правами, задуманное может не получиться или не работать на разных ОС в разных условиях. Загрузка blob происходит до вхождения в песочницу, поэтому шансов на успех больше.
Внутрипрофильные фильтры
Они бывают трех видов - --payload, --in-range, --out-range.
Значения фильтров действуют с момента их указания до следующего переопределения.
--payload=type1[,type2][,type3]...принимает список известных пейлоадов через запятую, "all" или "known". Список известных пейлоадов. По умолчанию--payload=all.--(in-range|out-range)=[(n|a|d|s|p)<int>](-|<)[(n|a|d|s|p)<int>]задает диапазоны счетчиков conntrack по входящему и исходящему направлениям. По умолчанию--in-range=x,--out-range=a.
Диапазоны задаются в формах : mX-mY, mX<mY, -mY, <mY, mX-, где m - режим счетчика, X - нижнее значение, Y - верхнее значение.
Режимы x и a задаются без диапазона и значения счетчика - единственной буквой. Знак - означает включающую верхнюю границу, < - исключающую.
Доступны следующие режимы счетчика :
- 'a' - всегда
- 'x' - никогда
- 'n' - номер пакета
- 'd' - номер пакета с данными
- 'b' - байт-позиция переданных данных
- 's' - tcp: relative sequence начала текущего пакета. работает в пределах 2 GB
- 'p' - tcp: relative sequence верхней границы текущего пакета (s + длина пейлоада). работает в пределах 2 GB
Caution
В winws2 по умолчанию включен параметр
--wf-tcp-empty=0. Он блокирует перехват пустых пакетов с ACK, что позволяет примерно в 2 раза сэкономить на процессоре при интенсивных скачиваниях. Пустые ACK в большинстве стратегий не нужны. Но это же и ломает счетчик "n" - он не будет показывать реальное количество пакетов по соединению. Если вам нужно точное значение, укажите параметр--wf-tcp-empty=1. В других системах точность счетчиков напрямую зависит от фильтров перехвата. Счетчик не может и не будет считать то, что не перехватывается.
nfqws2 следит за превышением верхней границы счетчиков для всех Lua инстансов. Если во всех инстансах превышена верхняя граница по направлению или инстансы вошли по направлению в состояние cutoff добровольно, происходит lua cutoff - выключение процессинга Lua в текущем потоке. Это нужно для экономии ресурсов процессора, поскольку проверка единственного bool признака практически не требует никаких ресурсов.
Типичная схема вызова инстансов внутри профиля
В рассматриваемом примере решается задача для пейлоадов tls_client_hello и http_req попробовать фейки, отдельные для каждого типа пейлоада, а если это не работает, перейти на multidisorder для tls и multisplit для http. Если и это не работает - крутить стратегии по кругу.
--filter-tcp=80,443 --filter-l7=http,tls
--out-range=-s34228
--in-range=-s5556 --lua-desync=circular
--in-range=x
--payload=tls_client_hello
--lua-desync=fake:blob=fake_default_tls:badsum:strategy=1
--lua-desync=multidisorder:strategy=2
--payload=http_req
--lua-desync=fake:blob=fake_default_http:badsum:strategy=1
--lua-desync=multisplit:strategy=2
Не суть важно как работают конкретные функции, сейчас важно понять как работают внутрипрофильные фильтры и как передаются параметры Lua инстансам.
- Профильный фильтр по tcp портам и типу протокола потока позволяет избежать вызовов Lua на другом трафике. Профиль вообще не будет задействован, если условия фильтра не выполнятся.
--out-rangeуказан, чтобы отсечь поток от Lua по исходящему направлению после relative sequence (32768+1460) - для экономии процессора. Такое значение выбрано из-за специфики функции circular - значение s32768 используется в детекторе успеха как порог срабатывания по умолчанию, 1460 - максимально возможная длина данных в tcp пакете. На Linux может быть не нужно, если используется фильтр connbytes.- сircular для своей работы требует начальные входящие пакеты потока, а по умолчанию они отключены. Поэтому включаем вплоть до позиции relative sequence 5556. По умолчанию детектор успеха реагирует на s4096. Добавлен еще 1 пакет макс 1460 байт.
- Остальные инстансы не нуждаются во входящем трафике. Снова отключаем. Действие
--in-range=xраспространяется до конца профиля. - Действие
--payloadраспространяется на два следующих за ним инстанса. - Строчка
--lua-desync=fake:blob=fake_default_tls:badsum:strategy=1вызывает функциюfakeс 3 аргументами :blob,badsum,strategy. Значением аргументаbadsumявляется пустая строка.
Прототип Lua функции
Стандартная Lua функция имеет прототип
function desync_f(ctx,desync)
- ctx - контекст для вызова некоторых C функций
- desync - таблица, содержащая все передаваемые значения, включая аргументы, диссект текущего пакета и т.д.
Функция возвращает вердикт по текущему пакету - VERDICT_PASS, VERDICT_MODIFY, VERDICT_DROP. Может не возвращать ничего, тогда результат приравниваться к VERDICT_PASS.
- VERDICT_PASS передает пакет как есть без учета изменений диссекта
- VERDICT_MODIFY выполняет реконструкцию и отправку текущего диссекта
- VERDICT_DROP дропает текущий пакет
- VERDICT_PRESERVE_NEXT - отдельный бит, который нужно прибавить к основному вердикту - использовать поля "next protocol" в ipv6 header и ipv6 extension headers. в ином случае они генерируются автоматом по содержимому диссекта
Результат всех lua-desync инстансов агрегируется : VERDICT_MODIFY замещает VERDICT_PASS, VERDICT_DROP замещает их обоих. VERDICT_PRESERVE_NEXT применяется, если хотя бы один инстанс его вернул.
Структура таблицы desync
Лучше всего изучать структуру desync по ее реальному содержимому, выполняя тестовую desync функцию pktdebug из zapret-lib.lua.
Пакет http-req от запроса по ipv6 к http://one.one.one.one
desync:
.target
.port
number 80
.ip6
string 26 06 47 00 47 00 00 00 00 00 00 00 00 00 10 01
.func
string pktdebug
.func_n
number 1
.profile_n
number 1
.l7payload
string http_req
.dis
.tcp
.th_dport
number 80
.th_x2
number 0
.th_off
number 8
.th_sum
number 18781
.th_win
number 64
.options
.1
.kind
number 1
.2
.kind
number 1
.3
.data
string 30 40 18 9A 6F A5 3E 89
.kind
number 8
.th_seq
number 19930989
.th_ack
number 1489231977
.th_flags
number 24
.th_urp
number 0
.th_sport
number 48118
.ip6
.ip6_flow
number 1871905881
.ip6_hlim
number 64
.ip6_dst
string 26 06 47 00 47 00 00 00 00 00 00 00 00 00 10 01
.exthdr
.ip6_plen
number 110
.ip6_src
string 1A E5 18 81 E1 CD E8 24 BA 16 39 FF FE 8A DE 12
.ip6_nxt
number 6
.payload
string 47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A 48 6F 73 74 3A 20 6F 6E 65 2E 6F 6E 65 2E 6F 6E 65 2E 6F 6E 65 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A 20 63 75 72 6C 2F 38 2E 38 2E 30 0D 0A 41 63 63 65 70 74 3A 20 2A 2F 2A 0D 0A 0D 0A
.l4proto
number 6
.transport_len
number 110
.l3_len
number 40
.l4_len
number 32
.reasm_offset
number 0
.reasm_data
string 47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A 48 6F 73 74 3A 20 6F 6E 65 2E 6F 6E 65 2E 6F 6E 65 2E 6F 6E 65 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A 20 63 75 72 6C 2F 38 2E 38 2E 30 0D 0A 41 63 63 65 70 74 3A 20 2A 2F 2A 0D 0A 0D 0A
.ifout
string eth0
.fwmark
number 0
.func_instance
string pktdebug_1_1
.replay
boolean false
.track
.pos
.dt
number 0.013066192
.server
.tcp
.pos
number 1
.rseq
number 1
.scale
number 13
.mss
number 1360
.winsize_calc
number 65535
.uppos
number 0
.seq0
number 1489231976
.seq
number 1489231977
.uppos_prev
number 0
.winsize
number 65535
.pcounter
number 1
.pdcounter
number 0
.pbcounter
number 0
.client
.tcp
.pos
number 79
.rseq
number 1
.scale
number 10
.mss
number 1380
.winsize_calc
number 65536
.uppos
number 79
.seq0
number 19930988
.seq
number 19930989
.uppos_prev
number 0
.winsize
number 64
.pcounter
number 3
.pdcounter
number 1
.pbcounter
number 78
.reverse
.tcp
.pos
number 1
.rseq
number 1
.scale
number 13
.mss
number 1360
.winsize_calc
number 65535
.uppos
number 0
.seq0
number 1489231976
.seq
number 1489231977
.uppos_prev
number 0
.winsize
number 65535
.pcounter
number 1
.pdcounter
number 0
.pbcounter
number 0
.direct
.tcp
.pos
number 79
.rseq
number 1
.scale
number 10
.mss
number 1380
.winsize_calc
number 65536
.uppos
number 79
.seq0
number 19930988
.seq
number 19930989
.uppos_prev
number 0
.winsize
number 64
.pcounter
number 3
.pdcounter
number 1
.pbcounter
number 78
.lua_state
.hostname
string one.one.one.one
.hostname_is_ip
boolean false
.lua_in_cutoff
boolean true
.lua_out_cutoff
boolean false
.t_start
number 1700000000
.incoming_ttl
number 51
.l7proto
string http
.arg
.testarg1
string val1
.testarg2
string val2
.tcp_mss
number 1360
.l7proto
string http
.outgoing
boolean true
desync
| Поле | Тип | Содержание | Примечание |
|---|---|---|---|
| func | string | имя desync функции | |
| func_n | number | номер инстанса внутри профиля | |
| func_instance | string | название инстанса | производная имени функции, номера инстанса и номера профиля |
| profile_n | number | номер профиля | |
| profile_name | string | название профиля | может отсутствовать |
| cookie | string | значение параметра nfqws2 --cookie для профиля | может отсутствовать |
| outgoing | bool | true , если направление исходящее | |
| ifin | string | имя входящего интерфейса | может отсутствовать |
| ifout | string | имя исходящего интерфейса | может отсутствовать |
| fwmark | number | fwmark текущего пакета | только в Linux |
| target | table | таблица, включающая ip адрес и порт, на базе которых проверяются ipset-ы и фильтры по портам | |
| replay | bool | проигрывание задержанного пакета (replay) | |
| replay_piece | number | номер проигрываемой части | нумерация с 1 |
| replay_piece_count | number | количество проигрываемых частей | |
| replay_piece_last | bool | последняя проигрываемая часть | |
| l7payload | string | тип пейлоада текущего пакета или группы пакетов | если неизвестно - unknown |
| l7proto | string | тип протокола потока | если неизвестно - unknown |
| reasm_data | string | результат сборки многопакетного сообщения, либо сам пейлоад, если сборки не было | пока применяется только для tcp |
| reasm_offset | number | смещение текущего переигрываемого пакета в сборке | пока применяется только для tcp |
| decrypt_data | string | результат сборки и дешифровки пейлоада или пейлоадов нескольких пакетов | применяется для quic |
| tcp_mss | number | MSS противоположного конца tcp соединения | присутствует всегда, только для tcp |
| track | table | данные, привязанные к записи conntrack | только если есть conntrack, может не быть |
| arg | table | все аргументы инстанса и их значения | подстановки % и # уже замещены |
| dis | table | диссект текущего пакета |
Структура диссекта
Диссект включает в себя поля ip, ip6, tcp, udp. ip присутствует в случае ipv4, ip6 - в случае ipv6. По наличиню ip или ip6 можно определить версию ip протокола. По наличию tcp или udp можно определять L4 протокол.
Сами таблицы хедеров копируют названия полей C структур из netinet/{ip,ip6,tcp,udp}.h.
ip адреса и ipv4 options передаются как raw string.
Для преобразования raw ip в текстовую форму можно использовать C функцию ntop. Она определяет версию ip автоматически по размеру.
ipv6 extension headers и tcp options представляются в форме таблиц.
Все числовые многобайтовые значения автоматически переведены из network byte order в machine byte order.
dissect
| Поле | Тип | Описание |
|---|---|---|
| ip | table | заголовок ipv4 |
| ip6 | table | заголовок ipv6 |
| tcp | table | заголовок tcp |
| udp | table | заголовок udp |
| icmp | table | заголовок icmp |
| l4proto | number | IPPROTO_TCP или IPPROTO_UDP |
| transport_len | number | длина пакета без L3 заголовков |
| l3_len | number | длина L3 заголовков, включая ip options и ipv6 extension headers |
| l4_len | number | длина L4 заголовка, включая tcp options |
| payload | string | L4 пейлоад или содержимое пакета после L3 заголовков для raw IP |
ip
| Поле | Описание |
|---|---|
| ip_v | версия ip - 4 |
| ip_hl | длина ip заголовка в блоках по 4 байта. 5 без ip options. |
| ip_tos | type of service. содержит DSCP |
| ip_len | полная длина ip пакета вместе со всеми заголовками и пейлоадом |
| ip_id | идентификация пакета для сборки из фрагментов |
| ip_off | offset фрагмента, флаги MF (more fragments) и DF (dont fragment) |
| ip_ttl | time to live - максимальное количество хопов |
| ip_p | номер ip протокола. как правило IPPROTO_TCP или IPPROTO_UDP |
| ip_sum | чексумма ip хедера |
| ip_src | ip источника |
| ip_dst | ip назначения |
| options | бинарный блок ip options (практически не используется, режется всеми) |
ip6
| Поле | Описание |
|---|---|
| ip6_flow | первые 4 байта ipv6 header : version (6), traffic class, flow label |
| ip6_plen | длина пакета за вычетом базового хедера ipv6 - IP6_BASE_LEN (40) байт |
| ip6_nxt | следующий протокол. если нет exthdr - IPPROTO_TCP (6) или IPPROTO_UDP (17) |
| ip6_hlim | hop limit. имеет тот же смысл, что и TTL в ipv4 |
| ip6_src | ipv6 адрес источника |
| ip6_dst | ipv6 адрес приемника |
| exthdr | массив таблиц расширенных хедеров (индекс от 1) |
ip6 exthdr
| Поле | Описание |
|---|---|
| type | тип хедера : IPPROTO_HOPOPTS, IPPROTO_ROUTING, IPPROTO_DSTOPTS, IPPROTO_MH, IPPROTO_HIP, IPPROTO_SHIM6, IPPROTO_FRAGMENT, IPPROTO_AH |
| next | тип следующего хедера. аналогично type. для последнего хедера может быть IPPROTO_TCP или IPPROTO_UDP |
| data | данные без первых двух байтов - типа и длины |
udp
| Поле | Описание |
|---|---|
| uh_sport | порт источника |
| uh_dport | порт приемника |
| uh_ulen | длина udp - header UDP_BASE_LEN (8) + длина пейлоада |
| uh_sum | чексумма udp |
tcp
| Поле | Описание |
|---|---|
| th_sport | порт источника |
| th_dport | порт приемника |
| th_x2 | зарезервированное поле. используется для расширенных tcp flags |
| th_off | размер tcp хедера в блоках по 4 байта |
| th_flags | tcp флаги : TH_FIN,TH_SYN,TH_RST,TH_PUSH,TH_ACK,TH_URG,TH_ECE,TH_CWR |
| th_seq | sequence number |
| th_ack | acknowledgement number |
| th_win | размер tcp окна |
| th_sum | чексумма tcp |
| th_urp | urgent pointer |
| options | массив таблиц tcp опций (индекс от 1) |
tcp options
| Поле | Описание |
|---|---|
| kind | тип опции : TCP_KIND_END, TCP_KIND_NOOP, TCP_KIND_MSS, TCP_KIND_SCALE, TCP_KIND_SACK_PERM, TCP_KIND_SACK, TCP_KIND_TS, TCP_KIND_MD5, TCP_KIND_AO, TCP_KIND_FASTOPEN |
| data | блок данных опции без kind и length. отсутствует для TCP_KIND_END и TCP_KIND_NOOP |
icmp
Заголовком icmp считается его постоянная часть - первые 8 байт. Они универсальны как для ipv4, так и для ipv6 версии. Остальное содержимое icmp зависит от типа icmp и располагается в payload, включая возможные специальные поля заголовка или обрезанный исходный пакет, на который был сгенерирован icmp.
| Поле | Описание |
|---|---|
| icmp_type | тип icmp |
| icmp_code | код icmp |
| icmp_cksum | чексумма icmp |
| icmp_data | 32-битное поле данных со смещения 4 |
Особенности приема многопакетных пейлоадов
Сборка многопакетного пейлоада называется реасм (reasm - reassemble).
Она производится автоматически C кодом, если встречен пейлоад, требующий сборки, доступен conntrack и не указан запрет на сборку через --reasm-disable.
На текущий момент таких пейлоадов 2 - tls_client_hello и quic_initial. Оба могут содержать постквантовую криптографию kyber, которая не помещается в один пакет.
Для tls_client_hello производится обычная сборка пейлоадов последовательных tcp сегментов и объединение их в единый блок reasm_data.
Для quic_initial отдельные пакеты накапливаются во внутреннем буфере, после чего происходит их дешифровка, объединение и дефрагментация разбросанных по пакетам и разным смещениям частей пейлоада (так делает chrome, чтобы заставить всех не упрощать свои алгоритмы, следовать стандартам и уметь корректно собирать пейлоад по частям).
Пока сборка не финализирована, выполняется накопление пакетов во внутреннем буфере без вызовов Lua. Как только она финализирована, происходит перепроигрывание отдельных частей (replay). В Lua инстансы приходит диссект каждого задержанного пакета, но при этом устанавливаются поля desync.replay=true, desync.replay_piece, desync.replay_count и desync.replay_piece_last.
В случае стандартной tcp сборки выставляется поле desync.reasm_data, содержащее полный блок собранных данных. В desync.dis.payload по-прежнему возвращаются пейлоады отдельных перепроигрываемых пакетов. Для tcp, если нет replay, desync.reasm_data содержит копию desync.dis.payload.
При сборке quic desync.reasm_data отсутствует. Вместо него передается поле desync.decrypt_data, содержащее результат дешифровки и дефрагментации всех пейлоадов, входящих в сборку. Для quic reasm_data содержит tls_client_hello без record layer.
Структура track
Таблица track присутствует в desync только, если для текущего пакета была найдена запись в conntrack.
Она может быть не найдена, если процесс nfqws2 не получил SYN или SYN,ACK пакет.
Например, соединение было установлено ранее, чем был запущен nfqws2, или вы не перехватили SYN и SYN,ACK из ядра,
или если вы принудительно выключили conntrack через --ctrack-disable.
Весь ваш код должен проверять наличие track прежде, чем обращаться к нему, иначе код будет падать с ошибками.
То же самое верно для опциональных полей track. Проверяйте ваш код с --ctrack-disable и на разных протоколах - tcp, udp.
track
| Поле | Тип | Описание | Примечание |
|---|---|---|---|
| incoming_ttl | number | ttl/hl первого входящего пакета по потоку | может не быть, если не определено |
| l7proto | string | протокол потока | есть всегда. если неизвестно - unknown |
| hostname | string | имя хоста. определяется на основе анализа L6/L7 протоколов | появляется только после определения |
| hostname_is_ip | bool | является ли hostname ip адресом | только если есть hostname |
| lua_state | table | таблица для хранения состояния, привязанного к потоку | есть всегда, передается с каждым пакетом потока |
| lua_in_cutoff | bool | отсечение Lua от входящего направления | только для чтения |
| lua_out_cutoff | bool | отсечение Lua от исходящего направления | только для чтения |
| t_start | number | unix time первого пакета потока | включает дробную часть с высокой точностью |
| pos | table | счетчики по различным направлениям | содержит таблицы client, server, direct, reverse |
Таблица track.pos содержит подтаблицы с набором счетчиков по двум направлениям - client и server.
client означает пакеты от клиента, server - пакеты от сервера.
direct and reverse являются просто ссылкам на client и server. Куда указывает direct и reverse зависит
от текущего направления - desync.outgoing и серверного режима - b_server.
direct всегда указывает на текущее направление, reverse - на противоположное.
track.pos содержит еще одно поле - dt. Время получения пакета в секундах с момента t_start.
Включает дробную часть с высокой точностью.
Список полей таблицы счетчиков приведен ниже. Подтаблица tcp присутствует только для tcp протокола.
| Поле | Описание | Примечание |
|---|---|---|
| pcounter | счетчик пакетов | |
| pdcounter | счетчик пакетов с данными | пакеты, у которых размер L4 пейлоада не равен 0 |
| pbcounter | счетчик переданных байт | считаются только размеры пейлоада L4, заголовки - нет |
| ip6_flow | последнее поле ip6.ip6_flow | отсутствует, если неизвестно или ip протокол - не ipv6 |
| tcp.seq0 | начальный sequence соединения | |
| tcp.seq | sequence текущего пакета | |
| tcp.rseq | relative sequence текущего пакета | вычисляется как seq-seq0 |
| tcp.rseq_over_2G | был переход rseq за границу 2 GB | s и p позиции больше нельзя учитывать |
| tcp.pos | relative sequence верхней границы текущего пакета | вычисляется как rseq+payload_size |
| tcp.uppos | максимальный pos в соединении | |
| tcp.uppos_prev | uppos в предыдущем пакете с данными | полезно для определения ретрансмиссий |
| tcp.winsize | последнее поле th_win | без коррекции по scale |
| tcp.scale | последнее значение tcp опции scale | |
| tcp.winsize_calc | коррекция winsize с учетом scale | эффективное значение tcp window size |
| tcp.mss | последнее значение tcp опции mss |
mss, winsize, scale передаются от одной стороны соединения другой стороне, чтобы она знала о допустимых параметрах партнера.
При использовани этих полей важно не перепутать сторону.
Если вам нужно узнать какие по размеру пакеты можно отсылать , вам нужно смотреть противоположную сторону - что она может принять.
mss дублируется в поле desync.tcp_mss независимо от наличия conntrack. Значение там уже рассчитано на то, что оно будет использовано
для вычисления размера пакета для отсылки.
Если вдруг conntrack нет или mss не был согласован сторонами, выставляется значение по умолчанию - DEFAULT_MSS (1220).
При работе с sequence нужно учитывать их 32-битную беззнаковую природу.
Если к 4294967280 (0xFFFFFFF0) прибавить 100, будет не 4294967380 (0x100000054), а 84 (0x54).
Если вы сложите в Lua эти числа - вы получите 4294967380, потому что там используется представление чисел с разрядностью более 32 и со знаком.
Для операций с sequence и их сравнения рекомендуется использовать C функции u32add и bitand.
Например, выражение 0==bitand(u32add(seq1, -seq2), 0x80000000) соответствует seq1>=seq2.
Но последний простой вариант не будет работать корректно, а первый - будет, если seq1 ушел от seq2 не более, чем на 2 GB.
Большее и не отследить через sequence. Нужно всегда учитывать, что при передаче больших объемов данных они не могут служить счетчиком.
p*counter являются 64-битными счетчиками, поэтому у них этой проблемы нет.
Особенности обработки icmp
Некоторые типы icmp могут содержать прикрепленный исходный пакет, на который был сгенерирован icmp. Они называются "related". Такие пейлоады распознаются, по ним ищется исходная запись conntrack. Если она находится, выбирается кэшированный профиль (тот, к которому относится прикрепленный пакет). Направление выбирается как обратное относительно найденной записи. Тип пейлоада устанавливается как "ipv4" или "ipv6", тип протокола сеанса - из профиля исходного пакета. Далее icmp проходит обычным образом по профилю. Нужно учитывать, что в функцию может прийти диссект с icmp, без tcp или udp, но с desync.track
Если icmp не содержит прикрепленного пакета, он инвалидный или не найдена запись conntrack, icmp проходит самостоятельно без track.
conntrack работает только с tcp и udp, он не ведет учет пингов или других типов icmp. При проходе icmp по записи conntrack никакие счетчики не меняются.
Особенности обработки raw ip
Если ip протокол не распознан как tcp,udp,icmp,icmpv6, он считается raw ip. В диссекте выдаются поля ip, ip6, payload. payload включает содержимое пакета после L3 заголовков. desync.track всегда отсутствует.
С интерфейс nfqws2
Перед выполнением --lua-init C код выставляет базовые константы, блобы и C функции.
Базовые константы
| Поле | Тип | Описание | Примечание |
|---|---|---|---|
| qnum | number | номер очереди NFQUEUE | только в Linux |
| divert_port | number | номер порта divert | только в BSD |
| desync_fwmark | number | fwmark для Linux, sockarg для BSD, 0 в Windows | |
| NFQWS2_VER | string | версия nfqws2 | строка, выводимая по --version |
| NFQWS2_COMPAT_VER | number | порядковый номер несовместимых изменений интерфейса с nfqws2 | увеличивается на 1 после каждого изменения |
| b_debug | bool | включен --debug | вывод отладочных сообщений |
| b_daemon | bool | включен --daemon | демонизация процесса, отвязка от tty |
| b_server | bool | включен --server | серверный режим |
| b_ipcache_hostname | bool | включен --ipcache-hostname | кэширование имен хостов, соответствующих IP адресам |
| b_ctrack_disable | bool | включен --ctrack-disable | отключен conntrack |
| VERDICT_PASS VERDICT_MODIFY VERDICT_DROP VERDICT_PRESERVE_NEXT |
number | код вердикта desync функции VERDICT_PRESERVE_NEXT складывается как бит |
|
| DEFAULT_MSS | number | значение MSS по умолчанию | 1220 |
| IP_BASE_LEN | number | базовый размер ipv4 хедера | 20 |
| IP6_BASE_LEN | number | базовый размер ipv6 хедера | 40 |
| TCP_BASE_LEN | number | базовый размер tcp хедера | 20 |
| UDP_BASE_LEN | number | размер udp хедера | 8 |
| TCP_KIND_END TCP_KIND_NOOP TCP_KIND_MSS TCP_KIND_SCALE TCP_KIND_SACK_PERM TCP_KIND_SACK TCP_KIND_TS TCP_KIND_MD5 TCP_KIND_AO TCP_KIND_FASTOPEN |
number | коды типов tcp опций (kinds) | |
| TH_FIN TH_SYN TH_RST TH_PUSH TH_ACK TH_URG TH_ECE TH_CWR |
number | tcp флаги | можно складывать через + |
| IP_MF | number | флаг IP "more fragments" | 0x8000, часть поля ip_off |
| IP_DF | number | флаг IP "dont fragment" | 0x4000, часть поля ip_off |
| IP_RF | number | флаг IP "reserved" | 0x2000, часть поля ip_off |
| IP_OFFMASK | number | битовая маска поля ip_off, соответствующая fragment offset | 0x1FFF |
| IP_FLAGMASK | number | битовая маска поля ip_off, соответствующая IP флагам | 0xE000 |
| IPTOS_ECN_MASK | number | битовая маска поля ip_tos, соответствующая ECN | 0x03 |
| IPTOS_ECN_NOT_ECT | number | Not ECN-Capable Transport | 0x00 |
| IPTOS_ECN_ECT1 | number | ECN Capable Transport(1) | 0x01 |
| IPTOS_ECN_ECT0 | number | ECN Capable Transport(0) | 0x02 |
| IPTOS_ECN_CE | number | Congestion Experienced | 0x03 |
| IPTOS_DSCP_MASK | number | битовая маска поля ip_tos, соответствующая DSCP | 0xFC |
| IP6F_MORE_FRAG | number | бит "More fragment" поля ip6f_offlg из ipv6 fragment header | 0x0001 |
| IPV6_FLOWLABEL_MASK | number | flow label в ip6_flow | 0x000FFFFF |
| IPV6_FLOWINFO_MASK | number | flow label, traffic class в ip6_flow | 0x0FFFFFFF |
| IPPROTO_IP IPPROTO_IPV6 IPPROTO_IPIP IPPROTO_ICMP IPPROTO_ICMPV6 IPPROTO_TCP IPPROTO_UDP IPPROTO_SCTP IPPROTO_HOPOPTS IPPROTO_ROUTING IPPROTO_FRAGMENT IPPROTO_AH IPPROTO_ESP IPPROTO_DSTOPTS IPPROTO_MH IPPROTO_HIP IPPROTO_SHIM6 IPPROTO_NONE |
number | номера IP протоколов | используются в ipv4 и ipv6 |
| ICMP_ECHOREPLY ICMP_DEST_UNREACH ICMP_REDIRECT ICMP_ECHO ICMP_TIME_EXCEEDED<brICMP_PARAMETERPROB ICMP_TIMESTAMP ICMP_TIMESTAMPREPLY ICMP_INFO_REQUEST ICMP_INFO_REPLY |
number | типы icmp | только ipv4 |
| ICMP_UNREACH_NET ICMP_UNREACH_HOST ICMP_UNREACH_PROTOCOL ICMP_UNREACH_PORT ICMP_UNREACH_NEEDFRAG ICMP_UNREACH_SRCFAIL ICMP_UNREACH_NET_UNKNOWN ICMP_UNREACH_HOST_UNKNOWN ICMP_UNREACH_NET_PROHIB ICMP_UNREACH_HOST_PROHIB ICMP_UNREACH_TOSNET ICMP_UNREACH_TOSHOST ICMP_UNREACH_FILTER_PROHIB ICMP_UNREACH_HOST_PRECEDENCE ICMP_UNREACH_PRECEDENCE_CUTOFF |
number | коды icmp для destination unreachable | только ipv4 |
| ICMP_REDIRECT_NET ICMP_REDIRECT_HOST ICMP_REDIRECT_TOSNET ICMP_REDIRECT_TOSHOST |
number | коды icmp для icmp redirect | только ipv4 |
| ICMP_TIMXCEED_INTRANS ICMP_TIMXCEED_REASS |
number | коды icmp для time exceeded | только ipv4 |
| ICMP6_ECHO_REQUEST ICMP6_ECHO_REPLY ICMP6_DST_UNREACH ICMP6_PACKET_TOO_BIG ICMP6_TIME_EXCEEDED ICMP6_PARAM_PROB MLD_LISTENER_QUERY MLD_LISTENER_REPORT MLD_LISTENER_REDUCTION ND_ROUTER_SOLICIT ND_ROUTER_ADVERT ND_NEIGHBOR_SOLICIT ND_NEIGHBOR_ADVERT ND_REDIRECT |
number | типы icmpv6 | только ipv6 |
| ICMP6_DST_UNREACH_NOROUTE ICMP6_DST_UNREACH_ADMIN ICMP6_DST_UNREACH_BEYONDSCOPE ICMP6_DST_UNREACH_ADDR ICMP6_DST_UNREACH_NOPORT |
number | коды icmpv6 для destination unreachable | только ipv6 |
| ICMP6_TIME_EXCEED_TRANSIT ICMP6_TIME_EXCEED_REASSEMBLY |
number | коды icmpv6 для time exceeded | только ipv6 |
| ICMP6_PARAMPROB_HEADER ICMP6_PARAMPROB_NEXTHEADER ICMP6_PARAMPROB_OPTION |
number | коды icmpv6 для parameter problem | только ipv6 |
Стандартные блобы
- fake_default_tls - фейковый tls_client_hello от firefox без kyber, SNI=www.microsoft.com
- fake_default_http - http запрос к "www.iana.org"
- fake_default_quic - 0x40 + 619*0x00
Переменные окружения
| env | Назначение |
|---|---|
| WRITEABLE | Директория, доступная на запись Lua. Результат опции --writeable |
| APPDATALOW | (только Windows) Расположение AppData для low mandatory level. Сюда тоже можно записывать, но предпочтительно использовать --writeable для кросс-платформенности. |
C функции
Логгинг
function DLOG(string)
function DLOG_ERR(string)
function DLOG_CONDUP(string)
Функции выводят строку с добавлением EOL в --debug лог.
- DLOG - обычный вывод
- DLOG_CONDUP - обычный вывод + вывод на консоль, если включено логирование в файл или syslog
- DLOG_ERR - аналогично DLOG_CONDUP, но все выводимое на консоль идет в stderr
Конвертация IP
function ntop(raw_ip)
function pton(string_ip)
- ntop конвертирует raw строку с байтами ipv4 или ipv6 адреса в читаемое строковое представление. версия IP определяется по размеру raw_ip - 4 или 16 байт. При несоответствии размера возвращается nil.
- pton конвертирует строковое представление ipv4 или ipv6 адреса в raw_ip. Если строковое представление не является корректным ipv4 или ipv6 адресом, возвращается nil.
Битовые операции
Lua 5.1, на котором основан luajit, не имеет встроенных битовых операций. Luajit имеет встроенный модуль bitop. Lua 5.3 имеет встроенные битовые операции, но не имеет встроенного модуля bitop. Он может быть подгружен, но только если не использована статическая компиляция и если модуль установлен. nfqws2 на github собирается статически.
При работе с полями сетевых пакетов без битовых операций никуда. Битовые операции и сдвиги обычно реализуются одной машинной командой. Для процессора это родные операции. Заменять их конструкциями, основанных на математике с плавающей точкой неразумно (возведение в степень, деление, округление и тд), особенно в условиях частого отсутствия FPU на роутерах и других embedded устройствах.
Чтобы не зависеть от всей этой неразберихи, в nfqws2 выставляется свой собственный набор битовых функций и функций сдвига, который не зависит от типа движка Lua и его версии. Все типы битовых операций работают с беззнаковыми числами от 8 до 48 бит. При передаче отрицательных чисел они интерпретируются в дополнительном коде. Например, в 32 битах -2 становится 0xFFFFFFFE, в 8 битах - 0xFE. Большая разрядность не поддерживается, поскольку возникают несовместимости между Lua 5.3+ и более старыми версиями. Только в Lua 5.3 реализован тип integer 64 bit. В более старых используется формат плавающей точки double с мантиссой 52 бит, что позволяет передать до 48 бит int.
Стандартные операции сдвига и побитовые логические операции :
function bitlshift(u48, bits)
function bitrshift(u48, bits)
function bitand(u48_1, u48_2, ...., u48_N)
function bitor(u48_1, u48_2, ...., u48_N)
function bitxor(u48_1, u48_2, ...., u48_N)
function bitnot(u48)
function bitnot8(u8)
function bitnot16(u16)
function bitnot24(u24)
function bitnot32(u32)
function bitnot48(u48)
bitand, bitor и bitxor работают с произвольным количеством чисел.
bitnot имеет версии для различных разрядностей. Вариант bitnot является синонимом bitnot48.
Операции получения и установки отдельных битов :
function bitget(u48, bit_from, bit_to)
function bitset(u48, bit_from, bit_to, set)
- bitget получает число из диапазона битов u48 с номерами от bit_from до bit_to. нумерация битов с 0.
- bitset записывает число set в диапазон битов u48 с номерами от bit_from до bit_to. нумерация битов с 0. старшие биты set, выходящие за пределы (bit_to-bit_from), игнорируются.
Операции с беззнаковыми числами
При операциях с числями без знака всегда важна разрядность. От нее зависит результат. Поэтому все функции имеют в своем названии разрядность. При передаче аргументов, выходящих за пределы разрядности, вызывается error.
uX
function u8(raw_string[, offset])
function u16(raw_string[, offset])
function u24(raw_string[, offset])
function u32(raw_string[, offset])
function u48(raw_string[, offset])
Эти функции используются для извлечения числовых полей в формате big endian из raw строки. offset - номер байта от начала raw строки, начиная с 1. Аналогичные встроенные средства (string.unpack) есть только в Lua 5.3.
buX
function bu8(u8)
function bu16(u16)
function bu24(u24)
function bu32(u32)
function bu48(u48)
Преобразуют число в raw строку в формате big endian.
Чтобы собрать структуру из числовых полей, можно использовать обычную операцию конкатенации строк ..
swapX
function swap16(u16)
function swap24(u24)
function swap32(u32)
function swap48(u48)
Инвертируют порядок следования байт в u16, u24, u32 или u48. Если в вашей структуре порядок байт little endian, можно использовать uX/buX + swapX.
uXadd
function u8add(u8_1, u8_2, ...., u8_N)
function u16add(u16_1, u16_2, ...., u16_N)
function u24add(u24_1, u24_2, ...., u24_N)
function u32add(u32_1, u32_2, ...., u32_N)
function u48add(u48_1, u48_2, ...., u48_N)
Функции для сложения произвольного количества беззнаковых чисел указанной разрядности. Операнды должны вписываться в указанную разрядность, иначе вызывается error. Перенос старшего разряда игнорируется.
Целочисленное деление
divint
Встроенное целочисленное деление есть только в Lua 5.3+, где есть тип данных integer. Чтобы не возиться с округлением, целочисленное деление реализовано C функцией :
function divint(dividend, divisor)
В этой функции нет ограничения на разрядность. Внутри используется тип int64_t. В Lua 5.3+ потерь разрядности нет, в более старых будут искажения чисел, если разрядность операндов или результата превышает размер мантиссы типа double.
Генерация случайных данных
brandom
Функции генерируют raw строку указанного размера , состоящую из случайных байт. Случайные данные не являются криптографически стойкими.
function brandom(size)
function brandom_az(size)
function brandom_az09(size)
- brandom возвращает байты от 0 до 255
- brandom_az - символы от 'a' до 'z'
- brandom_az09 - символы от 'a' до 'z' и числа от '0' до '9'
Парсинг
parse_hex
function parse_hex(hex_string)
Возвращает raw строку с байтами из hex строки. "1234ABCD" => "\0x11\x34\0xAB\0xCD". В случае некорректности hex_string возвращается nil.
Криптография
В Lua есть стандартный модуль биндинга к openssl, откуда можно взять широкий набор криптографических функций. Но завязываться на внешние модули нельзя - Lua обычно линкуется статически без возможности загрузки внешних модулей. Не должно быть лишних зависимостей и дополнительных файлов. openssl имеет размер несколько Mb, что критично для embedded систем.
nfqws2 не использует никакие криптобиблиотеки, но имеет минимальный набор криптографических операций для работы с некоторыми протоколами (QUIC). Эти функции выставляются в Lua и могут использоваться для любых целей.
bcryptorandom
function bcryptorandom(size)
Генерирует raw строку - криптографически стойкий блок случайных данных указанного размера.
Источник - getrandom() (Linux), getentropy() (BSD), /dev/random.
/dev/urandom используется как fallback, если остальные варианты не работают или блокируют.
Отсутствие данных в /dev/random - типичная проблема для Android.
bxor,bor,band
function bxor(data1, data2)
function band(data1, data2)
function bor(data1, data2)
Возвращает результат побайтового xor,and или or между raw строками data1 и data2. data1 и data2 должны быть равны по длине, иначе вызывается error.
hash
function hash(hash_type, data)
Возвращает raw строку - hash от блока данных - raw строки data. hash_type может быть "sha256" или "sha224".
aes
function aes(encrypt, key, data)
Простое шифрование или дешифрование одного блока aes.
- encrypt - true - encrypt , false - decrypt
- key - raw строка. размер должен быть 16,24,32 байт, что соответствует aes128,aes192,aes256
- data - raw строка. размер должен быть 16 байт
- возвращается raw строка 16 байт с результатом операции
- в случае неверных размеров key или data вызывается error
aes_gcm
function aes_gcm(encrypt, key, iv, data[, associated_data])
Шифрование в режиме aes-gcm.
- encrypt - true - encrypt , false - decrypt
- key - raw строка. размер key должен быть 16,24,32 байт, что соответствует aes128,aes192,aes256
- iv - raw строка 12 байт. ОБЯЗАТЕЛЬНО ГЕНЕРИРУЕТСЯ СЛУЧАЙНО ДЛЯ КАЖДОГО ШИФРУЕМОГО БЛОКА ДАННЫХ И ПЕРЕДАЕТСЯ ВМЕСТЕ С НИМ. При ПОВТОРНОМ ИСПОЛЬЗОВАНИИ iv С ТЕМ ЖЕ КЛЮЧОМ ШИФРОВАНИЕ ЛЕГКО ВЗЛАМЫВАЕТСЯ. Для генерации iv следует использовать bcryptorandom.
- data - raw строка произвольного размера. Шифр использует гамму, поэтому исходные данные не привязаны к размеру блока AES.
- associated_data - нешифруемые данные, передаваемые вместе с шифрованным сообщением и участвующие в подсчете atag. Может быть nil.
- возвращается 2 значения : raw строка - блок шифрованных данных и raw строка atag (authentication tag). atag может быть передан вместе с шифрованным сообщением, iv и associated_data для проверки их целостности.
- в случае неверных размеров key или iv вызывается error
aes_ctr
function aes_ctr(key, iv, data)
Шифрование в режиме aes-ctr.
- key - raw строка. размер key должен быть 16,24,32 байт, что соответствует aes128,aes192,aes256
- iv - raw строка 16 байт. ОБЯЗАТЕЛЬНО ГЕНЕРИРУЕТСЯ СЛУЧАЙНО ДЛЯ КАЖДОГО ШИФРУЕМОГО БЛОКА ДАННЫХ И ПЕРЕДАЕТСЯ ВМЕСТЕ С НИМ. При ПОВТОРНОМ ИСПОЛЬЗОВАНИИ iv С ТЕМ ЖЕ КЛЮЧОМ ШИФРОВАНИЕ ЛЕГКО ВЗЛАМЫВАЕТСЯ. Для генерации iv следует использовать bcryptorandom.
- data - raw строка произвольного размера. Шифр использует гамму, поэтому исходные данные не привязаны к размеру блока AES.
- шифрование работает по принципу xor с гаммой, поэтому симметрично. шифрование и дешифрование - одна операция.
- возвращается raw строка - блок шифрованных данных
- в случае неверных размеров key или iv вызывается error
hkdf
function hkdf(hash_type, salt, ikm, info, okm_len)
HKDF - HMAC-based Key Derivation Function. Генератор ключей на базе произвольных входных данных - input keying material (ikm). Функция включает extraction и expansion.
- hash_type может быть "sha256" или "sha224"
- salt - raw строка произвольного размера, может быть nil. "соль". несекретная информация. позволяет сделать результат разным на тех же ikm для разных значений salt. если nil, используется блок нулевых байт размером, равным размеру результата hash функции.
- ikm - raw строка - input keying material. на базе этих данных, salt и info генерируется okm - output keying material
- info - raw строка произвольного размера, может быть nil. аналогично salt, но salt подмешивается на extraction phase, а info - на expansion. если nil, то используется info нулевого размера.
- okm_len - требуемая длина okm - output keying material
- возвращается raw строка - okm
Компрессия
gunzip
function gunzip_init([windowBits])
function gunzip_end(zstream)
function gunzip_inflate(zstream, compressed_data[, expected_uncompressed_chunk_size])
- gunzip_init создает и возвращает контекст gzip потока для последующих вызовов других функций. Значение windowBits см. в документации по zlib (по умолчанию 47).
- gunzip_end освобождает контекст gzip. может быть освобожден сборщиком мусора, но лучше вызывать явно.
- gunzip_inflate разжимает очередную часть зипованных данных. Данные можно скармливать частями. Разжатые части конкатенируются для получения полных данных. Возвращается 2 аргумента : расжатые данные и bool признак конца gzip. В случае испорченных данных или при нехватке памяти возвращается nil.
- expected_uncompressed_chunk_size - необязательный параметр для оптимизации выделения памяти под разжимаемые данные. Если буфера не хватает, вызываются realloc, копирующие блоки памяти и влияющие на производительность. Размер следует выбирать согласно ожидаемой степени сжатия с небольшим запасом. По умолчанию - четырехкратный размер compressed_data.
gzip
function gzip_init([windowBits[, level[, memlevel]]])
function gzip_end(zstream)
function gzip_deflate(zstream, uncompressed_data[, expected_compressed_chunk_size])
- gzip_init создает и возвращает контекст gzip потока для последующих вызовов других функций. Значение windowBits см. в документации по zlib (по умолчанию 31). level - уровень сжатия от 1 до 9 (по умолчанию 9), memlevel - допустимый уровень использования памяти от 1 до 8 (по умолчанию 8).
- gzip_end освобождает контекст gzip. может быть освобожден сборщиком мусора, но лучше вызывать явно.
- вместо явного вызова gzip_end в Lua 5.5+ для контекста можно использовать to-be-closed variable
- gzip_deflate cжимает очередную часть данных. Данные можно скармливать частями. Cжатые части конкатенируются для получения полных данных. Для финализации потока по окончанию скармливания данных функция должна быть вызвана с uncompressed_data=nil или uncompressed_data="". Возвращается 2 аргумента : сжатые данные и bool признак конца gzip. При ошибках gzip или нехватке памяти возвращается nil.
- expected_compressed_chunk_size - необязательный параметр для оптимизации выделения памяти под cжимаемые данные. Если буфера не хватает, вызываются realloc, копирующие блоки памяти и влияющие на производительность. Размер следует выбирать согласно ожидаемой степени сжатия с небольшим запасом. По умолчанию - половина размера uncompressed_data.
Системные функции
uname
function uname()
Возвращает то же самое, что и команда uname в shell - название ядра ОС. "Linux", "FreeBSD", "OpenBSD". В Windows возвращается строка, начинающаяся с "CYGWIN", далее следует версия.
clock_gettime
function clock_gettime()
function clock_getfloattime()
Узнать точное время. clock_gettime возвращает 2 значения - unixtime в секундах и наносекунды. Встроенная функция os.time() не выдает наносекунды.
clock_getfloattime возвращает unixtime в формате с плавающей точкой. Наносекунды идут в дробную часть.
getpid
function getpid()
function gettid()
- getpid() возвращает идентификатор текущего процесса - PID
- gettid() возвращает идентификатор текущего потока - TID
stat
function stat(filename)
В случае успеха возвращает таблицу :
| Поле | Тип | Описание |
|---|---|---|
| type | string | тип файла : file, dir, socket, blockdev, chardev, fifo, unknown |
| size | number | размер файла |
| mtime | number | unixtime время модификации в формате с плавающей точкой |
| inode | number | inode. на Windows не влезает в тип number luajit, но влезает в integer Lua 5.3+ |
| dev | number | device id |
В случае неудачи возвращает 3 значения : nil, строка ошибки, код ошибки errno.
Опции по работе с пакетами
В следующих функциях будут использоваться стандартные наборы опций - rawsend и reconstruct. Они представляют собой таблицы со специфическими полями. Если nil, считается, что ни одно поле не задано.
standard reconstruct
Опции реконструкции диссектов - reconstruct_opts. Реконструкция - это сборка raw пакета из диссекта.
| Поле | Тип | Описание |
|---|---|---|
| keepsum | bool | взять чексумму L4 из диссекта, не считать и не портить |
| badsum | bool | испортить L4 checksum. посчитать чексумму и сделать xor со случайным значением от 1 до 0xFFFF |
| ip6_preserve_next | bool | использовать значения "next" из ip6.exthdr |
| ip6_last_proto | number | если ip6_preserve_next=false, IP протокол последнего exthdr |
При сборке ipv6 по умолчанию цепочка ip протоколов в exthdr собирается автоматически. У каждого exthdr есть поле type, поэтому понятно что вписывать в предыдущий exthdr, либо в основной ip6 хедер. next протокол последнего exthdr устанавливатеся как IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP, IPPROTO_ICMPV6 в зависимости от наличия в диссекте таблиц tcp, udp, icmp, ip6. В большинстве случаев это удобно, поскольку вам при вставлении exthdr не надо реконструировать всю цепочку next протоколов и не надо заполнять поля next. За вас это сделает реконструктор диссекта.
Режим ip6_preserve_next используется, если у вас специальная цель, требующая ручной крафтинг полей next protocol. В этом случае поля next в exthdr и ip6.ip6_nxt оставляются как есть.
Если не задан ip6_preserve_next и задан ip6_last_proto, ip протокол полезной нагрузки замещается на ip6_last_proto.
badsum вынесен в реконструкцию, поскольку чексуммы tcp и udp считаются на базе всего IP пакета. В сумме участвуют элементы хедера ip/ip6, весь хедер tcp и сам пейлоад. Поэтому по отдельным частям гарантированно испортить чексумму невозможно. Что бы вы туда не вписали, существует маленькая вероятность (1/65536), что она окажется верной.
Пакет формируется на базе L3 заголовка, далее к нему пристыковывается L4 заголовок - tcp,udp,icmp, если присутствует, в конце идет payload. В процессе реконструкции это всегда так - поля ip protocol не учитываются. Поэтому можно конструировать tcp,udp,icmp пакеты с измененным ip protocol.
standard rawsend
Опции отсылки raw пакетов - rawsend_opts
| Поле | Тип | Описание |
|---|---|---|
| repeats | number | количество повторов отсылки одного и того же пакета |
| fwmark | number | fwmark исходящего пакета. только для linux. по умолчанию 0. бит desync_mark устанавливается принудительно |
| ifout | string | исходящий интерфейс. может использоваться или не использоваться в разных ситуациях |
ifout всегда следует передавать таким, каким он пришел в диссекте.
Для Windows правильный ifout обязателен. На BSD он не используется.
На Linux используется только если включены опции --bind-fix4 или --bind-fix6 в зависимости от версии ip.
fwmark желательно передавать таким, каким он пришел в диссекте. При особых случаях, когда на этом построены ваши правила таблиц, можно домешивать туда свои биты.
repeats шлют бинарно идентичный пакет указанное количество раз, не разбираясь в его содержимом. Никаких изменений , в том числе ip_id, не производится. Если вам нужно изменение ip_id, можно его сделать 0, тогда Windows сама допишет увеличивающиеся значения. Другие системы - нет. Если вам надо на любых системах управлять ip_id, то repeats - не вариант.
Диссекция и реконструкция
Диссекция - процесс получения структурированного представления raw ip пакета. Реконструкция - обратный процесс - получение raw ip из диссекта.
dissect
function dissect(raw_ip)
Возвращает таблицу - диссект пакета raw_ip. Это та же самая операция, что происходит автоматически до вызова desync функций по профилю. Они получают уже готовый диссект.
reconstruct_dissect
function reconstruct_dissect(dissect[, reconstruct_opts])
Возвращает raw_ip. Все чексуммы считаются автоматически. L4 чексуммы портятся, если задан badsum в reconstruct_opts.
Реконструкция диссектов с IP фрагментацией происходит особым образом - с участием как Lua, так и C кода. Lua код должен подготовить диссект полного пакета, подлежащего фрагментации, но заполнить некоторые поля такими, какими они должны быть во фрагменте.
- ipv4 : ip.ip_len должен быть рассчитан таким, каким он должен быть во фрагменте. По ip.ip_len С код определяет размер фрагментированной части. Поле ip.ip_off должно содержать offset фрагмента и признак IP_MF, если это не последний фрагмент. ip.ip_id должен быть не 0.
- ipv6 : нужно вставить в ip6.exthdr fragment header, в котором заполнить ident, offset и бит IP6F_MORE_FRAG, если это не последний фрагмент. ip6.ip6_len должен быть рассчитан таким, каким он должен быть во фрагменте. По этой длине C код определяет размер фрагмента. Позицию fragment header выбирает Lua код. Все, что после fragment header, считается fragmentable part.
Если C код видит признаки необходимости фрагментации, он проверяет корректность рассчитанных длин и смещений, и если они корректны, после реконструкции сдвигает содержимое raw пакета в буфере реконструкции, чтобы получился фрагмент с нужными данными.
reconstruct_hdr
function reconstruct_tcphdr(tcp)
function reconstruct_udphdr(udp)
function reconstruct_icmphdr(icmp)
function reconstruct_iphdr(ip)
function reconstruct_ip6hdr(ip6 [,reconstruct_opts])
Реконструкция соответствующих raw заголовков из таблиц диссекта. Возвращает raw вариант заголовка.
- реконструкция ip6 задействует reconstruct_opts. из них берется ip6_preserve_next и ip6_last_proto.
- чексумма ip header считается автоматически, поскольку ни от чего больше не зависит
- чексуммы tcp, udp и icmp не считаются, поскольку зависят от других компонент
csum_fix
function csum_ip4_fix(raw_ipv4_header)
function csum_tcp_fix(raw_ip_header, raw_tcp_header, payload)
function csum_udp_fix(raw_ip_header, raw_udp_header, payload)
function csum_icmp_fix(raw_ip_header, raw_icmp_header, payload)
Функции для выправления чексумм. Поскольку строки в Lua immutable, они возвращают копию соответствующего заголовка с исправленной чексуммой.
- с csum_ipv4_fix все просто. на входе ip заголовок, на выходе ip заголовок с исправленной суммой
- csum_tcp_fix, csum_udp_fix, csum_icmp_fix берут raw ip заголовок (ipv4 или ipv6), tcp/udp/icmp заголовок и пейлоад. версия ip определяется автоматически. чексумма считается на базе L3, L4 заголовков и пейлоада.
Прямая реконструкция отдельных заголовков нужна редко. Обычно все задачи выполняют функции по работе с диссектами.
conntrack
function conntrack_feed(dissect/raw_packet[, reconstruct_opts])
"Скормить" conntrack пакет таким образом, как если бы он пришел из сети и был проанализирован. Пакет может быть таблицей-диссектом или raw string. reconstruct_opts имеет смысл только для диссектов. Возвращается 2 значения - track и bool признак "outgoing". outgoing принимает значение true, если создается новая запись conntrack и она удовлетворяет признакам клиента - SYN в случае tcp, любой пакет в случае udp. Если запись уже существует, outgoing = true, если запись была найдена по прямой паре - src_ip, src_port, dst_ip, dst_port. Если запись была найдена по обратной паре - dst_ip, dst_port, src_ip, src_port, outgoing = false.
Функция может пригодиться, если выполняется обфускация и передача данных в искаженном виде. Например, tcp преобразуется в icmp или портится флаг SYN. Принимающий конец выдаст первый искаженный пакет без track, потому что это либо не tcp и не udp, либо не было валидного tcp handshake. После деобфускации вы можете исправить ситуацию, выполнив conntrack_feed и присвоив desync.track его результат.
Если conntrack выключен или пакет не является валидным tcp или udp, возвращается nil.
Получение ip адресов
function get_source_ip(target)
Получить из таблицы маршрутизации ip адрес источника, который будет использован для подключению к ip адресу target. target представляет собой raw string ipv4 или ipv6. Функция возвращает raw string адреса источника или nil, если destination unreachable.
function get_ifaddrs()
Получить список IP адресов на всех интерфейсах (аналог ip addr, ifconfig, ipconfig). Возвращается таблица, индексы которой являются именами интерфейсов. Для Windows имена интерфейсов представлены строками "число.число" - IfIdx.SubIfIdx - как раз то, что выдает и берет WinDivert.
Содержимое интерфейса :
| Поле | Тип | Описание |
|---|---|---|
| index | number | индекс интерфейса |
| mtu | number | MTU. для loopback может быть 64K или даже 0xFFFFFFFF |
| flags | number | битовые флаги. зависят от ОС |
| ssid | string | SSID wifi сети, если известен. имена сетей получаются только при использовании --filter-ssid |
| guid iftype index6 speed_xmit speed_recv metric4 metric6 conntype |
number | (только Windows) дополнительные поля из GetAdaptersAddresses() |
| addr | table | таблица адресов с числовыми индексами от 1 |
Содержимое адреса :
| Поле | Тип | Описание |
|---|---|---|
| addr | string | ipv4 или ipv6 адрес |
| netmask | string | маска подсети |
| broadcast | string | (только ipv4) broadcast адрес |
| dst | string | содержимое ifa_dstaddr из getifaddrs() |
Единственное гарантированное поле - addr. Остальных может не быть.
Прием и отсылка пакетов
rawsend
function rawsend(raw_data[, rawsend_opts])
function rawsend_dissect(dissect[, rawsend_opts[, reconstruct_opts]])
- rawsend работает с raw строкой, содержащий полностью собранный ipv4 или ipv6 пакет
- rawsend_dissect собирает пакет из диссекта и отправляет
- dissect представляет собой таблицу, описанную в соответствующем разделе
raw_packet
function raw_packet(ctx)
Lua функции получают готовый диссект текущего пакета при вызове. raw представление требуется редко, поэтому в целях экономии ресурсов оно не выдается в desync. Его можно получить по запросу через функцию raw_packet.
Работа с пейлоадами
маркеры
- Абсолютный положительный маркер - числовое смещение внутри пейлоада.
- Абсолютный отрицательный маркер - числовое смещение внутри пейлоада от следующего за концом байта. -1 указывает на последний байт.
- Относительный маркер - положительное или отрицательное смещение относительно логической позиции внутри пейлоада.
Относительные позиции :
- method - начало метода HTTP ('GET', 'POST', 'HEAD', ...). Метод обычно всегда находится на позиции 0, но может сместиться из-за methodeol. Тогда позиция может стать 1 или 2.
- host - начало имени хоста
- endhost - байт, следующий за последним байтом имени хоста
- sld - начало домена 2 уровня в имени хоста
- endsld - байт, следующий за последним байтом домена 2 уровня в имени хоста
- midsld - середина домена 2 уровня в имени хоста
- sniext - начало поля данных SNI extension в TLS. Любой extension состоит из 2-байтовых полей type и length, за ними идет поле данных.
- extlen - поле длины TLS extensions
Относительные маркеры работают с логическими элементами отдельных известных пейлоадов, поэтому они не будут работать с чем попало.
Пример списка маркеров : 100,midsld,sniext+1,endhost-2,-10.
resolve_pos
Задача следующих функций - преобразовать маркеры в абсолютные позиции.
function resolve_pos(blob,l7payload_type,marker[,zero_based_pos])
function resolve_multi_pos(blob,l7payload_type,marker_list[,zero_based_pos])
function resolve_range(blob,l7payload_type,marker_list[,strict,zero_based_pos])
- resolve_pos работает с единственным маркером. если маркер не ресолвится, возвращается nil.
- resolve_multi_pos работает со списком маркеров через запятую. возвращает массив уникальных абсолютных позиций. если некоторые маркеры не ресолвятся - их не будет в результате.
- resolve_range ресолвит список из ровно 2 маркеров, представляющих собой диапазон внутри пейлоада. Если strict = true, и любой маркер не ресолвится, возвращается nil. Иначе если первый маркер не ресолвится - он заменяется на 0. Если не ресолвится второй маркер - он заменяется на длину пейлоада. Если не ресолвятся оба - возвращается nil.
- если задано zero_based_pos=true, все позиции начинаются с 0, иначе с 1, как это принято в Lua.
- при невалидных значениях l7payload_type, marker, marker_list, если количество маркеров не равно 2 для resolve_range - вызывается error
tls_mod
function tls_mod(blob, modlist[, payload])
- blob - блоб, с которым производится действие
- payload - содержимое, под которое модифицируется blob. для разных модов может быть произвольным или требуется валидный tls.
- modlist - список модификаций через запятую
Моды :
- rnd - заполнить поля random и session id случайными данными
- dupsid - скопировать session id с payload. выполняется после rnd, требует валидного tls в payload, иначе не применяется
- rndsni - замена sni на случайный. если длина оригинального sni >=7 символов, выбирается случайный поддомен из известных 3-буквенных TLD. иначе без точки случайные символы
[a-z][a-z0-9]* - sni=domain.com - замена sni на конкретный домен
- padencap - подкорректировать blob таким образом, чтобы payload стал частью padding extension. payload может быть произвольным.
Управление выполнением инстансов
instance_cutoff
function instance_cutoff(ctx[, outgoing])
Добровольное само-отсечение инстанса по выбранному направлению. Инстанс перестает вызываться в рамках текущего потока.
- outgoing = true - исходящее направление
- outgoing = false - входящее направление
- outgoing = nil - оба направления
lua_cutoff
function lua_cutoff(ctx[, outgoing])
Аналогично instance_cutoff, но от потока отсекается весь профиль.
При смене профиля после получения hostname или обнаружения протокола потока lua cutoff сбрасывается,
поскольку в новом профиле совершенно другой набор инстансов, которые не просили их отсекать.
Состояние lua cutoff может случиться и естественным образом, если все инстансы превысили верхнюю границу range или самоотсекли себя от направления.
execution_plan
function execution_plan(ctx)
Получить массив информации о всех последующих , еще не выполненных, инстансах текущего профиля, их внутрипрофильных фильтрах и аргументах.
Элемент plan
| Поле | Тип | Описание |
|---|---|---|
| func | string | имя desync функции |
| func_n | number | номер инстанса внутри профиля |
| func_instance | string | название инстанса. производная имени функции, номера инстанса и номера профиля |
| range | table | эффективный диапазон счетчиков --in-range или --out-range в зависимости от текущего направления |
| payload | table | эффективный фильтр payload . таблица с индексами - названиями типа пейлоада |
| payload_filter | string | эффективный фильтр payload . список названий пейлоадов через запятую (иное представление payload) |
range
| Поле | Тип | Описание |
|---|---|---|
| from | table | позиция нижней границы |
| to | table | позиция верхней границы |
| upper_cutoff | bool | true = верхняя граница невключительна, false = включительна |
pos - from,to
| Поле | Тип | Описание |
|---|---|---|
| mode | string | режим счетчика - a, x, n, d, b, s, p |
| pos | number | значение счетчика |
execution_plan_cancel
function execution_plan_cancel(ctx)
Однократная отмена выполнения всех следующих инстансов внутри профиля. Инстанс, выполняющий отмену, берет на себя координацию дальнейших действий и называется оркестратором.
Библиотека базовых функций zapret-lib.lua
Почти каждая функция имеет подробные комментарии о своем предназначении и параметрах. Чтение Lua кода и комментариев позволит лучше понять для чего нужна конкретная функция и как ее вызывать.
Базовые desync функции
Их можно в готовом виде использовать в --lua-desync.
luaexec
function luaexec(ctx, desync)
Выполнить произвольный Lua код, заданный в аргументе "code". Код может адресовать таблицу desync - она временно присваивается глобальной переменной desync, а по завершению кода глобальная переменная убирается.
Пример : --lua-desync=luaexec:code="desync.rnd=brandom(math.random(5,10))"
pass
function pass(ctx, desync)
Ничего не делать, только вывести в debug log сообщение "pass".
pktdebug
function pktdebug(ctx, desync)
Вывести структуру desync в debug log.
argdebug
function argdebug(ctx, desync)
Вывести таблицу аргументов в debug log.
posdebug
function posdebug(ctx, desync)
Вывести в debug log информацию о текущих conntrack позициях по прямому и обратному направлению.
detect_payload_str
function detect_payload_str(ctx, desync)
- arg: pattern - подстрока для поиска в desync.dis.payload
- arg: payload - присвоить это значение desync.l7payload в случае наличия подстроки
- arg: undetected - если присутствует, присвоить это значение desync.l7payload в случае отсутствия подстроки
Пример простейшего протокольного детектора. Ищет подстроку pattern в пейлоаде, в случае нахождения присваивает desync.l7payload = desync.arg.payload , иначе если есть desync.arg.undetected, присваивает desync.l7payload = desync.arg.undetected.
Подобного рода протокольные детекторы не имеют никакой силы для C кода. Он ничего не знает о вашем протоколе и вашем типе пейлоада. Ваше значение нельзя указать в параметре --payload. Но можно использовать payload фильтры многих desync функций.
desync_orchestrator_example
function desync_orchestrator_example(ctx, desync)
Тестовый оркестратор. Ничего специального не делает, только выполняет execution plan в исходном виде.
Служебные функции
var_debug
function var_debug(v)
Выводит в debug log информацию о параметре v - тип и значение. Если это таблица, происходит рекурсивный проход по вложенным значениям и таблицам, информация представляется в виде дерева.
deepcopy
function deepcopy(orig)
Копирует переменную orig. Основная цель - создать копию таблицы со всеми подтаблицами рекурсивно. Таблицы в Lua передаются по ссылке. Через какую бы переменную вы не изменяли таблицу, таблица существует только одна. Изменения будут видны через любые переменные, на нее ссылающиеся. Чтобы сделать реальную копию, нужно создать новую таблицу и присвоить ей все поля исходной таблицы, кроме подтаблиц. Подтаблицы надо точно так же копировать рекурсивно. Этим и занимается функция deepcopy.
Простые типы в Lua присваиваются по значению. Все строки хранятся в едином пуле, где исключено дублирование по содержимому. Строковые переменные ссылаются на пул. Строки менять невозможно, они - immutable, можно только присваивать другие строки. Если новая строка будет иной по значению, и ее нет в пуле, будет создан новый элемент пула. Иначе будет присвоена ссылка на существующий элемент.
logical_xor
function logical_xor(a,b)
Возвращает результат логического xor a и b. result = a and not b or not a and b
array_search
function array_search(a, v)
function array_field_search(a, f, v)
Линейный поиск в таблице a значения v. array_field_search предполагает, что элемент таблицы a - таблица, поиск идет по полю f.
Работа со строками
in_list
function in_list(s, v)
find_next_line
Включена ли строка v в список строк через запятую s. Например, abc включено в список xyz,abc,12345.
function find_next_line(s, pos)
Работает с многострочным текстом s. Строки разделяются EOL - '\n' или '\r\n'. Возвращается 2 значения - позиция начала строки и начала следующей строки, либо конца текста s, если строк больше нет.
Обслуживание raw string
hex
function string2hex(s)
function has_nonprintable(s)
function make_readable(s)
function str_or_hex(s)
function hexdump(s, max)
function hexdump_dlog(s)
- string2hex преобразует raw строку в символьное hex представление. байты разделены пробелами. "\xAB\xCD\x01\0x2" => "AB CD 01 02"
- has_nonprintable возвращает true, если в строке s есть символы, кроме 0x20-0x7F, '\n', '\r', '\t'
- make_readable заменяет все символы, кроме 0x20-0x7F, точками
- str_or_hex возвращает саму строку, если has_nonpritable(s) = false, иначе string2hex(s)
- hexdump преобразует начальные байт raw строки s (до max байт) в hex строку + результат make_readable. Классический hex dump.
- hexdump_dlog выполняет hexdump и выводит результат в debug log
pattern
function pattern(pat, offset, len)
pattern - это часть условно бесконечно повторяющейся raw строки pat, начинающаяся с позиции offset (нумерация с 1) и длиной len
blob
function blob(desync, name[, def])
function blob_or_def(desync, name[, def])
- blob - стандартная функция получения блоба. Если name начинается с
0x, то дальнейшее интерпретируется как HEX строка. Иначе читается переменная name сначала в desync. Если там нет, берется глобальная переменная. Если и ее нет, берется значение def. Если name = nil или пустая строка, вызывается error. - blob_or_def - возвращает def, если name = nil, иначе аналогично blob
function barray(a, packer)
function btable(a, packer)
- barray использует только числовые индексы, начиная с 1. порядок соблюдается
- btable использует все индексы, но не гарантирует порядок
Упаковка элементов массива a в порядке возрастания индекса от 1 до последнего.
packer - функция, берущая элемент a и возвращающая raw string.
Для числовых массивов в качестве packer можно использовать функции паковки чисел.
Возвращается raw string.
Обслуживание tcp sequence numbers
function seq_ge(seq1, seq2)
function seq_gt(seq1, seq2)
function seq_lt(seq1, seq2)
function seq_le(seq1, seq2)
function seq_within(seq, seq_low, seq_hi)
function is_retransmission(desync)
- seq_{ge|gt|lt|le} выполняет сравнение sequence numbers в пределах 2 GB. если разница больше - результат неверный. ge означает
>=, gt>, le<=, lt<. - seq_within -
seq_low <= seq <= seq_hi - is_retransmission - является ли текущий диссект tcp ретрансмиссией
Обслуживание позиций
function pos_counter_overflow(desync, mode[, reverse])
function pos_get_pos(track_pos, mode)
function pos_get(desync, mode[, reverse])
function pos_check_from(desync, range)
function pos_check_to(desync, range)
function pos_check_range(desync, range)
function pos_range_str(range)
function pos_str(desync, pos)
Параметр mode содержит строку с одной буквой режима счетчика - 'a','x','n','d','b','s','p'. По умолчанию функции работают с текущим направлением. Если есть параметр reverse и он задан как true, берется противоположное направление.
- pos_counter_overflow - true, если mode = 's' или 'p' и произошел выход relative tcp sequence за пределы 2 GB. Счетчики больше не могут использоваться.
- pos_get_pos - получить значение счетчика mode из таблицы счетчиков
track_pos.track_posможет бытьdesync.track.pos.{direct,reverse,client,server} - pos_get - получить значение счетчика mode по текущему или противоположному направлению
- pos_check_from - проверить удовлетворяет ли текущая позиция нижней границе range
- pos_check_to - проверить удовлетворяет ли текущая позиция верхней границе range
- pos_range - проверить удовлетворяет ли текущая позиция range (нижней и верхней границе)
- pos_str - преобразование таблицы позиции pos в стандартную строковую форму
<mode><pos>, напримерs100. - pos_range_str - преобразование таблицы диапазона range в стандартную строковую форму
<mode_from><pos_from>(-|<)<mode_to><pos_to>, напримерd1-p5000.
Диссекция
Диссекция - это разбор некоторого сообщения для представления в структурированной форме.
dissect_url
function dissect_url(url)
Возвращает таблицу, где разобраны части URL вида proto://creds@domain:port/uri.
Если какая-либо из частей отсутствует, соответствующего поля нет в таблице.
Пример разборки `https://user:pass@domain.com:12345/my_uri/script.php?a=1&b=3`
.proto string https .creds string user:pass .domain string domain.com .port string 12345 .uri string /my_uri/script.php?a=1&b=3
dissect_nld
function dissect_nld(domain, level)
Получение домена уровня level из domain. level=2 'www.microsoft.com' => 'microsoft.com'. Если уровня level нет, возвращается nil.
dissect_http
function http_dissect_req(http)
function http_dissect_reply(http)
function http_reconstruct_req(hdis[, unixeol])
Разборка HTTP запроса или ответа http. http представляет собой многострочный текст. Разборка представляет собой таблицу с вложенными подтаблицами. В заголовках выдаются позиции начала и конца названия заголовка и самого значения. Все позиции - внутри строки http.
Для нахождения хедеров по названию используйте array_field_search по полю "header_low", которое содержит название хедера в нижнем регистре.
Реконструктор http запроса берет таблицу-разбор и воссоздает raw string. Параметр unixeol заменяет стандартный для http перевод сктроки 0D0A на 0A. Это нестандарт и ломает все сервера, кроме nginx.
Пример разборки http запроса `http://testhost.com/testuri`
.uri
string /test_uri
.headers
.1
.header
string Content-Length
.header_low
string content-length
.value
string 330
.pos_start
number 43
.pos_end
number 61
.pos_header_end
number 56
.pos_value_start
number 59
.2
.header
string Host
.header_low
string host
.value
string testhost.com
.pos_start
number 24
.pos_end
number 41
.pos_header_end
number 27
.pos_value_start
number 30
.method
string GET
Пример разборки http ответа
.code
number 200
.headers
.1
.pos_header_end
number 28
.pos_value_start
number 31
.header
string Content-Type
.header_low
string content-type
.value
string text/html
.pos_start
number 17
.pos_end
number 39
.2
.pos_header_end
number 54
.pos_value_start
number 57
.header
string Content-Length
.header_low
string content-length
.value
string 650
.pos_start
number 41
.pos_end
number 59
dissect_tls
function tls_dissect(tls, offset, partialOK)
function tls_reconstruct(tdis)
Разборка и сборка TLS. Что понимают и могут функции :
- Любой TLS handshake без TLS record (включая client/server hello). Например, взятый из
desync.decrypt_dataот QUIC. - Любые TLS records. Handshake, certificate, change cipher spec и другие.
- Резаные handshake на несколько TLS records (например, результат
tpws --tlsrec) - (только dissect) Неполные блоки данных, если parialOK=true. Восстанавливается максимум возможного, но полноценно собрать уже не выйдет.
- Все handshake выносятся в отдельную таблицу. Для client/server hello выполняется диссект, остальные оставляются как raw data field.
- TLS extensions из client/server hello : server name, alpn, supported versions, compress certificate, signature algorithms, delegated credentials, supported groups, ec point formats, psk key exchange modes, key share, quic transport parameters. Остальные extensions не парсятся и оставляются как raw data field.
- Если есть record layer, реконструкция выполняется согласно длинам отдельных records. Если последняя часть не влезает, tls record расширяется под оставшиеся данные.
- При отсутствии изменений dissect+reconstruct дают бинарно идентичные блобы.
Функции не работают с DTLS.
tls_dissect возвращает таблицу - разбор raw строки tls со смещения offset (начиная с 1), reconstruct_dissect возвращает raw строку собранного разбора tdis. В случае ошибки возвращается nil.
Простейший способ получить образец диссекта : --payload=tls_client_hello --lua-desync=luaexec:code="var_debug(tls_dissect(desync.reasm_data))".
И вызвать TLS запрос.
Пример диссекта TLS от запроса к https://google.com
.rec
.1
.ver
number 769
.type
number 22
.name
string handshake
.len
number 512
.encrypted
boolean false
.htype
number 1
.data
string 01 00 01 FC 03 03 87 36 D1 0E 19 78 8F 8B 41 5E 05 74 92 EF E7 9D 3E 83 F3 9D F4 C4 C6 6C 3E DC 5A 8C EF FD BC B4 20 1C AF 31 7A EB D2 FD 8B 1F C6 E8 DB CF 02 28 93 C4 AE 13 E1 17 ED 62 D8 3D 2F DE 03 67 A1 1A 44 00 3C 13 02 13 03 13 01 C0 2C C0 30 00 9F CC A9 CC A8 CC AA C0 2B C0 2F 00 9E C0 24 C0 28 00 6B C0 23 C0 27 00 67 C0 0A C0 14 00 39 C0 09 C0 13 00 33 00 9D 00 9C 00 3D 00 3C 00 35 00 2F 01 00 01 77 FF 01 00 01 00 00 00 00 0F 00 0D 00 00 0A 67 6F 6F 67 6C 65 2E 63 6F 6D 00 0B 00 04 03 00 01 02 00 0A 00 16 00 14 00 1D 00 17 00 1E 00 19 00 18 01 00 01 01 01 02 01 03 01 04 00 10 00 0E 00 0C 02 68 32 08 68 74 74 70 2F 31 2E 31 00 16 00 00 00 17 00 00 00 31 00 00 00 0D 00 30 00 2E 04 03 05 03 06 03 08 07 08 08 08 1A 08 1B 08 1C 08 09 08 0A 08 0B 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02 00 2B 00 05 04 03 04 03 03 00 2D 00 02 01 01 00 33 00 26 00 24 00 1D 00 20 E0 40 E1 0A BF AD 5B 08 48 16 E5 A6 A9 90 E4 28 A1 67 40 1F AF A4 7B 9B 0A F9 32 2A 01 95 8B 5D 00 15 00 AE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
.handshake
.1
.dis
.ver
number 771
.type
number 1
.name
string client_hello
.cipher_suites
.1
number 4866
.2
number 4867
.3
number 4865
.4
number 49196
.5
number 49200
.6
number 159
.7
number 52393
.8
number 52392
.9
number 52394
.10
number 49195
.11
number 49199
.12
number 158
.13
number 49188
.14
number 49192
.15
number 107
.16
number 49187
.17
number 49191
.18
number 103
.19
number 49162
.20
number 49172
.21
number 57
.22
number 49161
.23
number 49171
.24
number 51
.25
number 157
.26
number 156
.27
number 61
.28
number 60
.29
number 53
.30
number 47
.compression_methods
.1
number 0
.ext
.1
.type
number 65281
.name
string renegotiation_info
.data
string 00
.2
.dis
.list
.1
.name
string google.com
.type
number 0
.type
number 0
.name
string server_name
.data
string 00 0D 00 00 0A 67 6F 6F 67 6C 65 2E 63 6F 6D
.3
.dis
.list
.1
number 0
.2
number 1
.3
number 2
.type
number 11
.name
string ec_point_formats
.data
string 03 00 01 02
.4
.dis
.list
.1
number 29
.2
number 23
.3
number 30
.4
number 25
.5
number 24
.6
number 256
.7
number 257
.8
number 258
.9
number 259
.10
number 260
.type
number 10
.name
string supported_groups
.data
string 00 14 00 1D 00 17 00 1E 00 19 00 18 01 00 01 01 01 02 01 03 01 04
.5
.dis
.list
.1
string h2
.2
string http/1.1
.type
number 16
.name
string application_layer_protocol_negotiation
.data
string 00 0C 02 68 32 08 68 74 74 70 2F 31 2E 31
.6
.type
number 22
.name
string encrypt_then_mac
.data
string
.7
.type
number 23
.name
string extended_master_secret
.data
string
.8
.type
number 49
.name
string post_handshake_auth
.data
string
.9
.dis
.list
.1
number 1027
.2
number 1283
.3
number 1539
.4
number 2055
.5
number 2056
.6
number 2074
.7
number 2075
.8
number 2076
.9
number 2057
.10
number 2058
.11
number 2059
.12
number 2052
.13
number 2053
.14
number 2054
.15
number 1025
.16
number 1281
.17
number 1537
.18
number 771
.19
number 769
.20
number 770
.21
number 1026
.22
number 1282
.23
number 1538
.type
number 13
.name
string signature_algorithms
.data
string 00 2E 04 03 05 03 06 03 08 07 08 08 08 1A 08 1B 08 1C 08 09 08 0A 08 0B 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02
.10
.dis
.list
.1
number 772
.2
number 771
.type
number 43
.name
string supported_versions
.data
string 04 03 04 03 03
.11
.dis
.list
.1
number 1
.type
number 45
.name
string psk_key_exchange_modes
.data
string 01 01
.12
.dis
.list
.1
.group
number 29
.kex
string E0 40 E1 0A BF AD 5B 08 48 16 E5 A6 A9 90 E4 28 A1 67 40 1F AF A4 7B 9B 0A F9 32 2A 01 95 8B 5D
.type
number 51
.name
string key_share
.data
string 00 24 00 1D 00 20 E0 40 E1 0A BF AD 5B 08 48 16 E5 A6 A9 90 E4 28 A1 67 40 1F AF A4 7B 9B 0A F9 32 2A 01 95 8B 5D
.13
.type
number 21
.name
string padding
.data
string 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
.session_id
string 1C AF 31 7A EB D2 FD 8B 1F C6 E8 DB CF 02 28 93 C4 AE 13 E1 17 ED 62 D8 3D 2F DE 03 67 A1 1A 44
.random
string 87 36 D1 0E 19 78 8F 8B 41 5E 05 74 92 EF E7 9D 3E 83 F3 9D F4 C4 C6 6C 3E DC 5A 8C EF FD BC B4
.type
number 1
.name
string client_hello
.data
string 01 00 01 FC 03 03 87 36 D1 0E 19 78 8F 8B 41 5E 05 74 92 EF E7 9D 3E 83 F3 9D F4 C4 C6 6C 3E DC 5A 8C EF FD BC B4 20 1C AF 31 7A EB D2 FD 8B 1F C6 E8 DB CF 02 28 93 C4 AE 13 E1 17 ED 62 D8 3D 2F DE 03 67 A1 1A 44 00 3C 13 02 13 03 13 01 C0 2C C0 30 00 9F CC A9 CC A8 CC AA C0 2B C0 2F 00 9E C0 24 C0 28 00 6B C0 23 C0 27 00 67 C0 0A C0 14 00 39 C0 09 C0 13 00 33 00 9D 00 9C 00 3D 00 3C 00 35 00 2F 01 00 01 77 FF 01 00 01 00 00 00 00 0F 00 0D 00 00 0A 67 6F 6F 67 6C 65 2E 63 6F 6D 00 0B 00 04 03 00 01 02 00 0A 00 16 00 14 00 1D 00 17 00 1E 00 19 00 18 01 00 01 01 01 02 01 03 01 04 00 10 00 0E 00 0C 02 68 32 08 68 74 74 70 2F 31 2E 31 00 16 00 00 00 17 00 00 00 31 00 00 00 0D 00 30 00 2E 04 03 05 03 06 03 08 07 08 08 08 1A 08 1B 08 1C 08 09 08 0A 08 0B 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02 00 2B 00 05 04 03 04 03 03 00 2D 00 02 01 01 00 33 00 26 00 24 00 1D 00 20 E0 40 E1 0A BF AD 5B 08 48 16 E5 A6 A9 90 E4 28 A1 67 40 1F AF A4 7B 9B 0A F9 32 2A 01 95 8B 5D 00 15 00 AE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Элементы, которые удалось разобрать, представлены в виде подтаблиц dis. Остальные остаются как raw data field. Для некоторых элементов заполняется поле "name". Оно служит для удобства визуального анализа и больше ни для чего не используется. Первичными являются поля type.
Для нахождения значений в списках используйте функции поиска в массивах.
Множество констант, связанных с TLS, определено в zapret-lib.lua. Прежде чем писать фиксированные значения, посмотрите нет ли нужной константы.
Таблица handshake индексируется по типу handshake. Самыми типичными являются TLS_HANDSHAKE_TYPE_CLIENT и TLS_HANDSHAKE_TYPE_SERVER.
Они имеют значения 1 и 2 соответственно, поэтому может показаться, что элементы handshake идут от 1 и по возрастающей. Это не так.
extensions и другие списки индексируются по номеру с 1, а не по типу, потому что важен порядок их следования, и может быть несколько элементов одного типа.
Если вы что-то добавляете свое, вам нужно воспроизвести минимальный вариант исходной структуры. Можно заполнить только raw data field. Если нет подтаблицы dis - при реконструкции будет взято оно. Если есть dis, то он должен быть заполнен корректно согласно рассматриваемому элементу данных.
Следущий пример кода ищет SNI extension в диссекте tdis, а в случае отсутствия добавляет в начало. Затем добавляется домен "example.com".
local idx_sni = array_field_search(tdis.handshake[TLS_HANDSHAKE_TYPE_CLIENT].dis.ext, "type", TLS_EXT_SERVER_NAME)
if not idx_sni then
table.insert(tdis.handshake[TLS_HANDSHAKE_TYPE_CLIENT].dis.ext, 1, { type = TLS_EXT_SERVER_NAME, dis = { list = {} } } )
idx_sni = 1
end
table.insert(tdis.handshake[TLS_HANDSHAKE_TYPE_CLIENT].dis.ext[idx_sni].dis.list, { name = "example.com", type = 0 } )
Работа с элементами L3 и L4 протоколов
find_tcp_options
function find_tcp_option(options, kind)
Вернуть первый элемент dis.tcp.options с опцией kind. nil, если не найдено.
ip6hdr
function find_ip6_exthdr(exthdr, proto)
Вернуть первый элемент dis.ip6.exthdr с type = proto.
function insert_ip6_exthdr(ip6, idx, header_type, data)
function del_ip6_exthdr(ip6, idx)
function fix_ip6_next(ip6, last_proto)
ip protocol
function ip_proto_l3(dis)
function ip_proto_l4(dis)
function ip_proto(dis)
Функции "додумывают" ip protocol полезной нагрузки диссекта dis.
- ip_proto_l3 - ipv4 - ip.ip_p , ipv6 - ip6.ip6_nxt или next из последнего extension header. nil, если next пуст.
- ip_proto_l4 - IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP, IPPROTO_ICMPV6 в зависимости от наличия в диссекте tcp,udp,icmp,ip6. nil, если tcp,udp,icmp отсутствуют.
- ip_proto - ip_proto_l4. если он вернул nil, то ip_proto_l3.
function fix_ip_proto(dis, proto)
Установить протокол полезной нагрузки диссекта dis как proto. Если proto не задан - использовать результат ip_proto(dis).
packet_len
Эти функции работают с диссектом ipv6 заголовка ip6 и его extension headers - ip6.exthdr. При вставлении или удалении extension headers сохраняется корректная цепочка следующих протоколов, начиная с базового ipv6 заголовка.
- insert_ip6_exthdr вставляет extension header с протоколом header_type и данными data в диссект ip6 по индексу ip6.exthdr idx. Если idx=nil, вставляется в конец. Размер data должен быть 6+N4 для IPPROTO_AH и 6+N8 для остальных, иначе будут ошибки при реконструкции.
- del_ip6_exthdr удаляет extension header по индексу ip6.exthdr idx.
- fix_ip6_next восстанавливает корректную цепочку следующих протоколов за счет ip6.ip6_nxt и полей type в ip6.exthdr.
function l3_base_len(dis)
function l3_extra_len(dis[, ip6_exthdr_last_idx])
function l3_len(dis)
function l4_base_len(dis)
function l4_extra_len(dis)
function l4_len(dis)
function l3l4_extra_len(dis)
function l3l4_len(dis)
function packet_len(dis)
Подсчет размеров различных элементов диссекта после реконструкции.
- l3_base_len - базовая длина ip/ip6 заголовка без опций и extension headers.
- l3_extra_len - длина ip options или суммарная длина всех extension headers. Если задан ip6_exthdr_last_idx, считаются extension headers до указанного индекса.
- l3_len - полная длина ip/ip6 с опциями и extension headers
- l4_base_len - базовая длина tcp или udp заголовка
- l4_extra_len - длина tcp options для tcp, 0 для udp
- l4_len - полная длина tcp заголовка с опциями или длина udp заголовка
- l3l4_extra_len - сумма l3_extra_len и l4_extra_len
- l3l4_len - полная длина ip/ip6 и tcp заголовков со всеми options и extension headers
- packet_len - полная длина реконструированного пакета, включая L4 пейлоад
Работа с именами хостов
genhost
function genhost(len[, template])
Генерирует случайный хост длиной len.
- Если есть template, генерирует случайный поддомен, чтобы вписаться в длину len. Если длины len не хватает, возвращает обрез template слева.
- Если template=nil, генерирует случайный поддомен одного из известных 3-буквенных TLD. Если len<7, генерирует случайный домен без точек длиной len.
Примеры :
-- template "google.com", len=16 : h82aj.google.com
-- template "google.com", len=11 : .google.com
-- template "google.com", len=10 : google.com
-- template "google.com", len=7 : gle.com
-- no template, len=6 : b8c54a
-- no template, len=7 : u9a.edu
-- no template, len=10 : jgha7c.com
host_ip
function host_ip(desync)
function host_or_ip(desync)
- host_ip возвращает символьное представление desync.target.ip или desync.target.ip6
- host_or_ip возвращает desync.track.hostname, если есть track и есть track.hostname, иначе host_ip(desync)
Операции с именами файлов и путями
function is_absolute_path(path)
function append_path(path,file)
function writeable_file_name(filename)
- is_absolute_path возвращает true, если путь path начинается с корня. Учитываются особенности путей CYGWIN.
- append_path дописывает имя файла или каталога file к пути path, разделяя их знаком '/'
- writeable_file_name возвращает filename, если filename содержит абсолютный путь или env
WRITEABLEотсутствует. Иначе берется путь из envWRITEABLEи к нему дописывается имя файла filename через append_path.
Чтение и запись файлов
function readfile(filename)
Читает весь файл. Вызывается error в случае ошибки при открытии или чтении файла.
function z_readfile(filename[, expected_ratio])
Автоматически определяет является ли файл gzip. Если да - разжимает, если нет - читает без изменений. Вызывается error в случае ошибки при открытии или чтении файла. expected_ratio - ожидаемое соотношение длины разжатых данных к длине сжатых данных (по умолчанию 4).
function writefile(filename, data)
Записывает data в файл. Вызывается error в случае ошибки при открытии файла.
Компрессия данных
function is_gzip_file(filename)
true, если файл является gzip, иначе false. При ошибке открытия файла вызывается error.
function gunzip_file(filename[, expected_ratio[, read_block_size]])
Разжимает файл и возвращает raw string. В случае ошибки открытия или чтения файла вызывается error. При нехватке памяти возвращается nil. read_block_size - частями какого размера читается файл (по умолчанию 16K). expected_ratio - ожидаемое соотношение длины разжатых данных к длине сжатых данных (по умолчанию 4).
function gzip_file(filename, data[, expected_ratio[, level[, memlevel[, compress_block_size]]]])
Сжимает raw строку в gzip файл. В случае ошибки открытия или чтения файла вызывается error. При испорченных gzip данных или нехватке памяти возвращается nil. level - уровень сжатия от 1 до 9 (по умолчанию 9), memlevel - допустимый уровень использования памяти от 1 до 8 (по умолчанию 8). compress_block_size - частями какого размера жмется файл (по умолчанию 16K). expected_ratio - ожидаемое соотношение длины разжатых данных к длине сжатых данных (по умолчанию 2).
autottl
function parse_autottl(s)
function autottl(incoming_ttl, attl)
Механизм autottl служит, чтобы автоматически на базе TTL входящего пакета определять такой TTL, который либо немного не достает до противопложного конца, либо немного превышает длину пути до него. delta - это положительная или отрицательная разница с догадкой о длине пути. min-max - допустимый диапазон. Если итоговый результат выходит за пределы диапазона, назначаются его крайние значения. Если delta<0 и результат оказывается равен пути или длиннее, либо если delta>=0 и результат оказывается короче пути, алгоритм уходит в ошибку и выдает nil.
Вычисление производится на базе предположения о симметричности входящего и исходящего пути и TTL по умолчанию, используемых в основных ОС - 64, 128, 255. Эвристика работает не всегда из-за потенциальной неверности данных предположений, но иногда ее можно настроить до приемлемого уровня погрешности.
- parse_autottl переводит строку вида
<delta>,<min>-<max>в таблицу с идентичными полями. Вызывается error, если формат s неверен. - autottl производит эвристическую догадку о длине в хопах на базе TTL входящих пакетов и вычисляет TTL , учитывая дельту и допустимый диапазон. Готовый incoming_ttl можно взять из desync. attl имеют формат таблицы, получаемой через parse_autottl.
Операции с диссектами
В следующих функция и функциях отсылки применяются стандартные блоки опций, представленных в виде полей отдельно передаваемой таблицы. Таблицы опций имеет формат desync.arg. Может передаваться прямо desync.arg без изменений.
standard ipid
ipid_options
| Поле | Описание |
|---|---|
| ip_id | режим назначения ip_id : seq, rnd, zero, none seq - последовательное rnd - случайное zero - ноль none - не менять |
| ip_id_conn | запоминать последнее сгенерированное значение seq и начинать с него в следующем пакете. не работает без desync.track |
Из seq далеко не всегда получится ожидаемая и преемлемая последовательность. ip_id применяется только в ряде функций, оно не применяется ко всем проходящим пакетам автоматически. Поскольку ОС не следит за измененными ip_id, в пакетах, которые не трогали, оно может начать идти заново. Windows заменяет нулевые ip_id на собственную последовательность, остальные ОС - нет.
На любых ОС можно "держать" какое-то время сквозной линейный порядок ip_id через смесь оригинальных и генерированных пакетов. Для этого нужно применять политику ip_id=seq:ip_id_conn ко всем поддерживающим ipid desync функциям, а для остальных пейлоадов использовать связку инстансов send с той же ip_id политикой и drop в ограниченном интервале --out-range. Слишком долго это делать не стоит, поскольку увеличит нагрузку на CPU.
standard fooling
fooling_options
| Поле | Описание |
|---|---|
| ip_ttl | изменить TTL в ipv4 заголовке на указанный |
| ip6_ttl | изменить TTL (HL) в ipv6 заголовке на указанный |
| ip_autottl | изменить TTL в ipv4 заголовке на автоматически определяемый по шаблону autottl delta,min-max. При невозможности определения TTL берется ip_ttl, если есть. Иначе TTL не меняется. |
| ip6_autottl | изменить HL в ipv6 заголовке на автоматически определяемый по шаблону autottl delta,min-max. При невозможности определения HL берется ip6_ttl, если есть. Иначе HL не меняется. |
| ip6_hopbyhop | вставить extension header "hop-by-hop options". по умолчанию данные - 6 нулей, но можно задать hex строку с данными. длина должна быть 6+N*8 |
| ip6_hopbyhop2 | вставить второй extension header "hop-by-hop options" |
| ip6_destopt | вставить extension header "destination options". по умолчанию данные - 6 нулей, но можно задать hex строку с данными. длина должна быть 6+N*8 |
| ip6_destopt2 | вставить второй extension header "destination options" |
| ip6_routing | вставить extension header "routing options". по умолчанию данные - 6 нулей, но можно задать hex строку с данными. длина должна быть 6+N*8 |
| ip6_ah | вставить extension header "authentication header". по умолчанию данные - 2 нуля и 4 случайных байта, но можно задать hex строку с данными. длина должна быть 6+N*4 |
| tcp_seq | положительное или отрицательное смещение sequence |
| tcp_ack | положительное или отрицательное смещение ack sequence |
| tcp_ts | положительное или отрицательное смещение timestamp. работает только если уже есть timestamp option |
| tcp_md5 | добавить MD5 header, если его еще нет. по умолчанию - случайные байты, но можно задать hex string длиной 16 байт |
| tcp_flags_set | установить флаги TCP. флаги представлены списком через запятую : FIN,SYN,RST,PUSH,ACK,FIN,URG,ECE,CWR |
| tcp_flags_unset | снять флаги TCP. аналогично tcp_flags_set |
| tcp_ts_up | поднять tcp timestamp опцию в самое начало, если она есть |
| tcp_nop_del | удалить все TCP опции NOP для освобождения места в заголовке TCP |
| fool | имя кастомной функции фулинга. она берет диссект и таблицу fooling_options |
ipv6 extension headers добавляются в следующем порядке:
- hopbyhop
- hopbyhop2
- destopt
- routing
- destopt2
- ah
tcp_ts_up - очень странное явление, обнаруженное в процессе тестирования nfqws2. Оказывается, если есть tcp опция timestamp, linux стабильно отбрасывает пакеты с валидным seq и инвалидным ack только если опция идет первой. nfqws1 не соблюдал порядок tcp опций, timestamp получался первым всегда. Поэтому оказалось, что старая версия работает стабильно , а новая нет. tcp_ts_up дублирует старое поведение - двигает timestamp в самый верх.
standard ipfrag
Опции IP фрагментации ipfrag_options содержат только два стандартных параметра. Остальное берут заменяемые функции фрагментаторы, для которых существуют свои специфические опции.
ipfrag_options
| Поле | Описание |
|---|---|
| ipfrag | имя функции фрагментатора. если нет, использовать ipfrag2. фрагментатор возвращает массив диссектов - фрагментов |
| ipfrag_disorder | отправить фрагменты в обратном порядке |
| ipfrag_pos_udp | (фрагментатор ipfrag2) позиция фрагментации udp. должна быть кратна 8, по умолчанию 8 |
| ipfrag_pos_tcp | (фрагментатор ipfrag2) позиция фрагментации tcp. должна быть кратна 8, по умолчанию 32 |
| ipfrag_next | (фрагментатор ipfrag2) тип следующего протокола в "fragment" extension header второго фрагмента |
apply_ip_id
function apply_ip_id(desync[, dis[, ipid_options[, def]]])
Применить политику ip_id из ipid_options к диссекту dis. Если dis = nil, берется desync.dis. Если ipid_options = nil, берется desync.arg. def содержит режим назначения по умолчанию. Если nil, применяется "seq".
apply_fooling
function apply_fooling(desync[, dis[, fooling_options]])
Применяет набор модификаций L3/L4 заголовков (фулинг), описанный в fooling_options, к диссекту dis. Если dis = nil, берется desync.dis. Если fooling_options = nil, берется desync.arg
ipfrag2
function ipfrag2(dis, ipfrag_options)
Стандартная функция фрагментатор. Возвращает массив из двух диссектов-фрагментов исходного диссекта dis. Задействуется через rawsend_dissect_ipfrag, если отсутствует поле ipfrag в ipfrag_options. Отдельно вызывать эту функцию вряд ли понадобится. Если вам нужно резать IP пакет как-то иначе, вы можете по аналогии создать свой фрагментатор и указать его в ipfrag_options.
В случае ipv6 fragment header вставляется после всех hopbyhop, routing и первого destopt. Это unfragmentable part. Дальше идет fragment header, а все остальное после него является fragmentable part. Unfragmentable part передается в каждом фрагменте с измененными полями fragment header, остальное режется поверх всего блока данных за fragment header согласно смещению фрагмента.
По стандарту в случае ipv6 фрагментации next протокол берется только из первого фрагмента с offset=0. В остальных фрагментах он не обязан совпадать и игнорируется. Манипуляция полем "next protocol" последующих фрагментов - известная и описанная в статьях penetration атака, позволяющая пробить некоторые фаерволы. ipfrag2 реализует эту возможность на двух фрагментах через указание параметра ipfrag_next. Некоторые фаерволы пробиваются только большим количество фрагментов - для этого потребуется своя функция фрагментатор.
wssize_rewrite
function wsize_rewrite(dis, arg)
Переписать в диссекте dis tcp.th_win и scale factor в tcp оцпии, если она присутствует. Увеличение scaling factor блокируется.
- arg: wsize - window size
- arg: scale - scale factor
- возвращает true, если какое-либо изменение было произведено
dis_reverse
function dis_reverse(dis)
Поменять местами ip адреса и порты src/dst и seq/ack.
IP адреса и интерфейсы
function update_ifaddrs()
Обертка вокруг C функции get_ifaddrs. Может так случиться, что надо узнавать адреса на каждом пакете. Каждый раз дергать get_ifaddrs тяжело для системы. Адреса и интерфейсы меняются редко. Хотелось бы из закэшировать. update_ifaddrs() берет на себя функцию ведения кэша, который обновляется не чаще 1 раза в секунду. Результат кладется в глобальную переменную ifaddrs.
function ip2ifname(ip)
Получить имя интерфейса, на котором находится ip адрес, используя кэш ifaddrs. nil, если не найден.
Отсылка
Следующие функции могут брать несколько блоков описанных выше опций, каждый из которых представлен полем параметра options. Во всех функциях используется options.reconstruct и (options.rawsend)[#standard-rawsend). Они соответствуют формату параметров C функции rawsend_dissect.
rawsend_dissect_ipfrag
function rawsend_dissect_ipfrag(dis[, options])
Отправить диссект dis с IP фрагментацией, заданной в options.ipfrag. Если отсутствует - отправить без фрагментации.
Использует кастомную функцию фрагментатор, если указано options.ipfrag.ipfrag.
Отсылает фрагменты в обратном порядке, если указано options.ipfrag.ipfrag_disorder.
rawsend_dissect_segmented
function rawsend_dissect_segmented(desync[, dis[, mss[, options]]])
Отправить диссект dis с автоматической tcp сегментацией на базе mss с применением options.fooling и options.ipid.
ipid применяется к каждому фрагменту. Для udp сегментация невозможна и не выполняется.
- Если dis отсутствует, берется desync.dis.
- Если mss отсутствует, берется desync.tcp_mss.
- Если options отсутствуют, они создаются на базе desync.arg.
rawsend_payload_segmented
function rawsend_payload_segmented(desync[, payload[, seq[, options]]])
Сконструировать временный диссект на базе desync.dis с опциональным замещением пейлоада на payload и опциональным смещением seq, применяя options, и отослать через rawsend_dissect_segmented. mss берется из desync.tcp_mss. Если options отсутствуют, они создаются на базе desync.arg.
Стандартные options формируются следующим образом :
- ipfrag, ipid, fooling принимают значение desync.arg
- rawsend : repeats берется из desync.arg.repeats, ifout и fwmark берутся из desync.arg, если они там есть, либо из desync (то, что пришло в desync функцию)
- reconstruct : берется только desync.arg.badsum, остальные опции не используются
Стандартные фильтры direction и payload
function direction_check(desync[, def])
function direction_cutoff_opposite(ctx, desync[, def])
Фильтр по направлению представляет собой строку "in", "out" или "any" и передается в desync.arg.dir. Если аргумент отсутствует, берется значение def.
- direction_check проверяет соответствие текущего направления фильтру.
- direction_cutoff_opposite выполняет instance cutoff на текущее направление, если оно не соответствует фильтру.
function payload_match_filter(l7payload[, l7payload_filter[, def]])
function payload_check(desync[, def])
Функции работают со строкой - списком пейлоадов через запятую. Особые значения - all и known. all означает любой пейлоад, known - не unknown и не empty. Префикс ~ в начале означает логическую инверсию - несоответствие списку.
См. распознавание протоколов.
- payload_match_filter проверяет соответствие l7payload списку l7payload_filter или def, если l7payload_filter=nil. Если оба nil - список берется как "known".
- payload_check вызывает
payload_match_filter(desync.l7payload, desync.arg.payload, def)
Работа с многопакетными пейлоадам
Обычно идет работа с целым reasm, вместо отдельных его частей. В этом и есть смысл сборки, чтобы не копаться в отдельных пакетах, а разбираться со всем сообщением сразу.
Стандартный сценарий предполагает работу после приема первой части replay и игнорирование, либо drop остальных частей. Выбор между игнорированием или drop может зависеть от успешности действий с reasm. Например, удалось или не удалось отправить reasm сегментированно. Если удалось - нужно дропнуть все остальные части, потому что иначе они пойдут дублями в оригинальной сегментации. Если возникла какая-то ошибка, сегментированные пакеты отправить не удалось, и при этом дропнуть все остальное, до адресата не дойдет полное сообщение, начнутся ретрансмиссии, поэтому лучше оставить как есть - так хоть ничего не поломается.
function replay_first(desync)
function replay_drop_set(desync, v)
function replay_drop(desync)
- replay_first возвращает true, если текущий диссект не является replay или является его первой частью
- replay_drop_set помечает в desync.track.lua_state boolean признак необходимости "v" дропнуть последующие части replay
- replay_drop возвращает true, если необходимо дропнуть текущую часть replay. Если часть последняя - автоматически снимает признак.
Функции работают корректно как с реплеем, так и обычными диссектами. Для обычных диссектов replay_first всегда true, replay_drop_set не меняет признак, replay_drop всегда false.
Оркестрация
В эту группу функций входят функции поддержки процесса оркестрации и прокладки. Прокладки - это дублеры функций C кода для ситуации, когда у нас нет контекста ctx для связи с C кодом. После начала оркестрации все дальнейшие инстансы вызываются оркестратором или вложенными оркестраторами. Последний ctx, который доступен, это ctx первого оркестратора. Если его передавать другим инстансам, они будут действовать от имени оркестратора, а не от своего, поэтому им следует передавать ctx=nil. После отмены execution plan C код не обслуживает последующие инстансы и не выдает на них ctx. Поэтому если нужно продолжить выполнение в стандартном стиле, необходимы дублирующие механизмы instance cutoff и фильтров range и payload.
Чтобы функции --lua-desync прозрачно работали под оркестрацией, необходимо использовать стандартные прокладки вместо прямых вызовов C функций,
берущих ctx. Чтобы корректно работали вложенные оркестраторы, нужно придерживаться стандартной схемы хранения execution plan в desync.plan
и пользоваться описанными ниже функциями-хелперами.
По сухому описанию может быть сложно понять как работает оркестрация. Рекомендуется изучить код реальных оркестраторов, а описанием пользоваться для уточнения смысла отдельных действий.
instance_cutoff_shim
function instance_cutoff_shim(ctx, desync[, dir])
Выполняет обычный instance cutoff по направлению dir, если ctx присутствует, иначе cutoff через дублирующий механизм, состояние которого хранится в desync.track.lua_state. dir = true - исходящее направление, dir = false - входящее, dir = nil - оба направления.
cutoff_shim_check
function cutoff_shim_check(desync)
Проверяет состояние instance cutoff для desync.func_instance по направлению desync.outgoing.
apply_arg_prefix
function apply_arg_prefix(desync)
Выполняет подстановку значений аргументов из desync.arg, начинающихся с % и #.
apply_execution_plan
function apply_execution_plan(desync, instance)
Копирует в desync идентификацию инстанса и его аргументы из элемента execution plan instance,
тем самым воссоздает состояние desync, как если бы instance был вызван напрямую C кодом.
execution plan выдается C функцией execution_plan() как массив, элементами которого являются instance.
verdict_aggregate
function verdict_aggregate(v1, v2)
Агрегация вердиктов v1 и v2. VERDICT_MODIFY замещает VERDICT_PASS, VERDICT_DROP замещает их обоих.
plan_instance_execute
function plan_instance_execute(desync, verdict, instance)
Выполняет элемент execution plan instance с учетом instance cutoff и стандартных фильтров payload и range.
Возвращает агрегацию verdict и вердикта instance.
plan_instance_pop
function plan_instance_pop(desync)
Берет, удаляет и возвращает первый элемент execution plan из desync.plan. Если элементов нет - возвращает nil.
plan_clear
function plan_clear(desync)
Очищает execution plan в desync.plan - удаляет все instance.
orchestrate
function orchestrate(ctx, desync)
Если оркестратор - первый, т.е. присутствует ctx, забирает execution plan и помещает его в desync.plan, а потом выполняет execution_plan_cancel().
Если ctx=nil - не делает ничего. Считается, что план уже находится в desync.plan.
replay_execution_plan
function replay_execution_plan(desync)
Выполняет весь execution plan из desync.plan с учетом instance cutoff и стандартных фильтров payload и range.
Библиотека программ атаки на DPI zapret-antidpi.lua
Стандартные наборы параметров
Многие функции берут стандартные наборы аргументов, классифицируемые по их назначанию.
Дополнительные фильтры по направлению и пейлоаду внутри функций anti-dpi сделаны в основном на случай не слишком грамотного написания опций командной строки как дополнительный предохранитель и защита от дурака, чтобы сделать флуд как это было в winws1 с --dpi-desync-any-protocol непреднамеренно было непросто.
Другая цель - дать возможность фильтрации по протоколам, о которых C код не знает и которые обнаруживаются Lua детекторами, такими как detect_payload_str.
В nfqws2 по умолчанию стоит запрет на передачу входящих (--in-range=x), неограниченная передача исходящих (--out-range=a ) и пропуск любых пейлоадов (--payload=all), что соответствуют поведению nfqws1 с опцией --dpi-desync-any-protocol. В nfqws1 все атаки были зашиты в C код, поэтому было известно какие техники работают с какими пейлоадами. Каким-то нужны были любые пакеты, в том числе пустые, а другим - только tls hello или http request.
nfqws2 ничего не знает о том, что нужно --lua-desync функциям. Поэтому фильтрация направления и типа пейлоадов ложится целиком на вас. По умолчанию стоит запрет только на входящие, потому что они используются редко, а пользователь может не написать ограничение, и все это пойдет на Lua функции и будет напрасно грузить процессор гигабайтами скачиваемого трафика.
standard direction
Фильтр по направлению. В большинстве функций, использующих фильтр по направлению, значение по умолчанию - "out", но есть и те, где по умолчанию "any". Фильтр по направлению можно реализовать и средствами C кода --in-range и --out-range.
standard direction
| Поле | Описание |
|---|---|
| dir | in - входящее направление out - исходящее направление any - любое направление |
standard payload
Фильтр по пейлоаду берет список типов пейлоада.
standard payload
| Поле | Описание |
|---|---|
| payload | список допустимых пейлоадов через запятую. ~ в начале означает инверсию |
Базовые функции
drop
function drop(ctx, desync)
- arg: standard direction
- arg: standard payload
- По умолчанию payload=all, direction=any, то есть drop всего.
Выносит VERDICT_DROP при выполнении условий фильтра.
send
function send(ctx, desync)
- arg: standard direction
- arg: standard fooling
- arg: standard ipid
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- режим ip_id по умолчанию - none
Отсылает текущий диссект c опциональным применением модификаций.
pktmod
function pktmod(ctx, desync)
- arg: standard direction
- arg: standard fooling
- arg: standard ipid
Применить модификации к текущему диссекту без отправки и вынесения вердикта.
Дурение http
http_hostcase
function http_hostcase(ctx, desync)
- arg: standard direction
- arg: spell - точное написание заголовка. по умолчанию "host"
Заменяет регистр http заголовка Host:
http_domcase
function http_domcase(ctx, desync)
- arg: standard direction
Меняет регистр написания домена внутри заголовка Host:. Верхний и нижний регистр чредуется каждый символ : rUtRaCkEr.oRg.
http_methodeol
function http_methodeol(ctx, desync)
- arg: standard direction
Вставляет '\r\n' перед методом, отрезая 2 последних символа из содержимого заголовка User-Agent:. Работает только на nginx, остальные сервера ломает.
Если используется совместно с другими функциями http тамперинга, должен идти последним.
http_unixeol
function http_unixeol(ctx, desync)
- arg: standard direction
Заменяет перевод строки 0D0A на 0A. Разницу в длине добавляет пробелами в конец хедера "User-Agent". Работает только на nginx, остальные сервера ломает.
Замена window size
wsize
function wsize(ctx, desync)
- arg: wsize - размер tcp окна
- arg: scale - scaling фактор. заменяется в tcp option, если есть. только уменьшение, увеличение блокируется
Меняет tcp.th_win и/или scaling factor в tcp оцпии в tcp пакете SYN,ACK, после чего выполняет instance cutoff. Если изменение выполнено - выставляет VERDICT_MODIFY.
Цель техники - подменить window size со стороны клиента или сервера, чтобы в ответ на это клиент отослал следующий пакет частями, поскольку целиком он не влезает в window size. Может приводить к замедлению. Устаревшая техника, рекомендуется применять только с сервера в крайнем случае для работы по клиентам, которые не делают ничего для обхода блокировки. С клиента лучше применять техники tcp сегментации, поскольку они лишены побочных эффектов в виде замедления скорости и имеют больше возможностей.
wssize
function wssize(ctx, desync)
- arg: standard direction
- arg: wsize - размер tcp окна
- arg: scale - scaling фактор. заменяется в tcp option, если есть. только уменьшение, увеличение блокируется
- arg: forced_cutoff - список типов пейлоадов через запятую, при получении которых выполняется instance cutoff. Если нужно применять wssize бесконечно, можно указать forced_cutoff=no, то есть несуществующий тип пейлоада, который не придет никогда.
Меняет tcp.th_win и/или scaling factor в tcp оцпии во всех tcp пакетах потока по направлению до достижения условия "cutoff". Если изменение выполнено - выставляет VERDICT_MODIFY. "cutoff" наступает при получении любого пакета с данными, если аргумент forced_cutoff не задан, или при получении одного из указанных в аргументе forced_cutoff пейлоадов. В этом случае выполняется instance cutoff.
Цель техники - заставить сервер фрагментировать свои ответы, когда DPI их проверяет (TLS 1.2).
Нужно держать сервер "в тонусе", чтобы он думал, что клиент не может принимать tcp сегменты большого размера и сам резал свои ответы,
но лишь до прохода критической фазы проверки.
После этого нужно отпустить воздействие, иначе оно приведет к катастрофической потере скорости вплоть до модемной.
Режет скорость в любом случае. Является техникой нулевой фазы - с хостлистами может применяться только с включенным --ipcache-hostname.
При использовании хостлистов может быть необходимо дублировать в отдельном профиле, который задействуется до узнавания hostname.
В этом случае будет применяться всегда без проверки на хостлист и всегда резать скорость.
Типичные параметры: wsize=1:scale=6. К применению рекомендуется только при отсутствии альтернатив.
Фейки
Фейки - это отдельные пакеты с ложной информацией, которую DPI должен принять, а сервер - нет. Фейки бывают прямые и скрытые. Прямые фейки - это отдельные пакеты, скрытые фейки - часть оригинальных модифицированных пакетов или групп пакетов.
Прямым фейкам всегда необходимо искажение какой-либо информации в заголовках пакета, чтобы пейлоад не попал в серверное приложение, иначе это поломает соединение. Без фулинга фейк должен повторять часть оригинальной информации, чтобы сервер не получил ложную информацию. Скрытые фейки не воспринимаются сервером в силу характеристик пакетов, частью которых они являются.
syndata
function syndata(ctx, desync)
- arg: standard fooling
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: blob - blob, содержащий фейковый payload. Должен помещаться в один пакет, сегментация невозможна.
- arg: tls_mod - применить указанный tls_mod к пейлоаду blob
Функция добавляет в tcp SYN пакет пейлоад, применяет к нему модификации и отправляет вместо оригинала, вынося VERDICT_DROP.
Если проходит пакет без SYN, выполняется instance cutoff.
Таким образом воздействие выполняется на все ретрансмиссии SYN, после чего функция прекращает работу.
Является стратегией нулевой фазы, которая работает с хостлистами только в режиме --ipcache-hostname.
tls_client_hello_clone
function tls_client_hello_clone(ctx, desync)
- arg: standard direction
- arg: blob - имя блоба, получающего результат
- arg: fallback - имя блоба, который копируется в результат, если пейлоад неверного типа или невалидный
- arg: sni_del_ext - удалить sni extension. остальные параметры не используются
- arg: sni_del - удалить все хосты
- arg: sni_snt - заменить на всех уже имеющихся хостах поле "server name type"
- arg: sni_snt_new - поле "server name type" для добавляемых хостов
- arg: sni_first - добавить хост в начало списка
- arg: sni_last - добавить хост в конец списка списка
Подготавливает blob с указанным именем в таблице desync, заполненный результатом модификации текущего reasm. Работает только с tcp и пейлоадом tls_client_hello. Если задана модификация SNI и SNI extension отсутствует - он добавляется в начало списка extensions.
Очередность выполнения операций :
- sni_del_ext . Все остальные операции со SNI теряют смысл и не выполнятся.
- sni_del
- sni_snt
- sni_first
- sni_last
Сама по себе эта функция никак не влияет на трафик, а лишь подготавливает данные для других функций.
fake
function fake(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: blob - blob, содержащий фейковый payload. Может быть любой длины - сегментация выполняется автоматически.
- arg: optional - отказ от операции, если blob отсутствует
- arg: tls_mod - применить указанный tls_mod к пейлоаду blob
- payload фильтр по умолчанию - "known"
Это прямой фейк - отдельный пакет или группа пакетов. Функция не выносит вердикт и не блокирует отправку оригинала.
rst
function rst(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: rstack - отсылка RST,ACK вместо RST
- payload фильтр по умолчанию - "known"
Отослать пустой TCP пакет с флагами RST или RST+ACK. Функция не выносит вердикт и не блокирует отправку оригинала.
TCP сегментация
multisplit
function multisplit(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: pos - список маркеров через запятую - точек разреза. По умолчанию "2".
- arg: seqovl - число - смещение относительно текущего sequence для создания дополнительной части сегмента, выходящей влево за границу tcp window
- arg: seqovl_pattern - blob, используемый для заполнения seqovl. По умолчанию 0x00
- arg: blob - заменить текущий пейлоад на указанный blob
- arg: optional - отказ от операции, если blob задан, но отсутствует. если seqovl_pattern задан, но отсутствует, использовать паттерн 0x00.
- arg: nodrop - отказ от вынесения VERDICT_DROP
- payload фильтр по умолчанию - "known"
Multisplit реализует последовательную сегментацию текущего диссекта или реасма с разрезом в определяемых списком маркеров позициях. Опционально поддерживается замена блока данных на произвольный blob и техника seqovl. Выносится VERDICT_DROP после успешной отправки всех сегментов, если не указано "nodrop".
Если происходит перепроигрывание (replay) задержанных пакетов и присутствует reasm, то вместо desync.dis.payload берется desync.reasm_data. Нарезание происходит только при перепроигрывании первой части reasm, по остальным частям выносится VERDICT_DROP, если отсылка была успешна и не указано "nodrop". Поскольку весь reasm уже отправлен нарезанным, нет смысла повторно отправлять его оригинальные части.
Может использоваться для отправки произвольных данных, в том числе фейков с заменой текущего пейлоада на произвольный blob.
О размерах частей и вписывании в MTU думать не нужно - применяется дополнительная автоматическая сегментация по MSS.
seqovl может быть только числом, маркеры не поддерживаются. Применяется к первому нарезаемому сегменту. К пейлоаду первого сегмента приписывается слева seqovl_pattern по размеру seqovl, а tcp.th_seq уменьшается на seqovl. Таким образом слева образуется блок данных, выходящий влево за tcp window, и поэтому он игнорируется сервером, а часть, входящая в tcp window - принимается.
seqovl - это фактически средство замешивания фейковых и реальных данных, средство создания скрытых фейков в реальных tcp сегментах. Если DPI не ведет учет sequence numbers, он может проглотить весь передаваемый сегмент и купиться на ложную информацию в начале, которая сервером принята не будет.
Особое преимущество seqovl - в отсутствии необходимости фулинга. Сервер принимает только часть сегмента за счет игры с sequence numbers, а не за счет модификации каких-то элементов ip и tcp заголовков, что привело бы к полному непринятию.
multidisorder
function multidisorder(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: pos - список маркеров через запятую - точек разреза. По умолчанию "2".
- arg: seqovl - маркер - смещение относительно текущего sequence для создания дополнительной части сегмента, выходящей влево
- arg: seqovl_pattern - blob, используемый для заполнения seqovl. По умолчанию 0x00
- arg: blob - заменить текущий пейлоад на указанный blob
- arg: optional - отказ от операции, если blob задан, но отсутствует. если seqovl_pattern задан, но отсутствует, использовать паттерн 0x00.
- arg: nodrop - отказ от вынесения VERDICT_DROP
- payload фильтр по умолчанию - "known"
Аналогично multisplit, но отправка сегментов производится в обратном порядке - с последнего по первый.
Техника seqovl в данном случае работает иначе. Она применятся ко второму в оригинальной очередности (предпоследнему отсылаемому) сегменту. seqovl может быть маркером. Например, можно сделать разрез по midsld, а seqovl сделать "midsld-1". seqovl обязательно должен быть меньше первого сегмента в оригинальной очередности (последнего отсылаемого), иначе эта ситуация распознается и seqovl отменяется.
Смысл seqovl в варианте disorder - в переписывании буфера сокета на принимающем конце. tcp сокет выдает данные в приложение последовательно, в порядке их оригинальной передачи. Если сначала приходит сегмент "спереди", не образующий с уже принятыми данными непрерывной последовательности, происходит задержка информации в буфере без выдачи ее приложению. Если далее приходит перекрывающийся по sequence сегмент, то информация из него переписыват уже имеющуюся в буфере. Так ведут себя все системы, кроме Windows, поэтому на Windows серверах эта техника не работает. Windows сохраняет старую информацию.
К предпоследнему отсылаемому сегменту (2-му по оригинальной очередности) приписывается слева seqovl_pattern размером seqovl (результат ресолвинга маркера), а tcp.th_seq уменьшается на seqovl.
Последний отсылаемый сегмент (1-й в оригинальной очередности) отправляется неизменным, переписывая в буфере сокета ложные данные из seqovl_pattern реальными. Восстанавливается непрерывная последовательность потока, данные передаются в сокет приложения.
multidisorder_legacy
function multidisorder_legacy(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: pos - список маркеров через запятую - точек разреза. По умолчанию "2".
- arg: seqovl - маркер - смещение относительно текущего sequence для создания дополнительной части сегмента, выходящей влево
- arg: optional - отказ от операции, если blob задан, но отсутствует. если seqovl_pattern задан, но отсутствует, использовать паттерн 0x00.
- arg: seqovl_pattern - blob, используемый для заполнения seqovl. По умолчанию 0x00
Реализация multidisorder, полностью совместимая с nfqws1.
Новый multidisorder работает с reasm целиком, а старый из nfqws1 - по отдельным частям replay. Поэтому порядок следования частей в случае многопакетных запросов будет разным.
В новом варианте не сохраняется оригинальная сегментация. Если какая-то часть разреза получилась больше MSS, она дополнительно режется по MSS и отправляется в порядке увеличения sequence. В старом варианте оригинальная сегментация сохранялась. Использовалась нормализация точек разреза по смещению каждой отдельной части reasm. Сегменты отправлялись в обратном порядке только внутри каждой части, но следующая часть шла с оригинальным инкрементом sequence. seqovl так же нормализовался и применялся только внутри того оригинального сегмента, в какой попала нормализованная позиция.
fakedsplit
function fakedsplit(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard reconstruct
- arg: standard rawsend
- arg: pos - один маркер - точка разреза. По умолчанию "2".
- arg: seqovl - число - смещение относительно текущего sequence для создания дополнительной части сегмента, выходящей влево за границу tcp window
- arg: seqovl_pattern - blob, используемый для заполнения seqovl. По умолчанию 0x00
- arg: blob - заменить текущий пейлоад на указанный blob
- arg: optional - отказ от операции, если blob задан, но отсутствует. если seqovl_pattern задан, но отсутствует, использовать паттерн 0x00.
- arg: nodrop - отказ от вынесения VERDICT_DROP
- arg: nofake1, nofake2, nofake3, nofake4 - отказ от отсылки отдельных фейков
- arg: pattern - blob, которым заполняются фейковые части. По умолчанию 0x00.
- payload фильтр по умолчанию - "known"
Функция работает аналогично multisplit с одной позицией разреза, но с замешиванием фейков между реальными сегментами. Фейки совпадают в размерах с отсылаемыми частями и формируются на базе pattern со смещением, которое соответствует смещению tcp sequence отсылаемой части относительно первой. Для фейков необходим фулинг, чтобы они не были приняты сервером.
Последовательность отсылки :
- Фейк 1-й части. (fake1)
- Реальная 1-я часть.
- Фейк 1-й части. (fake2)
- Фейк 2-й части. (fake3)
- Реальная 1-я часть.
- Фейк 2-й части. (fake4)
Цель данной техники - запутать DPI что есть оригинал, а что есть fake. Части одного размера, в одной мусор, в другой - реальные данные. Что выбрать ? Не знаем... Все выглядит как ретрансмиссии, имеет те же sequence и размеры.
- К оригиналам применяется только fooling_opts.tcp_ts_up. reconstruct_opts не применяются.
- К фейкам применяются fooling_opts и reconstruct_opts в полном объеме.
- ipid_opts и rawsend_opts применяются и к фейкам, и к оригиналом. ipfrag_opts не задействуются ни для фейков, ни для оригиналов.
В случае успеха отсылки выносится VERDICT_DROP, если не указано "nodrop". blob позволяет заменить текущией пейлоад на произвольный blob, и тем самым отослать любой совместимый пейлоад с тем же разбиением.
fakeddisorder
function fakeddisorder(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard reconstruct
- arg: standard rawsend
- arg: pos - один маркер - точка разреза. По умолчанию "2".
- arg: seqovl - маркер - смещение относительно текущего sequence для создания дополнительной части сегмента, выходящей влево
- arg: seqovl_pattern - blob, используемый для заполнения seqovl. По умолчанию 0x00
- arg: blob - заменить текущий пейлоад на указанный blob
- arg: optional - отказ от операции, если blob задан, но отсутствует. если seqovl_pattern задан, но отсутствует, использовать паттерн 0x00.
- arg: nodrop - отказ от вынесения VERDICT_DROP
- arg: nofake1, nofake2, nofake3, nofake4 - отказ от отсылки отдельных фейков
- arg: pattern - blob, которым заполняются фейковые части. По умолчанию 0x00.
- payload фильтр по умолчанию - "known"
Функция работает аналогично multidisorder с одной позицией разреза, но с замешиванием фейков между реальными сегментами. Фейки совпадают в размерах с отсылаемыми частями и формируются на базе pattern со смещением, которое соответствует смещению tcp sequence отсылаемой части относительно первой. Для фейков необходим фулинг, чтобы они не были приняты сервером.
Последовательность отсылки :
- Фейк 2-й части. (fake1)
- Реальная 2-я часть.
- Фейк 2-й части. (fake2)
- Фейк 1-й части. (fake3)
- Реальная 1-я часть.
- Фейк 1-й части. (fake4)
Кроме запутывания DPI в реальных и фейковых сегментах, добавляется еще и запутывание в их последовательности.
- К оригиналам применяется только fooling_opts.tcp_ts_up. reconstruct_opts не применяются.
- К фейкам применяются fooling_opts и reconstruct_opts в полном объеме.
- ipid_opts и rawsend_opts применяются и к фейкам, и к оригиналом. ipfrag_opts не задействуются ни для фейков, ни для оригиналов.
В случае успеха отсылки выносится VERDICT_DROP, если не указано "nodrop". blob позволяет заменить текущией пейлоад на произвольный blob, и тем самым отослать любой совместимый пейлоад с тем же разбиением.
hostfakesplit
function hostfakesplit(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard reconstruct
- arg: standard rawsend
- arg: host - шаблон для генерации фейкового хоста - random.template
- arg: midhost - маркер для дополнительного разреза сегмента с реальным хостом
- arg: disorder_after - маркер для дополнительного разреза заключительной реальной части и отправки сегментов в обратном порядке
- arg: nofake, nofake2 - отказ от отсылки отдельных фейков
- arg: blob - заменить текущий пейлоад на указанный blob
- arg: optional - отказ от операции, если blob задан, но отсутствует
- arg: nodrop - отказ от вынесения VERDICT_DROP
- payload фильтр по умолчанию - "known"
Это специальный "резатель" с замешиванием фейков для пейлоадов, в которых присутствует имя хоста - http_req и tls_client_hello. Двумя основными точкам разреза являются начало имени хоста - маркер "host" и конец имени хоста - маркер "endhost". Дополнительными и опциональными точками разреза являются маркер midhost (должен быть в пределах host..endost) и маркер disorder_after (должен быть больше endhost). При разрезе по disorder_after части отправляются в обратном порядке. Для фейков необходим фулинг, чтобы они не были приняты сервером.
Последовательность отсылки :
- Реальная часть до host
- Фейк host..endhost-1 (fake1)
- Реальная часть host..endhost, либо 2 части : host..midhost-1, midhost..endhost-1
- Фейк host..endhost-1 (fake2)
- Реальная часть после host, либо 2 части : disorder_after..-1, endhost..disorder_after-1
- К оригиналам применяется только fooling_opts.tcp_ts_up. reconstruct_opts не применяются.
- К фейкам применяются fooling_opts и reconstruct_opts в полном объеме.
- ipid_opts и rawsend_opts применяются и к фейкам, и к оригиналом. ipfrag_opts не задействуются ни для фейков, ни для оригиналов.
В случае успеха отсылки выносится VERDICT_DROP, если не указано "nodrop". blob позволяет заменить текущией пейлоад на произвольный blob, и тем самым отослать любой совместимый пейлоад с тем же разбиением.
tcpseg
function tcpseg(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: standard fooling
- arg: standard ipid
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: pos - список из двух маркеров, определяющий границы tcp сегмента
- arg: seqovl - число - смещение относительно текущего sequence для создания дополнительной части сегмента, выходящей влево за границу tcp window
- arg: seqovl_pattern - blob, используемый для заполнения seqovl. По умолчанию 0x00
- arg: blob - заменить текущий пейлоад на указанный blob
- arg: optional - отказ от операции, если blob задан, но отсутствует. если seqovl_pattern задан, но отсутствует, использовать паттерн 0x00.
- payload фильтр по умолчанию - "known"
Отсылает часть текущего диссекта, реасма, или произвольного blob, ограниченную двумя маркерами pos с опциональным применением техники seqovl таким же способом, как и в multisplit. Дополнительная сегментация при превышении MSS производится автоматически.
В случае reasm работает только при приеме его первой части (т.к. работает целиком по reasm, а не отдельным его частям).
Вердикт не выносится.
С помощью tcpseg можно сделать seqovl без сегментации, используя маркеры "0,-1". Для замещения текущего диссекта можно комбинировать с drop.
oob
function oob(ctx, desync)
- arg: standard fooling
- arg: standard ipid
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: char - 1 символ oob
- arg: byte - числовое значение байта OOB 0..255
- arg: urp - позиционный маркер для urgent pointer, "b" или "e". по умолчанию - "b"
Функция перехватывает TCP handshake, сдвигая sequence влево на 1 байт, затем вставляет 1 байт OOB в первый отсылаемый пейлоад и отсылает его. После отработки функция уходит в cutoff по обоим направлениям.
ОС получателя выбрасывает OOB байт из потока, DPI может не выбрасывать и получать оригинальное сообщение со вставленным в него посторонним байтом, что может портить смысл всего сообщения.
- OOB - устаревший, но все еще поддерживаемый в ОС механизм. Имеется 2 стандарта. Один предполагает, что th_urp указывает на байт OOB, другой - на следующий за ним байт. Поэтому значение th_urp=0 невалидно по одному из стандартов, но все еще может работать. Чтобы задействовать его укажите "urp=b". Как показывает практика, "b" работает только на Linux серверах, остальные ломает.
- Значение маркера urp означает позицию самого OOB байта. th_urp , кроме случая "b", увеличивается на 1 - согласно интерпретации RFC, поддерживаемой в основных ОС.
- "urp=e" вставляет OOB байт после самого последнего байта пейлоада - для обхода DPI как правило бесполезно, поскольку DPI получает все оригинальное сообщение.
- Для протоколов, в которых в самом начале сервер ждет запроса от клиента, требуется разрешение входящих в пределах
--in-range=-s1. В Windows--wf-tcp-inне нужен. Достаточно автоматически перехватываемых SYN. К таким протоколам относятся http и tls. - Для протоколов, где сервер что-то шлет до первого сообщения от клиента, требуется разрешить все входящие пакеты до отсылки первого исходящего пакета с данными. В Windows
--wf-tcp-inобязателен. - Не может быть отфильтровано по пейлоаду, поскольку после начала модификации tcp handshake соскок уже невозможен, иначе поедут sequence.
- oob - длящаяся десинхронизация. Если возможно переключение профилей до момента окончания работы oob, oob должен быть дублирован в другой профиль, иначе TCP будет сломан из-за десинхронизации sequence.
- Функция помечает специальный флаг, что была вызвана с самого начала соединения. Если управление получено после перескока профиля, происходит немедленный cutoff.
- Фильтрация по хостлистам возможна только при
--ipcache-hostname. Если в кэше хоста еще нет, и профиль получает управление не с начала соединения, срабатывает предыдущий пункт. - Не может работать с функциями, предполагающими отправку текущего пейлоада - multisplit, multidisorder, fakedsplit, fakeddisorder и тд. Они будут работать, но будут слать дубль без OOB. Ожидаемый результат "разбить на сегменты и всунуть OOB" как в tpws получить таким образом невозможно.
- Если пейлоад многопакетный - отсылается весь reasm. OOB вставляется в тот сегмент, куда попал urp. В этом сегменте th_urp нормализуется по смещению сегмента, выставляется флаг TH_URG. Остальные части шлются как есть. Функция дропает весь replay, затем уходит в cutoff по обоим направлениям.
Дурение udp
Для udp намного меньше вариантов, чем для tcp, в силу простоты данного протокола. С ним особо нечего делать. От stateful DPI могут помочь фейки. От stateless они не помогут. Может помочь фрагментация на уровне IP. Для ipv6 могут сработать extension headers.
Из остального - только искажение самого пейлоада. Не все программы стерпят искажение информации, многие просто отбросят искаженные пакеты. Но есть и такие, в которых понятно что можно "подкрутить".
udplen
function udplen(ctx, desync)
- arg: standard direction
- arg: standard payload
- arg: min - не трогать пакеты с длиной L4 пейлоада меньше
- arg: max - не трогать пакеты с длиной L4 пейлоада больше
- arg: increment - на сколько увеличить (+) или уменьшить (-) длину L4 пейлоада
- arg: pattern - blob, которым заполняется конец пакета при увеличении длины
- arg: pattern_offset - начальное смещение внутри pattern
- payload фильтр по умолчанию - "known"
Функция увеличивает или уменьшает длину L4 пейлоада udp. При уменьшении часть информации обрезается и теряется, при увеличении - заполняется pattern. Сегментация udp невозможна - при превышении MTU или PMTU ОС выполнит фрагментацию на уровне IP. Ошибка в случае превышения MTU будет только на Linux, остальные системы молча не отправят пакет (WinDivert и ipdivert не имеют средств обнаружения ошибки).
dht_dn
function dht_dn(ctx, desync)
- arg: standard direction
- arg: dn - число N, следующее после 'd' в сообщении dht
dht использует формат bencode для передачи сообщений. 'd' - это тип данных directory. Сообщения dht как правило начинаются с 'd1' или 'd2' и заканчиваются 'e' (end). В некоторых DPI прописаны именно такие сигнатуры - только 'd1' или 'd1'+'d2'. Но можно поставить и 'd3', 'd4', ..., если корректно отредактировать содержимое, не нарушив формат bencode. Этим и занимается данная функция. Работает только по пейлоаду с типом "dht".
Другие функции
synack
function synack(ctx, desync)
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
Отсылает перед SYN пакет SYN,ACK, чтобы запутать DPI относительно направления tcp соединения. Атака называется в литературе "TCB turnaround". Ломает NAT - через NAT применение невозможно. Применение на проходящем трафике требует nftables и режим POSTNAT. После прохода пакета без SYN выполняет instance cutoff. Вердикт не выносит.
synack_split
function synack_split(ctx, desync)
- arg: standard ipfrag
- arg: standard reconstruct
- arg: standard rawsend
- arg: mode - "syn", "synack" или "acksyn"
Методика предназначена для серверов. В литературе имеет название "TCP split handshake". Заменяет исходящий от сервера пакет SYN,ACK на SYN, 2 пакета SYN + ACK или 2 пакета ACK + SYN. В случае успеха отсылки выносит VERDICT_DROP. После прохода пакета без SYN,ACK выполняет instance cutoff.
Многие DPI ожидают стандартный ответ на SYN в виде SYN,ACK. В реальности получается, что ответ на SYN является тоже SYN, а потом клиент шлет серверу SYN,ACK. Тем самым DPI перестает понимать какая сторона является клиентом, а какая сервером, и алгоритм проверки дает сбой. Атака может сработать даже если клиент не делает ничего для обхода блокировки. Может использоваться совместно с клиентскими техниками для точечного пробития tcp.
Библиотека программ автоматизации и оркестрации zapret-auto.lua
Стандартный порядок применения инстансов линеен - слева направо с учетом внутрипрофильных фильтров и instance cutoff. nfqws2 никаких других вариантов не предоставляет.
Конечно, вы можете написать свою Lua функцию, которая сделает что нужно и когда нужно. Но вам придется при этом изобрести какое-то количество велосипедов, дублировать код или того хуже - патчить стандартные функции antidpi, добавляя туда ваши хотелки, а потом это самостоятельно поддерживать.
Суть механизмов оркестрации в отделении управляющей логики от логики непосредственных действий, чтобы не надо было ничего патчить, а если и писать свои функции, то писать в них только сам алгоритм управления, не мешая его с алгоритмами действий.
Оркестрация неразрывно связана с понятием плана выполнения (execution plan). Он включает в себя список инстансов, которые нужно вызвать последовательно с их параметрами и фильтрами. Базовый линейный оркестратор заложен в C код. Но эту роль может взять на себя и Lua функция, в которой можно запрограммировать любую логику.
Например, можно сделать автоматические стратегии - если одна не работает, использовать другую. C код имеет подобную логику только в механизме автоматических хостлистов. Но она не реализует динамическую смену стратегий.
Хранилище состояний
Логика автоматизации как правило простирается между пакетами и опирается на conntrack. Для хранения состояния потока используются элементы desync.track.lua_state.
Другая часть информации простирается между потоками и привязана к хосту. Для хранения этой информации используются глобальные таблицы, индексируемые по ключам.
automate_conn_record
function automate_conn_record(desync)
Возвращает таблицу - хранилище состояния автоматизации, привязанное к потоку. Функции автоматизации могут использовать любые поля.
standard_hostkey
function standard_hostkey(desync)
- arg: reqhost - требовать наличие hostname, не работать по IP
- arg: nld - номер уровня домена, до которого отсекается hostname. если не задано - не отсекается
Стандартный генератор host-ключей. Функция вычисляет строку-ключ, связанный с именем хоста или IP адресом, если имя хоста отсутствует. Если не может - возвращает nil.
automate_host_record
function automate_host_record(desync)
- arg: key - ключ внутри глобальной таблицы autostate. если не задано, в качестве ключа используется имя текущего инстанса
- arg: hostkey - имя функции генератора host-ключей. если не задано, используется standard_hostkey
Возвращат таблицу - хранилище состояния автоматизации, привязанное к хосту или IP адресу. Используется 2 ключа - key и hostkey. Итоговое расположение - autostate.key.hostkey. Если hostkey получить не удается - возвращается nil.
Обслуживание удач и неудач
Простое, но далеко неточное, обьяснение понятиям "удача" и "неудача" - "сайт открывается" и "сайт не открывается". В логике автоматизации эти состояния абстрактны. Любой процесс может претерпеть удачу или неудачу. Детект состояния удачи или неудачи нужен для подсчета неудач через счетчик. При неудаче счетчик увеличивается, при успехе - сбрасывается, при достижении целевого значения - возвращается признак, по которому внешняя логика может выполнить любое действие. Например, сменить стратегию.
automate_failure_counter
function automate_failure_counter(hrec, crec, fails, maxtime)
- hrec - хост хранилище
- crec - потоковое хранилище
- fails - целевое количество неудач
- maxtime - время в секундах, после которого с момента предыдущей неудачи следующая неудача начинает счет заново
Возвращает true, если счетчик достиг значения fails. При этом счетчик сбрасывается.
automate_failure_counter_reset
function automate_failure_counter_reset(hrec)
- hrec - хост хранилище
Сбрасывает значение счетчика.
Детекция удач и неудач
Детекторы удач и неудач - это заменяемые функции, берущие в качестве параметров desync и crec и возвращающие true , если зафиксирована удача или неудача.
automate_failure_check
function automate_failure_check(desync, hrec, crec)
- hrec - хост хранилище
- crec - потоковое хранилище
- arg: success_detector - имя функции детектора удач. если не задано, используется standard_success_detector.
- arg: failure_detector - имя функции детектора неудач. если не задано, используется standard_failure_detector.
- arg: fails - целевое значение счетчика неудач. По умолчанию - 3.
- arg: maxtime - максимальное время в секундах между неудачами, после которого счетчик сбрасывается. По умолчанию - 60 сек.
Функция выполняет ведение счетчика неудач, вызывая детекторы удачи и неудачи. Возвращает true, если счетчик достиг целевого значения. При этом счетчик сбрасывается автоматически.
standard_success_detector
Стандартные детекторы удач и неудач требуют перенаправления входящего и исходящего трафика в объеме, необходимом для срабатывания их критериев. По relative sequence - нужны пакеты до указанного rseq + длина максимального пейлоада одного пакета (1460 байт для tcp).
function standard_success_detector(desync, crec)
- crec - потоковое хранилище
- arg: maxseq - исходящий relative sequence, при достижении которого фиксируется удача. по умолчанию 32768. Смысл : отправлено достаточно много без застревания потока из-за блокировки.
- arg: inseq - входящий relative sequence, при достижении которого фиксируется удача. по умолчанию 4096. Смысл : оппонент прислал достаточно много, чтобы это не было реакцией DPI.
- arg: udp_out, udp_in - принято более udp_in udp пакетов при условии, что udp_out>0. Смысл : оппонент прислал достаточно много, чтобы это не было реакцией DPI.
Стандартный детектор удач.
standard_failure_detector
function standard_failure_detector(desync, crec)
- crec - потоковое хранилище
- arg: maxseq - считать ретрансмиссии в пределах исходящих relative sequence от 1 до maxseq. По умолчанию - 32768.
- arg: retrans - считать неудачей не менее retrans ретрансмиссий. По умолчанию - 3.
- arg: reset - посылать ретрансмиттеру RST, чтобы прекратить долгое ожидание
- arg: inseq - считать неудачей RST и http redirect в пределах входящих relative sequence от 1 до inseq. По умолчанию - 4096.
- arg: no_rst - не определять RST как неудачу
- arg: no_http_redirect - не определять http redirect как неудачу
- arg: udp_out, udp_in - считать неудачей ситуацию, когда по потоку отослано >=udp_out пакетов, а принято <=udp_in пакетов. Смысл : мы много шлем, а нам не отвечают или отвечают мало.
Стандартный детектор неудач.
http редиректом от DPI считается то же самое , что и в автохослистах.
Оркестраторы
circular
function circular(ctx, desync)
- arg: standard host storage
- arg: standard checker
- arg: (только для стандартного детектора) standard success detector
- arg: (только для стандартного детектора) standard failure detector
Оркестратор позволяет считать неудачи и менять стратегии по кругу по достижению счетчиком неудач целевого значения fails. Все последующие инстансы маркируются аргументом "strategy", который содержит номер стратегии, начиная от 1. Инстансы, не имеющие аргумента "strategy", circular не вызывает. Номера стратегии должны идти непрерывно от 1 до последней, промежутки не допускаются, иначе вызывается error. Если в любом инстансе стратегии N имеется аргумент "final", эта стратегия является последней - дальнейшее хождение по кругу блокируется.
Пример запуска :
--lua-desync=circular:fails=4:retrans=2:maxseq=16384
--lua-desync=argdebug:v=1.1:strategy=1
--lua-desync=argdebug:v=1.2:strategy=1
--lua-desync=argdebug:v=2.1:strategy=2
--lua-desync=argdebug:v=3.1:strategy=3:final
--lua-desync=argdebug:v=3.2:strategy=3
--lua-desync=argdebug:v=3.3:strategy=3
Вместо реальных стратегий в примере используется вывод отладочной информации, чтобы проследить операции circular в debug log. Имеется 3 стратегии. 1-я стратегия включает 2 инстанса, 2-я - 1, 3-я - 3. 3-я стратегия является финальной, переход к 1 блокируется. Тренироваться нужно, дергая заблокированные сайты через curl, чтобы увидеть работу детектора неудач.
repeater
function repeater(ctx, desync)
- arg: instances - сколько ближайших инстансов повторять
- arg: repeats - количество повторов
- arg: stop - не проигрывать однократно последующие инстансы после "instances"
- arg: clear - очистить execution plan после повторений
- arg: iff - имя функции условия для продолжения цикла повторов. если не задано - условие всегда true
- arg: neg - инвертировать значение iff. по умолчанию - false
Смысл repeater заключен в самом названии - он повторяет последующие инстансы в количестве instances repeats раз. Повторение идет по принципу 1-2-3-1-2-3-1-2-3-4-5-6. 4-5-6 в данном случае - последующие за 1-2-3 инстансы, если instances=3. Если задано stop или clear, 4-5-6 не вызываются. clear дополнительно очищает execution plan - бывает нужно для взаимодействия с вышестоящими оркестраторами.
Функция iff позволяет задать дополнительное динамическое условие продолжения цикла повторов. Если xor(iff, neg) = false, выполнение цикла прерывается.
repeater может сколько угодно раз быть вложенным. В примере последовательность вызова получается : 1 1 1 2 2 2 1 1 1 2 2 2 3. Параметр stop у вложенных инстансов не позволяет выполнить инстансы, не относящиеся к собственному циклу повтора. Первый repeater не ограничен stop, поэтому он выполняет 3.
--lua-desync=repeater:repeats=2:instances=4
--lua-desync=repeater:repeats=3:stop --lua-desync=argdebug:v=1
--lua-desync=repeater:repeats=3:stop --lua-desync=argdebug:v=2
--lua-desync=argdebug:v=3
condition
function condition(ctx, desync)
- arg: iff - имя функции условия
- arg: neg - инвертировать значение iff. по умолчанию - false
condition вызывает iff. если iff xor neg = true, выполняются все инстансы plan, иначе план очищается.
stopif
function condition(ctx, desync)
- arg: iff - имя функции условия
- arg: neg - инвертировать значение iff. по умолчанию - false
stopif вызывает iff. если iff xor neg = true, план очищается, иначе не делается ничего.
stopif может быть полезен как вложенный оркестратор. Например, его можно использовать с circular, чтобы блокировать выполнение стратегий при определенных условиях. condition для этих целей не подходит, потому что ничего не знает о вышестоящих оркестраторах, не знает о параметре "strategy", и будет выполнять инстансы без разбору до конца. stopif очистит план, тем самым прекратив дальнейшее выполнение вышестоящего оркестратора.
iff функции
Они используются в нескольких оркестраторах. Берут desync в качестве параметра. Могут содержать любую логику, которую можно запрограммировать на Lua. В базовом комплекте есть несколько iff функций для демонстрации возможностей и тестирования.
cond_true
function cond_true(desync)
Всегда true
cond_false
function cond_false(desync)
Всегда false
cond_random
function cond_random(desync)
- arg: percent - процент выпадения true. по умолчанию 50
Случайное значение true с вероятностью percent, иначе false.
cond_payload_str
function cond_payload_str(desync)
- arg: pattern - строка, которая ищется в пейлоаде
Возвращает true, если в desync.dis.payload присутствует подстрока pattern. Это простейший сигнатурый детектор. Если C код не распознает нужный вам протокол, вы можете написать свой сигнатурный детектор и запускать последующие инстансы под оркестратором condition с вашим детектором в качестве iff.
Вспомогательные программы
ip2net
Утилита ip2net предназначена для преобразования ipv4 или ipv6 списка ip в список подсетей с целью сокращения размера списка. Входные данные берутся из stdin, выходные выдаются в stdout.
-4 ; лист - ipv4 (по умолчанию)
-6 ; лист - ipv6
--prefix-length=min[-max] ; диапазон рассматриваемых длин префиксов. например : 22-30 (ipv4), 56-64 (ipv6)
--v4-threshold=mul/div ; ipv4 : включать подсети, в которых заполнено по крайней мере mul/div адресов. например : 3/4
--v6-threshold=N ; ipv6 : минимальное количество ip для создания подсети
В списке могут присутствовать записи вида ip/prefix и ip1-ip2. Такие записи выкидываются в stdout без изменений. Они принимаются командой ipset. ipset умеет для листов hash:net из ip1-ip2 делать оптимальное покрытие ip/prefix. ipfw из FreeBSD понимает ip/prefix, но не понимает ip1-ip2. ip2net фильтрует входные данные, выкидывая неправильные IP адреса.
Выбирается подсеть, в которой присутствует указанный минимум адресов. Для ipv4 минимум задается как процент от размера подсети (mul/div. например, 3/4), для ipv6 минимум задается напрямую.
Размер подсети выбирается следующим алгоритмом: Сначала в указанном диапазоне длин префиксов ищутся подсети, в которых количество адресов - максимально. Если таких сетей найдено несколько, берется наименьшая сеть (префикс больше). Например, заданы параметры v6_threshold=2 prefix_length=32-64, имеются следующие ipv6 :
1234:5678:aaaa::5
1234:5678:aaaa::6
1234:5678:aaac::5
Результат будет :
1234:5678:aaa8::/45
Эти адреса так же входят в подсеть /32. Однако, нет смысла проходиться ковровой бомбардировкой, когда те же самые адреса вполне влезают в /45 и их ровно столько же. Если изменить v6_threshold=4, то результат будет:
1234:5678:aaaa::5
1234:5678:aaaa::6
1234:5678:aaac::5
То есть ip не объединятся в подсеть, потому что их слишком мало.
Если изменить prefix_length=56-64, результат будет:
1234:5678:aaaa::/64
1234:5678:aaac::5
Требуемое процессорное время для вычислений сильно зависит от ширины диапазона длин префиксов, размера искомых подсетей и длины листа. Если ip2net думает слишком долго, не используйте слишком большие подсети и уменьшите диапазон длин префиксов. Учтите, что арифметика mul/div - целочисленная. При превышении разрядной сетки 32 bit результат непредсказуем. Не надо делать такое: 5000000/10000000. 1/2 - гораздо лучше.
mdig
Программа предназначена для многопоточного ресолвинга больших листов через системный DNS. Она берет из stdin список доменов и выводит в stdout результат ресолвинга. Ошибки выводятся в stderr.
--family=<4|6|46> ; выбор семейства IP адресов : ipv4, ipv6, ipv4+ipv6
--threads=<threads_number> ; количество потоков. по умолчанию 1.
--eagain=<eagain_retries> ; количество попыток повтора после EAI_AGAIN. по умолчанию 10
--eagain-delay=<ms> ; время ожидания в мсек между повторами по EAI_AGAIN. по умолчанию 500.
--verbose ; дебаг-лог на консоль
--stats=N ; выводить статистику каждые N доменов
--log-resolved=<file> ; сохранять успешно отресолвленные домены в файл
--log-failed=<file> ; сохранять неудачно отресолвленные домены в файл
--dns-make-query=<domain> ; вывести в stdout бинарный DNS запрос по домену. если --family=6, запрос будет AAAA, иначе A.
--dns-parse-query ; распарсить бинарный DNS ответ и выдать все ivp4 и ipv6 адреса из него в stdout
Параметры --dns-make-query и --dns-parse-query позволяют провести ресолвинг одного домена через произвольный канал.
Например, следующим образом можно выполнить DoH запрос, используя лишь mdig и curl :
mdig --family=6 --dns-make-query=rutracker.org | curl --data-binary @- -H "Content-Type: application/dns-message" https://cloudflare-dns.com/dns-query | mdig --dns-parse-query
blockcheck2
blockcheck2 - это средство автоматизации проверки стратегий. Представляет собой posix shell script. Имеет модульную структуру тестов.
Тест - это набор подключаемых шелл-скриптов для тестирования блока стратегий. Тестируемые стратегии могут формироваться алгоритмом скрипта в зависимости от различных условий или успешности предыдущих проверок, чтобы сократить общее время проверок.
Наборы тестов располагаются в субдиректориях blockcheck2.d. Название субдиректории есть название теста.
blockcheck2 по умолчанию работает в интерактивном режиме, выводя сообщения и спрашивая у пользователя параметры. Но параметров слишком много, поэтому спрашиваются далеко не все из них, а только основные. Оставшиеся передаются через shell переменные.
Типичная схема запуска с переменными :
BATCH=1 DOMAINS=bbc.com CURL_CMD=1 SKIP_DNSCHECK=1 /opt/zapret2/blockcheck2.sh
Если нужно записать лог, это делается стандартными для shell средствами :
/opt/zapret2/blockcheck2.sh | tee /tmp/blockcheck2.log
В win bundle можно пользоваться cygwin prompt (cygwin/cygwin-admin.cmd). Там уже созданы alias-ы для запуска blockcheck из первой версии zapret, blockcheck2, winws, winws2 с подключенными стандартными Lua скриптами. Это удобно, потому что не нужно думать о путях и каждый раз писать или вставлять откуда-то огромный текст, тем боллее при наличии национальных символов и пробелов в путях, путаясь в экранировании символов и кодировках.
Возможно последовательное тестирование нескольких доменов. Для этого нужно задать их через пробел.
Поддерживаются URI вида rutracker.org/forum/index.php. Префикса с протоколом вида https:// быть не должно. По умолчанию используется URI - корень ('/'). http тестируется через метод GET, https - через метод HEAD, поскольку под tls все равно ничего не видно. Однако, бывают ситуации, когда идет блок не сразу - по длинному ответу сервера. В этом случае можно пользоваться CURL_HTTPS_GET=1 и указать URI, по которому сервер возвращает длинный ответ.
blockcheck2 не является панацеей, не является средством генерации магических букв, которые надо куда-то вписать, чтобы сайты начали открываться. Это настраиваемое средство исследования DPI и автоматизации рутинной работы. Понимание выдаваемых результатов и способов их применения ложится на пользователя.
Не стоит так же ждать от blockcheck каких-то сверхсложных алгоритмов выбора стратегий. shell скрипты не являются полноценным языком программирования, не содержат средств работы со сложными структурами данных. Программирование на shell при работе со сложными данными часто превращается в мучение, поскольку их надо как-то записывать в линейный набор переменных окружения.
blockcheck2 работает на всех поддерживаемых платформах - Linux, FreeBSD, OpenBSD, Windows. На Windows самый простой способ им воспользоваться - через win bundle - минимальную систему cygwin, преднастроенную для zapret.
Проверка DNS
Ничего не будет работать, если на заблокированные домены провайдер будет отдавать подмененные IP адреса, если только ваш клиент или ОС не поддерживают передачу запросов через шифрованные каналы (DoH, например). Даже если броузер и заработает из-за встроенного DoH, существуют и другие программы, в которых поддержки нет. Если сама ОС поддерживает шифрованный DNS - можно воспользоваться этой возможностью, а если нет - решение проблемы DNS ложится на вас.
Если IP подменяются только на DNS серверах провайдера, а доступ к другим DNS не блокируется и не подменяется, можно использовать другие DNS. Например, публичные - 1.1.1.1, 8.8.8.8, 9.9.9.9. Если доступ к другим DNS блокируется или подменяется, нужно шифровать DNS канал. Может быть и так, что подмена идет только на порту 53, а на других портах DNS ответы не подменяются. Но такую конфигурацию не поддерживают клиенты. Нужно или сделать прозрачное перенаправление на роутере, или использовать агрегатор DNS (dnsmasq, например), который поддерживает обращение к DNS на нестандартных портах.
yandex предоставляет DNS на портах 1253. На openwrt он прописывается довольно просто :
/etc/config/dhcp
config dnsmasq
list server '77.88.8.88#1253'
/etc/config/network
config interface 'wan'
option peerdns '0'
blockcheck2 способен определить подменяется ли DNS и перехватывается ли обращение к сторонним DNS. Если есть подмена, он автоматически переходит на DoH. Список внешних DNS серверов, доменов для тестирования подмены, выбранный DoH сервер и список DoH серверов для автоматического выбора меняются через shell переменные. Тем же способом можно и отказаться от тестирования.
Переменная SECURE_DNS позволяет принудительно отключить переход на DoH или наоборот - форсировать его, даже если подмены нет.
Основные режимы тестирования
Множественные попытки
Ситуация нестабильности стратегий - явление частое. Бывает у провайдера балансировка нагрузки , и разные запросы проходят через разные DPI. То работает, то не работает. Стабильность стратегий тестируется за счет множественных повторений - попыток. Количество попыток задается в диалоге или через shell переменные.
Поддерживается параллельный режим. В нем каждая попытка выполняется в отдельном дочернем процессе, а потом собираются результаты со всех процессов. Режим включается только через переменную PARALLEL. Может значительно ускорить тестирование, но так же может и нарваться на rate limit - ситуацию, когда сервер вас ограничивает или банит из-за слишком частой долбежки.
Уровни сканирования
- standard - использовать алгоритм теста, позволяющий исключить тестирование неактуальных по его мнению стратегий в зависимости от успешности предыдущих или по каким-то другим критериям. В случае множественных попыток тестирование не прекращается при неудаче. Процент успеха и ошибки curl тоже могут служить полезной информации при анализе ситуации.
- quick - то же самое, что и standard, но при использовании множественных попыток прекращение тестирования после первой неудачи
- force - тестировать максимально много без учета результата предыдущих тестов
Поддерживаемые протоколы
blockcheck2 тестирует стратегии через curl. Доступна проверка http, https через TLS 1.2, https через TLS 1.3 и http3 (quic). Поддержка TLS 1.3 и quic может отсутствовать в curl, который установлен в вашей системе. Если это так, можно взять статический curl с curl.se, записать его куда-нибудь, дать права на исполнение и указать переменную CURL=<путь>/curl. Тогда блокчек будет использовать его вместо системного. На OpenWRT с минимумом места на диске можно использовать /tmp, который есть tmpfs и хранится в RAM.
TLS 1.2 - более сложен для обхода DPI, чем TLS 1.3 по причине возврата в TLS server hello сертификата в открытом виде, где прописан домен. По ответу сервера DPI может блокировать соединение, и с этим что-то сделать довольно сложно. Для обхода TLS 1.2 лучшая тактика - сделать так, чтобы DPI удовлетворился запросом от клиента и не стал проверять ответ сервера. Если это никак не выходит, есть wssize, но он снижает скорость и не работает с хостлистами.
TLS 1.3 выдает минимум незашифрованной информации в TLS server hello. Там нет информации о домене в открытом виде, как и нет практически никакого fingerprint, поэтому DPI не может блокировать по домену за счет анализа ответа сервера, но может блокировать по протоколу TLS вообще, если в его правилах стоит запрет TLS.
Проверка блока по IP
zapret не может обойти блок по IP. Блоки по IP бывают разные.
- Полный блок IP. Не доходит ничего. Нет ни пингов, ни коннекта на порт. Без проксирования ничего сделать нельзя.
- Блок по порту или L4 протоколу. Например, пинги ходят, но не коннектят никакие tcp порты, udp тоже ходят. Или заблокирован порт tcp 443. Блокировка tcp порта означает, что все пакеты с этим destination port блокируются. Коннект повисает на бесконечной отправке SYN запросов, 3-way handshake не проходит. Без проксирования ничего сделать нельзя.
- Частичный блок по ip/port. 3-way handshake проходит, но дальше все, что бы вы туда ни отправляли, приводит лишь к зависанию или к приходу RST. Сам факт, что коннект проходит, уже говорит о потенциальной возможности наличия неких "белых" сообщений, которые открывают доступ. Это могут быть запросы с белыми SNI или пакеты с какими-то другим протоколом - не TLS. Пробить этот блок можно только если у вас есть готовая информация чем его пробивать или вы достаточно владеете техническими средствами и у вас достаточно упорства, чтобы проверять варианты вручную.
blockcheck2 способен содействовать в определении IP блока, но он не выносит самостоятельный вердикт - вы должны сами решить по его действиям есть ли блок.
Первым делом проверяется доступность порта через nc или ncat. Они должны быть установлены, из коробки их может не быть. Предпочителен ncat, поскольку у него больше возможностей и точно нет проблем с ipv6. Если не установлено - тест отменяется. Вынесению автоматического решения препятствует несогласованность exit кодов разных вариантов netcat. Где-то вообще не возвращается информация об успехе или неудаче, а только сообщения, зависящие от версии.
Вы должны посмотреть лог, и если не увидите успеха подключения на порт, значит это может говорить о блокировке ip/порта. Может быть блокировка только части IP ресурса. Если продолжить ничего не делая - стратегии будут работать нестабильно, потому что запросы пойдут на случайный IP из доступных. Если блокированы все ip/port - дальнейшая проверка полностью теряет смысл - везде будут ошибки.
Далее следует проверка частичного IP блока через curl. Исследуется ситуация, когда коннект на порт есть. Задача выяснить происходит ли блокировка на IP блокированного домена при использовании неблокированного домена и наоборот. Если на IP блокированного домена не проходят неблокированные домены, но они проходят на своих родных IP адресах, значит есть частичный блок по IP. Если блокированные домены не проходят на IP неблокированных доменов, а сами неблокированные домены - проходят, значит есть блок по SNI. Блоки по IP и SNI могут сочетаться.
Оценка успеха или неуспеха этих проверок и есть та загвоздка, из-за которой интерпретация результата ложится на человека. Что есть успех и не успех ? Это очень сильно варьируется и может зависеть как от DPI, так и от самого сервера.
Например, ошибка TLS при запросе через SNI iana.org на IP rutracker.org, может свидетельствовать об успехе. Ошибка сертификата может быть как успехом, так и неудачей - неверный сертификат может вернуть как сам сервер, так и DPI в результате MiTM атаки. Вам важно понять вернул ли сервер хоть какой-то ответ и является ли это действительно ответом сервера или является результатом работы DPI. TLS alert при запросе домена, который не хостится на сервере, явление нормальное и частое. Но может так же и быть, что сервер выдает страницу на любой SNI в TLS - тоже нормальная ситуация.
Подвисание - это обычно неудача, но может быть и следствием проблем на самом сервере или если он вас забанил. RST - вероятно неудача, но может быть и легитимным ответом сервера или его системы DDoS защиты.
Вариантов здесь много, поэтому смотрите и рассуждайте.
Примеры блокировки только по домену без блока по IP
> testing iana.org on it's original
!!!!! AVAILABLE !!!!!
> testing rutracker.org on 192.0.43.8 (iana.org)
curl: (28) Operation timed out after 1002 milliseconds with 0 bytes received
> testing iana.org on 172.67.182.196 (rutracker.org)
HTTP/1.1 409 Conflict
> testing iana.org on 104.21.32.39 (rutracker.org)
HTTP/1.1 409 Conflict
> testing iana.org on it's original ip
!!!!! AVAILABLE !!!!!
> testing rutracker.org on 192.0.43.8 (iana.org)
curl: (28) Connection timed out after 1001 milliseconds
> testing iana.org on 172.67.182.196 (rutracker.org)
curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure
> testing iana.org on 104.21.32.39 (rutracker.org)
curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure
> testing iana.org on it's original ip
!!!!! AVAILABLE !!!!!
> testing rutracker.org on 192.0.43.8 (iana.org)
HTTP/1.1 307 Temporary Redirect
Location: https://www.gblnet.net/blocked.php
> testing iana.org on 172.67.182.196 (rutracker.org)
HTTP/1.1 409 Conflict
> testing iana.org on 104.21.32.39 (rutracker.org)
HTTP/1.1 409 Conflict
> testing iana.org on it's original ip
!!!!! AVAILABLE !!!!!
> testing rutracker.org on 192.0.43.8 (iana.org)
curl: (35) Recv failure: Connection reset by peer
> testing iana.org on 172.67.182.196 (rutracker.org)
curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure
> testing iana.org on 104.21.32.39 (rutracker.org)
curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure
Пример полного IP блока или блока TCP порта при отсутствии блока по домену
* port block tests ipv4 startmail.com:80
ncat -z -w 1 145.131.90.136 80
145.131.90.136 does not connect. netcat code 1
ncat -z -w 1 145.131.90.152 80
145.131.90.152 does not connect. netcat code 1
* curl_test_http ipv4 startmail.com
- checking without DPI bypass
curl: (28) Connection timed out after 2002 milliseconds
UNAVAILABLE code=28
- IP block tests (requires manual interpretation)
> testing iana.org on it's original ip
!!!!! AVAILABLE !!!!!
> testing startmail.com on 192.0.43.8 (iana.org)
HTTP/1.1 302 Found
Location: https://www.iana.org/
> testing iana.org on 145.131.90.136 (startmail.com)
curl: (28) Connection timed out after 2002 milliseconds
> testing iana.org on 145.131.90.152 (startmail.com)
curl: (28) Connection timed out after 2002 milliseconds
Стандартные тесты
Тест standard
Основной тест, состоящий из ряда подтестов в blockcheck2.d/standard. Если вам не нужны какие-то тесты, можно скопировать директорию standard в my и оставить там только нужные скрипты. def.in нужен обязательно.
Принимает ряд дополнительных переменных :
MIN_TTL - минимальный TTL
MAX_TTL - максимальный TTL. 0 отключает тесты TTL
MIN_AUTOTTL_DELTA - минимальная отрицательная дельта autottl
MAX_AUTOTTL_DELTA - максимальная отрицательная дельта autottl. 0 отключает тесты AUTOTTL
FAKE_REPEATS - количество повторов fake
FOOLINGS46_TCP - список через пробел фулингов TCP для ipv4+ipv6
FOOLINGS6_TCP - список через пробел особых фулингов TCP для ipv6
FAKE_HTTP - путь к файлу с фейком для http
FAKE_HTTPS - путь к файлу с фейком для https
FAKED_PATTERN_HTTP - путь к файлу с fakedsplit/fakeddisorder pattern для http
FAKED_PATTERN_HTTPS - путь к файлу с fakedsplit/fakeddisorder pattern для https
FAKE_QUIC - путь к файлу с фейком для quic
SEQOVL_PATTERN_HTTP - путь к файлу с seqovl pattern для http
SEQOVL_PATTERN_HTTPS - путь к файлу с seqovl pattern для https
MULTIDISORDER=multidisorder_legacy - заменить multidisorder на вариант из nfqws1
NOTEST_BASIC_HTTP=1 - отмена тестов 10-http-basic.sh
NOTEST_MISC_HTTP=1 - отмена тестов http 15-misc.sh
NOTEST_MISC_HTTPS=1 - отмена тестов https 15-misc.sh
NOTEST_MULTI_HTTP=1 - отмена тестов http 20-multi.sh
NOTEST_MULTI_HTTPS=1 - отмена тестов https 20-multi.sh
NOTEST_SEQOVL_HTTP=1 - отмена тестов http 23-seqovl.sh
NOTEST_SEQOVL_HTTPS=1 - отмена тестов https 23-seqovl.sh
NOTEST_SYNDATA_HTTP=1 - отмена тестов http 24-syndata.sh
NOTEST_SYNDATA_HTTPS=1 - отмена тестов https 24-syndata.sh
NOTEST_FAKE_HTTP=1 - отмена тестов http 25-fake.sh
NOTEST_FAKE_HTTPS=1 - отмена тестов https 25-fake.sh
NOTEST_FAKED_HTTP=1 - отмена тестов http 25-faked.sh
NOTEST_FAKED_HTTPS=1 - отмена тестов https 25-faked.sh
NOTEST_HOSTFAKE_HTTP=1 - отмена тестов http 35-hostfake.sh
NOTEST_HOSTFAKE_HTTPS=1 - отмена тестов https 35-hostfake.sh
NOTEST_FAKE_MULTI_HTTP=1 - отмена тестов http 50-fake-multi.sh
NOTEST_FAKE_MULTI_HTTPS=1 - отмена тестов https 50-fake-multi.sh
NOTEST_FAKE_FAKED_HTTP=1 - отмена тестов http 55-fake-faked.sh
NOTEST_FAKE_FAKED_HTTPS=1 - отмена тестов https 55-fake-faked.sh
NOTEST_FAKE_HOSTFAKE_HTTP=1 - отмена тестов http 60-fake-hostfake.sh
NOTEST_FAKE_HOSTFAKE_HTTPS=1 - отмена тестов https 60-fake-hostfake.sh
NOTEST_QUIC=1 - отмена тестов 90-quic.sh
Тест custom
Простой тестировщик по списку стратегий из файлов. Стратегии должны быть на отдельных строчках, переносы строки не допускаются. Используются отдельные списки для протоколов - list_http.txt, list_https_tls12.txt, list_https_tls13.txt, list_quic.sh. В файлах поддерживаются комментарии, начинающиеся на #.
При записи параметров следует учитывать, что они будут интерпретироваься как параметры shell. Специальные символы нужно экранировать по правилам shell.
Если вы оставите "<" без кавычек на всем параметре или возьмете в кавычки параметр --luaexec=code=print("abc"), будет ошибка. Если в lua коде используются строки - их стоит заключать в одинарные кавычки, а вокруг параметра - двойные.
blockcheck2 будет выдавать параметры стратегий без экранирования.
Самый правильный способ применения - скопировать в свою поддиректорию внутри blockcheck2.d и заполнить txt файлы с тестами. Выбрать свое имя теста в диалоге.
Summary
В конце теста выдаются все успешные стратегии по каждому домену и каждой версии ip протокола. Если тестировалось более одного домена, дополнительно выдается пересечение успешных стратегий. Однако, ему можно полностью доверять только при использовании SCANLEVEL=force. Иначе возможна ситуация, когда для первого домена не стали тестировать стратегии, которые подошли бы для последующих.
Shell переменные
CURL - замена программы curl
CURL_MAX_TIME - время таймаута curl в секундах
CURL_MAX_TIME_QUIC - время таймаута curl для quic. если не задано, используется значение CURL_MAX_TIME
CURL_MAX_TIME_DOH - время таймаута curl для DoH серверов
CURL_CMD=1 - показывать команды curl
CURL_OPT - дополнительные параметры curl. `-k` - игнор сертификатов. `-v` - подробный вывод протокола
CURL_HTTPS_GET=1 - использовать метод GET вместо HEAD для https
DOMAINS - список тестируемых доменов или доменов с URI через пробел
TEST - имя теста
IPVS=4|6|46 - тестируемые версии ip протокола
ENABLE_HTTP=0|1 - включить тест plain http
ENABLE_HTTPS_TLS12=0|1 - включить тест https TLS 1.2
ENABLE_HTTPS_TLS13=0|1 - включить тест https TLS 1.3
ENABLE_HTTP3=0|1 - включить тест QUIC
REPEATS - количество попыток тестирования
PARALLEL=0|1 - включить параллельные попытки. может обидеть сайт из-за долбежки и привести к неверному результату
SCANLEVEL=quick|standard|force - уровень сканирования
BATCH=1 - пакетный режим без вопросов и ожидания ввода в консоли
HTTP_PORT, HTTPS_PORT, QUIC_PORT - номера портов для соответствующих протоколов
SKIP_DNSCHECK=1 - отказ от проверки DNS
SKIP_IPBLOCK=1 - отказ от тестов блокировки по порту или IP
PKTWS_EXTRA_POST - дополнительные параметры nfqws/dvtws/winws, указываемые после основной стратегии
PKTWS_EXTRA_POST_1 .. PKTWS_EXTRA_POST_9 - отдельно дополнительные параметры, содержащие пробелы
PKTWS_EXTRA_PRE - дополнительные параметры для nfqws/dvtws/winws, указываемые перед основной стратегией
PKTWS_EXTRA_PRE_1 .. PKTWS_EXTRA_PRE_9 - отдельно дополнительные параметры, содержащие пробелы
DNSCHECK_DNS - список сторонних DNS через пробел для тестирования подмены DNS
DNSCHECK_DOM - список доменов через пробел для тестирования подмены DNS
SECURE_DNS=0|1 - принудительно выключить или включить DoH
DOH_SERVERS - список URL DoH через пробел для автоматического выбора работающего сервера
DOH_SERVER - конкретный DoH URL, отказ от поиска
UNBLOCKED_DOM - незаблокированный домен, который используется для тестов IP block
SIMULATE=1 - включить режим симуляции для отладки логики скрипта. отключаются реальные запросы через curl, заменяются рандомным результатом.
SIM_SUCCESS_RATE=<percent> - вероятность успеха симуляции в процентах
Почему не открывается
Блокчек показывает OK, но сайт не открывается. Почему ? blockcheck проверяет доступность одного отдельно взятого "domain[/uri]" с отдельно взятым протоколом, не более. Броузер делает куда больше. В чем же разница ?
- Может все начинаться с DNS. blockcheck использует системный или doh, а броузер - наоборот. Такие сайты, как instagram, частично заблокированы по IP. Успех зависит от того, какой именно IP выдается конкретным DNS. Если же у вас DNS отравлен провайдером, а ваш клиент не поддерживает шифрованный DNS, то это сразу конец всем обходам. Вы пойдете на IP адрес заглушки, и , естественно, никакой zapret не поможет. Бывает, провайдер вообще не выдает DNS ответ на заблокированный домен или выдает 127.0.0.1.
- Сайт - это не один домен и не один URI. Жмете F12 в броузере и во вкладке "сеть" смотрите куда лезет сайт. Он может споткнуться на другом домене.
- Цель броузера - открыть сайт как можно быстрее, не нагружая пользователя техническими терминами, которые для него неотличимы от китайского языка. Поэтому он лезет на сайт по разным протоколам так, чтобы страница открылась быстрее. Может скакать между ipv4 и ipv6, между tls и quic. Уже 4 комбинации. А ведь для каждого из них отдельный тест блокчека. Все ли они пробились одной стратегией ? Корректно ли вы перенесли эти стратегии в рабочий конфиг ? Правильно ли их объединили ?
- Отдельный важный параметр - kyber. Постквантовая криптография, которая однопакетный запрос TLS/QUIC превращает в 2 или 3 пакетный. Само по себе фактор при обходе DPI. Современные броузеры обычно используют kyber. curl - зависит от его старости и от старости криптобиблиотеки, с которой он слинкован. OpenSSL 3.5.0 выдает kyber, старее - нет. LibreSSL или mbedTLS не выдают kyber. Сейчас. А завтра выдадут, потому что тренд идет в ту сторону.
- Блокираторы ничем не брезгуют. Бывает они даже привязываются к фингерпринту клиента. Что это такое ? Наличие/порядок tls extensions прежде всего, характерный для броузера или curl. Никак нельзя забанить IP адреса, но нужно устранить очередной новый VPN клиент ? Смотрят его handshake, находят уникальные особенности и рубят по ним. Заодно отрубается что-то еще. Но им на это уже плевать.
- ECH - технология зашифрованной передачи SNI, чтобы блокираторы не видели к какому ресурсу обращаются. Технология отличная, но, к сожалению, запоздавшая. Точка, когда технология уже окончательно вьелась в стандарты де-факто, не была пройдена. Поэтому могут банить как по самому наличию ECH, так и по внешнему SNI-заглушке. На cloudflare это "cloudflare-ech.com". curl дергает сайт без ECH, а броузер может с ним - вот и разница.
- Версия TLS протокола. По умолчанию blockcheck2 тестирует TLS 1.2 как самый тяжелый для обхода вариант. Броузер скорее всего полезет по TLS 1.3. Были случаи, когда блокираторы намеренно банили протокол TLS 1.3, потому что его использует и требует популярный метод обхода через VLESS-REALITY. Уровень пофигизма на сопутствующий ущерб уже настолько высок, что поломка множества легальных ресурсов их не останавливает.
- Знаменитый "16 кб" блок. Как проверить 16 кб блок ? Дернуть curl какой-то URI, на котором сайт выдаст достаточно длинную страницу. Если загрузка виснет посередине - это оно. По умолчанию blockcheck2 на https использует HEAD, чтобы не мучать сервер и экономить трафик - все равно под https ничего не видно. Это можно поменять через CURL_HTTPS_GET=1. Но так вы скорее всего начнете получать сплошные "UNAVAILABLE". Стандартный вариант дает стратегии, которые будут работать при обходе 16кб блока, поэтому более информативен. Почему же они это делают ? Поставлена задача загнать всех под российскую юрисдикцию, а для этого гнобят крупные хостинги - cloudfare, hetzner, akamai, aws и другие. CDN часто используются для скрытого проброса прокси или VPN. Им надо оставить несколько важных сайтов. hp.com - откуда еще драйвера для LaserJet качать ? А hp на одном из таких хостингов. Получается белый список. Следовательно, вам нужен пейлоад, который ему удовлетворяет. Но беда в том, что блокчек , как и сам zapret, - всего лишь инструмент. Он не включает в себя готовых рецептов. Вам самим нужно искать чем пробивать конкретные хостинги. Сейчас это так, а завтра будет еще что-то другое. Автор не будет гнаться за ситуацией, не будет оперативно менять блокчек, чтобы он вам выдавал готовые copy+paste рецепты. Вам нужно самим смотреть на ситуацию и искать решение. Сегодня белые списки в стиле "16 кб" пробиваются или белым SNI, или каким-то другим белым типом протокола, не TLS. Завтра будет что-то другое. Стандартный чекер берет целый список переменных для кустомизации сканирования. Есть возможность подсунуть что-то до и что-то после стратегии. Наконец, свет клином не сошелся на блокчеке. Иногда удобнее вручную проверять.
- В продолжение предыдущего пункта - широко развилась практика вводить особые правила на диапазонах IP адресов. Поэтому вы можете найти стратегию, работающую для большинства ресурсов, но на некоторых она работать не будет.
- Ходит информация о поведении DPI в стиле "наказать шалуна". DPI видит попытки его задурить и на некоторое время блокирует доступ по IP. Раз-два прошло, потом никакие конекты не проходят. Если IP динамический, помогает переподключение к линии провайдера, но лишь до новой попытки. Другой вариант - отправка udp пакета на триггерный IP (например, через торрент клиент) и последующий блок некоторых IP диапазонов. Автор не сталкивался с подобными блокировками, но сами динамические блокировки - это тот путь, которым прошел Китай. В России тоже можно ожидать.
- Нестабильность стратегии. Балансировка на провайдерах - явление частое. Трафик может идти то через один DPI, то через другой, и стратегия может работать через раз. blockcheck2 по умолчанию тестирует только одну попытку. Если есть сомнения в стабильности - следует увеличить количество попыток минимум до 5. Можно использовать PARALLEL=1, но это может привести к срабатыванию защиты самого ресурса от долбежки.
Гланая цель блокираторов - устранить массовое нарушение их блокировок. Если 99% нажимает на кнопку, и она не срабатывает, для них решение не работает. На деле же требуется техническое исследование что они там натворили очередной раз. Много ручных тестов для выявление алгоритма их действий и нащупывание обходного решения.
Скрипты запуска
Под скриптами запуска понимается обвязка для Linux, позволяющая устанавливать, настраивать, удалять, запускать и останавливать программу. Сюда же входит система обслуживания файлов IP и хостлистов. Она поддерживает openwrt и классические Linux с systemd и openrc. Для остальных Linux и прошивок возможна настройка параметров, но обеспечить автозапуск вам нужно самостоятельно.
Из прошивок гарантируется работа только на OpenWRT, начиная с 18-й версии. На более старых может работать или частично не работать. На keenetic работает под entware, но только в составе дополнительных "обеспечительных мер", которые выходят за рамки проекта zapret2 и поддерживаются сторонними разработчиками. На остальных прошивках прикрутка ложится целиком на пользователя. Есть и готовые интеграции в разные прошивки, но их поддержка лежит целиком на их авторах. Официально автором zapret они не поддерживаются.
nfqws2 может работать и самостоятельно без скриптов запуска. Но вам необходимо будет обеспечить автоматический запуск, передачу параметров, настройки фаервола.
На Windows система запуска не требуется - все как правило решается батниками для запуска winws2 в интерактивном режиме или управления службой.
На BSD работает только система получения листов из ipset. На FreeBSD она умеет загружать ipset-ы (tables) в ipfw. На OpenBSD файлы с ip листами загружает сам pf.
Файл config
Используется всеми компонентами скриптов запуска, имеет название "config" и располагается в корне директории zapret. Представляет собой shell include, в котором присваиваются переменные и поддерживаются комментарии, начинающиеся с '#'. Возможно и применение любых shell конструкций - таких, как адресация других переменных или арифметические операторы.
Сам nfqws2 ничего не знает о скриптах запуска и файле config, не принимает ссылку на него в командной строке.
| Параметр | Назначение |
|---|---|
| TMPDIR | временная директория вместо /tmp . полезно, если на устройстве мало памяти и tmpfs в /tmp не хватает |
| WS_USER | пользователь, под которым запускается nfqws2. по умолчанию используется автоматическое значение в зависимости от ОС |
| FWTYPE | тип фаервола - iptables, nftables, ipfw. по умолчанию определяется автоматически |
| SET_MAXELEM | максимальное количество записей в создаваемых ipset-ах |
| IPSET_OPT | опции ipset от iptables |
| IPSET_HOOK | скрипт, который получает имя ipset в $1, выдает в stdout список ip, и они добавляются в ipset |
| IP2NET_OPT4 IP2NET_OPT6 |
настройки ip2net для скриптов получения ip листов |
| MDIG_THREADS | количество потоков mdig. используется при ресолвинге хостлистов |
| MDIG_EAGAIN | количество попыток при получении EAI_AGAIN |
| MDIG_EAGAIN_DELAY | задержка в мсек между попытками при получении EAI_AGAIN |
| AUTOHOSTLIST_INCOMING_MAXSEQ AUTOHOSTLIST_RETRANS_MAXSEQ AUTOHOSTLIST_RETRANS_THRESHOLD AUTOHOSTLIST_RETRANS_RESET AUTOHOSTLIST_FAIL_THRESHOLD AUTOHOSTLIST_FAIL_TIME AUTOHOSTLIST_UDP_IN AUTOHOSTLIST_UDP_OUT |
параметры автохостлистов |
| AUTOHOSTLIST_DEBUGLOG | включение autohostlist debug log. лог пишется в ipset/zapret-hosts-auto-debug.log |
| GZIP_LISTS | применять ли сжатие gzip для генерируемых хост и ip листов |
| DESYNC_MARK | марк-бит для предотвращения зацикливания |
| DESYNC_MARK_POSTNAT | марк-бит для пометки потоков, идущих по схеме POSTNAT |
| FILTER_MARK | если задан, перехватывает пакеты только с этим битом mark. Полезно для организации особых фильтров, например, по ip источника локальной сети. |
| POSTNAT | использовать режим перехвата после NAT на nftables. По умолчанию - 1. На iptables перехват всегда до NAT. |
| NFQWS2_ENABLE | включение стандартного режима nfqws2 |
| NFQWS2_PORTS_TCP NFQWS2_PORTS_UDP |
порты перехвата для стандартного режима nfqws2 |
| NFQWS2_TCP_PKT_OUT NFQWS2_TCP_PKT_IN NFQWS2_UDP_PKT_OUT NFQWS2_UDP_PKT_IN |
ограничители connbytes по tcp/udp и направлению для стандартного режима nfqws2 |
| NFQWS2_PORTS_TCP_KEEPALIVE NFQWS2_PORTS_UDP_KEEPALIVE |
список портов tcp/udp, для которых выключается ограничитель connbytes по исходящему направлению для стандартного режима nfqws2 |
| NFQWS2_OPT | параметры командной строки стандартного режима nfqws2 |
| MODE_FILTER | режим фильтрации : none,ipset,hostlist,autohostlist |
| FLOWOFFLOAD | режим offload : donttouch,none,software,hardware |
| OPENWRT_LAN OPENWRT_WAN4 OPENWRT_WAN6 |
список через пробел lan и wan интерфейсов для ipv4 и ipv6 в OpenWRT. НЕ ИНТЕРФЕЙСЫ Linux, ИНТЕРФЕЙСЫ netifd ! по умолчанию "lan" и "wan" |
| IFACE_LAN IFACE_WAN IFACE_WAN6 |
список через проблем lan и wan интерфейсов для ipv4 и ipv6 в классическом Linux. ИНТЕРФЕЙСЫ Linux ! |
| INIT_APPLY_FW | должны ли применять стартовые скрипты правила firewall |
| INIT_FW_PRE_UP_HOOK INIT_FW_POST_UP_HOOK INIT_FW_PRE_DOWN_HOOK INIT_FW_POST_DOWN_HOOK |
хук-скрипты, вызываемые до, после поднятия и до, после опускания firewall |
| DISABLE_IPV4 DISABLE_IPV6 |
отключить версии ip протокола |
| FILTER_TTL_EXPIRED_ICMP | фильтровать "time exceeded" в ответ на пакеты, принадлежащие потокам, прошедшим через zapret |
| GETLIST | скрипт внутри ipset, вызываемый из ipset/get_config.sh . если не указано - ipset/get_ipban.sh |
- По стандартным режимом nfqws2 понимается то, что запускается с параметрами
NFQWS2_OPTпри включенииNFQWS2_ENABLE. В противовес custom скриптам, откуда могут запускаться нестандартные, кастомные, инстансы nfqws2. - netifd интерфейсы - это то, что видно в
/etc/config/networkили в Luci, и что берет команда ifstatus. Не являются интерфейсами Linux. Интерфейсы Linux указываются в параметре device в определении интерфейса и видны по командеip link. Например, интерфейс netifd - "lan", интерфейс Linux - "br-lan". В OPENWRT_LAN надо вписывать "lan", а не "br-lan", иначе это работать не будет. - Указание LAN интерфейсов нужно только для flow offloading в nftables, больше ни для чего не используется.
- Параметры командной строки
NFQWS2_OPTвключают только стратегию. Стандартные Lua файлы, служебные параметры типа--qnumили--userдобавляются автоматически. Можно добавлять свои--blobили--lua-init. NFQWS2_OPTберет маркеры<HOSTLIST>и<HOSTLIST_NOAUTO>. Это подстановка стандартных листов. Маркеры замещаются на параметры--hostlistи--hostlist-autoв зависимости от режимаMODE_FILTER.<HOSTLIST_NOAUTO>добавляет автохослист как обычный лист без авто. В параметры вписываются только те файлы стандартных листов, которые по факту есть. Маркеры могут вписываться в разные профили, и только вы можете знать куда их вписывать , а куда нет, исходя из логики ваших стратегий.- Прямое указание параметров в
NFQWS2_OPTтипа--hostlist=/opt/zapret2/ipset/zapret-hosts-user.txtкрайне не приветствуется, поскольку ломает логикуMODE_FILTERи скриптов получения листов - их результат может не быть учтен. - Класть свои файлы в
/opt/zapret2чревато тем, что их снесет инсталятор при обновлении zapret2. Используете расположение вне каталога zapret2.
Система ведения листов
Располагается в директории ipset и состоит из shell скриптов, которые обслуживают файлы с фиксированными именами внутри этой же директории.
Стандартные файлы листов
Разделяются на хостлист и ip листы, на пользовательские и генерируемые. Пользовательские листы ведутся вручную и не изменяются программно, генерируемые являются результатом работы программы и не предназначены для ручного редактирования. Изменения в генерируемых листах могут быть переписаны. Одним из вариантов генерируемых листов являются скачиваемые листы.
Пользовательские хостлисты могут содержать имена хостов, ipv4 и ipv6 адреса или подсети CIDR. Генерируемые хостлисты могут содержать только имена хостов.
Все листы могут сжиматься gzip. В этом случае к их именам добавляется ".gz". Сжатие gzip обычно не используется для пользовательских листов, потому что они как правило не слишком большие, а в gzip их неудобно редактировать. Для генерируемых листов gzip обычно используется, чтобы сократить занимаемое на диске место. Будет ли применено сжатие генерируемых листов зависит от переменной config GZIP_LISTS.
ip листы разделяются на ipv4 и ipv6. ipv6 листы имеют в конце файла перед расширением символ "6".
В зависимости от режима хостлисты могут ресолвиться в ip листы через mdig или применяться как есть. Если хостлисты применяются как есть в nfqws2, учитываются только имена доменов, а IP адреса и подсети - нет.
Включающие ip листы загоняются в сеты ядра и применяются в правилах таблиц только, если указан MODE_FILTER=ipset. Исключающий ip лист загоняется в сеты ядра и применяется всегда в правилах таблиц.
| Хостлист | Тип | Назначение | ip листы |
|---|---|---|---|
| zapret-hosts-user.txt | пользовательский | включающий | zapret-ip-user.txt zapret-ip-user6.txt |
| zapret-hosts-user-exclude.txt | пользовательский | исключающий | zapret-ip-exclude.txt zapret-ip-exclude6.txt |
| zapret-hosts-user-ipban.txt | пользовательский | заворот трафика | zapret-ip-user-ipban.txt zapret-ip-user-ipban6.txt |
| -- | генерируемый | заворот трафика | zapret-ip-ipban.txt zapret-ip-ipban6.txt |
| zapret-hosts.txt | генерируемый | включающий | zapret-ip.txt zapret-ip6.txt |
Скрипты ipset
Все скрипты, генерирующие хостлисты, вызывают get_ipban.sh. Все скрипты, генерирующие ip листы, вызывают get_user.sh. Можно сказать, что ipban ресолвится всегда вне зависимости от того, используете ли вы хостлисты или ip листы, а user листы ресолвятся всегда, если вы используете любые скрипты получения ip листов.
clear_lists.sh
Удаляет все генерируемые листы из ipset
create_ipset.sh
Загоняет все имеющиеся ip листы в соответствующие ipset. Под ipset здесь понимаются ipset от iptables, set от nftables или tables от ipfw. Скрипт выбирает ipset backend в зависимости от переменной config FWTYPE или автоматически в зависимости от ОС и установленных компонент, если переменная не задана. Нужно ли загонять ipv4 или ipv6 версии зависит от DISABLE_IPV4 и DISABLE_IPV6.
Берет 1 параметр командной строки. Им может быть "clear" - очистить ipset-ы или "no-update" - загонять только если ipset еще не созданы, не выполнять обновление.
Имена ipset-ов :
- nozapret, nozapret6 - исключение IP адресов
- zapret, zapret6 - включение IP адресов
- ipban, ipban6 - отдельный включающий список для стороннего перенаправления или проксирования
ipfw использует set-ы, включающие как ipv4, так и ipv6 адреса, поэтому варианты "6" не применяются.
get_config.sh
Выполняет скрипт из ipset, заданный в переменной config GETLIST. Если переменная не задана, выполняет get_ipban.sh.
get_user.sh
Ресолвит zapret-hosts-user.txt, zapret-hosts-exclude.txt и zapret-hosts-ipban.txt.
get_ipban.sh
Ресолвит zapret-hosts-exclude.txt и zapret-hosts-ipban.txt.
get_exclude.sh
Ресолвит zapret-hosts-exclude.txt.
get_antifilter_*.sh
Загружает ip или хостлисты с https://antifilter.network.
Роскомнадзор в своей непрестанной заботе о благополучии граждан Российской Федерации ведет несколько списков ресурсов, на которые гражданам ходить нельзя. К сожалению, из-за нехватки сил, вызванной думами о будущем России, они не могут донести содержимое этого списка до каждого гражданина Российской Федерации.
Мы решили оказать посильную помощь Роскомнадзору и предоставить каждому желающему актуальные и полные списки IP-адресов, на которые ходить нельзя. На их основе вы можете даже автоматизировать своё нехождение туда.
На сайте указано, что списки берутся с zapret-info, который приказал долго жить, поэтому актуальность под вопросом.
get_antizapret_domains.sh
Загружает хостлисты с https://antizapret.prostovpn.org.
Хостлист со старого сервиса обхода блокировок "prostovpn.org"
get_refilter_*.sh
Загружает ip или хостлист с https://github.com/1andrevich/Re-filter-lists.
Re:filter — это попытка создать актуальный список заблокированных доменов и IP-адресов в РФ, а также популярных и заблокированных для пользователей из России.
Исключает казино, порно, проституцию, наркотики и тому подобное.
get_reestr_*.sh
Загружает ip или хостлисты с https://github.com/bol-van/rulist. IP листы содержат как ipv4, так и ipv6.
get_reestr_resolvable_domains.sh- список заблокированных доменов, которые ресолвятся. Среди заблокированных доменов больше половины уже мертвы. Чтобы не перегружать лист они удаляются.get_reestr_preresolved.sh- периодический ресолв списка заблокированных доменов. Не помогает от "прыгающих" доменов с регулярно изменяющимися IP и от доменов, которые ресолвятся по-разному в зависимости от geoip.get_reestr_preresolved_smart.sh- предыдущий список + подсети некоторых проблемных AS + исключение некоторых точно незаблокированных (белых) AS. Проблемными IP считаются популярные CDN с прыгающими IP и хостеры, к которым в России применяются особые более жесткие правила фильтрации. На момент написания проблемные AS : AS32934 (facebook,instagram), AS13414 (twitter) , AS13335 (cloudflare), AS15169 (google), AS16509 (amazon), AS16276 (ovh), AS24940 (hetzner). Белые AS : AS47541 (vk), AS35237 (sberbank), AS47764 (mail.ru), AS13238 (yandex).
Система ipban
Это просто система ресолвинга (и, возможно, скачивания) списка, в результате действий которой появляются ipset-ы ядра "ipban" и "ipban6". zapret сам с ними ничего не делает. Они нужны, чтобы вы самостоятельно могли настроить PBR (policy based routing) или выборочный заворот на прокси соединений в сторону адресов ipban. Из названия следует, что ip адреса из этого списка не обходятся автономно, требуется VPN или прокси. Чтобы не гнать на них все, и существует PBR или выборочный заворот. Инструкции по настройке выходят за рамки проекта, но кое-какая информация есть в документации zapret1.
Типичная схема применения ipban начинается с создания ipset из сохраненных листов.
#!/bin/sh
. /opt/zapret2/init.d/openwrt/functions
#. /opt/zapret2/init.d/sysv/functions
create_ipset no-update
Нельзя быть уверенным что поднимается раньше - ваш скрипт или zapret. Запуск должен быть синхронизирован и не выполняться параллельно. Лучший способ этого достичь - использовать INIT_FW_*_HOOK.
Стартовые скрипты
Имеются только для Linux и OpenWRT. Вариант для Linux - в init.d/sysv, для OpenWRT - в init.d/openwrt. Основной исполняемый файл - zapret2. Требуемое действие передается в аргументе $1. Процедура запуска разделена на запуск демонов - процессов nfqws2, и запуск firewall - выставление правил iptables/nftables.
| Команда ($1) | Действие |
|---|---|
| start stop restart |
запуск/останов/перезапуск демонов и firewall. firewall не запускается, если INIT_APPLY_FW не равно 1. На openwrt с fw3 (iptables) firewall запускается отдельно, команды работают только с демонами и не трогают firewall |
| start_daemons stop_daemons restart_daemons |
запуск/останов/перезапуск демонов |
| start_fw stop_fw restart_fw |
запуск/останов/перезапуск firewall. На openwrt с fw3 (iptables) команды работают, но firewall запускается отдельно через firewall include в /etc/config/firewall. Отдельные операции не рекомендованы. |
| reload_ifsets | (только для nftables) перезагрузка сетов wanif, wanif6, lanif |
| list_ifsets | (только для nftables) показ wanif, wanif6, lanif и flowtable |
| list_table | (только для nftables) показ таблицы zapret2 |
sysv вариант предназначен для любых Linux, не являющихся OpenWRT. На системах с различными системами запуска все равно работает sysv скрипт, а прикрутка к системам запуска представляет собой лишь адаптер, его запускающий. На системах с неподдерживаемыми системами запуска и на прошивках вы сами должны знать куда прикрутить "zapret2 start" и "zapret2 stop", чтобы работал автостарт и останов в рамках вашей системы запуска.
Интеграция с firewall
Если в ОС используется система управления firewall, между ней и zapret могут произойти конфликты. Чаще всего бывают гоночные состояния - соревнование кто первый заполнит правила или снесет чужие. Это приводит к хаосу - то работает, то не работает, то работает что-то одно, то вообще непонятно что происходит. Гоночные состояния обычно случаются на iptables, потому что там одни таблицы на всех. С nftables проблем обычно не возникает, поскольку каждый использует свою таблицу. Но если вдруг система управления firewall решит снести весь ruleset, это будет тоже гоночное состояние.
Если случаются гонки или конфликты, то лучший способ решения - синхронизация. Вы отключаете в config INIT_APPLY_FW, после чего скрипты запуска по команде start перестают запускать firewall и создавать конфликт. Далее вы разбираетесь в своей системе firewall как после поднятия ее правил запустить сторонний скрипт, поднимающий дополнительные правила. Этим скриптом должен быть "zapret2 start_fw". Так же можно прикрутить stop_fw и restart_fw. Либо можно пойти обратным путем - использовать как основу поднятие firewall от zapret и использовать firewall хуки, в которых дергаются команды по управлению системой управления firewall. Нужно проследить, чтобы система управления firewall не сносила правила zapret.
Если система управления firewall работает только со своими правилами и крайне плохо уживается со сторонними, можно попробовать отказаться от скриптов запуска и выяснить как прикрутить правила NFQUEUE согласно ее правилам, а демоны запускать отдельно через систему запуска вашего дистрибутива. Если это нежелательно или невозможно, стоит задуматься о смене системы управления firewall на что-то другое или полном отказе от нее.
Интеграция с OpenWRT firewall
Готовая интеграция с firewall имеется для OpenWRT. Скрипты запуска автоматически определяют fw3 и отключат управление firewall через start/stop/restart. Вместо этого в /etc/config/firewall прописывается firewall include firewall.zapret2, который и запускает правила zapret синхронно после поднятия fw3. Дополнительно вешается хук 90-zapret2 в /etc/hotplug.d/iface на поднятие/опускание интерфейсов. В варианте fw3 происходит рестарт fw3, чтобы правила применились к новым интерфейсам или наоборот - были удалены для более несуществующих интерфейсов. В варианте nftables происходит лишь перезагрузка сетов wanif, wanif6, lanif и flowtable.
custom скрипты
Стандартный инстанс NFQWS2_OPT не всегда может решить специфические задачи. Перехват осуществляется только по портам. Нет возможности вписать дополнительные условия - например, перехватить особый пейлоад на любом порту, задать особый фильтр connbytes или использовать специальный kernel ipset и применять особые стратегии к этому перехваченному трафику.
Все это очень частно и не годится для реализации в основном функционале. Поэтому и создана система custom скриптов, которые представляют собой shell includes и располагаются в init.d/sysv/custom.d или init.d/openwrt/custom.d. Их основная задача - поднять нужные вам правила firewall и запустить инстансы nfqws2 с нужными параметрами. Возможны и другие вспомогательные действия.
custom скрипт может иметь следующие shell функции, которые вызываются системой запуска :
- zapret_custom_daemons - поднятие и останов демонов. $1 = 1 - поднятие, 0 - останов.
- zapret_custom_firewall - поднятие и снятие правил iptables. $1 = 1 - поднятие, 0 - снятие.
- zapret_custom_firewall_nft - поднятие правил nftables. останов не требуется, поскольку основной код при останове очищает цепочки nft вместе с custom правилами.
- zapret_custom_firewall_nft_flush - вызывается при останове nftables, чтобы можно было удалить объекты, выходящие за рамки стандартных цепочек - такие, как собственные set-ы или собственные цепочки.
Если вам не нужны iptables или nftables, функции для соответствующего типа firewall можно не писать. В функциях крайне желательно пользоваться хелперами основного кода - так вы будете следовать идеологии скриптов запуска без необходимости сосредотачиваться на частностях. Можно свободно адресовать переменные config и добавлять туда свои.
Лучший способ начать писать свои скрипты - изучить готовые в init.d/custom.d.examples.linux.
custom хелперы
Это функции из основного кода скриптов запуска, которые будут полезны при написании custom скриптов.
Получение динамических номеров
alloc_dnum()
# $1 - имя переменной, которой присваивается номер демона
alloc_qnum()
# $1 - имя переменной, которой присваивается номер очереди
Функции получения динамических номеров из пула необходимы, чтобы между разными скриптами запуска не возникло конфликтов. Номер очереди - уникальное значение, 2 инстанса не могут висеть на одной очереди. Второй вылетит с ошибкой. Номер демона нужен для отслеживания PID. При пересечении не будет корректно работать start/stop/restart.
Следует вызывать не из функции, а из основного кода скрипта - вне функции. custom скрипты всегда выполняются в алфавитном порядке - это стандартная схема ".d" директорий в unix. При одинаковом наборе скриптов всегда для каждого скрипта будут возвращаться одни и те же значения qnum и dnum как при старте, так и при останове, поэтому можно их использовать в качестве уникальных номеров и не попасть на чужие номера. Если после старта был изменен состав custom скриптов - правило нарушится, и будут проблемы. Поэтому лучше не менять состав custom скриптов , пока zapret2 запущен.
Работа с демонами
do_nfqws()
# $1 - 1 - старт , 0 - останов
# $2 - номер демона
# $3 - параметры nfqws2
Запуск или останов инстанса nfqws2. Базовые параметры приписываются автоматически. К базовым параметрам относится выбор user, fwmark и подключение стандартных скриптов Lua - zapret-lib.lua, zapret-antidpi.lua, zapret-auto.lua.
Номер очереди вы должны указать сами в --qnum.
filter_apply_hostlist_target()
# $1 - имя переменной с опциями nfqws2
Осуществляет замену маркеров <HOSTLIST> и <HOSTLIST_NOAUTO> в $1 зависимости от режима фильтрации MODE_FILTER и наличия файлов листов в ipset.
standard_mode_daemons()
# $1 - 1 - старт , 0 - останов
Запуск или останов стандартного инстанса nfqws2. Можно локально переопределить любые переменные config.
Работа с iptables
fw_nfqws_post()
fw_nfqws_pre()
# $1 - 1 - поднятие, 0 - опускание
# $2 - фильтр iptables для ipv4
# $3 - фильтр iptables для ipv6
# $4 - номер очереди
Поднятие и опускание правил перенаправления в очередь для nfqws2. Фильтры пишутся отдельно для ipv4 и ipv6, поскольку могут содержать специфические элементы для одной версии ip протокола, которые вызовут ошибку для другой версии.
"post" означает цепочку для выходного трафика, "pre" - для входного.
К правилам приписывается слева $FW_EXTRA_PRE, справа $FW_EXTRA_POST.
Что не следует писать в фильтр :
- Проверку стандарных exclude ipset - nozapret, nozapret6.
- Проверку по DESYNC_FWMARK.
Будут ли применены правила для каждой версии ip зависит от настроек config.
zapret_do_firewall_standard_tpws_rules_ipt()
# $1 - 1 - поднятие, 0 - опускание
Применить или снять правила iptables для стандартного инстанса nfqws2. Можно локально переопределять переменные config и применять FW_EXTRA_POST и FW_EXTRA_PRE.
filter_apply_ipset_target()
# $1 - имя переменной с правилами фильтра iptables
# $2 - имя переменной с правилами фильтра ip6tables
Добавляет в переменные $1 и $2 проверку на стандартные set-ы zapret/zapret6 в исходящем направлении (dst).
reverse_nfqws_rule_stream()
# stdin - правила фильтра iptables или ip6tables
reverse_nfqws_rule()
# $@ правила фильтра iptables или ip6tables
Замена элементов iptables фильтра с прямого на обратное направление - меняется dst на src. Результат выводится в stdout.
ipt()
ipta()
ipt_del()
ipt6()
ipt6a()
ipt6_del()
# $@ - правило iptables
ipt_add_del()
ipta_add_del()
ipt6_add_del()
ipt6a_add_del()
# $1 - 1 - добавление, 0 - удаление
# $2+ - все остальные аргументы определяют правило iptables
Набор функций для управления правилами iptables. Все функции, добавляющие правила, проверяют сначала наличие правила, и если оно уже есть - не добавляют. Это необходимо для предотвращения дублирования правил. Ко всем правилам добавляется слева $FW_EXTRA_PRE, справа - $FW_EXTRA_POST. Буква "6" означает работу с ipv6, ее отсутствие - работу с ipv4.
Все правила начинаются с имени цепочки без команды iptables '-A', '-D', '-I' и т.д.
- ipt - iptables -I - добавление в начало
- ipta - iptables -A - добавление в конец
- ipt_del - iptables -D - удаление
- ipt_add_del - добавление через -I или удаление. в $1 - 1 - добавление, 0 - удаление. Само правило идет начиная с $2.
- ipta_add_del - как в предыдущем варианте, только -A вместо -I
ipt_first_packets()
# $1 - количество пакетов или строка "keepalive"
Выдает в stdout "-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes $RANGE". RANGE определяется как "1:$1". Если $1 = "keepalive", не выдается ничего (нет фильтра по connbytes).
Работа с nftables
nft_fw_nfqws_post()
nft_fw_nfqws_pre()
# $1 - фильтр nftables для ipv4
# $2 - фильтр nftables для ipv6
# $3 - номер очереди
Поднятие и опускание правил перенаправления в очередь для nfqws2. Фильтры пишутся отдельно для ipv4 и ipv6, поскольку могут содержать специфические элементы для одной версии ip протокола, которые вызовут ошибку для другой версии.
"post" означает цепочку для выходного трафика, "pre" - для входного. Тип цепочки выбирается в зависимости от переменной POSTNAT. Если вам нужна конкретная цепочка - можно переопределить POSTNAT на один вызов или как локальную переменную в функции.
К правилам приписывается слева $FW_EXTRA_PRE, справа $FW_EXTRA_POST.
Что не следует писать в фильтр :
- Проверку стандарных exclude ipset - nozapret, nozapret6.
- Проверку по DESYNC_FWMARK.
Будут ли применены правила для каждой версии ip зависит от настроек config.
zapret_do_firewall_standard_tpws_rules_nft()
Применить правила nftables для стандартного инстанса nfqws2. Можно локально переопределять переменные config и применять FW_EXTRA_POST и FW_EXTRA_PRE.
nft_filter_apply_ipset_target()
# $1 - имя переменной с правилами фильтра nftables для ipv4
# $2 - имя переменной с правилами фильтра nftables для ipv6
Добавляет в переменные $1 и $2 проверку на стандартные set-ы zapret/zapret6 в исходящем направлении (dst).
nft_reverse_nfqws_rule()
# $@ - правило фильтра nftables
Замена элементов nftables фильтра с прямого на обратное направление - меняется dst на src. Результат выводится в stdout.
nft_add_chain()
# $1 - имя цепочки
# $2 - параметры внутри { }
nft_delete_chain()
# $1 - имя цепочки
nft_create_set()
# $1 - имя сета
# $2 - параметры внутри { }
nft_del_set()
# $1 - имя сета
nft_flush_set()
# $1 - имя сета
nft_set_exists()
# $1 - имя сета
nft_add_set_element()
# $1 - имя сета или map
# $2 - элемент
nft_add_set_elements()
# $1 - имя сета или map
# $2,$3,... - элементы
nft_flush_chain()
# $1 - имя цепочки
nft_add_rule()
# $1 - имя цепочки
# $2+ - правило nftables
nft_insert_rule()
# $1 - имя цепочки
# $2+ - правило nftables
Функции работают с таблицей zapret2 и позволяют избавиться от прямого вызова nft с указанием жестко заданного имени таблицы. Названия говорят сами за себя.
nft_first_packets()
# $1 - количество пакетов или "keepalive"
Выдает в stdout "ct original packets $RANGE". RANGE определяется как "1-$1", если $1 > 1, "1", если $1 = 1. Если $1 = "keepalive", не выдается ничего (нет фильтра по connbytes).
Дополнительные функции
Множество полезных хелперов расположены в common/base.sh. Их назначение несложно понять по коду.
Инсталлятор
Состоит из файлов install_bin.sh, install_prereq.sh, install_easy.sh, uninstall_easy.sh. Используются файлы из common.
install_bin.sh- средство автоматического поиска и настройки подходящих по архитектуре бинарных файлов изbinaries. Создает линки в директориях nfq2, mdig, ip2net на файлы внутри одной из субдиректорий из binaries. Заточено на максимально обрезанные прошивки, где много чего нет. Работает почти на всем, но не 100%. Отсутствие или обрезанный вариант какой-нибудь стандартной программы может сломать скрипт - типично для неподдерживаемых прошивок типа padavan, merlin и тому подобных. Если не работает - создайте линки вручную.install_prepreq.sh- установщик пре-реквизитов - требуемых пакетов для работы zapret. Работает в OpenWRT и на большинстве дистрибутивов Linux, но не на всех, поскольку могут использоваться очень разные системы управления пакетами и иметься индивидуальные особенности, которые все учесть невозможно. Если скрипт не справляется - требуется установить пакеты вручную.install_easy.sh- основной установщик. Рассчитан на запуск из любого расположения. Работает в диалоговом режиме, задает вопросы. Автоматически настраивает binaries и устанавливает пре-реквизиты (отдельный запуск предыдущих скриптов не требуется). На неподдерживаемых системах Linux не может прописать автозапуск - вам это придется сделать вручную.uninstall_easy.sh- деинсталлятор. Не может убрать автозапуск на неподдерживаемых системах. Предлагает удалить пре-реквизиты только на OpenWRT, на остальных системах - нет. Не удаляет саму директорию установки - для полного удаления ее надо удалить самостоятельно.
Отдельные файлы install_bin.sh и install_prereq.sh полезны, когда вы не собираетесь запускать установку, но вам нужен рабочий blockcheck2 или есть необходимость запускать стартовые скрипты самостоятельно.
Инсталлятор всегда копирует файлы в /opt/zapret2, если исходный вариант находится где-то в другом месте. При замещении целевой директории удаляет все файлы оттуда. Опционально сохраняет стандартные файлы - config, custom скрипты, user-листы из ipset, автохостлист.
При копировании в /opt/zapret2 выставляет нужные права на файлы и директории, даже если они были испорчены (например, перепаковкой в Windows) в исходном расположении. Для запуска из расположения с испорченными правами достаточно вызвать "sh install_easy.sh".
Бинарные файлы присутствуют только в релизах. После git clone их нет. В случае отсутствия binaries инсталлятор попытается их собрать. Для этого ему нужен компилятор C, make и несколько dev пакетов. Подробности см в docs/compile.
Ниже описаны действия, выполняемые инсталлятором для интеграции с различными вариантами Linux. Можно отказаться от инсталлятора, сделав эти действия самостоятельно и настроив config.
Принципы интеграции с OpenWRT
- Автозапуск.
ln -s /opt/zapret2/init.d/openwrt/zapret2 /etc/init.d
/etc/init.d/zapret2 enable
- Обновление правил firewall по событию поднятия/опускания интерфейсов.
ln -s /opt/zapret2/init.d/openwrt/90-zapret2 /etc/hotplug.d/iface
- (только для fw3) Интеграция с firewall.
ln -s /opt/zapret2/init.d/openwrt/firewall.zapret2 /etc
uci add firewall include
uci set firewall.@include[-1].path="/etc/firewall.zapret"
uci set firewall.@include[-1].reload=1
uci commit
- Обновление листов - cron job на вызов
/opt/zapret2/ipset/get_config.shночью в случайное время каждые 2 дня - для обновления листов. Рассчет идет на роутер, работающий круглосуточно.
Шпаргалка OpenWRT
- Запуск службы : /etc/init.d/zapret2 start
- Останов службы : /etc/init.d/zapret2 stop
- Рестарт службы : /etc/init.d/zapret2 restart
- Состояние службы : /etc/init.d/zapret2 status
- Отключение автозапуска : /etc/init.d/zapret2 disable
- Включение автозапуска : /etc/init.d/zapret2 enable
Принципы интеграции с systemd
- Автозапуск
cp /opt/zapret2/init.d/systemd/zapret2.service /lib/systemd/system
systemctl daemon-reload
systemctl enable zapret2
- Таймер для обновления листов - в случайное время суток каждые 2 дня. Рассчет идет на любую систему, в том числе десктопную, которую могут включать только днем.
cp /opt/zapret2/init.d/systemd/zapret2-list-update.* /lib/systemd/system
systemctl daemon-reload
systemctl enable zapret2-list-update.timer
systemctl start zapret2-list-update.timer
Шпаргалка systemd
- Запуск службы : systemctl start zapret2
- Останов службы : systemctl stop zapret2
- Рестарт службы : systemctl restart zapret2
- Состояние службы : systemctl status zapret2
- Отключение автозапуска : systemctl disable zapret2
- Включение автозапуска : systemctl enable zapret2
Принципы интеграции с openrc
- Автозапуск
ln -s /opt/zapret2/init.d/openrc/zapret2 /etc/init.d
rc-update add zapret2
- Обновление листов - cron job на вызов
/opt/zapret2/ipset/get_config.shднем в случайное время каждые 2 дня. Рассчет идет на любую систему, в том числе десктопную, которую могут включать только днем.
Шпаргалка openrc
- Запуск службы : rc-service zapret2 start
- Останов службы : rc-service zapret2 stop
- Рестарт службы : rc-service zapret2 restart
- Состояние службы : rc-service zapret2 status
- Отключение автозапуска : rc-update del zapret2
- Включение автозапуска : rc-update add zapret2
Альтернативная установка на systemd
На классическом Linux с systemd можно использовать готовый шаблонный юнит init.d/systemd/nfqws2@.service для запуска инстансов nfqws2.
cp /opt/zapret2/init.d/systemd/nfqws2\@.service /lib/systemd/systemsystemctl daemon-reloadmkdir /etc/zapret2- Создаете в
/etc/zapret2текстовый файл с параметрами коммандной строки nfqws2. Файл называется INSTANCE.conf, где INSTANCE - название инстанса, которое вы придумываете сами. - Автозапуск :
systemctl enable nfqws2@INSTANCE - Запуск :
systemctl start nfqws2@INSTANCE - Останов :
systemctl stop nfqws2@INSTANCE - Перезапуск :
systemctl restart nfqws2@INSTANCE
Этот способ не поднимает правила iptables/nftables - вам это придется сделать отдельно, как и написать сами правила. Правила нужно прописать куда-то, чтобы они поднимались после старта системы. Например, можно сделать отдельный systemd unit с запуском шелл скрипта или nft -f /path/to/file.nft.
Другие прошивки
Для статических бинарников не имеет значения на чем они запущены: PC, android, приставка, роутер, любой другой девайс. Подойдет любая прошивка, дистрибутив Linux. Статические бинарники запустятся на всем. Им нужно только ядро с необходимыми опциями сборки или модулями. Но кроме бинарников в проекте используются еще и скрипты, в которых задействуются некоторые стандартные программы.
Основные причины почему нельзя просто так взять и установить эту систему на что угодно:
- отсутствие доступа к девайсу через shell
- отсутствие рута
- отсутствие раздела r/w для записи и энергонезависимого хранения файлов
- отсутствие возможности поставить что-то в автозапуск
- отсутствие cron
- неотключаемый flow offload или другая проприетарщина в netfilter
- недостаток модулей ядра или опций его сборки
- недостаток модулей iptables (/usr/lib/iptables/lib*.so)
- недостаток стандартных программ (типа ipset, curl) или их кастрированность (облегченная замена)
- кастрированный или нестандартный шелл sh
Если в вашей прошивке есть все необходимое, то вы можете адаптировать zapret под ваш девайс в той или иной степени.
Пересобрать ядро или модули для него будет скорее всего достаточно трудно. Для этого вам необходимо будет по крайней мере получить исходники вашей прошивки. User mode компоненты могут быть привнесены относительно безболезненно, если есть место куда их записать. Специально для девайсов, имеющих область r/w, существует проект entware. Некоторые прошивки даже имеют возможность его облегченной установки через веб интерфейс. entware содержит репозиторий user-mode компонент, которые устанавливаются в /opt. С их помощью можно компенсировать недостаток ПО основной прошивки, за исключением ядра.
Можно попытаться использовать sysv init script. В случае ругани на отсутствие каких-то базовых программ, их следует восполнить посредством entware. Перед запуском скрипта путь к дополнительным программам должен быть помещен в PATH.
Обратите внимание, что entware патчен на предмет замены стандартных путей файлов типа /etc/passwd на /opt/etc/passwd. Статические бинарники zapret собраны без учета этой особенности, поэтому оцпия --user может не работать - юзер ищется в /etc/passwd, который находится на r/o разделе, в том время как adduser добавляет юзеров в /opt/etc/passwd. Поэтому может понадобиться раскомментировать в config WS_USER и указать юзера, который есть в /etc/passwd.
Для keenetic существуют дополнительные важные особенности, без учета которых zapret будет "слетать" каждые несколько минут или не работать с udp. Существуют готовые сторонние решения по интеграции с keenetic.
Подробное описание настроек для других прошивок выходит за рамки данного проекта.
OpenWrt является одной из немногих относительно полноценных linux систем для embedded devices. Она характеризуется следующими вещами, которые и послужили основой выбора именно этой прошивки:
- полный root доступ к девайсу через shell. на заводских прошивках чаще всего отсутствует, на многих альтернативных есть
- корень r/w. это практически уникальная особенность OpenWrt. заводские и большинство альтернативных прошивок построены на базе squashfs root (r/o), а конфигурация хранится в специально отформатированной области встроенной памяти, называемой nvram. не имеющие r/w корня системы сильно кастрированы. они не имеют возможности доустановки ПО из репозитория без специальных вывертов и заточены в основном на чуть более продвинутого, чем обычно, пользователя и управление имеющимся функционалом через веб интерфейс, но функционал фиксированно ограничен. альтернативные прошивки, как правило, могут монтировать r/w раздел в какую-то область файловой системы, заводские обычно могут монтировать лишь флэшки, подключенные к USB, и не факт, что есть поддержка unix файловых системы. может быть поддержка только fat и ntfs.
- возможность выноса корневой файловой системы на внешний носитель (extroot) или создания на нем оверлея (overlay)
- наличие менеджера пакетов opkg и репозитория софта
- flow offload предсказуемо, стандартно и выборочно управляем, а так же отключаем
- в репозитории есть все модули ядра, их можно доустановить через opkg. ядро пересобирать не нужно.
- в репозитории есть все модули iptables, их можно доустановить через opkg
- в репозитории есть огромное количество стандартных программ и дополнительного софта
- наличие SDK, позволяющего собрать недостающее
Особенности Windows
zapret изначально был написан для unix. Для облегчения портирования под Windows используется обертка cygwin, эмулирующая unix среду. Если winws2 запускается отдельно - требуется cygwin1.dll в той же директории, если в составе cygwin среды - cygwin1.dll не должно быть в той же директории, иначе не запустится !
cygwin эмулирует общее пространство PID процессов и сигналы только в пределах одного cygwin1.dll ! Чтобы иметь возможность послать сигнал, нужно, чтобы winws2 и программа для посылки сигнала (kill, killall) запускались с одним cygwin1.dll.
Можно использовать отдельно установленную среду cygwin, а можно скачать готовый zapret-win-bundle, где уже присутствует минимальный комплект cygwin. cygwin prompt настроен с aliases на запуск blockcheck, blockcheck2, winws, winws2, winws2 со станадртными Lua скриптами, чтобы не возиться с путями, а сосредоточиться на главном.
32-битные версии Windows в zapret-win-bundle не поддерживаются.
Windows 7
Требования к подписи драйверов Windows изменились в 2021 году. Официальные бесплатные обновления Windows 7 закончились в 2020. После этого несколько лет продолжали идти платные обновления по программе ESU. Именно в этих ES* обновлениях находится обновление ядра Windows 7, позволяющее загрузить драйвер WinDivert 2.2.2-A, который идет в поставке zapret. Поэтому варианты следующие :
-
Взять
WinDivert64.sysиWinDivert.dllверсии 2.2.0-C или 2.2.0-D отсюда и заменить эти 2 файла. -
Использовать готовый патчер-взламыватель ESU - "BypassESU". Его надо искать по названию.
-
Использовать UpdatePack7R2 от simplix
Warning
Но с этим паком есть проблема. Автор из Украины, он очень обиделся на русских. Если в панели управления стоит регион RU или BY, появляется неприятный диалог. Чтобы эту проблему обойти, можно поставить временно любой другой регион, потом вернуть. Так же нет никаких гарантий, что автор не поместил туда какой-то зловредный код. Использовать на свой страх и риск.
Более безопасный вариант - скачать последнюю нормальную довоенную версию : 22.2.10. Ее достаточно, чтобы WinDivert 2.2.2-A заработал на Windows 7.
Windows Server
winws2 слинкован с wlanapi.dll, который по умолчанию не установлен в Windows Server.
Для решения этой проблемы запустите power shell под администратором и выполните команду Install-WindowsFeature -Name Wireless-Networking.
После чего перезагрузите систему.
Windows ARM64
Главная проблема - отсутствие подписанного WinDivert драйвера. Поэтому требуется включение режима тестовой подписи : bcdedit /set {current} testsigning on.
Неподписанный драйвер WinDivert64.sys есть в комплекте zapret-win-bundle.
Там же есть и батник для наката драйвера на Win11 arm64.
Другая проблема - отсутствие cygwin для платформ, отличных от x86. Однако, win11 содержит эмуляцию x64, поэтому можно использовать билд для x64, но с замененным драйвером WinDivert64.sys. DLL при этом менять не нужно - только sys.
На win10 arm64 нет эмуляции x64, но есть эмуляция x86 32 бит. Теоретически можно использовать вариант win32 и положить рядом драйвер WinDivert64.sys для arm64. Этот вариант не проверялся.