Кастомный фильтр WinDivert для дискорда

Привет. Разбираюсь с блокировкой дискорда. Почитал форум, решение есть, но как я понял есть проблема - дурение на всех высоких портах udp может сломать другие протоколы типа торрента.

По словам @bol-van войс блочат именно по STUN.

Решаюсь написать кастомный фильтр для WinDivert:

Первая попытка успешная! Я сделал простейший фильтр на длину udp Payload, которую подсмотрел в Wireshark. Войсчат в дискорде работает.

Моя конфигурация

start “zapret: http,https,quic” /min “%~dp0winws.exe” ^
–wf-raw=“(tcp and remotePort == 443) or (udp and (udp.PayloadLength == 74 or udp.PayloadLength == 20))” ^
–filter-tcp=443 --hostlist=“%~dp0list-discord.txt” --dpi-desync=disorder2 --new ^
–filter-udp=50000-65535 --dpi-desync=fake --dpi-desync-cutoff=d4 --dpi-desync-repeats=6 --dpi-desync-any-protocol

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

--dpi-desync-cutoff=d4 --dpi-desync-repeats=6
Эти ключи необходимы? Что если их убрать?

repeats не знаю, а без cutoff будет беда, усиленная кратно через repeats

я бы рекомендовал сохранить стандартный фильтр через --wf-save
там есть ряд важных ограничителей
и над ним уже издеваться

а дальше стоит вписать ограничитель на подсети дисккорда. они проскакивали в обсуждениях дискорда удп на гитхабе
подсети стоит обьединить, чтобы не писать кучу /24

я не исследовал блокировку дискорда
вывод о стуне был сделан на базе постов в обсуждениях. может еще что-то есть

в течение 2 недель собираюсь выкатить юзер моде реализацию ипсетов
но когда подсетей мало лучше использовать wf-raw

Можно ли определить конкретный пакет по его началу?
Конкретно пакеты auth в discord:

0000   xx xx xx xx xx xx 2c f0 5d a3 58 ee 08 00 45 00
0010   00 66 d1 4d 00 00 80 11 5a 70 c0 a8 32 32 22 00
0020   f9 ee e5 0c c3 58 00 52 6d fc 00 01 00 46 00 00
0030   e4 df 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0050   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0060   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0070   00 00 f4 f7

0000   xx xx xx xx xx xx 2c f0 5d a3 58 ee 08 00 45 00
0010   00 66 d1 4e 00 00 80 11 5a 6f c0 a8 32 32 22 00
0020   f9 ee e5 0d c3 58 00 52 bf d4 00 01 00 46 00 00
0030   e4 df 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0050   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0060   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0070   00 00 a3 1e

У пакетов общие 5 байт в начале payload и 2 байта в конце.

Можно ли указать это для нахождения нужного пакета?
Первые 4-5 байт данных не изменяются.
–wf-raw=“udp.Payload[0:3]==0x00010046”
При указании ничего не работает…

Во-первых, udp.Payload[i] работает с байтами. Для работы с 32-разрядными словами (4 байта) используйте udp.Payload32[i].
Во-вторых, я не уверен, что i можно указывать как диапазон. Цитата из доков:

The packet*[i], tcp.Payload*[i] and udp.Payload*[i] fields take an index parameter (i). The following indexing schemes are supported:

  • Undecorated integer (e.g., packet32[10]): evaluates to the i th word from the start of the packet/payload. This is essentially C-style array indexing;
  • Negative decorated integer (e.g., packet32[-10]): evaluates to the i th word from the end of the packet/payload. Here the index (-1) is the first full word that fits; and
  • Byte decorated (negative) integer (e.g., packet32[10b] or packet32[-10b]): evaluated to the word offset by i bytes from the start (or end) of the packet/payload.

Рабочий пример из winws для детекта входящего HTTP redirect

// HTTP/1.? 30(2|7)

tcp.PayloadLength>=12 and tcp.Payload32[0]==0x48545450 and tcp.Payload16[2]==0x2F31 and tcp.Payload[6]==0x2E and tcp.Payload16[4]==0x2033 and tcp.Payload[10]==0x30 and (tcp.Payload[11]==0x32 or tcp.Payload[11]==0x37)

байты идут в network byte order. То есть big endian.

Написал фильтр на основе wf-save, со всеми подсетями дискорда.
Да простит бог меня за это…

rules.txt

!impostor and !loopback and
((outbound and (((tcp.DstPort == 80) or (tcp.DstPort == 443)) or ((udp.DstPort == 443) or
(udp.DstPort >= 50000 and udp.DstPort <= 65535 and (
(ip.DstAddr >= 66.22.196.0 and ip.DstAddr <= 66.22.199.255) or
(ip.DstAddr >= 66.22.200.0 and ip.DstAddr <= 66.22.207.255) or
(ip.DstAddr >= 66.22.208.0 and ip.DstAddr <= 66.22.223.255) or
(ip.DstAddr >= 66.22.224.0 and ip.DstAddr <= 66.22.227.255) or
(ip.DstAddr >= 66.22.230.0 and ip.DstAddr <= 66.22.231.255) or
(ip.DstAddr >= 66.22.232.0 and ip.DstAddr <= 66.22.239.255) or
(ip.DstAddr >= 66.22.240.0 and ip.DstAddr <= 66.22.247.255) or
(ip.DstAddr >= 66.22.248.0 and ip.DstAddr <= 66.22.248.255))
))) and
(((ip.DstAddr < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and (ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and (ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and (ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and (ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255)) or ((ipv6.DstAddr > ::1) and (ipv6.DstAddr < 2001::0 or ipv6.DstAddr >= 2001:1::0) and (ipv6.DstAddr < fc00::0 or ipv6.DstAddr >= fe00::0) and (ipv6.DstAddr < fe80::0 or ipv6.DstAddr >= fec0::0) and (ipv6.DstAddr < ff00::0 or ipv6.DstAddr >= ffff::0))))
or
(inbound and tcp and (tcp.Ack and tcp.Syn or tcp.Rst or tcp.Fin) and ((tcp.SrcPort == 80) or (tcp.SrcPort == 443)) and (((ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and (ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and (ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and (ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and (ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255)) or ((ipv6.SrcAddr > ::1) and (ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and (ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and (ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and (ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0)))))

Проверил по-быстрому с этим конфигом

cmd

start “zapret: discord” /min “%~dp0winws.exe” ^
–wf-raw=@testwf.txt ^
–filter-tcp=443 --hostlist=“%~dp0list-youtube.txt” --hostlist=“%~dp0list-blacklist.txt” --hostlist=“%~dp0list-discord.txt” --dpi-desync=split --dpi-desync-split-pos=1 --dpi-desync-fooling=badseq --dpi-desync-repeats=7 --new ^
–filter-udp=50000-65535 --dpi-desync=fake --dpi-desync-any-protocol --dpi-desync-cutoff=d4 --dpi-desync-repeats=6

Коннектит ко всем серверам, кроме Бразилии, Гонконга, Индии, иногда Японии и юга США, Сингапура и Австралии. Но! Один раз даже к Роттердаму не подключился, но перезаход помог. Возможно зависит от настроек, но нужны ли эти сервера?

Полагаю, что тот список подсетей включал только российские сервера. Сделал фильтр по заголовку пакета, попробуй его:

Спойлер

!impostor and !loopback and
((outbound and (((tcp.DstPort == 80) or (tcp.DstPort == 443)) or ((udp.DstPort == 443) or
(udp.DstPort >= 50000 and udp.DstPort <= 65535 and udp.PayloadLength == 74 and udp.Payload32[0] == 0x00010046
))) and
(((ip.DstAddr < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and (ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and (ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and (ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and (ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255)) or ((ipv6.DstAddr > ::1) and (ipv6.DstAddr < 2001::0 or ipv6.DstAddr >= 2001:1::0) and (ipv6.DstAddr < fc00::0 or ipv6.DstAddr >= fe00::0) and (ipv6.DstAddr < fe80::0 or ipv6.DstAddr >= fec0::0) and (ipv6.DstAddr < ff00::0 or ipv6.DstAddr >= ffff::0))))
or
(inbound and tcp and (tcp.Ack and tcp.Syn or tcp.Rst or tcp.Fin) and ((tcp.SrcPort == 80) or (tcp.SrcPort == 443)) and (((ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and (ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and (ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and (ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and (ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255)) or ((ipv6.SrcAddr > ::1) and (ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and (ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and (ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and (ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0)))))

Он, конечно потенциально может сработать на пакеты с пейлодом 74 байта, заголовок которых начинается на 0x00010046, но я думаю что это маловероятно.
https://fossies.org/linux/misc/nDPI-4.10.tar.gz/nDPI-4.10/src/lib/protocols/discord.c?M=26
Нашел такой же метод в этом коде.

UPD Закинул все мои пресеты на форк github GitHub - gepron1x/zapret-win-bundle: zapret and blockcheck binary bundle for windows (discord presets)
preset_discord и preset_full соответственно :slight_smile:

Как я понял это список подсетей серверов от провайдера i3D.net B.V, но есть еще от Гугла(были еще от Psychz Networks, но, кажись, от них отказались). Собственно поэтому на ту же Японию, юг США и Роттердам коннектит с переменным успехом, так как они поделены между провайдерами и тут как повезёт куда тебя кинет. Финский сервак, к которому походу только при автоматической подборке сервера кидает, например, вообще весь от Гугла.

Я может попозже попробую дополнить фильтр остальными айпишниками, но пока второй вариант работает.

Ну вот что-то такое вышло. В этих диапазонах явно не только дискорд, но работают все сервера из списка за исключением Африки и, возможно, юга США, которые с переменным успехом работают. Финский тоже работает, на треть работает Франкфуртовский, но к нему вряд ли коннектить будет, так как к финскому пинг меньше(причем даже до Московского больше).
Но честно сказать не знаю кому нужны какие-то сервера кроме Московского, Роттердамского и финского/Стокгольмского. Наверное можно объединить с фильтром по заголовку и так меньше всего флуда будет?

filter.txt

!impostor and !loopback and
((outbound and (((tcp.DstPort == 80) or (tcp.DstPort == 443)) or ((udp.DstPort == 443) or
(udp.DstPort >= 50000 and udp.DstPort <= 65535 and (
(ip.DstAddr >= 66.22.196.0 and ip.DstAddr <= 66.22.199.255) or
(ip.DstAddr >= 66.22.200.0 and ip.DstAddr <= 66.22.207.255) or
(ip.DstAddr >= 66.22.208.0 and ip.DstAddr <= 66.22.223.255) or
(ip.DstAddr >= 66.22.224.0 and ip.DstAddr <= 66.22.227.255) or
(ip.DstAddr >= 66.22.230.0 and ip.DstAddr <= 66.22.231.255) or
(ip.DstAddr >= 66.22.232.0 and ip.DstAddr <= 66.22.239.255) or
(ip.DstAddr >= 66.22.240.0 and ip.DstAddr <= 66.22.247.255) or
(ip.DstAddr >= 66.22.248.0 and ip.DstAddr <= 66.22.248.255) or
(ip.DstAddr >= 35.215.192.0 and ip.DstAddr <= 35.215.255.255) or
(ip.DstAddr >= 35.215.128.0 and ip.DstAddr <= 35.215.191.255) or
(ip.DstAddr >= 35.207.192.0 and ip.DstAddr <= 35.207.255.255) or
(ip.DstAddr >= 35.213.0.0 and ip.DstAddr <= 35.213.127.255) or
(ip.DstAddr >= 35.214.128.0 and ip.DstAddr <= 35.214.255.255) or
(ip.DstAddr >= 34.0.240.0 and ip.DstAddr <= 34.0.255.255) or
(ip.DstAddr >= 35.213.128.0 and ip.DstAddr <= 35.213.191.255) or
(ip.DstAddr >= 35.213.192.0 and ip.DstAddr <= 35.213.255.255) or
(ip.DstAddr >= 35.217.0.0 and ip.DstAddr <= 35.217.63.255))
))) and
(((ip.DstAddr < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and (ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and (ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and (ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and (ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255)) or ((ipv6.DstAddr > ::1) and (ipv6.DstAddr < 2001::0 or ipv6.DstAddr >= 2001:1::0) and (ipv6.DstAddr < fc00::0 or ipv6.DstAddr >= fe00::0) and (ipv6.DstAddr < fe80::0 or ipv6.DstAddr >= fec0::0) and (ipv6.DstAddr < ff00::0 or ipv6.DstAddr >= ffff::0))))
or
(inbound and tcp and (tcp.Ack and tcp.Syn or tcp.Rst or tcp.Fin) and ((tcp.SrcPort == 80) or (tcp.SrcPort == 443)) and (((ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and (ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and (ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and (ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and (ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255)) or ((ipv6.SrcAddr > ::1) and (ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and (ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and (ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and (ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0)))))

Инфу брал отсюда, отсюда и тут еще сверялся.

Прекрасно, очень хорошо сделано. Для этого и задумывались --wf-save и --wf-raw.
Жаль, что нет ipset-ов в windivert.

Но еще стоит помнить, что базовый фильтр немного изменяется при включении autohostlist режима.
Если вдруг понадобится автолист, там нужен перехват RST и http redirect.

Решение рабочее, но, боюсь, флуд в сети все равно будет во время разговоров и трансляций в дс. Обойти блокировку достаточно в момент подключения, блочат именно тот первый пакет с длиной 74, дальше войс нормально работает

Что нужно сделать с этим первым пакетом, чтобы его пропустило? Написал клиентскую dll для Discord для перенаправления tcp трафика на socks5/https (просто кидаю dll в папку с программой и всё работает за исключением звонков), а с проксированием udp стало лень возиться (половину кода написал, но потом плюнул и решил для звонков использовать VPN). Если можно как-то отредактировать какой-то пакет, чтобы дальше всё работало, то сделать это легко на уровне процесса.

Сделал отправку просто одного нулевого байта перед первым после bind пакетом и всё заработало, можно разговаривать голосом.

Но еще стоит помнить, что базовый фильтр немного изменяется при включении autohostlist режима. Если вдруг понадобится автолист, там нужен перехват RST и http redirect.

Что-то подобное в inbound? И это получается отдельный фильтр, который надо использовать только если hostlist-auto включен, а в ином случае лучше без него?

inbound

(inbound and tcp and ((tcp.Ack and tcp.Syn or tcp.Rst or tcp.Fin) or (tcp.PayloadLength>=12 and tcp.Payload32[0]==0x48545450 and tcp.Payload16[2]==0x2F31 and tcp.Payload[6]==0x2E and tcp.Payload16[4]==0x2033 and tcp.Payload[10]==0x30 and (tcp.Payload[11]==0x32 or tcp.Payload[11]==0x37))) and ((tcp.SrcPort == 443)) and (((ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and (ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and (ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and (ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and (ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255)) or ((ipv6.SrcAddr > ::1) and (ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and (ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and (ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and (ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0)))))

Он не мешает особо, только раздувает код и вносит какой-то оверхед на процессинг фильтра в windivert.sys. Насколько он значителен судить не берусь, но в коде windivert есть начальный парсинг. На каждый пакет он текст не мучает. Уже все переведено в токенный псевдо-код.

Не будет, если cutoff сделать. у windivert нет conntrack. Там нельзя задать номер пакета в “соединении”.
Потому придется в user-mode в winws. У pktws есть свой коннтрак.
windivert фильтр отсеивает левые IP прямо в ядре. Это замена iptables фильтрам.