## WinDivert фильтры (`--wf-*`) в zapret2 для Windows **WinDivert** — это драйвер для Windows, который позволяет перехватывать и модифицировать сетевые пакеты на уровне ядра. В zapret2 для Windows используется программа **winws2** (вместо nfqws2 для Linux), которая работает с WinDivert. **WinDivert фильтр применяется ПЕРВЫМ** — это фундаментальная архитектура. ### Порядок фильтрации (pipeline): ``` ┌─────────────────────────────────────────────────────────────────┐ │ СЕТЕВОЙ СТЕК │ │ (все пакеты) │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 1️⃣ WinDivert ФИЛЬТР (--wf-*) │ │ [уровень драйвера] │ │ │ │ Пропускает только пакеты, соответствующие фильтру │ │ Остальные идут мимо nfqws напрямую в сеть │ └──────────────────────────┬──────────────────────────────────────┘ │ только захваченные пакеты ▼ ┌─────────────────────────────────────────────────────────────────┐ │ nfqws │ │ │ │ 2️⃣ --filter-tcp/udp — фильтр по портам │ │ 3️⃣ --ipset — фильтр по IP │ │ 4️⃣ --hostlist — фильтр по доменам (из payload) │ │ 5️⃣ desync actions — применение обхода DPI │ └─────────────────────────────────────────────────────────────────┘ ``` ### Почему это важно: - **Производительность**: WinDivert фильтрует на уровне драйвера — CPU-efficient - **nfqws видит только нужные пакеты** — не тратит ресурсы на разбор всего трафика - `--wf-*` = **грубый фильтр** (порты, протоколы, IP версия) - `--hostlist` = **тонкий фильтр** (по содержимому пакета, доменам) --- **Да, `--wf-*` применяется первым, на уровне драйвера, до того как пакет попадёт в nfqws.** --- ## 📦 **Основные параметры WinDivert фильтров** ### 1. **`--wf-iface`** - Фильтр по сетевому интерфейсу **Синтаксис:** ```bash --wf-iface=<int>[.<int>] ``` **Параметры:** - `int` - индекс сетевого интерфейса - `int.int` - индекс интерфейса и подинтерфейса **Примеры:** ```bash --wf-iface=1 # интерфейс 1 --wf-iface=2.0 # интерфейс 2, подинтерфейс 0 ``` --- ### 2. **`--wf-l3`** - Фильтр L3 протокола **Синтаксис:** ```bash --wf-l3=ipv4|ipv6 ``` **Параметры:** - `ipv4` - только IPv4 - `ipv6` - только IPv6 - Можно указывать несколько через запятую **Примеры:** ```bash --wf-l3=ipv4 --wf-l3=ipv4,ipv6 ``` --- ### 3. **`--wf-tcp-in`** - TCP входящие порты **Синтаксис:** ```bash --wf-tcp-in=[~]port1[-port2] ``` **Параметры:** - `port` - конкретный порт - `port1-port2` - диапазон портов - `~port` - отрицание (все кроме) - Можно указывать несколько через запятую **Примеры:** ```bash --wf-tcp-in=80,443 # входящие на порты 80 и 443 --wf-tcp-in=1000-2000 # диапазон --wf-tcp-in=~443 # все кроме 443 ``` --- ### 4. **`--wf-tcp-out`** - TCP исходящие порты **Синтаксис:** ```bash --wf-tcp-out=[~]port1[-port2] ``` **Параметры:** (аналогично `--wf-tcp-in`) **Примеры:** ```bash --wf-tcp-out=80,443 # исходящие на порты 80 и 443 ``` --- ### 5. **`--wf-udp-in`** - UDP входящие порты **Синтаксис:** ```bash --wf-udp-in=[~]port1[-port2] ``` **Параметры:** (аналогично TCP) **Примеры:** ```bash --wf-udp-in=53,443 # DNS и QUIC входящие ``` --- ### 6. **`--wf-udp-out`** - UDP исходящие порты **Синтаксис:** ```bash --wf-udp-out=[~]port1[-port2] ``` **Параметры:** (аналогично TCP) **Примеры:** ```bash --wf-udp-out=443 # QUIC исходящие ``` --- ### 7. **`--wf-tcp-empty`** - Обработка пустых TCP пакетов **Синтаксис:** ```bash --wf-tcp-empty=[0|1] ``` **Параметры:** - `0` - не обрабатывать пустые TCP пакеты без флагов SYN/RST/FIN (по умолчанию) - `1` - обрабатывать пустые TCP пакеты **Примеры:** ```bash --wf-tcp-empty=1 # включить обработку пустых пакетов ``` --- ### 8. **`--wf-raw-part`** - Частичный raw фильтр ⭐ **Синтаксис:** ```bash --wf-raw-part=<filter>|@<filename> ``` **Параметры:** - `filter` - строка фильтра на языке WinDivert - `@filename` - загрузить фильтр из файла - Можно указывать **несколько раз** - они объединяются через OR **Особенности:** - Фильтры работают на уровне ядра (очень быстро) - Язык фильтров похож на tcpdump/Wireshark - Позволяет фильтровать по содержимому payload - Экономит CPU, не перенаправляя весь поток **Примеры:** ```bash # Прямо в командной строке --wf-raw-part="outbound and udp.DstPort=443 and udp.PayloadLength>=256" # Из файла --wf-raw-part=@windivert_part.discord_media.txt # Несколько фильтров [email protected] [email protected] [email protected] ``` --- ### 9. **`--wf-filter-lan`** - Фильтр локальных сетей **Синтаксис:** ```bash --wf-filter-lan=0|1 ``` **Параметры:** - `0` - не фильтровать локальные сети - `1` - исключить локальные IP адреса (по умолчанию) **Исключаемые диапазоны:** - IPv4: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 - IPv6: ::1/128, fc00::/7, fe80::/10, ff00::/8 **Примеры:** ```bash --wf-filter-lan=0 # не исключать локальные сети ``` --- ### 10. **`--wf-raw`** - Полный raw фильтр **Синтаксис:** ```bash --wf-raw=<filter>|@<filename> ``` **Параметры:** - `filter` - полная строка фильтра WinDivert - `@filename` - загрузить из файла - **ЗАМЕНЯЕТ** все `--wf-tcp`, `--wf-udp`, `--wf-raw-part` **Примеры:** ```bash --wf-raw="!impostor and !loopback and outbound and tcp.DstPort=443" --wf-raw=@full_filter.txt ``` --- ### 11. **`--wf-save`** - Сохранить фильтр и выйти **Синтаксис:** ```bash --wf-save=<filename> ``` **Параметры:** - `filename` - путь для сохранения сгенерированного фильтра - После сохранения программа завершается **Примеры:** ```bash --wf-tcp-out=80,443 --wf-save=generated_filter.txt ``` --- ### 🔤 Язык фильтров WinDivert Основные операторы: Логические: - and - И - or - ИЛИ - not или ! - НЕ Сравнения: - == или = - равно - != - не равно - <, >, <=, >= - сравнения Направление: - outbound - исходящие пакеты - inbound - входящие пакеты Специальные: - impostor - пакеты от других драйверов - loopback - loopback интерфейс --- ### Доступные поля: #### **IP (IPv4):** ``` ip # IPv4 пакет ip.SrcAddr # IP источника ip.DstAddr # IP назначения ip.Length # Длина IP пакета ip.TTL # Time To Live ip.Protocol # Протокол (6=TCP, 17=UDP) ``` #### **IPv6:** ``` ipv6 # IPv6 пакет ipv6.SrcAddr # IPv6 источника ipv6.DstAddr # IPv6 назначения ipv6.Length # Длина пакета ipv6.HopLimit # Hop Limit ipv6.NextHdr # Следующий заголовок ``` #### **TCP:** ``` tcp # TCP пакет tcp.SrcPort # Порт источника tcp.DstPort # Порт назначения tcp.Syn # SYN флаг tcp.Rst # RST флаг tcp.Fin # FIN флаг tcp.Ack # ACK флаг tcp.Psh # PSH флаг tcp.PayloadLength # Длина payload tcp.Payload[N] # Байт payload по индексу N tcp.Payload16[N] # 16-bit значение (little-endian) tcp.Payload32[N] # 32-bit значение (little-endian) ``` #### **UDP:** ``` udp # UDP пакет udp.SrcPort # Порт источника udp.DstPort # Порт назначения udp.PayloadLength # Длина payload udp.Payload[N] # Байт payload по индексу N udp.Payload16[N] # 16-bit значение (little-endian) udp.Payload32[N] # 32-bit значение (little-endian) ``` #### **Интерфейс:** ``` ifIdx # Индекс интерфейса subIfIdx # Индекс подинтерфейса ``` --- ## 📝 **Примеры фильтров** ### Пример 1: QUIC Initial (IETF) ``` outbound and udp.PayloadLength>=256 and udp.Payload[0]>=0xC0 and udp.Payload[0]<0xD0 and udp.Payload[1]=0 and udp.Payload16[1]=0 and udp.Payload[4]=1 ``` ### Пример 2: STUN ``` outbound and udp.PayloadLength>=20 and udp.Payload32[1]=0x2112A442 and udp.Payload[0]<0x40 ``` ### Пример 3: WireGuard ``` outbound and udp.PayloadLength=148 and udp.Payload[0]=0x01 ``` ### Пример 4: Discord Media ``` outbound and ip and udp.DstPort>=50000 and udp.DstPort<=50099 and udp.PayloadLength=74 and udp.Payload32[0]=0x00010046 and udp.Payload32[2]=0 and udp.Payload32[3]=0 ``` ### Пример 5: TLS Client Hello ``` outbound and tcp.PayloadLength>=5 and tcp.Payload[0]=0x16 and tcp.Payload[1]=0x03 and tcp.Payload[2]>=0x01 and tcp.Payload[2]<=0x03 ``` ### Пример 6: HTTP Redirect (302/307) ``` 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) ``` --- ## 💡 **Полные примеры использования** ### Пример 1: Базовый перехват HTTP/HTTPS ```bash winws2 ^ --wf-tcp-out=80,443 ^ [email protected] [email protected] ^ --filter-tcp=80,443 --filter-l7=http,tls ^ --lua-desync=fake:blob=fake_default_tls ``` ### Пример 2: С частичными фильтрами ```bash winws2 ^ --wf-tcp-out=80,443 ^ --wf-raw-part=@windivert_part.discord_media.txt ^ --wf-raw-part=@windivert_part.stun.txt ^ --wf-raw-part=@windivert_part.quic_initial_ietf.txt ^ [email protected] [email protected] ^ --filter-tcp=80,443 --filter-l7=http,tls ^ --lua-desync=fake:blob=fake_default_tls ^ --new ^ --filter-l7=quic,stun,discord ^ --lua-desync=fake:blob=fake_default_quic:repeats=2 ``` ### Пример 3: Только QUIC с фильтром ```bash winws2 ^ --wf-raw-part=@windivert_part.quic_initial_ietf.txt ^ [email protected] [email protected] ^ --filter-l7=quic ^ --lua-desync=fake:blob=fake_default_quic:repeats=6 ``` ### Пример 4: Полный raw фильтр ```bash winws2 ^ --wf-raw="!impostor and !loopback and outbound and tcp.DstPort=443 and tcp.PayloadLength>0" ^ [email protected] [email protected] ^ --filter-tcp=443 ^ --lua-desync=fake:blob=fake_default_tls ``` --- ## ⚠️ **Важные особенности и ограничения** ### 1. **Отсутствие conntrack** - WinDivert НЕ отслеживает соединения (нет аналога connbytes из iptables) - Если перехватывать порт целиком, весь трафик идет через winws2 - Это нагружает процессор при передаче больших объемов данных ### 2. **Ограничения языка фильтров** - Нет операций с битовыми полями - Нет сдвигов (shift) - Нет побитовой логики (AND, OR, XOR) - Фильтры могут пропускать неправильные пакеты - Дофильтрация выполняется в winws2 ### 3. **Рекомендации** - Используйте `--wf-raw-part` для точной фильтрации payload - Это экономит CPU, отсекая ненужный трафик на уровне ядра - Комбинируйте несколько `--wf-raw-part` для разных протоколов ### 4. **Автоматическая генерация** - winws2 автоматически добавляет `!impostor and !loopback` - Автоматически добавляет фильтр для SYN/RST/FIN (для conntrack) - Автоматически добавляет фильтр HTTP redirect (302/307) - Автоматически исключает локальные сети (если `--wf-filter-lan=1`) --- ## 📚 **Документация WinDivert** Полная документация языка фильтров: https://reqrypt.org/windivert-doc.html#filter_language --- ## 🎯 **Резюме** **WinDivert фильтры** — это мощный механизм для перехвата пакетов в Windows на уровне ядра: - **`--wf-tcp-in/out`**, **`--wf-udp-in/out`** — простая фильтрация по портам - **`--wf-raw-part`** — частичные фильтры по содержимому payload (рекомендуется!) - **`--wf-raw`** — полный контроль над фильтром - **Язык фильтров** — похож на tcpdump, но с ограничениями - **Производительность** — фильтры работают в ядре, очень быстро Используйте `--wf-raw-part` для оптимальной производительности и точной фильтрации! ## Разница между `--wf-tcp-out=80,443` и `--wf-tcp-out=80 --wf-tcp-out=443` ### 🔍 **Анализ кода** Изучив функцию `wf_make_pf` в коде (строки 1193-1222), я могу точно объяснить разницу: --- ## ✅ **Вариант 1: `--wf-tcp-out=80,443` (ПРАВИЛЬНЫЙ)** **Что происходит:** 1. Параметр обрабатывается **один раз** 2. Функция `wf_make_pf` парсит строку `"80,443"` и разбивает по запятой 3. Генерируется **один** фильтр WinDivert: ``` ((tcp.DstPort == 80) or (tcp.DstPort == 443)) ``` **Результат:** - ✅ Один вызов парсера - ✅ Оптимальный фильтр - ✅ Меньше строковых операций --- ## ❌ **Вариант 2: `--wf-tcp-out=80 --wf-tcp-out=443` (НЕПРАВИЛЬНЫЙ)** **Что происходит:** 1. Первый параметр `--wf-tcp-out=80` обрабатывается 2. Генерируется фильтр: `((tcp.DstPort == 80))` 3. **Второй параметр ПЕРЕЗАПИСЫВАЕТ первый!** 4. Генерируется новый фильтр: `((tcp.DstPort == 443))` **Результат:** - ❌ **Первый порт (80) теряется!** - ❌ Работает только последний указанный порт (443) - ❌ Два вызова парсера (лишняя работа) --- ## 📊 **Доказательство из кода** Посмотрим на обработку параметра (строки 2330-2337): ```c case IDX_WF_TCP_OUT: hash_wf_tcp_out = hash_jen(optarg, strlen(optarg)); if (!wf_make_pf(optarg, "tcp", "SrcPort", params.wf_pf_tcp_src_out, WINDIVERT_PORTFILTER_MAX) || !wf_make_pf(optarg, "tcp", "DstPort", params.wf_pf_tcp_dst_out, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-tcp-out\n"); exit_clean(1); } break; ``` **Ключевой момент:** Каждый вызов `--wf-tcp-out` **перезаписывает** буферы `params.wf_pf_tcp_src_out` и `params.wf_pf_tcp_dst_out`, а не добавляет к ним! --- ## 🔬 **Как работает `wf_make_pf`** ```c static bool wf_make_pf(char *opt, const char *l4, const char *portname, char *buf, size_t len) { // ... for (n = 0, p = opt, *buf = '(', buf[1] = 0; p; n++) // Начинает с '(' { if ((e = strchr(p, ','))) // Ищет запятую { c = *e; *e = 0; } if (!pf_parse(p, &pf)) return false; // Генерирует условие для порта if (pf.from == pf.to) snprintf(s1, sizeof(s1), "(%s.%s %s %u)", l4, portname, pf.neg ? "!=" : "==", pf.from); if (n) strncat(buf, " or ", len - strlen(buf) - 1); // Добавляет "or" между портами strncat(buf, s1, len - strlen(buf) - 1); // ... } strncat(buf, ")", len - strlen(buf) - 1); // Закрывает скобку return true; } ``` **Функция:** 1. Парсит строку по запятым 2. Для каждого порта генерирует условие 3. Объединяет их через `or` 4. Возвращает один готовый фильтр --- ## 📈 **Производительность** ### Вариант 1: `--wf-tcp-out=80,443` **Время парсинга:** - 1 вызов `wf_make_pf` - 2 итерации цикла внутри функции - 1 выделение строки **Итоговый фильтр:** ``` ((tcp.DstPort == 80) or (tcp.DstPort == 443)) ``` **Производительность WinDivert:** ⚡ **Оптимальная** - Один компактный фильтр - Эффективная проверка в ядре --- ### Вариант 2: `--wf-tcp-out=80 --wf-tcp-out=443` **Время парсинга:** - 2 вызова `wf_make_pf` - 2 выделения строк - **Первый результат выбрасывается!** **Итоговый фильтр:** ``` ((tcp.DstPort == 443)) // Порт 80 потерян! ``` **Производительность WinDivert:** ❌ **Неправильная работа** - Фильтрует только последний порт - Порт 80 не перехватывается! --- ## 🎯 **Вывод** ### ✅ **Используйте:** ```bash --wf-tcp-out=80,443 ``` **Преимущества:** 1. ✅ **Работает правильно** - оба порта перехватываются 2. ✅ **Быстрее парсинг** - один вызов вместо двух 3. ✅ **Меньше памяти** - одна строка вместо двух 4. ✅ **Читабельнее** - короче командная строка --- ### ❌ **НЕ используйте:** ```bash --wf-tcp-out=80 --wf-tcp-out=443 ``` **Проблемы:** 1. ❌ **Не работает!** - только последний порт (443) 2. ❌ **Медленнее** - лишний вызов функции 3. ❌ **Больше кода** - длиннее командная строка 4. ❌ **Теряются данные** - первый порт (80) игнорируется --- ## 📋 **Правильные примеры** ```bash # Правильно: несколько портов через запятую --wf-tcp-out=80,443 --wf-tcp-out=80,443,8080,8443 --wf-udp-out=53,443,853 # Правильно: диапазоны --wf-tcp-out=1000-2000 --wf-tcp-out=80,443,8000-9000 # Правильно: отрицание --wf-tcp-out=~443 --wf-tcp-out=~80,~443 # НЕПРАВИЛЬНО: повторяющиеся параметры --wf-tcp-out=80 --wf-tcp-out=443 # ❌ Работает только 443! ``` --- ## 🔧 **Техническое объяснение** Параметры командной строки в `getopt_long` обрабатываются **последовательно**. Когда встречается один и тот же параметр дважды, второй вызов **перезаписывает** результат первого, потому что они записываются в **один и тот же буфер** (`params.wf_pf_tcp_dst_out`). Это не баг, а особенность дизайна - параметры WinDivert фильтров не предназначены для множественного указания. Вместо этого используется **встроенная поддержка списков через запятую**. --- **Итог:** Всегда используйте запятые для перечисления портов в одном параметре. Это правильно, быстрее и работает как задумано!