winws: ssid-filter

This commit is contained in:
bol-van 2024-06-19 19:46:16 +03:00
parent eebe68ca65
commit fc6bae8fcd
13 changed files with 223 additions and 68 deletions

Binary file not shown.

View File

@ -49,6 +49,7 @@ Task of `iptables` is done inside `winws` through `windivert` filters. `Windiver
--wf-udp=[~]port1[-port2] ; UDP port filter. ~ means negation. multiple comma separated values allowed.
--wf-raw=<filter>|@<filename> ; raw windivert filter string or filename
--wf-save=<filename> ; save windivert filter string to a file and exit
--ssid-filter=ssid1[,ssid2,ssid3,...] ; enable winws only if any of specified wifi SSIDs connected
```
`--wf-l3`, `--wf-tcp`, `--wf-udp` can take multiple comma separated arguments.
@ -59,6 +60,8 @@ If you can't find index this way use `winws --debug` to see index there. Subinte
Multiple `winws` processes are allowed. However, it's discouraged to intersect their filters.
`--ssid-filter` allows to enable `winws` only if specified wifi networks are connected. `winws` auto detects SSID appearance and disappearance.
`Cygwin` shell does not run binaries if their directory has it's own copy of `cygwin1.dll`.
That's why exists separate standalone version in `binaries/win64/zapret-tpws`.
`Cygwin` is required for `blockcheck.sh` support but `winws` itself can be run standalone without cygwin.

View File

@ -55,6 +55,7 @@ https://learn.microsoft.com/en-us/security-updates/SecurityAdvisories/2015/30339
--wf-udp=[~]port1[-port2] ; фильтр портов для udp. ~ означает отрицание
--wf-raw=<filter>|@<filename> ; задать напрямую фильтр windivert из параметра или из файла. имени файла предшествует символ @.
--wf-save=<filename> ; сохранить сконструированный фильтр windivert в файл для последующей правки вручную
--ssid-filter=ssid1[,ssid2,ssid3,...] ; включать winws только когда подключена любая из указанных wifi сетей
Параметры --wf-l3, --wf-tcp, --wf-udp могут брать несколько значений через запятую.
@ -70,6 +71,10 @@ autottl и autohostlist. При включении autohostlist так же пе
Можно запускать несколько процессов winws с разными стратегиями. Однако, не следует делать пересекающиеся фильтры.
В --ssid-filter можно через запятую задать неограниченное количество имен wifi сетей (SSID). Если задана хотя бы одна сеть,
то winws включается только, если подключен указанный SSID. Если SSID исчезает, winws отключается. Если SSID появляется снова,
winws включается. Это нужно, чтобы можно было применять раздельное дурение к каждой отдельной wifi сети.
Если в путях присутствуют национальные символы, то при вызове winws из cmd или bat кодировку нужно использовать OEM.
Для русского языка это 866. Пути с пробелами нужно брать в кавычки.

View File

