mirror of
https://github.com/bol-van/zapret.git
synced 2024-11-11 17:29:16 +05:00
nfqws: QUIC protocol recognition
This commit is contained in:
parent
38dca7da70
commit
0a5ffc1a54
Binary file not shown.
@ -169,6 +169,7 @@ nfqws takes the following parameters:
|
||||
--dpi-desync-fake-http=<filename> ; file containing fake http request. replacement for built-in
|
||||
--dpi-desync-fake-tls=<filename> ; file containing fake TLS ClientHello (for https). replacement for built-in
|
||||
--dpi-desync-fake-unknown=<filename> ; file containing unknown protocol fake payload. default is 256 zeroes
|
||||
--dpi-desync-fake-quic=<filename> ; file containing fake QUIC Initial
|
||||
--dpi-desync-fake-unknown-udp=<filename> ; 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=<filename> ; 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`.
|
||||
|
||||
|
@ -215,6 +215,7 @@ nfqws
|
||||
--dpi-desync-fake-http=<filename> ; файл, содержащий фейковый http запрос для dpi-desync=fake, на замену стандартному w3.org
|
||||
--dpi-desync-fake-tls=<filename> ; файл, содержащий фейковый tls clienthello для dpi-desync=fake, на замену стандартному w3.org
|
||||
--dpi-desync-fake-unknown=<filename> ; файл, содержащий фейковый пейлоад неизвестного протокола для dpi-desync=fake, на замену стандартным нулям 256 байт
|
||||
--dpi-desync-fake-quic=<filename> ; файл, содержащий фейковый QUIC Initial
|
||||
--dpi-desync-fake-unknown-udp=<filename> ; файл, содержащий фейковый пейлоад неизвестного udp протокола для dpi-desync=fake, на замену стандартным нулям 64 байт
|
||||
--dpi-desync-cutoff=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N
|
||||
--hostlist=<filename> ; применять дурение только к хостам из листа
|
||||
@ -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, она бесполезна для анализа на уровне отдельных пакетов.
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
18
nfq/desync.c
18
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=");
|
||||
|
19
nfq/nfqws.c
19
nfq/nfqws.c
@ -528,6 +528,7 @@ static void exithelp()
|
||||
" --dpi-desync-fake-http=<filename>\t; file containing fake http request\n"
|
||||
" --dpi-desync-fake-tls=<filename>\t; file containing fake TLS ClientHello (for https)\n"
|
||||
" --dpi-desync-fake-unknown=<filename>\t; file containing unknown protocol fake payload\n"
|
||||
" --dpi-desync-fake-quic=<filename>\t; file containing fake QUIC Initial\n"
|
||||
" --dpi-desync-fake-unknown-udp=<filename> ; 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=<filename>\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));
|
||||
|
@ -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;
|
||||
|
141
nfq/protocol.c
141
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(p<e && (*p==' ' || *p=='\t')) p++;
|
||||
s=p;
|
||||
while(s<e && (*s!='\r' && *s!='\n' && *s!=' ' && *s!='\t')) s++;
|
||||
if (s>p)
|
||||
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<slen;i++) host[i]=tolower(p[i]);
|
||||
host[slen]=0;
|
||||
if (slen >= 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 **
|
||||
// <CompressionMethods>
|
||||
// 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<len) return false;
|
||||
data += l; len -= l;
|
||||
l = ntohs(*(uint16_t*)data);
|
||||
data += 2; len -= 2;
|
||||
if (l < len) return false;
|
||||
|
||||
uint16_t ntype=htons(type);
|
||||
while(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<elen) break;
|
||||
if (etype==ntype)
|
||||
uint16_t etype = *(uint16_t*)data;
|
||||
size_t elen = ntohs(*(uint16_t*)(data + 2));
|
||||
data += 4; l -= 4;
|
||||
if (l < elen) break;
|
||||
if (etype == ntype)
|
||||
{
|
||||
if (ext && len_ext)
|
||||
{
|
||||
@ -104,7 +104,7 @@ bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **
|
||||
}
|
||||
return true;
|
||||
}
|
||||
data+=elen; l-=elen;
|
||||
data += elen; l -= elen;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -114,19 +114,76 @@ bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len
|
||||
const uint8_t *ext;
|
||||
size_t elen;
|
||||
|
||||
if (!TLSFindExt(data,len,0,&ext,&elen)) return false;
|
||||
if (!TLSFindExt(data, len, 0, &ext, &elen)) return false;
|
||||
// u16 data+0 - name list length
|
||||
// u8 data+2 - server name type. 0=host_name
|
||||
// u16 data+3 - server name length
|
||||
if (elen<5 || ext[2]!=0) return false;
|
||||
size_t slen = ntohs(*(uint16_t*)(ext+3));
|
||||
ext+=5; elen-=5;
|
||||
if (slen<elen) return false;
|
||||
if (elen < 5 || ext[2] != 0) return false;
|
||||
size_t slen = ntohs(*(uint16_t*)(ext + 3));
|
||||
ext += 5; elen -= 5;
|
||||
if (slen < elen) return false;
|
||||
if (ext && len_host)
|
||||
{
|
||||
if (slen>=len_host) slen=len_host-1;
|
||||
for(size_t i=0;i<slen;i++) host[i]=tolower(ext[i]);
|
||||
host[slen]=0;
|
||||
if (slen >= 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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user