From 427f16776c108bf7311931cd7c0fd9d71135f336 Mon Sep 17 00:00:00 2001 From: bol-van Date: Tue, 1 Feb 2022 20:35:52 +0300 Subject: [PATCH] nfqws: hop-by-hop ipv6 fooling and desync --- blockcheck.sh | 7 ++++-- docs/changes.txt | 4 ++++ docs/readme.eng.md | 19 ++++++++++++++--- docs/readme.txt | 21 +++++++++++++++--- nfq/darkmagic.c | 53 ++++++++++++++++++++++++++++++++++++++++------ nfq/darkmagic.h | 12 ++++++----- nfq/desync.c | 38 ++++++++++++++++++++++++++++++++- nfq/desync.h | 4 +++- nfq/nfqws.c | 10 ++++++--- 9 files changed, 144 insertions(+), 24 deletions(-) diff --git a/blockcheck.sh b/blockcheck.sh index d5df72e..89a388e 100755 --- a/blockcheck.sh +++ b/blockcheck.sh @@ -543,7 +543,7 @@ pktws_check_domain_bypass() # $2 - encrypted test : 1/0 # $3 - domain - local strategy tests='fake' ttls s e desync pos fooling frag sec="$2" + local strategy tests='fake' ttls s f e desync pos fooling frag sec="$2" [ "$sec" = 0 ] && { for s in '--hostcase' '--hostspell=hoSt' '--hostnospace' '--domcase'; do @@ -575,12 +575,15 @@ pktws_check_domain_bypass() pktws_curl_test_update $1 $3 --dpi-desync=$desync $e done } + [ "$IPV" = 6 ] && pktws_curl_test_update $1 $3 $e --dpi-desync=hopbyhop for desync in $tests; do s="--dpi-desync=$desync" for ttl in $ttls; do pktws_curl_test_update $1 $3 $s --dpi-desync-ttl=$ttl $e && break done - for fooling in badsum badseq md5sig; do + f="badsum badseq md5sig" + [ "$IPV" = 6 ] && f="$f hopbyhop hopbyhop2" + for fooling in $f; do pktws_curl_test_update $1 $3 $s --dpi-desync-fooling=$fooling $e && [ "$fooling" = "md5sig" ] && echo 'WARNING ! although md5sig fooling worked it will not work on all sites. it typically works only on linux servers.' done diff --git a/docs/changes.txt b/docs/changes.txt index 806cdd0..8acee53 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -214,3 +214,7 @@ nfqws: UDP desync with conntrack support (any-protocol only for now) v44 nfqws: ipfrag + +v45 + +nfqws: hop-by-hop ipv6 desync and fooling diff --git a/docs/readme.eng.md b/docs/readme.eng.md index 10088f5..83774bb 100644 --- a/docs/readme.eng.md +++ b/docs/readme.eng.md @@ -139,11 +139,11 @@ nfqws takes the following parameters: --pidfile= ; write pid to file --user= ; drop root privs --uid=uid[:gid] ; drop root privs - --dpi-desync=[[,] ; desync dpi state. modes : synack fake rst rstack disorder disorder2 split split2 ipfrag2 + --dpi-desync=[[,] ; desync dpi state. modes : synack fake rst rstack hopbyhop disorder disorder2 split split2 ipfrag2 --dpi-desync-fwmark= ; override fwmark for desync packet. default = 0x40000000 --dpi-desync-ttl= ; set ttl for desync packet --dpi-desync-ttl6= ; set ipv6 hop limit for desync packet. by default ttl value is used - --dpi-desync-fooling=none|md5sig|ts|badseq|badsum ; can take multiple comma separated values + --dpi-desync-fooling= ; can take multiple comma separated values : none md5sig badseq badsum hopbyhop hopbyhop2 --dpi-desync-retrans=0|1 ; (fake,rst,rstack only) 0(default)=reinject original data packet after fake 1=drop original data packet to force its retransmission --dpi-desync-repeats= ; send every desync packet N times --dpi-desync-skip-nosni=0|1 ; 1(default)=do not apply desync to requests without hostname in the SNI @@ -201,7 +201,12 @@ add tcp option **MD5 signature**. All of them have their own disadvantages : This way you cant hurt anything, but good chances it will help to open local ISP websites. If automatic solution cannot be found then use `zapret-hosts-user-exclude.txt`. Some router stock firmwares fix outgoing TTL. Without switching this option off TTL fooling will not work. - +* `hopbyhop` is ipv6 only. This fooling adds empty extension header `hop-by-hop options` or two headers in case of `hopbyhop2`. + Packets with two hop-by-hop headers violate RFC and discarded by all operating systems. + All OS accept packets with one hop-by-hop header. + Some ISPs/operators drop ipv6 packets with hop-by-hop options. Fakes will not be processed by the server either because + ISP drops them or because there are two same headers. + DPIs may still anaylize packets with one or two hop-by-hop headers. `--dpi-desync-fooling` takes multiple comma separated values. @@ -234,6 +239,14 @@ Mode `split2` disables sending of fake segments. It can be used as a faster alte In `disorder2` and 'split2` modes no fake packets are sent, so ttl and fooling options are not required. +`hopbyhop` desync mode (it's not the same as `hopbyhop` fooling !) is ipv6 only. One hop-by-hop header +is added to all desynced packets. +Extra header increases packet size and can't be applied to the maximum size packets. +If it's not possible to send modified packet original one will be sent. +The idea here is that DPI sees 0 in the next header field of the main ipv6 header and does not +walk through the extension header chain until transport header is found. +`hopbyhop` mode cannot be used with second phase modes. + There are DPIs that analyze responses from the server, particularly the certificate from the ServerHello that contain domain name(s). The ClientHello delivery confirmation is an ACK packet from the server with ACK sequence number corresponding to the length of the ClientHello+1. diff --git a/docs/readme.txt b/docs/readme.txt index 82378e9..1e2469f 100644 --- a/docs/readme.txt +++ b/docs/readme.txt @@ -1,4 +1,4 @@ -zapret v.44 +zapret v.45 English ------- @@ -187,11 +187,11 @@ nfqws --hostnospace ; убрать пробел после "Host:" и переместить его в конец значения "User-Agent:" для сохранения длины пакета --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase --domcase ; домен после Host: сделать таким : TeSt.cOm - --dpi-desync=[,][,,][, ; бит fwmark для пометки десинхронизирующих пакетов, чтобы они повторно не падали в очередь. default = 0x40000000 --dpi-desync-ttl= ; установить ttl для десинхронизирующих пакетов --dpi-desync-ttl6= ; установить ipv6 hop limit для десинхронизирующих пакетов. если не указано, используется значение ttl - --dpi-desync-fooling=none|md5sig|ts|badseq|badsum ; дополнительные методики как сделать, чтобы фейковый пакет не дошел до сервера + --dpi-desync-fooling= ; дополнительные методики как сделать, чтобы фейковый пакет не дошел до сервера. none md5sig badseq badsum hopbyhop hopbyhop2 --dpi-desync-retrans=0|1 ; (только для fake,rst,rstack) 0(default)=отправлять оригинал следом за фейком 1=дропать оригинал, заставляя ОС выполнять ретрансмиссию через 0.2 сек --dpi-desync-repeats= ; посылать каждый генерируемый в nfqws пакет N раз (не влияет на остальные пакеты) --dpi-desync-skip-nosni=0|1 ; 1(default)=не применять dpi desync для запросов без hostname в SNI, в частности для ESNI @@ -257,6 +257,11 @@ nfqws Некоторые стоковые прошивки роутеров фиксируют исходящий TTL, без отключения этой опции через них работать не будет. КАКИМ СТОИТ ВЫБИРАТЬ TTL : найдите минимальное значение, при котором обход еще работает. Это и будет номер хопа вашего DPI. +* hopbyhop относится только к ipv6. Добавляется ipv6 extenstion header "hop-by-hop options". + В варианте hopbyhop2 добавляются 2 хедера, что является нарушением стандарта и гарантированно отбрасывается + стеком протоколов во всех ОС. Один хедер hop-by-hop принимается всеми ОС, однако на некоторых каналах/провайдерах + такие пакеты могут фильтроваться и не доходить. Расчет идет на то, что DPI проанализирует пакет с hop-by-hop, + но он либо не дойдет до адресата всилу фильтров провайдера, либо будет отброшен сервером, потому что хедера два. Режимы дурения могут сочетаться в любых комбинациях. --dpi-desync-fooling берет множество значений через запятую. @@ -288,6 +293,16 @@ nfqws disorder2 и split2 не предполагают отсылку фейк пакетов, поэтому опции ttl и fooling неактуальны. +Режим десинхронизации hopbyhop (не путать с fooling !) относится только к ipv6 и заключается в добавлении +хедера "hop-by-hop options" во все пакеты, попадающие под десинхронизацию. +Здесь надо обязательно понимать, что добавление хедера увеличивает размер пакета, потому не может быть применен +к пакетам максимального размера. Это имеет место при передачи больших сообщений. +В случае невозможности отослать пакет дурение будет отменено, пакет будет выслан в оригинале. +Расчет идет на то, что DPI увидит 0 в поле next header основного заголовка ipv6 и не будет скакать по +extension хедерам в поисках транспортного хедера. Таким образом не поймет, что это tcp или udp, и пропустит пакет +без анализа. Возможно, какие-то DPI на это купятся. +hopbyhop исключает применение режимов 2-й фазы. + Есть DPI, которые анализируют ответы от сервера, в частности сертификат из ServerHello, где прописаны домены. Подтверждением доставки ClientHello является ACK пакет от сервера с номером ACK sequence, соответствующим длине ClientHello+1. В варианте disorder обычно приходит сперва частичное подтверждение (SACK), потом полный ACK. diff --git a/nfq/darkmagic.c b/nfq/darkmagic.c index 01a0edf..7e58fb6 100644 --- a/nfq/darkmagic.c +++ b/nfq/darkmagic.c @@ -200,19 +200,40 @@ bool prepare_tcp_segment6( uint8_t *buf, size_t *buflen) { uint16_t tcpoptlen = tcpopt_len(fooling,timestamps,scale_factor); - uint16_t ip_payload_len = sizeof(struct tcphdr) + tcpoptlen + len; + uint16_t transport_payload_len = sizeof(struct tcphdr) + tcpoptlen + len; + uint16_t ip_payload_len = transport_payload_len + 8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) + 16*!!(fooling & FOOL_HOPBYHOP2); uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len; if (pktlen>*buflen) return false; struct ip6_hdr *ip6 = (struct ip6_hdr*)buf; struct tcphdr *tcp = (struct tcphdr*)(ip6+1); + uint8_t proto; + + if (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2)) + { + struct ip6_hbh *hbh = (struct ip6_hbh*)tcp; + tcp = (struct tcphdr*)((uint8_t*)tcp+8); + memset(hbh,0,8); + // extra HOPBYHOP header. standard violation + if (fooling & FOOL_HOPBYHOP2) + { + hbh = (struct ip6_hbh*)tcp; + tcp = (struct tcphdr*)((uint8_t*)tcp+8); + memset(hbh,0,8); + } + hbh->ip6h_nxt = IPPROTO_TCP; + proto = 0; // hop by hop options + } + else + proto = IPPROTO_TCP; + uint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen; - fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, IPPROTO_TCP, ttl); + fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl); fill_tcphdr(tcp,fooling,tcp_flags,nseq,nack_seq,src->sin6_port,dst->sin6_port,nwsize,scale_factor,timestamps,badseq_increment,badseq_ack_increment); memcpy(payload,data,len); - tcp6_fix_checksum(tcp,ip_payload_len,&ip6->ip6_src,&ip6->ip6_dst); + tcp6_fix_checksum(tcp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst); if (fooling & FOOL_BADSUM) tcp->th_sum^=htons(0xBEAF); *buflen = pktlen; @@ -273,19 +294,39 @@ bool prepare_udp_segment6( const void *data, uint16_t len, uint8_t *buf, size_t *buflen) { - uint16_t ip_payload_len = sizeof(struct udphdr) + len; + uint16_t transport_payload_len = sizeof(struct udphdr) + len; + uint16_t ip_payload_len = transport_payload_len + 8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) + 16*!!(fooling & FOOL_HOPBYHOP2); uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len; if (pktlen>*buflen) return false; struct ip6_hdr *ip6 = (struct ip6_hdr*)buf; struct udphdr *udp = (struct udphdr*)(ip6+1); + uint8_t proto; + + if (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2)) + { + struct ip6_hbh *hbh = (struct ip6_hbh*)udp; + udp = (struct udphdr*)((uint8_t*)udp+8); + memset(hbh,0,8); + // extra HOPBYHOP header. standard violation + if (fooling & FOOL_HOPBYHOP2) + { + hbh = (struct ip6_hbh*)udp; + udp = (struct udphdr*)((uint8_t*)udp+8); + memset(hbh,0,8); + } + hbh->ip6h_nxt = IPPROTO_UDP; + proto = 0; // hop by hop options + } + else + proto = IPPROTO_UDP; uint8_t *payload = (uint8_t*)(udp+1); - fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, IPPROTO_UDP, ttl); + fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl); fill_udphdr(udp, src->sin6_port, dst->sin6_port, len); memcpy(payload,data,len); - udp6_fix_checksum(udp,ip_payload_len,&ip6->ip6_src,&ip6->ip6_dst); + udp6_fix_checksum(udp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst); if (fooling & FOOL_BADSUM) udp->uh_sum^=htons(0xBEAF); *buflen = pktlen; diff --git a/nfq/darkmagic.h b/nfq/darkmagic.h index bdbf62c..8ed768b 100644 --- a/nfq/darkmagic.h +++ b/nfq/darkmagic.h @@ -14,11 +14,13 @@ // returns netorder value uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment); -#define FOOL_NONE 0 -#define FOOL_MD5SIG 1 -#define FOOL_BADSUM 2 -#define FOOL_TS 4 -#define FOOL_BADSEQ 8 +#define FOOL_NONE 0x00 +#define FOOL_MD5SIG 0x01 +#define FOOL_BADSUM 0x02 +#define FOOL_TS 0x04 +#define FOOL_BADSEQ 0x08 +#define FOOL_HOPBYHOP 0x10 +#define FOOL_HOPBYHOP2 0x20 #define SCALE_NONE ((uint8_t)-1) diff --git a/nfq/desync.c b/nfq/desync.c index 20a3c86..42547cb 100644 --- a/nfq/desync.c +++ b/nfq/desync.c @@ -67,7 +67,11 @@ bool desync_valid_zero_stage(enum dpi_desync_mode mode) } bool desync_valid_first_stage(enum dpi_desync_mode mode) { - return mode==DESYNC_FAKE || mode==DESYNC_RST || mode==DESYNC_RSTACK; + return mode==DESYNC_FAKE || mode==DESYNC_RST || mode==DESYNC_RSTACK || mode==DESYNC_HOPBYHOP; +} +bool desync_only_first_stage(enum dpi_desync_mode mode) +{ + return mode==DESYNC_HOPBYHOP; } bool desync_valid_second_stage(enum dpi_desync_mode mode) { @@ -95,6 +99,8 @@ enum dpi_desync_mode desync_mode_from_string(const char *s) return DESYNC_SPLIT2; else if (!strcmp(s,"ipfrag2")) return DESYNC_IPFRAG2; + else if (!strcmp(s,"hopbyhop")) + return DESYNC_HOPBYHOP; return DESYNC_INVALID; } @@ -378,6 +384,21 @@ packet_process_result dpi_desync_tcp_packet(uint8_t *data_pkt, size_t len_pkt, s DLOG("sending fake RST/RSTACK\n"); b = true; break; + case DESYNC_HOPBYHOP: + if (ip6hdr) + { + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_orig,FOOL_HOPBYHOP,0,0, + data_payload, len_payload, pkt1, &pkt1_len)) + { + return res; + } + DLOG("resending original packet with hop-by-hop options\n"); + if (!rawsend((struct sockaddr *)&dst, params.desync_fwmark, pkt1, pkt1_len)) + return res; + } + // this mode is final, no other options available + return drop; } if (b) @@ -640,6 +661,21 @@ packet_process_result dpi_desync_udp_packet(uint8_t *data_pkt, size_t len_pkt, s hexdump_limited_dlog(fake,fake_size,PKTDATA_MAXDUMP); DLOG("\n") b = true; break; + case DESYNC_HOPBYHOP: + if (ip6hdr) + { + if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, + ttl_orig,FOOL_HOPBYHOP, + data_payload, len_payload, pkt1, &pkt1_len)) + { + return res; + } + DLOG("resending original packet with hop-by-hop options\n"); + if (!rawsend((struct sockaddr *)&dst, params.desync_fwmark, pkt1, pkt1_len)) + return res; + } + // this mode is final, no other options available + return drop; } if (b) diff --git a/nfq/desync.h b/nfq/desync.h index 2a21cd6..98445f6 100644 --- a/nfq/desync.h +++ b/nfq/desync.h @@ -29,7 +29,8 @@ enum dpi_desync_mode { DESYNC_DISORDER2, DESYNC_SPLIT, DESYNC_SPLIT2, - DESYNC_IPFRAG2 + DESYNC_IPFRAG2, + DESYNC_HOPBYHOP }; extern const char *fake_http_request_default; @@ -38,6 +39,7 @@ extern const uint8_t fake_tls_clienthello_default[517]; enum dpi_desync_mode desync_mode_from_string(const char *s); bool desync_valid_zero_stage(enum dpi_desync_mode mode); bool desync_valid_first_stage(enum dpi_desync_mode mode); +bool desync_only_first_stage(enum dpi_desync_mode mode); bool desync_valid_second_stage(enum dpi_desync_mode mode); void desync_init(); diff --git a/nfq/nfqws.c b/nfq/nfqws.c index 3fb30e5..f803518 100644 --- a/nfq/nfqws.c +++ b/nfq/nfqws.c @@ -505,7 +505,7 @@ static void exithelp() " --hostspell\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" " --hostnospace\t\t\t\t; remove space after Host: and add it to User-Agent: to preserve packet size\n" " --domcase\t\t\t\t; mix domain case : Host: TeSt.cOm\n" - " --dpi-desync=[,][,] ; try to desync dpi state. modes : synack fake rst rstack disorder disorder2 split split2 ipfrag2\n" + " --dpi-desync=[,][,] ; try to desync dpi state. modes : synack fake rst rstack hopbyhop disorder disorder2 split split2 ipfrag2\n" #ifdef __linux__ " --dpi-desync-fwmark=\t; override fwmark for desync packet. default = 0x%08X (%u)\n" #elif defined(SO_USER_COOKIE) @@ -513,7 +513,7 @@ static void exithelp() #endif " --dpi-desync-ttl=\t\t\t; set ttl for desync packet\n" " --dpi-desync-ttl6=\t\t; set ipv6 hop limit for desync packet. by default ttl value is used.\n" - " --dpi-desync-fooling=[,]\t; can use multiple comma separated values. modes : none md5sig ts badseq badsum\n" + " --dpi-desync-fooling=[,]\t; can use multiple comma separated values. modes : none md5sig ts badseq badsum hopbyhop hopbyhop2\n" #ifdef __linux__ " --dpi-desync-retrans=0|1\t\t; 0(default)=reinject original data packet after fake 1=drop original data packet to force its retransmission\n" #endif @@ -810,7 +810,7 @@ int main(int argc, char **argv) fprintf(stderr, "invalid desync combo : %s+%s+%s\n",mode,mode2,mode3); exit_clean(1); } - if (params.desync_mode2 && !(desync_valid_first_stage(params.desync_mode) && desync_valid_second_stage(params.desync_mode2))) + if (params.desync_mode2 && (desync_only_first_stage(params.desync_mode) || !(desync_valid_first_stage(params.desync_mode) && desync_valid_second_stage(params.desync_mode2)))) { fprintf(stderr, "invalid desync combo : %s+%s\n", mode,mode2); exit_clean(1); @@ -864,6 +864,10 @@ int main(int argc, char **argv) } else if (!strcmp(p,"badseq")) params.desync_fooling_mode |= FOOL_BADSEQ; + else if (!strcmp(p,"hopbyhop")) + params.desync_fooling_mode |= FOOL_HOPBYHOP; + else if (!strcmp(p,"hopbyhop2")) + params.desync_fooling_mode |= FOOL_HOPBYHOP2; else if (strcmp(p,"none")) { fprintf(stderr, "dpi-desync-fooling allowed values : none,md5sig,ts,badseq,badsum\n");