mirror of
https://github.com/bol-van/zapret2.git
synced 2026-03-13 22:03:09 +00:00
291 lines
30 KiB
Markdown
291 lines
30 KiB
Markdown
# zapret2 v0.1
|
||
|
||
## Зачем это нужно
|
||
|
||
Автономное средство противодействия DPI, которое не требует подключения каких-либо сторонних серверов. Может помочь
|
||
обойти блокировки или замедление сайтов HTTP(S), сигнатурный анализ TCP и UDP протоколов, например, с целью блокировки
|
||
VPN. Может использоваться для частичной прозрачной обфускации протоколов.
|
||
|
||
Проект нацелен прежде всего на маломощные embedded устройства - роутеры, работающие под OpenWrt. Поддерживаются
|
||
традиционные Linux-системы, FreeBSD, OpenBSD, Windows. В некоторых случаях возможна самостоятельная прикрутка
|
||
решения к различным прошивкам.
|
||
|
||
## Чем это отличается от zapret1
|
||
|
||
zapret2 является дальнейшим развитием проекта zapret.
|
||
Проблема его основной части *nfqws1* в том, что он перегружен опциями и в условиях нарастающего противостояния регулятора и пользователей
|
||
не обеспечивает достаточную гибкость воздействия на трафик.
|
||
Обход DPI требует все более тонких и специфических воздействий, которые меняются со временем, а старые перестают работать.
|
||
|
||
Стратегии - это программы, управляющие сценарием атаки на DPI. В *nfqws1* они зашиваются в C код. Написание C кода - занятие нелегкое,
|
||
требующее достаточной квалификации разработчика и времени.
|
||
|
||
Цель *nfqws2* - сделать так, чтобы программы стратегий мог написать любой человек, владеющий знаниями в области сетей, понимающий уязвимости DPI
|
||
или хотя бы область , в которой их можно искать, плюс владеющий базовыми навыками программирования.
|
||
|
||
*nfqws2* оставляет в себе практически тот же функционал - распознавание протоколов, реассемблинг, дешифровка, управление профилями, хостлисты, ipset-ы, базовая фильтрация.
|
||
Но он полностью лишается возможностей самостоятельно воздействовать на трафик. Часть "дурения" переносится в скриптовой язык программирования LUA.
|
||
|
||
LUA код получает от C кода структурированное представление приходящих пакетов в виде дерева (диссекты), подобного тем, что вы видите в wireshark.
|
||
Туда же приходят результаты сборки или дешифровки частей некоторых протоколов (tls, quic).
|
||
С код предоставляет функции-хелперы, позволяющие отсылать пакеты, работать с двоичными данными, разбирать TLS, искать маркер-позции и т.д.
|
||
Имеется библиотека хелперов, написанных на LUA, а так же готовая библиотека программ атаки на DPI (стратегий), реализующая функции *nfqws1* в расширенном варианте
|
||
и с большей гибкостью.
|
||
|
||
Вы всегда сможете взять и дописать что-то свое. В этом и есть смысл, чтобы борьбой с DPI смог заняться любой, кто разбирается в пакетах.
|
||
Мог "потыкать" его, проверить свои идеи. А потом поделиться с друзьями своим решением "одного клика".
|
||
zapret2 - инструмент для таких энтузиастов. Но это не готовое решение для чайников. Проект не ставит себе целью сделать все простым для всех.
|
||
Автор считает, что это невозможно в принципе по обьективным причинам.
|
||
|
||
|
||
## С чего начать
|
||
|
||
Хотелось бы избежать "талмуда" на главной странице. Поэтому начнем со способа запуска *nfqws2* и описания способов портирования стратегий *nfqws1* - как в *nfqws2* сделать то же самое, что можно было в *nfqws1*.
|
||
Когда вы поймете как это работает, вы можете посмотреть LUA код, находящийся "под капотом". Разобрать как он работает, попробовать написать что-то свое.
|
||
"талмуд" обязательно будет, как он есть у любых более-менее сложных проектов. Он нужен как справочник.
|
||
|
||
### Механика обработки трафика
|
||
|
||
Изначально сетевой трафик в любой ОС появляется в ядре. Первая задача - извлечь его оттуда и перенаправить на процесс *nfqws2*.
|
||
Эта задача решается в Linux с помощью iptables и nftables, в BSD - ipfw и pf, в Windows - windivert.
|
||
Процесс перенаправления трафика из ядра отнимает достаточно много ресурсов, поэтому лучше всего отфильтровать как можно больше прямо в нем.
|
||
|
||
Для экспериментов на Linux можно начать со следующих nftables, которые перенаправят начальные пакеты соединений на порты tcp 80,443 и udp 443 в очередь NFQUEUE с номером 200.
|
||
|
||
```
|
||
nft delete table inet ztest
|
||
nft create table inet ztest
|
||
nft add chain inet ztest post "{type filter hook postrouting priority 101;}"
|
||
nft add rule inet ztest post meta mark and 0x40000000 == 0 tcp dport "{80,443}" ct original packets 1-12 queue num 200 bypass
|
||
nft add rule inet ztest post meta mark and 0x40000000 == 0 udp dport "{443}" ct original packets 1-12 queue num 200 bypass
|
||
|
||
sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1
|
||
nft add chain inet ztest pre "{type filter hook prerouting priority -101;}"
|
||
nft add rule inet ztest pre meta mark and 0x40000000 == 0 tcp sport "{80,443}" ct reply packets 1-12 queue num 200 bypass
|
||
nft add rule inet ztest pre meta mark and 0x40000000 == 0 udp sport "{443}" ct reply packets 1-12 queue num 200 bypass
|
||
|
||
nft add chain inet ztest predefrag "{type filter hook output priority -401;}"
|
||
nft add rule inet ztest predefrag "mark & 0x40000000 != 0x00000000 notrack"
|
||
```
|
||
|
||
В windows функция перехвата вшита прямо в код движка для windows, который называется *winws2*. Он использует драйвер windivert.
|
||
Для перехвата портов целиком используются параметры `--wf-tcp-in`, `--wf-tcp-out`, `--wf-udp-in`, `--wf-udp-out`.
|
||
Они относятся к протоколам tcp или udp, к входящим или исходящим пакетам. Например, `--wf-tcp-out=80,443`.
|
||
Для более точного перехвата пишутся фильтры на языке фильтров windivert. Он похож на язык фильтров tcpdump или wireshark.
|
||
Фильтры отдаются *winws2* в параметрах `--wf-raw-part`. Конструктор фильтров обьединяет все указанные опции перехвата в
|
||
единый raw фильтр и запускает перехват windivert.
|
||
|
||
К сожалению, самый болезненный недостаток windivert (а так же BSD ipfw и pf) - отсутствие ограничителя на номер пакета в соединении (connbytes в iptables, ct packets в nftables).
|
||
windivert вообще не отслеживает соединения. Поэтому если перехватывать порт целиком, то все соединение по указанному направлению
|
||
пойдет на перехват, что нелегко для процессора, если там передаются многие мегабайты.
|
||
Поэтому по возможности пишите собственные фильтры windivert, проверяющие тип пейлоада хотя бы частично. Дофильтрацию может выполнить *winws2*.
|
||
|
||
Дальше под рутом нужно запустить *nfqws2* с параметрами командной строки. Они строятся примерно следующим образом :
|
||
|
||
```
|
||
nfqws2 --qnum 200 --debug --lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua \
|
||
--filter-tcp=80,443 --filter-l7=tls,http \
|
||
--payload=tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \
|
||
--payload=http_req --lua-desync=fake:blob=fake_default_http:tcp_md5 \
|
||
--payload=tls_client_hello,http_req --lua-desync=multisplit:pos=1:seqovl=5:seqovl_pattern=0x1603030000
|
||
```
|
||
|
||
Данный пример предполагает, что в той же директории находятся файлы `zapret-lib.lua` - библиотека хелперов на LUA и `zapret-antidpi.lua` - библиотека базовых стратегий.
|
||
`--lua-init` может содержать LUA код в виде строки. Так удобно писать простой код, например присвоить константу переменной, чтобы не создавать файлы ради этой мелочи.
|
||
Либо подцепляется файл, если значение параметра начинается с `@`. Код из `--lua-init` выполняется 1 раз при старте.
|
||
|
||
Далее указаны параметры `--lua-desync`. Они содержат имя LUA функции, вызываемой при обработке каждого пакета, проходящего через профиль мульистратегии.
|
||
После двоеточия и через двоеточия следуют параметры для данной функции в формате `param[=value]`. В примере реализована стратегия
|
||
|
||
```
|
||
nfqws --qnum 200 --debug \
|
||
--filter-tcp=80,443 --filter-l7=tls,http \
|
||
--dpi-desync=fake,multisplit --dpi-desync-fooling=md5sig --dpi-desync-split-pos=1,midsld \
|
||
--dpi-desync-split-seqovl=5 --dpi-desync-split-seqovl-pattern=0x1603030000 \
|
||
--dpi-desync-fake-tls-mod=rnd,rndsni,dupsid
|
||
```
|
||
|
||
Что сразу заметно - это наличие понятия "payload". В *nfqws1* были только протоколы соединения, которые участвовали в фильтрации профилей.
|
||
Они так же остались в *nfqws2*, но введено другое понятие - тип пейлоада. Пейлоад - это содержание текущего пакета.
|
||
Тип пейлоада - тип данных, содержащихся в пакете или группе пакетов. Например, протокол соединения может быть tls, а пейлоады - tls_client_hello, tls_server_hello, unknown.
|
||
|
||
Другое важное отличие - отсутствие жестко определенных фаз десинхронизации. То, что вы раньше писали как `fake,multisplit` реализуется двумя
|
||
последовательно вызываемыми LUA функциями. Их может быть столько, сколько нужно, учитывая логику прохождения пакетов и операций с ними, и у каждой могут быть свои параметры.
|
||
Может даже несколько раз вызываться одна и так же функция с разными параметрами. Так, например, можно послать несколько фейков, причем с разными фулингами.
|
||
Конкретный вызов `--lua-desync` функции называется инстансом. Инстанс - это связка имени функции, номера вызова внутри профиля и номера самого профиля.
|
||
Это похоже на одну программу, которую можно запустить много раз с разными параметрами.
|
||
|
||
Другое немаловажное отличие - поддержка автоматической tcp сегментации. Вам больше не нужно думать о размерах отсылаемых tcp пакетов.
|
||
По каждому соединению отслеживается MSS. Если пакет не влезает в MSS, выполняется сегментация.
|
||
Например, это может случиться при отправке tls фейка с kyber. Или если вы режете kyber tls так, что одна из частей получается размером 1600 байт,
|
||
что, очевидно, не влезает в MTU. Или если вы задали seqovl=10000. В *nfqws1* такое значение вызвало бы ошибку. В *nfqws2* будет отправлено
|
||
несколько tcp сегментов с начальным sequence -10000 общим размером 10000 байт, в последнем из которых будет кусок оригинального сообщения.
|
||
|
||
В *nfqws2* нет жестко зашитых параметров кастомных фейков типа `--dpi-desync-fake-tls`, `dpi-desync-fake-http` и тд.
|
||
Вместо них есть блобы. Блоб (blob) - это переменная LUA типа *string*, содержащая блок двоичных данных произвольной длины. От 1 байта до гигабайтов.
|
||
*nfqws2* автоматически инициализирует блобы со стандартными фейками tls, http, quic, как это и было в *nfqws1*.
|
||
Блобы могут быть заданы как hex-строка прямо в параметре desync функции, либо пред-загружены при старте с помощью параметра `--blob=name:0xHEX|[+ofs]@filename`
|
||
|
||
Что касается профилей мультистратегии и хостлистов , то они остались практически в неизменном виде. За исключением одной тонкости автолиста.
|
||
Теперь профиль с автолистом берет на себя только те соединения, для которых уже известен хост. Пока хоста нет - они проходят мимо.
|
||
В *nfqws1* они падали на профиль с автолистом.
|
||
|
||
```
|
||
nfqws2 --qnum 200 --debug --lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua \
|
||
--filter-tcp=80 --filter-l7=http --hostlist=mylist1.txt --lua-desync=multisplit --new \
|
||
--filter-tcp=80 --filter-l7=http --hostlist-exclude=mylist2.txt --lua-desync=fake:blob=0x00000000:ip_ttl=5:ip6_ttl=3 --lua-desync=multidisorder:pos=5,endhost-1 --new \
|
||
--filter-tcp=443 --filter-l7=tls --hostlist=mylist1.txt --lua-desync=multidisorder
|
||
```
|
||
|
||
Параметры *nfqws1* start/cutoff (`--dpi-desync-start`, `--dpi-desync-cutoff`, ...) теперь называются диапазонами (ranges).
|
||
Остались только 2 range : `--in-range` и `--out-range`. Они относятся к входящему и исходящему направлению соответственно.
|
||
Да, теперь можно полноценно работать как с входящими пакетами, так и с исходящими. Есть и специальный режим для сервера - `--server`, который
|
||
адаптирует интерпретацию IP адресов и портов источника/приемника, чтобы корректно работали ipset-ы и фильтры.
|
||
|
||
range задается как `mX-mY`, `mX<mY`, `-mY`, `<mY`, `mX-`.
|
||
Буква `m` означает режим счетчика. `n` - номер пакета, `d` - номер пакета с данными, `b` - количество переданных байт, `s` - относительный sequence для tcp.
|
||
После буквы режима пишется число. Например, `n5-s10000` означает с 5-го по очереди пакета до смещения 10000 байт относительно начала tcp соединения.
|
||
Есть и режимы, не требущие числа - `a` - всегда, `x` - никогда.
|
||
Если разделителем указан знак '-' - конечная позиция включительна, а если '<' - не включительна.
|
||
|
||
Установка по умолчанию `--in-range=x --out-range=a --payload all`. То есть отсекаются все входящие пакеты и берутся все исходящие.
|
||
|
||
`--in-range`, `--out-range` и `--payload` фильтры могут присутствовать множественно в одном профиле.
|
||
Их действие распространяется на все последующие `--lua-desync` функции до следующего фильтра того же типа или до конца профиля.
|
||
Следующий профиль снова принимает значения по умолчанию.
|
||
|
||
Что будет, если вы не напишите фильтр `--payload` для fake или multisplit ? В *nfqws1* без `--dpi-desync-any-protocol` они работали только по известным пейлоадам.
|
||
В *nfqws2* "any protocol" - режим по умолчанию. Однако, функции из библиотеки `zapret-antidpi.lua` написаны так, что по умолчанию работают только по известные пейлоадам
|
||
и не работают по пустым пакетам или unknown - точно так же, как это было в *nfqws1*.
|
||
Но лучше все-же писать фильтры `--payload`, потому что они работают на уровне C кода, который выполняется существенно быстрее, чем LUA.
|
||
|
||
Диссект пакета проходит поочередно по всем `--lua-desync` функциям профиля, для которых не выполняется условие отсечения (cutoff).
|
||
Отсечение может быть по range, payload или добровольное отсечение instance. Последний вариант - когда инстанс сам отказывается обрабатывать пакеты
|
||
по входящему, исходящему или обоим направлениям. Например, задача стратегии synack - отреагировать только на пакет с tcp флагами SYN,ACK. После этого функция не нужна, в ее коде вызывается функция отсечения.
|
||
Это сделано для экономии ресурсов процессора.
|
||
Если все функции в профиле точно никогда больше не будут вызваны по соединению + направлению - вошли в превышение верхней границы range или выполнили добровольный cutoff, то движок LUA не вызывается вообще.
|
||
|
||
От инстанса к инстансу содержимое диссекта может меняться функциями. Следующая функция видит изменения предыдущей.
|
||
Каждый инстанс выносит свой вердикт - что делать с текущим диссектом. VERDICT_PASS - означает отправить как есть,
|
||
VERDICT_MODIFY - отправить модифицированную версию, VERDICT_DROP - дропнуть диссект (не отправлять).
|
||
Итоговый вердикт формируется на основании вердиктов отдельных инстансов.
|
||
Если какой-либо инстанс выдал VERDICT_DROP - итоговый результат - всегда VERDICT_DROP.
|
||
Если ни один инстанс не выдал VERDICT_DROP, а какой-либо инстанс выдал VERDICT_MODIFY, то будет VERDICT_MODIFY.
|
||
Если все инстансы выдали VERDICT_PASS - будет VERDICT_PASS.
|
||
|
||
Например, функция pktmod применяет фулинг к текущему диссекту и выставляет вердикт VERDICT_MODIFY.
|
||
Если после этого будет вызван инстанс multisplit, то произойдет резка текущего уже измененного диссекта, отправка частей и в случае успеха VERDICT_DROP.
|
||
Получается, что мы применили фулинг и отправили с этим фулингом все tcp сегменты.
|
||
|
||
### Примеры портирования стратегий *nfqws1*
|
||
|
||
Кратко, на примерах, покажем как стратегии с *nfqws1* переписываются под *nfqws2*.
|
||
Для краткости здесь опущены директивы `--qnum`, `--lua-init`, `--wf-tcp-out` и тому подобное, что не касается напрямую стратегий и их непосредственного обслуживания.
|
||
|
||
Параметр `--filter-l7` относится к фильтру профиля мультистратегии. Здесь приведен как указание, что будет обрабатываться только конкретный протокол.
|
||
|
||
|
||
Автоматического использования ttl для ipv6 больше нет. Нужно писать отдельно для ipv4 и ipv6. Если не будет написано для ipv6, то к нему не будет применен ttl.
|
||
Функция pktmod применяет фулинг к текущему диссекту.
|
||
|
||
```
|
||
nfqws \
|
||
--filter-l7=http --dpi-desync=fake --dpi-desync-fake-http=0x00000000 --dpi-desync-ttl=6 \
|
||
--orig-ttl=1 --orig-mod-start=s1 --orig-mod-cutoff=d1
|
||
|
||
nfqws2 \
|
||
--filter-l7=http \
|
||
--payload=http_req --lua-desync=fake:blob=0x00000000:ip_ttl=6:ip6_ttl=6 \
|
||
--payload=empty --out-range="s1<d1" --lua-desync=pktmod:ip_ttl=1:ip6_ttl=1
|
||
```
|
||
|
||
badseq без параметров в *nfqws1* применял инкремент -10000 для syn и -66000 для ack.
|
||
В функциях `zapret-antidpi.lua` понятия badseq нет. Есть фулинги - уменьшить seq или ack на указанное значение.
|
||
|
||
tcp_ts_up - очень странное явление, обнаруженное в процессе тестирования *nfqws2*.
|
||
Оказывается, если есть tcp опция timestamp, linux стабильно отбрасывает пакеты с валидным seq и инвалидным ack только если опция идет первой.
|
||
*nfqws1* не соблюдал порядок tcp опций, timestamp получался первым всегда.
|
||
Поэтому оказалось, что старая версия работает стабильно , а новая нет.
|
||
tcp_ts_up дублирует старое поведение - двигает timestamp в самый верх.
|
||
|
||
```
|
||
nfqws \
|
||
--filter-l7=http \
|
||
--dpi-desync=fakedsplit --dpi-desync-fooling=badseq --dpi-desync-badseq-increment=0 --dpi-desync-split-pos=method+2
|
||
|
||
nfqws2 \
|
||
--filter-l7=http \
|
||
--payload=http_req --lua-desync=fakedsplit:pos=method+2:tcp_ack=-66000:tcp_ts_up
|
||
```
|
||
|
||
autottl пишется полностью в формате `delta,min-max`. Вместо двоеточия используется запятая, чтобы не конфликтовать с разделителем параметров функции.
|
||
|
||
```
|
||
nfqws \
|
||
--filter-l7=tls \
|
||
--dpi-desync=fakedsplit --dpi-desync-fakedsplit-pattern=tls_clienthello_google_com.bin \
|
||
--dpi-desync-ttl=1 --dpi-desync-autottl=-1 --dpi-desync-split-pos=method+2 --dpi-desync-fakedsplit-mod=altorder=1
|
||
|
||
nfqws2 \
|
||
--blob=tls_google:@tls_clienthello_google_com.bin \
|
||
--filter-l7=tls \
|
||
--payload tls_client_hello,http_req \
|
||
--lua-desync=fakedsplit:pattern=tls_google:pos=method+2:nofake1:ip_ttl=1:ip6_ttl=1:ip_autottl=-1,3-20:ip6_autottl=-1,3-20
|
||
```
|
||
|
||
Здесь важен порядок вызова функций.
|
||
wssize работает как модификатор диссекта - переписывает window size и scale factor. syndata должна быть отослана с модифицированными
|
||
wsize и scale. Если перепутать порядок следования, то syndata будет отправлена без wssize. Поскольку важна модификация, начиная с SYN пакета,
|
||
то wssize не сработает ожидаемым образом.
|
||
|
||
```
|
||
nfqws --dpi-desync=syndata,multisplit --dpi-desync-split-pos=midsld --wssize 1:6
|
||
|
||
nfqws2 --lua-desync=wssize:wsize=1:scale=6 --lua-desync=syndata --lua-desync=multisplit:pos=midsld
|
||
```
|
||
|
||
В первом примере все модификации tls применяются на лету.
|
||
Это так же означает, что рандомы будут применяться каждый раз, а не один раз, как в *nfqws1*.
|
||
Поведение можно привести к варианту *nfqws1* при желании - во втором примере показано как.
|
||
|
||
```
|
||
nfqws1 \
|
||
--filter-l7 tls \
|
||
--dpi-desync=fake --dpi-desync-fooling=datanoack --dpi-desync-fake-tls=! \
|
||
--dpi-desync-fake-tls-mod=rnd,rndsni,dupsid
|
||
|
||
nfqws2
|
||
--filter-l7 tls \
|
||
--payload=tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_flags_unset=ack:tls_mod=rnd,rndsni,dupsid,padencap \
|
||
--payload=empty --out-range="s1<d1" --lua-desync=pktmod:ip_ttl=1:ip6_ttl=1
|
||
|
||
|
||
nfqws2 \
|
||
--lua-init="fake_default_tls=tls_mod(fake_default_tls,'rnd,rndsni')" \
|
||
--filter-l7 tls \
|
||
--payload=tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_flags_unset=ack:tls_mod=dupsid,padencap \
|
||
--payload=empty --out-range="s1<d1" --lua-desync=pktmod:ip_ttl=1:ip6_ttl=1
|
||
```
|
||
|
||
IP фрагментация является теперь опцией процесса отсылки. Функция send отсылает текущий диссект, применяя указанные модификаторы, но не дропает оригинал.
|
||
Чтобы оригинал не пошел следом - применяется функция drop. Она ничего не делает, только выносит VERDICT_DROP.
|
||
При желании ipfrag можно применить и к fake, multisplit и другим функциям. Так же можно писать свои функции IP фрагментации.
|
||
Функция по умолчанию ipfrag2 делить пакет на 2 части. Но вы можете написать функцию, которая разделит его на 10 частей и указать ее как `ipfrag=my_frag_function`.
|
||
Функция фрагментации получает диссект подлежащего фрагментации пакета на вход и возвращает массив диссектов - фрагментов.
|
||
|
||
```
|
||
nfqws --dpi-desync=ipfrag2 --dpi-desync-ipfrag-pos-udp=8
|
||
nfqws2 --lua-desync=send:ipfrag:ipfrag_pos_udp=8 --lua-desync=drop
|
||
```
|
||
|
||
### Какие есть еще параметры
|
||
|
||
Как узнать какие есть еще функции и какие у них бывают параметры ? Смотрите `zapret-antidpi.lua`. Перед каждой функцией подробно описано какие параметры она берет.
|
||
Описание стандартных блоков параметров есть в начале. Позже - по мере сил и возможностей - будет писаться талмуд - справочник с руководством по программированию
|
||
*nfqws2* и описание стандартных библиотек.
|
||
|
||
### Очень важный совет
|
||
|
||
Научитесь пользоваться `--debug` логом. Без него будет очень сложно понять *nfqws2* на начальном этапе и приспособиться к новой схеме.
|
||
Ошибок будет много. Особенно, когда вы начнете писать свой LUA код. Их надо читать.
|