249 KiB
МАНУАЛ В ПРОЦЕССЕ НАПИСАНИЯ
Введение
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, средство обфускации wireguard протокола zapret-wgobfs.lua и средство записи дампа пакетов в cap файлы zapret-pcap.lua.
Функции перенаправления трафика из ядра в 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 протокола, ipset-ы - списки IP адресов, L4 - порты tcp или udp, 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.
Приведенные далее тестовые примеры предназначены для своей системы запуска или запуска вручную. Скрипты запуска zapret сами генерируют необходимые правила, никаких ip/nf tables самому писать не нужно.
Перехват трафика с помощью 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-raw-part принимает частичные windivert фильтры. Синтаксис аналогичен --wf-raw. --wf-raw-part может быть несколько.
Частичные фильтры встраиваются конструктором в итоговый фильтр по принципу OR. Или указанные порты, или ваш фильтр1 или ваш фильтр2.
--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 ; любой текст. игнорируется
--daemon ; отключиться от консоли (демонизироваться)
--pidfile=<filename> ; запись PID в файл
--ctrack-timeouts=S:E:F[:U] ; таймауты conntrack для стадий tcp SYN, ESTABLISHED, FIN и для udp
--ctrack-disable=[0|1] ; 1 отключает conntrack
--server=[0|1] ; серверный режим. для обслуживания listener-ов меняются многие аспекты выбора направления и ip/port источника/приемника
--ipcache-lifetime=<int> ; время жизни записей кэша IP в секундах. 0 - без ограничений.
--ipcache-hostname=[0|1] ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы
--reasm-disable=[proto[,proto]] ; отключить сборку фрагментов для списка пейлоадов : 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 код из строки или из файла
--lua-gc=<int> ; интервал вызова сборщика мусора LUA в секундах. 0 отключает периодический вызов.
MULTI-STRATEGY:
--new ; начало нового профиля
--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-l7=proto[,proto] ; фильтр профиля : список протоколов потока. полный список доступен в help тексте программы.
--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-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] ; внутрипрофильный фильтр : фильтр пейлоада для последующих инстансов внутри профиля. список пейлоадов доступен в help тексте программы.
--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=<functon>[: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-udp-in=[~]port1[-port2] ; windivert конструктор : udp порты или диапазоны портов для перехвата по входящему направлению. список через запятую
--wf-tcp-in=[~]port1[-port2] ; windivert конструктор : tcp порты или диапазоны портов для перехвата по исходящему направлению. список через запятую
--wf-udp-in=[~]port1[-port2] ; windivert конструктор : udp порты или диапазоны портов для перехвата по исходящему направлению. список через запятую
--wf-tcp-empty=[~]port1[-port2] ; windivert конструктор : перехватывать пустые tcp пакеты ACK. по умолчанию - нет.
--wf-raw-part=<filter>|@<filename> ; windivert конструктор : частичный windivert raw фильтр. обьединяется по принципу ИЛИ.
--wf-filter-lan=0|1 ; windivert конструктор : отфильтровывать неглобальные IP адреса. по умолчанию - да.
--wf-raw=<filter>|@<filename> ; полный windivert фильтр. замещает конструктор.
--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 <глобальные_параметры>
<фильтр 1> <стратегия 1> --new
<фильтр 2> <стратегия 2> --new
...............
<фильтр N> <стратегия N>
Когда на вход поступает пакет, и для него еще нет записи в conntrack, происходит выбор профиля. Фильтры профиля проверяются от первого до последнего - с начала в конец - слева направо, и никак иначе. Всегда выигрывает только один профиль - по первому совпадению условий фильтра, а все остальные не задействуются. Если не сработал ни один фильтр, выбирается пустой профиль с номером 0, не предполагающий никаких действий с трафиком.
Все условия, кроме --filter-l7 и хостлистов, являются однозначными и известными с момента начала обработки потока (с начала соединения).
В начале как правило еще неизвестнен протокол потока и имя хоста, выделяемое из сообщений потока.
Когда эти величины становятся известны, происходит поиск профиля заново. Если выбирается другой профиль - происходит перескок.
Таких перескоков может быть до двух, поскольку есть только 2 величины, влияющие на выбор, неизвестные с самого начала.
Для протоколов потока tls, http, quic обычно бывает только 1 перескок, поскольку определение протокола и имени хоста
происходит по одному пакету или группе пакетов. Для XMPP это 2 перескока - сначала определяется xmpp как таковой,
затем ловится переход на TLS, и только в нем выделяется имя хоста.
При написании стратегий следует их продумывать с учетом логики перескоков.
Если нужно, чтобы стратегия начала работу с самого первого пакета и продолжила работать дальше после изменения профиля,
нужно дублировать вызовы во всех профилях, по которым может пройти поток.
Шаблоны профилей
Когда имеется много сложных и повторяющихся стратегий, может быть удобно использовать шаблоны.
Шаблон - это такой же профиль, только он не идет в работу, а попадает в отдельный список шаблонов.
Шаблоном профиль становится через задание параметра --template=<name>.
Далее он может быть импортирован (--import=<name>). Импорт предполагает копирование профиля полностью,
а не только тех настроек, которые в нем были указаны - все остальные настройки имеют значения по умолчанию.
Любые настройки текущего профиля стираются, включая и имя.
Поэтому --import надо писать в начале, а потом добавлять уникальные для профиля параметры.
Шаблоны могут импортировать и друг друга. Для шаблона обязательно уникальное имя, а при импорте имя копируется,
поэтому обязательно надо задать уникальное имя через --name.
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>. Он не импортирует шаблоны.
В шаблонах допустимы любые параметры, относящиеся к профилям, включая и фильтры.
Фильтрация по листам
Если имеются фильтры по хостлистам, и есть хотя бы один домен в любом хостлисте или указан автохостлист, то профиль никогда не будет выбран при отсутствующем имени хоста. Случай, когда нет автохостлиста, а все файлы листов пустые, приравнивается к отсутствию фильтра по хостлисту.
Если нет автохостлиста, но есть записи в обычных хостлистах, то профиль выбирается только если текущий хост проходит по любому из включающих хостлистов, но не проходит ни по одному из исключающих.
Если есть автохостлист, то при наличии имени хоста профиль выбирается всегда вне зависимости от его вхождения в какие-либо листы этого профиля. Действия зависят от вхождения в листы.
- Если хост входит в исключающие листы, не происходит никаких действий и не происходит попытки выяснить работает ли ресурс.
- Если хост не входит в исключающие листы, но входит во включающие - происходит применение стратегии без попыток выяснить работает ли ресурс с ней.
- Если хост не входит ни в исключающие листы, ни во включающие - стратегия не применяется, происходит обнаружение
неудачи доступа к ресурсу. Если случилась неудача, увеличивается счетчик неудач. Если случается удача или превышается
интервал времени между неудачами
--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 - 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лог.
Сигналы
- 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.
Песочница
В целях безопасности 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 dispay 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 кодом. - При обработке профиля через
--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][,type2]...принимает список известных пейлоадов через зяпятую, "all" или "known". Список известных пейлоадов доступен в help тексте nfqws2. По умолчанию--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
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 and
--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 дропает текущий пакет
Результат всех lua-desync инстансов аггрегируется : VERDICT_MODIFY замещает VERDICT_PASS, VERDICT_DROP замещает их обоих.
Структура таблицы 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 | название профиля | может отсутствовать |
| template_n | number | номер шаблона, на базе которого создан профиль | может отсутствовать |
| template_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 | тип пейлоада текущего пакета или группы пакетов. список возможных в help тексте nfqws2 | если неизвестно - unknown |
| l7proto | string | тип протокола потока | если неизвестно - unknown |
| reasm_data | string | результат сборки многопакетного сообщения, либо сам пейлоад, если сборки не было | пока применяется только для tcp |
| reasm_offset | string | смещение текущего переигрываемого пакета в сборке | пока применяется только для 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 |
| 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 пейлоад |
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_src | 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_FIN,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 |
Особенности приема многопакетных пейлоадов
Сборка многопакетного пейлоада называется реасм (reasm - reassemble).
Она производится автоматически C кодом, если встречен пейлоад, требующий сборки, доступен conntrack и не указан запрет на сборку через --reasm-disable.
На текущий момент таких пейлоадов 2 - tls_client_hello и quic_initial. Оба могут содержать постквантовую криптографию kyber, которая не помещается в один пакет.
Для tls_clien_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 | протокол потока. список возможных доступен в help тексте nfqws2 | есть всегда. если неизвестно - 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, заголовки - нет |
| 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-битными счетчиками, поэтому у них этой проблемы нет.
С интерфейс 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 |
number | код вердикта desync функции | |
| 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_FIN 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_ECT1 | number | ECN Capable Transport(0) | 0x02 |
| IPTOS_ECN_CE1 | number | Congestion Experienced | 0x03 |
| IPTOS_DSCP_MASK | number | битовая маска поля ip_tos, соответствующая DSCP | 0xFC |
| IP6F_MORE_FRAG | number | бит "More fragment" поля ip6f_offlg из ipv6 fragment header | 0x0001 |
| IPPROTO_IP IPPROTO_IPV6 IPPROTO_ICMP IPPROTO_TCP IPPROTO_UDP IPPROTO_ICMPV6 IPPROTO_HOPOPTS IPPROTO_ROUTING IPPROTO_FRAGMENT IPPROTO_AH IPPROTO_ESP IPPROTO_DSTOPTS IPPROTO_MH IPPROTO_HIP IPPROTO_SHIM6 IPPROTO_NONE |
number | номера IP протоколов | используются в ipv4 и ipv6 |
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 до 32 бит. При передаче отрицательных чисел они интерпретируются в дополнительном коде. Например, в 32 битах -2 становится 0xFFFFFFFE, в 8 битах - 0xFE. Большая разрядность не поддерживается, поскольку возникают несовместимости между LUA 5.3+ и более старыми версиями. Только в LUA 5.3 реализован тип integer 64 bit. В более старых используется формат плавающей точки double с мантиссой 53 бит.
Стандартные операции сдвига и побитовые логические операции :
function bitlshift(u32, bits)
function bitrshift(u32, bits)
function bitand(u32_1, u32_2, ...., u32_N)
function bitor(u32_1, u32_2, ...., u32_N)
function bitxor(u32_1, u32_2, ...., u32_N)
function bitnot(u32)
bitand, bitor и bitxor работает с произвольным количеством чисел.
Операции получения и установки отдельных битов :
function bitget(u32, bit_from, bit_to)
function bitset(u32, bit_from, bit_to, set)
- bitget получает число из диапазона битов u32 с номерами от bit_from до bit_to. нумерация битов с 0.
- bitset записывает число set в диапазон битов u32 с номерами от 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)
Эти функции используются для извлечения числовых полей в формате big endian из raw строки. offset - номер байта от начала raw строки, начиная с 1. Аналогичные встроенные средства (string.unpack) есть только в LUA 5.3.
buX
function bu8(u8)
function bu16(u16)
function bu24(u24)
function bu32(u32)
Преобразуют число в raw строку в формате big endian.
Чтобы собрать структуру из числовых полей, можно использовать обычную операцию конкатенации строк ..
swapX
function swap16(u16)
function swap32(u32)
uXadd
Инвертируют порядок следования байт в u16 или u32. Если в вашей структуре порядок байт little endian, можно использовать uX/buX + swap.
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)
Функции для сложения произвольного количества беззнаковых чисел указанной разрядности. Операнды должны вписываться в указанную разрядность, иначе вызывается 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 строку - криптографически стойкий блок случайных данных указанного размера. Источник - /dev/random
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
Системные функции
uname
function uname()
Возвращает то же самое, что и команда uname в shell - название ядра ОС. "Linux", "FreeBSD", "OpenBSD". В Windows возвращается строка, начинающаяся с "CYGWIN", далее следует версия.
clock_gettime
function clock_gettime()
Узнать точное время. Возвращает 2 значения - unixtime в секундах и наносекунды. Встроенная функция os.time() не выдает наносекунды.
getpid
function getpid()
function gettid()
- getpid() возвращает идентификатор текущего процесса - PID
- gettid() возвращает идентификатор текущего потока - TID
Опции по работе с пакетами
В следующих функциях будут использоваться стандартные наборы опций - rawsend и reconstruct. Они представляют собой таблицы со специфическими полями. Если nil, считается, что ни одно поле не задано.
standard reconstruct
Опции реконструкции диссектов - reconstruct_opts. Реконструкция - это сборка raw пакета из диссекта.
| Поле | Тип | Описание |
|---|---|---|
| badsum | bool | испортить L4 checksum. посчитать чексумму и сделать xor со случайным значением от 1 до 0xFFFF |
| ip6_preserve_next | bool | использовать значения "next" из ip6.exthdr |
| ip6_last_proto | number | если ip6_preserve_next=true, IP протокол последнего exthdr |
При сборке ipv6 по умолчанию цепочка ip протоколов в exthdr собирается автоматически. У каждого exthdr есть поле type, поэтому понятно что вписывать в предыдущий exthdr, либо в основной ip6 хедер. next протокол последнего exthdr устанавливатеся как IPPROTO_TCP или IPPROTO_UDP в зависимости от наличия в диссекте таблиц tcp или udp. В большинстве случаев это удобно, поскольку вам при вставлении exthdr не надо реконструировать всю цепочку next протоколов. За вас это сделает реконструктор диссекта.
Режим ip6_preserve_next используется, если у вас специальная цель, требующая ручной крафтинг полей next protocol. В этом случае невозможно автоматически узнать что вы хотите вписать в последний exthdr. Туда вписывается ip6_last_proto, либо IPPROTO_NONE, если ip6_last_proto не задан.
badsum вынесен в реконструкцию, поскольку чексуммы tcp и udp считаются на базе всего IP пакета. В сумме участвуют элементы хедера ip/ip6, весь хедер tcp и сам пейлоад. Поэтому по отдельным частям гарантированно испортить чексумму невозможно. Что бы вы туда не вписали, существует маленькая вероятность (1/65536), что она окажется верной.
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_iphdr(ip)
function reconstruct_ip6hdr(ip6, reconstruct_opts)
Реконструкция соответствующих raw заголовков из таблиц диссекта. Возвращает raw вариант заголовка.
- реконструкция ip6 задействует reconstruct_opts. из них берется ip6_preserve_next и ip6_last_proto.
- чексумма ip header считается автоматически, поскольку ни от чего больше не зависит
- чексуммы tcp и udp не считаются, поскольку зависят от других компонент
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)
Функции для выправления чексумм. Поскольку строки в LUA immutable, они возвращают копию соответствующего заголовка с исправленной чексуммой.
- с csum_ipv4_fix все просто. на входе ip заголовок, на выходе ip заголовок с исправленной суммой
- csum_tcp_fix и csum_udp_fix берут raw ip заголовок (ipv4 или ipv6), tcp/udp заголовок и пейлоад. версия ip определяется автоматически. чексумма считается на базе L3, L4 заголовков и пейлоада.
Прямая реконструкция отдельных заголовков нужна редко. Обычно все задачи выполняют функции по работе с диссектами. Но если у вас особый случай, например вам нужно сконструировать icmp, то вам придется собирать его по частям и использовать rawsend для отсылки. csum_ip4_fix вам наверняка понадобится.
Прием и отсылка пакетов
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
Управление выполнением инстансов
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_filter | string | эффективный --payload-filter . список названий пейлоадов через запятую |
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)
posdebug
Вывести таблицу аргументов в debug log.
function posdebug(ctx, desync)
Вывести в debug log информацию о текущих conntrack позициях по прямому и обратному направлению.
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
Работа со строками
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
Обслуживание 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)
Разборка HTTP запроса или ответа http. http представляет собой многострочный текст. Разборка представляет собой таблицу с вложенными подтаблицами. В заголовках выдаются позиции начала и конца названия заголовка и самого значения. Названия полей в таблице headers соответствуют названию заголовков в нижнем регисте. Все позиции - внутри строки http.
Пример разборки http запроса `http://testhost.com/testuri`
.uri
string /test_uri
.headers
.content-length
.header
string Content-Length
.value
string 330
.pos_start
number 43
.pos_end
number 61
.pos_header_end
number 56
.pos_value_start
number 59
.host
.header
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
.content-type
.pos_header_end
number 28
.pos_value_start
number 31
.header
string Content-Type
.value
string text/html
.pos_start
number 17
.pos_end
number 39
.content-length
.pos_header_end
number 54
.pos_value_start
number 57
.header
string Content-Length
.value
string 650
.pos_start
number 41
.pos_end
number 59
Работа с элементами 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)
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.
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 опцию в самое начало, если она есть |
| fool | имя кастомной функции фулинга. она берет диссект и таблицу fooling_options |
ipv6 extension headers добавляются в следующем порядке:
- hopbyhop
- hopbyhop2
- destopt
- routing
- destopt2
- ah
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, если какое-либо изменение было произведено
Отсылка
Следующие функции могут брать несколько блоков описанных выше опций, каждый из которых представлен полем параметра 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 сегментация невозможна и не выполняется.
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)
Функции работают со строкой - списком пейлоадов через запятую. Список известных типов пейлоада можно получить из help текста nfqws2. Все пустые пакеты имеют пейлоад empty, неизвестные - unknown. Особые значения - 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 непреднамеренно было непросто.
В 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
Фильтр по пейлоаду берет список типов пейлоада. Список известных типов пейлоада можно получить из help текста nfqws2. Все пустые пакеты имеют пейлоад empty, неизвестные - unknown. Особые значения - all и known. all означает любой пейлоад, known - не unknown и не empty.
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, остальные сервера ломает.
Замена 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.
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: 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: 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: 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 реальными. Восстанавливается непрерывная последовательность потока, данные передаются в сокет приложения.
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: 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: 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: 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
- payload фильтр по умолчанию - "known"
Отсылает часть текущего диссекта, реасма, или произвольного blob, ограниченную двумя маркерами pos с опциональным применением техники seqovl таким же способом, как и в multisplit. Дополнительная сегментация при превышении MSS производится автоматически.
В случае reasm работает только при приеме его первой части (т.к. работает целиком по reasm, а не отдельным его частям).
Вердикт не выносится.
С помощью tcpseg можно сделать seqovl без сегментации, используя маркеры "0,-1". Для замещения текущего диссекта можно комбинировать с drop.
Дурение 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 пакет не дойдет до адресата. Ошибка в случае превышения 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 относительно направления IP соединения. Атака называется в литературе "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_conn_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: 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 считается то же самое , что и в автохослистах.