@ -5,7 +5,7 @@ CFLAGS_MAC = -mmacosx-version-min=10.8
CFLAGS_CYGWIN = -Wno-address-of-packed-member -static
LIBS_LINUX = -lnetfilter_queue -lnfnetlink -lz
LIBS_BSD = -lz
LIBS_CYGWIN = -lz -Lwindivert -lwindivert
LIBS_CYGWIN = -lz -Lwindivert -lwindivert -lwlanapi
SRC_FILES = *.c crypto/*.c
all: nfqws

View File

@ -2,5 +2,5 @@
#include "gcm.h"
// mode : ENCRYPT, DECRYPT
// mode : AES_ENCRYPT, AES_DECRYPT
int aes_gcm_crypt(int mode, uint8_t *output, const uint8_t *input, size_t input_length, const uint8_t *key, const size_t key_len, const uint8_t *iv, const size_t iv_len, const uint8_t *adata, size_t adata_len, uint8_t *atag, size_t atag_len);

View File

@ -28,8 +28,8 @@
#include <string.h>
#define ENCRYPT 1 // specify whether we're encrypting
#define DECRYPT 0 // or decrypting
#define AES_ENCRYPT 1 // specify whether we're encrypting
#define AES_DECRYPT 0 // or decrypting
#if defined(_MSC_VER)
#include <basetsd.h>

View File

@ -186,7 +186,7 @@ int gcm_setkey(gcm_context *ctx, // pointer to caller-provided gcm context
// encrypt the null 128-bit block to generate a key-based value
// which is then used to initialize our GHASH lookup tables
if ((ret = aes_setkey(&ctx->aes_ctx, ENCRYPT, key, keysize)) != 0)
if ((ret = aes_setkey(&ctx->aes_ctx, AES_ENCRYPT, key, keysize)) != 0)
return(ret);
if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0)
return(ret);
@ -266,7 +266,7 @@ int gcm_start(gcm_context *ctx, // pointer to user-provided GCM context
ctx->add_len = 0;
ctx->mode = mode; // set the GCM encryption/decryption mode
ctx->aes_ctx.mode = ENCRYPT; // GCM *always* runs AES in ENCRYPTION mode
ctx->aes_ctx.mode = AES_ENCRYPT; // GCM *always* runs AES in ENCRYPTION mode
if (iv_len == 12) { // GCM natively uses a 12-byte, 96-bit IV
memcpy(ctx->y, iv, iv_len); // copy the IV to the top of the 'y' buff
@ -338,7 +338,7 @@ int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context
return(ret);
// encrypt or decrypt the input to the output
if (ctx->mode == ENCRYPT)
if (ctx->mode == AES_ENCRYPT)
{
for (i = 0; i < use_len; i++) {
// XOR the cipher's ouptut vector (ectr) with our input
@ -481,7 +481,7 @@ int gcm_auth_decrypt(
(which is an identical XORing to reverse the previous one)
and also to re-generate the matching authentication tag
*/
gcm_crypt_and_tag(ctx, DECRYPT, iv, iv_len, add, add_len,
gcm_crypt_and_tag(ctx, AES_DECRYPT, iv, iv_len, add, add_len,
input, output, length, check_tag, tag_len);
// now we verify the authentication tag in 'constant time'

View File

@ -14,6 +14,9 @@
#include "params.h"
#include "nfqws.h"
#ifdef __CYGWIN__
#include <wlanapi.h>
#endif
uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment)
{
@ -959,8 +962,83 @@ void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_fac
static HANDLE w_filter = NULL;
static OVERLAPPED ovl = { .hEvent = NULL };
static const struct str_list_head *wlan_filter_ssid = NULL;
static DWORD wlan_filter_tick=0;
uint32_t w_win32_error=0;
bool wlan_filter_match(const struct str_list_head *ssid_list)
{
DWORD dwCurVersion;
HANDLE hClient = NULL;
PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
PWLAN_INTERFACE_INFO pIfInfo;
PWLAN_CONNECTION_ATTRIBUTES pConnectInfo;
DWORD connectInfoSize, k;
bool bRes;
struct str_list *ssid;
size_t len;
// no filter given. always matches.
if (!ssid_list || LIST_EMPTY(ssid_list))
{
w_win32_error = 0;
return true;
}
w_win32_error = WlanOpenHandle(2, NULL, &dwCurVersion, &hClient);
if (w_win32_error != ERROR_SUCCESS) goto fail;
w_win32_error = WlanEnumInterfaces(hClient, NULL, &pIfList);
if (w_win32_error != ERROR_SUCCESS) goto fail;
for (k = 0; k < pIfList->dwNumberOfItems; k++)
{
pIfInfo = pIfList->InterfaceInfo + k;
if (pIfInfo->isState == wlan_interface_state_connected)
{
w_win32_error = WlanQueryInterface(hClient,
&pIfInfo->InterfaceGuid,
wlan_intf_opcode_current_connection,
NULL,
&connectInfoSize,
(PVOID *)&pConnectInfo,
NULL);
if (w_win32_error != ERROR_SUCCESS) goto fail;
// printf("%s\n", pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID);
LIST_FOREACH(ssid, ssid_list, next)
{
len = strlen(ssid->str);
if (len==pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength && !memcmp(ssid->str,pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID,len))
{
WlanFreeMemory(pConnectInfo);
goto found;
}
}
WlanFreeMemory(pConnectInfo);
}
}
w_win32_error = 0;
fail:
bRes = false;
ex:
if (pIfList) WlanFreeMemory(pIfList);
if (hClient) WlanCloseHandle(hClient, 0);
return bRes;
found:
w_win32_error = 0;
bRes = true;
goto ex;
}
static bool wlan_filter_match_rate_limited(void)
{
DWORD dwTick = GetTickCount() / 1000;
if (wlan_filter_tick == dwTick) return true;
wlan_filter_tick = dwTick;
return wlan_filter_match(wlan_filter_ssid);
}
static HANDLE windivert_init_filter(const char *filter, UINT64 flags)
{
LPSTR errormessage = NULL;
@ -1006,8 +1084,9 @@ void rawsend_cleanup(void)
CloseHandle(ovl.hEvent);
ovl.hEvent=NULL;
}
wlan_filter_ssid = NULL;
}
bool windivert_init(const char *filter)
bool windivert_init(const char *filter, const struct str_list_head *ssid_filter)
{
rawsend_cleanup();
w_filter = windivert_init_filter(filter, 0);
@ -1020,6 +1099,7 @@ bool windivert_init(const char *filter)
rawsend_cleanup();
return false;
}
wlan_filter_ssid = ssid_filter;
return true;
}
return false;
@ -1037,6 +1117,11 @@ static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len,
errno=EINTR;
return false;
}
if (!wlan_filter_match_rate_limited())
{
errno=ENODEV;
return false;
}
usleep(0);
if (WinDivertRecvEx(hFilter, packet, *len, &recv_len, 0, wa, NULL, &ovl))
{
@ -1057,6 +1142,11 @@ static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len,
errno=EINTR;
return false;
}
if (!wlan_filter_match_rate_limited())
{
errno=ENODEV;
return false;
}
usleep(0);
}
if (!GetOverlappedResult(hFilter,&ovl,&rd,TRUE))

