diff --git a/binaries/win64/winws.exe b/binaries/win64/winws.exe index 4f5aba5..5c7cb51 100644 Binary files a/binaries/win64/winws.exe and b/binaries/win64/winws.exe differ diff --git a/binaries/win64/zapret-winws/winws.exe b/binaries/win64/zapret-winws/winws.exe index 4f5aba5..5c7cb51 100644 Binary files a/binaries/win64/zapret-winws/winws.exe and b/binaries/win64/zapret-winws/winws.exe differ diff --git a/docs/windows.eng.md b/docs/windows.eng.md index 77d2e92..e235001 100644 --- a/docs/windows.eng.md +++ b/docs/windows.eng.md @@ -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=|@ ; raw windivert filter string or filename --wf-save= ; 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. diff --git a/docs/windows.txt b/docs/windows.txt index 54e964c..1e9e618 100644 --- a/docs/windows.txt +++ b/docs/windows.txt @@ -55,6 +55,7 @@ https://learn.microsoft.com/en-us/security-updates/SecurityAdvisories/2015/30339 --wf-udp=[~]port1[-port2] ; фильтр портов для udp. ~ означает отрицание --wf-raw=|@ ; задать напрямую фильтр windivert из параметра или из файла. имени файла предшествует символ @. --wf-save= ; сохранить сконструированный фильтр 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. Пути с пробелами нужно брать в кавычки. diff --git a/nfq/Makefile b/nfq/Makefile index a2399f1..b334934 100644 --- a/nfq/Makefile +++ b/nfq/Makefile @@ -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 diff --git a/nfq/crypto/aes-gcm.h b/nfq/crypto/aes-gcm.h index 1741930..d836001 100644 --- a/nfq/crypto/aes-gcm.h +++ b/nfq/crypto/aes-gcm.h @@ -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); diff --git a/nfq/crypto/aes.h b/nfq/crypto/aes.h index a8f94d1..b04724d 100644 --- a/nfq/crypto/aes.h +++ b/nfq/crypto/aes.h @@ -28,8 +28,8 @@ #include -#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 diff --git a/nfq/crypto/gcm.c b/nfq/crypto/gcm.c index ed3d631..92a6e8f 100644 --- a/nfq/crypto/gcm.c +++ b/nfq/crypto/gcm.c @@ -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' diff --git a/nfq/darkmagic.c b/nfq/darkmagic.c index e1a6e17..6873197 100644 --- a/nfq/darkmagic.c +++ b/nfq/darkmagic.c @@ -14,6 +14,9 @@ #include "params.h" #include "nfqws.h" +#ifdef __CYGWIN__ +#include +#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)) diff --git a/nfq/darkmagic.h b/nfq/darkmagic.h index 8108185..eb7ef97 100644 --- a/nfq/darkmagic.h +++ b/nfq/darkmagic.h @@ -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); diff --git a/nfq/nfqws.c b/nfq/nfqws.c index 89ba98e..fd8ab9a 100644 --- a/nfq/nfqws.c +++ b/nfq/nfqws.c @@ -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(¶ms.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(¶ms.ssid_filter)); + printf("logical network now present\n"); + fflush(stdout); + } + + if (!windivert_init(windivert_filter, ¶ms.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(¶ms.hostlist_exclude); StrPoolDestroy(¶ms.hostlist); HostFailPoolDestroy(¶ms.hostlist_auto_fail_counters); +#ifdef __CYGWIN__ + strlist_destroy(¶ms.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=|@\t\t\t; raw windivert filter string or filename\n" " --wf-save=\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=\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(¶ms.hostlist_files); LIST_INIT(¶ms.hostlist_exclude_files); -#ifndef __CYGWIN__ +#ifdef __CYGWIN__ + LIST_INIT(¶ms.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(¶ms.ssid_filter, p)) + { + fprintf(stderr, "strlist_add failed\n"); + exit_clean(1); + } + p = e; + + } + } + break; + #endif } } diff --git a/nfq/params.h b/nfq/params.h index b0e5f4a..9098c34 100644 --- a/nfq/params.h +++ b/nfq/params.h @@ -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; diff --git a/nfq/protocol.c b/nfq/protocol.c index df23dcb..c718c35 100644 --- a/nfq/protocol.c +++ b/nfq/protocol.c @@ -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