diff --git a/binaries/freebsd-x64/dvtws b/binaries/freebsd-x64/dvtws index df76a5c..99966d9 100755 Binary files a/binaries/freebsd-x64/dvtws and b/binaries/freebsd-x64/dvtws differ diff --git a/docs/readme.eng.md b/docs/readme.eng.md index beb95cd..026c32f 100644 --- a/docs/readme.eng.md +++ b/docs/readme.eng.md @@ -169,6 +169,7 @@ nfqws takes the following parameters: --dpi-desync-fake-http= ; file containing fake http request. replacement for built-in --dpi-desync-fake-tls= ; file containing fake TLS ClientHello (for https). replacement for built-in --dpi-desync-fake-unknown= ; file containing unknown protocol fake payload. default is 256 zeroes + --dpi-desync-fake-quic= ; file containing fake QUIC Initial --dpi-desync-fake-unknown-udp= ; file containing unknown udp protocol fake payload --dpi-desync-cutoff=[n|d|s]N ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N --hostlist= ; apply fooling only to the listed hosts (one host per line, subdomains auto apply) @@ -420,9 +421,12 @@ Set conntrack timeouts appropriately. UDP attacks are limited. Its not possible to fragment UDP on transport level, only on network (ip) level. Only desync modes `fake`,`hopbyhop`,`destopt`,`ipfrag1` and `ipfrag2` are applicable. `fake`,`hopbyhop`,`destopt` can be used in combo with `ipfrag2`. -No protocol recognition is implemented yet so only `--dpi-desync-any-protocol` will work. -Conntrack supports udp. `--dpi-desync-cutoff` will work. UDP conntrack timeout can be set in the 4th -parameter of `--ctrack-timeouts`. + +QUIC initial packets are recognized. Decryption and hostname extraction is not supported so `--hostlist` parameter will not work. +For other protocols desync use `--dpi-desync-any-protocol`. + +Conntrack supports udp. `--dpi-desync-cutoff` will work. UDP conntrack timeout can be set in the 4th parameter of `--ctrack-timeouts`. + Fake attack is useful only for stateful DPI and useless for stateless dealing with each packet independently. By default fake payload is 64 zeroes. Can be overriden using `--dpi-desync-fake-unknown-udp`. diff --git a/docs/readme.txt b/docs/readme.txt index 2b358f4..4c8f669 100644 --- a/docs/readme.txt +++ b/docs/readme.txt @@ -215,6 +215,7 @@ nfqws --dpi-desync-fake-http= ; файл, содержащий фейковый http запрос для dpi-desync=fake, на замену стандартному w3.org --dpi-desync-fake-tls= ; файл, содержащий фейковый tls clienthello для dpi-desync=fake, на замену стандартному w3.org --dpi-desync-fake-unknown= ; файл, содержащий фейковый пейлоад неизвестного протокола для dpi-desync=fake, на замену стандартным нулям 256 байт + --dpi-desync-fake-quic= ; файл, содержащий фейковый QUIC Initial --dpi-desync-fake-unknown-udp= ; файл, содержащий фейковый пейлоад неизвестного udp протокола для dpi-desync=fake, на замену стандартным нулям 64 байт --dpi-desync-cutoff=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N --hostlist= ; применять дурение только к хостам из листа @@ -454,8 +455,8 @@ window size итоговый размер окна стал максимальн Атаки на udp более ограничены в возможностях. udp нельзя фрагментировать иначе, чем на уровне ip. Для UDP действуют только режимы десинхронизации fake,hopbyhop,destopt,ipfrag1,ipfrag2. Возможно сочетание fake,hopbyhop,destopt с ipfrag2. -Обязательно указание --dpi-desync-any-protocol, иначе десинхронизация работать не будет, -поскольку протокол неизвестен, а никакие протоколы пока не определяются. +Поддерживается определение пакетов QUIC Initial без расшифровки содержимого и имени хоста, то есть параметр +--hostlist не будет работать. Для десинхронизации других протоколов обязательно указывать --dpi-desync-any-protocol. Реализован conntrack для udp. Можно пользоваться --dpi-desync-cutoff. Таймаут conntrack для udp можно изменить 4-м параметром в --ctrack-timeouts. Атака fake полезна только для stateful DPI, она бесполезна для анализа на уровне отдельных пакетов. diff --git a/init.d/openwrt/custom-nfqws-quic4all b/init.d/openwrt/custom-nfqws-quic4all index be4b333..3d7e5a6 100644 --- a/init.d/openwrt/custom-nfqws-quic4all +++ b/init.d/openwrt/custom-nfqws-quic4all @@ -1,5 +1,5 @@ # this custom script in addition to MODE=nfqws runs desync to all QUIC initial packets, without ipset/hostlist filtering -# need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync-any-protocol --dpi-desync=fake --dpi-desync-cutoff=d4" +# need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync=fake" # NOTE : do not use TTL fooling. chromium QUIC engine breaks sessions if TTL expired in transit received QNUM2=$(($QNUM+10)) diff --git a/init.d/sysv/custom-nfqws-quic4all b/init.d/sysv/custom-nfqws-quic4all index fbfd04c..90e045f 100644 --- a/init.d/sysv/custom-nfqws-quic4all +++ b/init.d/sysv/custom-nfqws-quic4all @@ -1,5 +1,5 @@ # this custom script in addition to MODE=nfqws runs desync to all QUIC initial packets, without ipset/hostlist filtering -# need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync-any-protocol --dpi-desync=fake --dpi-desync-cutoff=d4" +# need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync=fake" # NOTE : do not use TTL fooling. chromium QUIC engine breaks sessions if TTL expired in transit received QNUM2=$(($QNUM+10)) diff --git a/nfq/desync.c b/nfq/desync.c index c080cc6..2f432e3 100644 --- a/nfq/desync.c +++ b/nfq/desync.c @@ -656,8 +656,19 @@ packet_process_result dpi_desync_udp_packet(uint8_t *data_pkt, size_t len_pkt, s size_t fake_size; bool b; - if (!params.desync_any_proto) return res; - DLOG("applying tampering to unknown protocol\n") + if (IsQUICInitial(data_payload,len_payload)) + { + DLOG("packet contains QUIC initial\n") + fake = params.fake_quic; + fake_size = params.fake_quic_size; + } + else + { + if (!params.desync_any_proto) return res; + DLOG("applying tampering to unknown protocol\n") + fake = params.fake_unknown_udp; + fake_size = params.fake_unknown_udp_size; + } enum dpi_desync_mode desync_mode = params.desync_mode; uint8_t fooling_orig = FOOL_NONE; @@ -667,9 +678,6 @@ packet_process_result dpi_desync_udp_packet(uint8_t *data_pkt, size_t len_pkt, s else ttl_fake = params.desync_ttl ? params.desync_ttl : ttl_orig; extract_endpoints(ip, ip6hdr, NULL, udphdr, &src, &dst); - fake = params.fake_unknown_udp; - fake_size = params.fake_unknown_udp_size; - if (params.debug) { printf("dpi desync src="); diff --git a/nfq/nfqws.c b/nfq/nfqws.c index f055aba..4c20ee1 100644 --- a/nfq/nfqws.c +++ b/nfq/nfqws.c @@ -528,6 +528,7 @@ static void exithelp() " --dpi-desync-fake-http=\t; file containing fake http request\n" " --dpi-desync-fake-tls=\t; file containing fake TLS ClientHello (for https)\n" " --dpi-desync-fake-unknown=\t; file containing unknown protocol fake payload\n" + " --dpi-desync-fake-quic=\t; file containing fake QUIC Initial\n" " --dpi-desync-fake-unknown-udp= ; file containing unknown udp protocol fake payload\n" " --dpi-desync-cutoff=[n|d|s]N\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n" " --hostlist=\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply)\n", @@ -614,6 +615,7 @@ int main(int argc, char **argv) memcpy(params.fake_tls,fake_tls_clienthello_default,params.fake_tls_size); params.fake_http_size = strlen(fake_http_request_default); memcpy(params.fake_http,fake_http_request_default,params.fake_http_size); + params.fake_quic_size = 256; params.fake_unknown_size = 256; params.fake_unknown_udp_size = 64; params.wscale=-1; // default - dont change scale factor (client) @@ -676,9 +678,10 @@ int main(int argc, char **argv) {"dpi-desync-fake-http",required_argument,0,0},// optidx=28 {"dpi-desync-fake-tls",required_argument,0,0},// optidx=29 {"dpi-desync-fake-unknown",required_argument,0,0},// optidx=30 - {"dpi-desync-fake-unknown-udp",required_argument,0,0},// optidx=31 - {"dpi-desync-cutoff",required_argument,0,0},// optidx=32 - {"hostlist",required_argument,0,0}, // optidx=33 + {"dpi-desync-fake-quic",required_argument,0,0},// optidx=31 + {"dpi-desync-fake-unknown-udp",required_argument,0,0},// optidx=32 + {"dpi-desync-cutoff",required_argument,0,0},// optidx=33 + {"hostlist",required_argument,0,0}, // optidx=34 {NULL,0,NULL,0} }; if (argc < 2) exithelp(); @@ -955,18 +958,22 @@ int main(int argc, char **argv) params.fake_unknown_size = sizeof(params.fake_unknown); load_file_or_exit(optarg,params.fake_unknown,¶ms.fake_unknown_size); break; - case 31: /* dpi-desync-fake-unknown-udp */ + case 31: /* dpi-desync-fake-quic */ + params.fake_quic_size = sizeof(params.fake_quic); + load_file_or_exit(optarg,params.fake_quic,¶ms.fake_quic_size); + break; + case 32: /* dpi-desync-fake-unknown-udp */ params.fake_unknown_udp_size = sizeof(params.fake_unknown_udp); load_file_or_exit(optarg,params.fake_unknown_udp,¶ms.fake_unknown_udp_size); break; - case 32: /* desync-cutoff */ + case 33: /* desync-cutoff */ if (!parse_cutoff(optarg, ¶ms.desync_cutoff, ¶ms.desync_cutoff_mode)) { fprintf(stderr, "invalid desync-cutoff value\n"); exit_clean(1); } break; - case 33: /* hostlist */ + case 34: /* hostlist */ if (!LoadHostList(¶ms.hostlist, optarg)) exit_clean(1); strncpy(params.hostfile,optarg,sizeof(params.hostfile)); diff --git a/nfq/params.h b/nfq/params.h index 433bc23..f10739a 100644 --- a/nfq/params.h +++ b/nfq/params.h @@ -48,8 +48,8 @@ struct params_s uint32_t desync_badseq_increment, desync_badseq_ack_increment; char hostfile[256]; strpool *hostlist; - uint8_t fake_http[1432],fake_tls[1432],fake_unknown[1432],fake_unknown_udp[1472]; - size_t fake_http_size,fake_tls_size,fake_unknown_size,fake_unknown_udp_size; + uint8_t fake_http[1432],fake_tls[1432],fake_unknown[1432],fake_unknown_udp[1472],fake_quic[1472]; + size_t fake_http_size,fake_tls_size,fake_unknown_size,fake_unknown_udp_size,fake_quic_size; bool droproot; uid_t uid; gid_t gid; diff --git a/nfq/protocol.c b/nfq/protocol.c index 1c514b1..2348162 100644 --- a/nfq/protocol.c +++ b/nfq/protocol.c @@ -22,22 +22,22 @@ bool IsHttp(const uint8_t *data, size_t len) } bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host) { - const uint8_t *p, *s, *e=data+len; + const uint8_t *p, *s, *e = data + len; p = (uint8_t*)strncasestr((char*)data, "\nHost:", len); if (!p) return false; - p+=6; - while(pp) + p += 6; + while (p < e && (*p == ' ' || *p == '\t')) p++; + s = p; + while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++; + if (s > p) { - size_t slen = s-p; + size_t slen = s - p; if (host && len_host) { - if (slen>=len_host) slen=len_host-1; - for(size_t i=0;i= len_host) slen = len_host - 1; + for (size_t i = 0; i < slen; i++) host[i] = tolower(p[i]); + host[slen] = 0; } return true; } @@ -45,7 +45,7 @@ bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_hos } bool IsTLSClientHello(const uint8_t *data, size_t len) { - return len>=6 && data[0]==0x16 && data[1]==0x03 && data[2]>=0x01 && data[2]<=0x03 && data[5]==0x01 && (ntohs(*(uint16_t*)(data+3))+5)<=len; + return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] >= 0x01 && data[2] <= 0x03 && data[5] == 0x01 && (ntohs(*(uint16_t*)(data + 3)) + 5) <= len; } bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext) { @@ -66,36 +66,36 @@ bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t ** // // u16 ExtensionsLength - size_t l,ll; + size_t l, ll; - l = 1+2+2+1+3+2+32; + l = 1 + 2 + 2 + 1 + 3 + 2 + 32; // SessionIDLength - if (len<(l+1)) return false; - ll = data[6]<<16 | data[7]<<8 | data[8]; // HandshakeProtocol length - if (len<(ll+9)) return false; - l += data[l]+1; + if (len < (l + 1)) return false; + ll = data[6] << 16 | data[7] << 8 | data[8]; // HandshakeProtocol length + if (len < (ll + 9)) return false; + l += data[l] + 1; // CipherSuitesLength - if (len<(l+2)) return false; - l += ntohs(*(uint16_t*)(data+l))+2; + if (len < (l + 2)) return false; + l += ntohs(*(uint16_t*)(data + l)) + 2; // CompressionMethodsLength - if (len<(l+1)) return false; - l += data[l]+1; + if (len < (l + 1)) return false; + l += data[l] + 1; // ExtensionsLength - if (len<(l+2)) return false; + if (len < (l + 2)) return false; - data+=l; len-=l; - l=ntohs(*(uint16_t*)data); - data+=2; len-=2; - if (l=4) + uint16_t ntype = htons(type); + while (l >= 4) { - uint16_t etype=*(uint16_t*)data; - size_t elen=ntohs(*(uint16_t*)(data+2)); - data+=4; l-=4; - if (l=len_host) slen=len_host-1; - for(size_t i=0;i= len_host) slen = len_host - 1; + for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]); + host[slen] = 0; } return true; } + + +#define QUIC_MAX_CID_LENGTH 20 +/* Returns the QUIC draft version or 0 if not applicable. */ +static inline uint8_t quic_draft_version(uint32_t version) { + /* IETF Draft versions */ + if ((version >> 8) == 0xff0000) { + return (uint8_t)version; + } + /* Facebook mvfst, based on draft -22. */ + if (version == 0xfaceb001) { + return 22; + } + /* Facebook mvfst, based on draft -27. */ + if (version == 0xfaceb002 || version == 0xfaceb00e) { + return 27; + } + /* GQUIC Q050, T050 and T051: they are not really based on any drafts, + * but we must return a sensible value */ + if (version == 0x51303530 || + version == 0x54303530 || + version == 0x54303531) { + return 27; + } + /* https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 + "Versions that follow the pattern 0x?a?a?a?a are reserved for use in + forcing version negotiation to be exercised" + It is tricky to return a correct draft version: such number is primarily + used to select a proper salt (which depends on the version itself), but + we don't have a real version here! Let's hope that we need to handle + only latest drafts... */ + if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) { + return 29; + } + /* QUIC (final?) constants for v1 are defined in draft-33, but draft-34 is the + final draft version */ + if (version == 0x00000001) { + return 34; + } + /* QUIC Version 2 */ + /* TODO: for the time being use 100 as a number for V2 and let see how v2 drafts evolve */ + if (version == 0x709A50C4) { + return 100; + } + return 0; +} +bool IsQUICInitial(uint8_t *data, size_t len) +{ + // long header, fixed bit, type=initial + if (len < 512 || (data[0] & 0xF0) != 0xC0) return false; + uint8_t *p = data + 1; + uint32_t ver = ntohl(*(uint32_t*)p); + if (quic_draft_version(ver) < 11) return false; + p += 4; + if (!*p || *p > QUIC_MAX_CID_LENGTH) return false; + return true; +} diff --git a/nfq/protocol.h b/nfq/protocol.h index 188afbc..423847e 100644 --- a/nfq/protocol.h +++ b/nfq/protocol.h @@ -9,3 +9,4 @@ bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_hos bool IsTLSClientHello(const uint8_t *data, size_t len); bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext); bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host); +bool IsQUICInitial(uint8_t *data, size_t len);