View File

@ -18,6 +18,7 @@
#endif
#include "packet_queue.h"
#include "pools.h"
#ifndef IPPROTO_DIVERT
#define IPPROTO_DIVERT 258
@ -151,9 +152,10 @@ bool tcp_has_fastopen(const struct tcphdr *tcp);
#ifdef __CYGWIN__
extern uint32_t w_win32_error;
bool windivert_init(const char *filter);
bool windivert_init(const char *filter, const struct str_list_head *ssid_filter);
bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa);
bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa);
bool wlan_filter_match(const struct str_list_head *ssid_list);
#else
// should pre-do it if dropping privileges. otherwise its not necessary
bool rawsend_preinit(bool bind_fix4, bool bind_fix6);

View File

@ -410,72 +410,99 @@ static int win_main(const char *windivert_filter)
WINDIVERT_ADDRESS wa;
char ifout[22];
if (!windivert_init(windivert_filter))
return w_win32_error;
printf("windivert initialized. capture is started.\n");
pre_desync();
// cygwin auto flush fails when piping
fflush(stdout);
fflush(stderr);
for (id=0;;id++)
for(;;)
{
len = sizeof(packet);
if (!windivert_recv(packet, &len, &wa))
if (!wlan_filter_match(&params.ssid_filter))
{
if (errno==ENOBUFS)
printf("logical network is not present. waiting it to appear.\n");
fflush(stdout);
do
{
DLOG("windivert: ignoring too large packet\n")
continue; // too large packet
if (bQuit)
{
DLOG("QUIT requested\n")
return 0;
}
usleep(500000);
}
else if (errno==EINTR)
{
DLOG("QUIT requested\n")
break;
}
fprintf(stderr, "windivert: recv failed. errno %d\n", errno);
while (!wlan_filter_match(&params.ssid_filter));
printf("logical network now present\n");
fflush(stdout);
}
if (!windivert_init(windivert_filter, &params.ssid_filter))
return w_win32_error;
}
*ifout=0;
if (wa.Outbound) snprintf(ifout,sizeof(ifout),"%u.%u", wa.Network.IfIdx, wa.Network.SubIfIdx);
DLOG("packet: id=%u len=%zu %s IPv6=%u IPChecksum=%u TCPChecksum=%u UDPChecksum=%u IfIdx=%u.%u\n", id, len, wa.Outbound ? "outbound" : "inbound", wa.IPv6, wa.IPChecksum, wa.TCPChecksum, wa.UDPChecksum, wa.Network.IfIdx, wa.Network.SubIfIdx)
if (wa.Impostor)
{
DLOG("windivert: passing impostor packet\n")
verdict = VERDICT_PASS;
}
else if (wa.Loopback)
{
DLOG("windivert: passing loopback packet\n")
verdict = VERDICT_PASS;
}
else
{
mark=0;
// pseudo interface id IfIdx.SubIfIdx
verdict = processPacketData(&mark, ifout, packet, &len);
}
switch (verdict & VERDICT_MASK)
{
case VERDICT_PASS:
case VERDICT_MODIFY:
if ((verdict & VERDICT_MASK)==VERDICT_PASS)
DLOG("packet: id=%u reinject unmodified\n", id)
else
DLOG("packet: id=%u reinject modified len=%zu\n", id, len)
if (!windivert_send(packet, len, &wa))
fprintf(stderr,"windivert: reinject of packet id=%u failed\n", id);
break;
default:
DLOG("packet: id=%u drop\n", id);
}
printf("windivert initialized. capture is started.\n");
// cygwin auto flush fails when piping
fflush(stdout);
fflush(stderr);
for (id=0;;id++)
{
len = sizeof(packet);
if (!windivert_recv(packet, &len, &wa))
{
if (errno==ENOBUFS)
{
DLOG("windivert: ignoring too large packet\n")
continue; // too large packet
}
else if (errno==ENODEV)
{
printf("logical network disappeared. deinitializing windivert.\n");
rawsend_cleanup();
break;
}
else if (errno==EINTR)
{
DLOG("QUIT requested\n")
return 0;
}
fprintf(stderr, "windivert: recv failed. errno %d\n", errno);
return w_win32_error;
}
*ifout=0;
if (wa.Outbound) snprintf(ifout,sizeof(ifout),"%u.%u", wa.Network.IfIdx, wa.Network.SubIfIdx);
DLOG("packet: id=%u len=%zu %s IPv6=%u IPChecksum=%u TCPChecksum=%u UDPChecksum=%u IfIdx=%u.%u\n", id, len, wa.Outbound ? "outbound" : "inbound", wa.IPv6, wa.IPChecksum, wa.TCPChecksum, wa.UDPChecksum, wa.Network.IfIdx, wa.Network.SubIfIdx)
if (wa.Impostor)
{
DLOG("windivert: passing impostor packet\n")
verdict = VERDICT_PASS;
}
else if (wa.Loopback)
{
DLOG("windivert: passing loopback packet\n")
verdict = VERDICT_PASS;
}
else
{
mark=0;
// pseudo interface id IfIdx.SubIfIdx
verdict = processPacketData(&mark, ifout, packet, &len);
}
switch (verdict & VERDICT_MASK)
{
case VERDICT_PASS:
case VERDICT_MODIFY:
if ((verdict & VERDICT_MASK)==VERDICT_PASS)
DLOG("packet: id=%u reinject unmodified\n", id)
else
DLOG("packet: id=%u reinject modified len=%zu\n", id, len)
if (!windivert_send(packet, len, &wa))
fprintf(stderr,"windivert: reinject of packet id=%u failed\n", id);
break;
default:
DLOG("packet: id=%u drop\n", id);
}
// cygwin auto flush fails when piping
fflush(stdout);
fflush(stderr);
}
}
return 0;
}
@ -521,6 +548,9 @@ static void cleanup_params(void)
StrPoolDestroy(&params.hostlist_exclude);
StrPoolDestroy(&params.hostlist);
HostFailPoolDestroy(&params.hostlist_auto_fail_counters);
#ifdef __CYGWIN__
strlist_destroy(&params.ssid_filter);
#endif
}
static void exit_clean(int code)
{
@ -760,6 +790,8 @@ static void exithelp(void)
" --wf-udp=[~]port1[-port2]\t\t\t; UDP port filter. ~ means negation. multiple comma separated values allowed.\n"
" --wf-raw=<filter>|@<filename>\t\t\t; raw windivert filter string or filename\n"
" --wf-save=<filename>\t\t\t\t; save windivert filter string to a file and exit\n"
"\nLOGICAL NETWORK FILTER:\n"
" --ssid-filter=ssid1[,ssid2,ssid3,...]\t\t; enable winws only if any of specified wifi SSIDs connected\n"
#endif
"\nHOSTLIST FILTER:\n"
" --hostlist=<filename>\t\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
@ -915,7 +947,9 @@ int main(int argc, char **argv)
LIST_INIT(&params.hostlist_files);
LIST_INIT(&params.hostlist_exclude_files);
#ifndef __CYGWIN__
#ifdef __CYGWIN__
LIST_INIT(&params.ssid_filter);
#else
if (can_drop_root()) // are we root ?
{
params.uid = params.gid = 0x7FFFFFFF; // default uid:gid
@ -1002,6 +1036,7 @@ int main(int argc, char **argv)
{"wf-udp",required_argument,0,0}, // optidx=53
{"wf-raw",required_argument,0,0}, // optidx=54
{"wf-save",required_argument,0,0}, // optidx=55
{"ssid-filter",required_argument,0,0}, // optidx=56
#endif
{NULL,0,NULL,0}
};
@ -1499,6 +1534,24 @@ int main(int argc, char **argv)
strncpy(wf_save_file, optarg, sizeof(wf_save_file));
wf_save_file[sizeof(wf_save_file) - 1] = '\0';
break;
case 56: /* ssid-filter */
{
char *e,*p = optarg;
while (p)
{
e = strchr(p,',');
if (e) *e++=0;
if (*p && !strlist_add(&params.ssid_filter, p))
{
fprintf(stderr, "strlist_add failed\n");
exit_clean(1);
}
p = e;
}
}
break;
#endif
}
}

View File

@ -65,7 +65,9 @@ struct params_s
size_t fake_http_size,fake_tls_size,fake_quic_size,fake_wg_size,fake_dht_size,fake_unknown_size,fake_syndata_size,fake_unknown_udp_size;
int udplen_increment;
#ifndef __CYGWIN__
#ifdef __CYGWIN__
struct str_list_head ssid_filter;
#else
bool droproot;
uid_t uid;
gid_t gid;

View File

@ -600,7 +600,7 @@ bool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, si
header[0] = packet0;
for(uint8_t i = 0; i < pkn_len; i++) header[header_len - 1 - i] = (uint8_t)(pkn >> (8 * i));
if (aes_gcm_crypt(DECRYPT, clean, decrypt_begin, cryptlen, aeskey, sizeof(aeskey), aesiv, sizeof(aesiv), header, header_len, atag, sizeof(atag)))
if (aes_gcm_crypt(AES_DECRYPT, clean, decrypt_begin, cryptlen, aeskey, sizeof(aeskey), aesiv, sizeof(aesiv), header, header_len, atag, sizeof(atag)))
return false;
// check if message was decrypted correctly : good keys , no data corruption