7 Commits

Author SHA1 Message Date
bol-van
a2ffa3455d nfqws: minor beautify text 2025-03-24 11:20:51 +03:00
bol-van
60b97dbed0 nfqws: remove debug printfs 2025-03-24 11:14:38 +03:00
bol-van
e56e4f5f35 update changes 2025-03-24 10:32:02 +03:00
bol-van
5305ea83c8 fakes: GGC kyber with inter-packet CRYPTO frag 2025-03-24 09:44:50 +03:00
bol-van
14b3dd459b nfqws: define reasm buffer sizes 2025-03-24 09:34:37 +03:00
bol-van
66fda2c33d nfqws: support QUIC multi packet CRYPTO fragmentation 2025-03-23 23:29:16 +03:00
bol-van
77df43b9cb nfqws: minor optimize 2025-03-22 13:03:31 +03:00
6 changed files with 114 additions and 41 deletions

View File

@@ -470,3 +470,4 @@ tpws: detect WSL 1 and warn about non-working options
v70.5 v70.5
nfqws: multiple --dpi-desync-fake-xxx nfqws: multiple --dpi-desync-fake-xxx
nfqws: support of inter-packet fragmented QUIC CRYPTO

View File

@@ -66,6 +66,9 @@ const uint8_t fake_tls_clienthello_default[648] = {
#define PKTDATA_MAXDUMP 32 #define PKTDATA_MAXDUMP 32
#define IP_MAXDUMP 80 #define IP_MAXDUMP 80
#define TCP_MAX_REASM 16384
#define UDP_MAX_REASM 16384
bool desync_valid_zero_stage(enum dpi_desync_mode mode) bool desync_valid_zero_stage(enum dpi_desync_mode mode)
{ {
return mode==DESYNC_SYNACK || mode==DESYNC_SYNDATA; return mode==DESYNC_SYNACK || mode==DESYNC_SYNDATA;
@@ -609,7 +612,7 @@ static uint16_t IP4_IP_ID_FIX(const struct ip *ip)
static bool runtime_tls_mod(int fake_n,const struct fake_tls_mod_cache *modcache, uint8_t fake_tls_mod, const uint8_t *fake_data, size_t fake_data_size, const uint8_t *payload, size_t payload_len, uint8_t *fake_mod) static bool runtime_tls_mod(int fake_n,const struct fake_tls_mod_cache *modcache, uint8_t fake_tls_mod, const uint8_t *fake_data, size_t fake_data_size, const uint8_t *payload, size_t payload_len, uint8_t *fake_mod)
{ {
bool b=false; bool b=false;
if (IsTLSClientHello(fake_data,fake_data_size,false)) if (modcache) // it's filled only if it's TLS
{ {
if (fake_tls_mod & FAKE_TLS_MOD_PADENCAP) if (fake_tls_mod & FAKE_TLS_MOD_PADENCAP)
{ {
@@ -954,7 +957,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint
!(ctrack->req_seq_finalized && seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end))) !(ctrack->req_seq_finalized && seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end)))
{ {
// do not reconstruct unexpected large payload (they are feeding garbage ?) // do not reconstruct unexpected large payload (they are feeding garbage ?)
if (!reasm_orig_start(ctrack,IPPROTO_TCP,TLSRecordLen(dis->data_payload),16384,dis->data_payload,dis->len_payload)) if (!reasm_orig_start(ctrack,IPPROTO_TCP,TLSRecordLen(dis->data_payload),TCP_MAX_REASM,dis->data_payload,dis->len_payload))
{ {
reasm_orig_cancel(ctrack); reasm_orig_cancel(ctrack);
return verdict; return verdict;
@@ -1300,7 +1303,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint
switch(l7proto) switch(l7proto)
{ {
case TLS: case TLS:
if ((fake_item->size <= sizeof(fake_data_buf)) && if ((fake_item->size <= sizeof(fake_data_buf)) &&
runtime_tls_mod(n,(struct fake_tls_mod_cache *)fake_item->extra, dp->fake_tls_mod, fake_item->data, fake_item->size, rdata_payload, rlen_payload, fake_data_buf)) runtime_tls_mod(n,(struct fake_tls_mod_cache *)fake_item->extra, dp->fake_tls_mod, fake_item->data, fake_item->size, rdata_payload, rlen_payload, fake_data_buf))
{ {
fake_data = fake_data_buf; fake_data = fake_data_buf;
@@ -1359,6 +1362,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint
(!multisplit_count && (dp->desync_mode2==DESYNC_MULTISPLIT || dp->desync_mode2==DESYNC_MULTIDISORDER)))) (!multisplit_count && (dp->desync_mode2==DESYNC_MULTISPLIT || dp->desync_mode2==DESYNC_MULTIDISORDER))))
{ {
reasm_orig_cancel(ctrack); reasm_orig_cancel(ctrack);
rdata_payload=NULL;
pkt1_len = sizeof(pkt1); pkt1_len = sizeof(pkt1);
if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,
@@ -1952,29 +1956,82 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint
return verdict; // cannot be first packet return verdict; // cannot be first packet
} }
} }
uint8_t defrag[UDP_MAX_REASM];
uint8_t defrag[16384];
size_t hello_offset, hello_len, defrag_len = sizeof(defrag); size_t hello_offset, hello_len, defrag_len = sizeof(defrag);
if (QUICDefragCrypto(pclean,clean_len,defrag,&defrag_len)) bool bFull;
if (QUICDefragCrypto(pclean,clean_len,defrag,&defrag_len,&bFull))
{ {
bool bIsHello = IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len); if (bFull)
bool bReqFull = bIsHello ? IsTLSHandshakeFull(defrag+hello_offset,hello_len) : false;
DLOG(bIsHello ? bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n" : "packet does not contain TLS ClientHello\n");
if (ctrack)
{ {
if (bIsHello && !bReqFull && ReasmIsEmpty(&ctrack->reasm_orig)) DLOG("QUIC initial contains CRYPTO with full fragment coverage\n");
bool bIsHello = IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len);
bool bReqFull = bIsHello ? IsTLSHandshakeFull(defrag+hello_offset,hello_len) : false;
DLOG(bIsHello ? bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n" : "packet does not contain TLS ClientHello\n");
if (ctrack)
{ {
// preallocate max buffer to avoid reallocs that cause memory copy if (bIsHello && !bReqFull && ReasmIsEmpty(&ctrack->reasm_orig))
if (!reasm_orig_start(ctrack,IPPROTO_UDP,16384,16384,clean,clean_len)) {
// preallocate max buffer to avoid reallocs that cause memory copy
if (!reasm_orig_start(ctrack,IPPROTO_UDP,UDP_MAX_REASM,UDP_MAX_REASM,clean,clean_len))
{
reasm_orig_cancel(ctrack);
return verdict;
}
}
if (!ReasmIsEmpty(&ctrack->reasm_orig))
{
verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6);
if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload))
{
DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed));
}
else
{
DLOG_ERR("rawpacket_queue failed !\n");
reasm_orig_cancel(ctrack);
return verdict;
}
if (bReqFull)
{
replay_queue(&ctrack->delayed);
reasm_orig_fin(ctrack);
}
return ct_new_postnat_fix_udp(ctrack, dis->ip, dis->ip6, dis->udp, &dis->len_pkt);
}
}
if (bIsHello)
{
bHaveHost = TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, sizeof(host), TLS_PARTIALS_ENABLE);
if (!bHaveHost && dp->desync_skip_nosni)
{ {
reasm_orig_cancel(ctrack); reasm_orig_cancel(ctrack);
DLOG("not applying tampering to QUIC ClientHello without hostname in the SNI\n");
return verdict; return verdict;
} }
} }
if (!ReasmIsEmpty(&ctrack->reasm_orig)) else
{ {
if (!quic_reasm_cancel(ctrack,"QUIC initial without ClientHello")) return verdict;
}
}
else
{
DLOG("QUIC initial contains CRYPTO with partial fragment coverage\n");
if (ctrack)
{
if (ReasmIsEmpty(&ctrack->reasm_orig))
{
// preallocate max buffer to avoid reallocs that cause memory copy
if (!reasm_orig_start(ctrack,IPPROTO_UDP,UDP_MAX_REASM,UDP_MAX_REASM,clean,clean_len))
{
reasm_orig_cancel(ctrack);
return verdict;
}
}
verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6);
if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload)) if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload))
{ {
@@ -1986,28 +2043,9 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint
reasm_orig_cancel(ctrack); reasm_orig_cancel(ctrack);
return verdict; return verdict;
} }
if (bReqFull)
{
replay_queue(&ctrack->delayed);
reasm_orig_fin(ctrack);
}
return ct_new_postnat_fix_udp(ctrack, dis->ip, dis->ip6, dis->udp, &dis->len_pkt); return ct_new_postnat_fix_udp(ctrack, dis->ip, dis->ip6, dis->udp, &dis->len_pkt);
} }
} if (!quic_reasm_cancel(ctrack,"QUIC initial fragmented CRYPTO")) return verdict;
if (bIsHello)
{
bHaveHost = TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, sizeof(host), TLS_PARTIALS_ENABLE);
if (!bHaveHost && dp->desync_skip_nosni)
{
reasm_orig_cancel(ctrack);
DLOG("not applying tampering to QUIC ClientHello without hostname in the SNI\n");
return verdict;
}
}
else
{
if (!quic_reasm_cancel(ctrack,"QUIC initial without ClientHello")) return verdict;
} }
} }
else else
@@ -2026,7 +2064,7 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint
{ {
// received payload without host. it means we are out of the request retransmission phase. stop counter // received payload without host. it means we are out of the request retransmission phase. stop counter
ctrack_stop_retrans_counter(ctrack); ctrack_stop_retrans_counter(ctrack);
reasm_orig_cancel(ctrack); reasm_orig_cancel(ctrack);
if (IsWireguardHandshakeInitiation(dis->data_payload,dis->len_payload)) if (IsWireguardHandshakeInitiation(dis->data_payload,dis->len_payload))

View File

@@ -844,7 +844,16 @@ bool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, si
return !memcmp(data + pn_offset + pkn_len + cryptlen, atag, 16); return !memcmp(data + pn_offset + pkn_len + cryptlen, atag, 16);
} }
bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len) struct range64
{
uint64_t offset,len;
};
#define MAX_DEFRAG_PIECES 128
static int cmp_range64(const void * a, const void * b)
{
return (((struct range64*)a)->offset < ((struct range64*)b)->offset) ? -1 : (((struct range64*)a)->offset > ((struct range64*)b)->offset) ? 1 : 0;
}
bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len, bool *bFull)
{ {
// Crypto frame can be split into multiple chunks // Crypto frame can be split into multiple chunks
// chromium randomly splits it and pads with zero/one bytes to force support the standard // chromium randomly splits it and pads with zero/one bytes to force support the standard
@@ -853,13 +862,15 @@ bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,siz
if (*defrag_len<10) return false; if (*defrag_len<10) return false;
uint8_t *defrag_data = defrag+10; uint8_t *defrag_data = defrag+10;
size_t defrag_data_len = *defrag_len-10; size_t defrag_data_len = *defrag_len-10;
uint8_t ft; uint8_t ft;
uint64_t offset,sz,szmax=0,zeropos=0,pos=0; uint64_t offset,sz,szmax=0,zeropos=0,pos=0;
bool found=false; bool found=false;
struct range64 ranges[MAX_DEFRAG_PIECES];
int i,range=0;
while(pos<clean_len) while(pos<clean_len)
{ {
// frame type
ft = clean[pos]; ft = clean[pos];
pos++; pos++;
if (ft>1) // 00 - padding, 01 - ping if (ft>1) // 00 - padding, 01 - ping
@@ -867,6 +878,7 @@ bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,siz
if (ft!=6) return false; // dont want to know all possible frame type formats if (ft!=6) return false; // dont want to know all possible frame type formats
if (pos>=clean_len) return false; if (pos>=clean_len) return false;
if (range>=MAX_DEFRAG_PIECES) return false;
if ((pos+tvb_get_size(clean[pos])>=clean_len)) return false; if ((pos+tvb_get_size(clean[pos])>=clean_len)) return false;
pos += tvb_get_varint(clean+pos, &offset); pos += tvb_get_varint(clean+pos, &offset);
@@ -875,7 +887,7 @@ bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,siz
pos += tvb_get_varint(clean+pos, &sz); pos += tvb_get_varint(clean+pos, &sz);
if ((pos+sz)>clean_len) return false; if ((pos+sz)>clean_len) return false;
if ((offset+sz)>defrag_data_len) return false; if ((offset+sz)>defrag_data_len) return false; // defrag buf overflow
if (zeropos < offset) if (zeropos < offset)
// make sure no uninitialized gaps exist in case of not full fragment coverage // make sure no uninitialized gaps exist in case of not full fragment coverage
memset(defrag_data+zeropos,0,offset-zeropos); memset(defrag_data+zeropos,0,offset-zeropos);
@@ -886,6 +898,10 @@ bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,siz
found=true; found=true;
pos+=sz; pos+=sz;
ranges[range].offset = offset;
ranges[range].len = sz;
range++;
} }
} }
if (found) if (found)
@@ -897,6 +913,23 @@ bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,siz
phton64(defrag+2,szmax); phton64(defrag+2,szmax);
defrag[2] |= 0xC0; // 64 bit value defrag[2] |= 0xC0; // 64 bit value
*defrag_len = (size_t)(szmax+10); *defrag_len = (size_t)(szmax+10);
qsort(ranges, range, sizeof(*ranges), cmp_range64);
//for(i=0 ; i<range ; i++)
// printf("RANGE %zu len %zu\n",ranges[i].offset,ranges[i].len);
for(i=0,offset=0,*bFull=true ; i<range ; i++)
{
if (ranges[i].offset!=offset)
{
*bFull = false;
break;
}
offset += ranges[i].len;
}
//printf("bFull=%u\n",*bFull);
} }
return found; return found;
} }

View File

@@ -87,5 +87,6 @@ uint8_t QUICDraftVersion(uint32_t version);
bool QUICExtractDCID(const uint8_t *data, size_t len, quic_cid_t *cid); bool QUICExtractDCID(const uint8_t *data, size_t len, quic_cid_t *cid);
bool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, size_t *clean_len); bool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, size_t *clean_len);
bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len); // returns true if crypto frames were found . bFull = true if crypto frame fragments have full coverage
bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len, bool *bFull);
//bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello); //bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello);