diff --git a/nfq/desync.c b/nfq/desync.c index 0fdccd15..09f374bf 100644 --- a/nfq/desync.c +++ b/nfq/desync.c @@ -878,11 +878,12 @@ static uint16_t IP4_IP_ID_ADD(uint16_t ip_id, uint16_t inc, t_ip_id_mode mode) // fake_mod buffer must at least sizeof(desync_profile->fake_tls) -// size does not change // return : true - altered, false - not altered -static bool runtime_tls_mod(int fake_n, const struct fake_tls_mod_cache *modcache, const struct fake_tls_mod *tls_mod, const uint8_t *fake_data, size_t fake_data_size, const uint8_t *payload, size_t payload_len, uint8_t *fake_mod) +// fake_data_size_out receives the new size if altered +static bool runtime_tls_mod(int fake_n, const struct fake_tls_mod_cache *modcache, const struct fake_tls_mod *tls_mod, const uint8_t *fake_data, size_t fake_data_size, size_t fake_mod_buf_size, const uint8_t *payload, size_t payload_len, uint8_t *fake_mod, size_t *fake_data_size_out) { bool b = false; + *fake_data_size_out = fake_data_size; if (modcache) // it's filled only if it's TLS { if (tls_mod->mod & FAKE_TLS_MOD_PADENCAP) @@ -928,6 +929,74 @@ static bool runtime_tls_mod(int fake_n, const struct fake_tls_mod_cache *modcach DLOG("fake[%d] applied dupsid tls mod\n", fake_n); } } + // Apply altsni - randomly select domain from pool + if (tls_mod->mod & FAKE_TLS_MOD_ALTSNI) + { + if (!tls_mod->altsni || !tls_mod->altsni->count) + DLOG("fake[%d] altsni set but no domains loaded\n", fake_n); + else + { + const uint8_t *ext; + size_t extlen, slen; + const uint8_t *current_data = b ? fake_mod : fake_data; + size_t current_size = *fake_data_size_out; + + if (!TLSFindExt(current_data, current_size, 0, &ext, &extlen, false)) + { + DLOG("fake[%d] altsni: cannot find SNI extension\n", fake_n); + } + else + { + size_t sniext_offset = ext - current_data; + if (!TLSAdvanceToHostInSNI(&ext, &extlen, &slen)) + { + DLOG("fake[%d] altsni: invalid SNI structure\n", fake_n); + } + else + { + const char *random_sni = tls_mod->altsni->domains[random() % tls_mod->altsni->count]; + size_t slen_new = strlen(random_sni); + ssize_t slen_delta = (ssize_t)slen_new - (ssize_t)slen; + size_t sni_offset = ext - current_data; + size_t new_size = (slen_delta >= 0) ? current_size + (size_t)slen_delta : current_size - (size_t)(-slen_delta); + + if (new_size > fake_mod_buf_size) + { + DLOG("fake[%d] not enough space for altsni (need %zu, have %zu)\n", fake_n, new_size, fake_mod_buf_size); + } + else + { + if (!b) memcpy(fake_mod, fake_data, fake_data_size); + + uint8_t *sniext = fake_mod + sniext_offset; + uint8_t *sni = fake_mod + sni_offset; + + if (slen_delta) + { + memmove(sni + slen_new, sni + slen, fake_mod + current_size - (sni + slen)); + // Update TLS record length + phton16(fake_mod + 3, (uint16_t)(pntoh16(fake_mod + 3) + slen_delta)); + // Update handshake length + phton24(fake_mod + 6, (uint32_t)(pntoh24(fake_mod + 6) + slen_delta)); + // Update extensions length + if (modcache->extlen_offset) + phton16(fake_mod + modcache->extlen_offset, (uint16_t)(pntoh16(fake_mod + modcache->extlen_offset) + slen_delta)); + // Update SNI extension length (ext type 2 bytes before data) + phton16(sniext - 2, (uint16_t)(pntoh16(sniext - 2) + slen_delta)); + // Update SNI list length + phton16(sniext, (uint16_t)(pntoh16(sniext) + slen_delta)); + // Update hostname length (2 bytes before hostname) + phton16(sni - 2, (uint16_t)(pntoh16(sni - 2) + slen_delta)); + *fake_data_size_out = new_size; + } + memcpy(sni, random_sni, slen_new); + b = true; + DLOG("fake[%d] applied altsni: %s (size_delta=%zd)\n", fake_n, random_sni, slen_delta); + } + } + } + } + } } return b; } @@ -1963,7 +2032,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint { struct blob_item *fake_item; - size_t fake_size; + size_t fake_size, fake_size_mod; uint8_t *fake_data; uint8_t fake_data_buf[FAKE_MAX_TCP]; int n = 0; @@ -1973,11 +2042,12 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint LIST_FOREACH(fake_item, fake, next) { n++; + fake_size_mod = fake_item->size; switch (l7proto) { case TLS: if ((fake_item->size <= sizeof(fake_data_buf)) && - runtime_tls_mod(n, (struct fake_tls_mod_cache *)fake_item->extra, (struct fake_tls_mod *)fake_item->extra2, 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, (struct fake_tls_mod *)fake_item->extra2, fake_item->data, fake_item->size, sizeof(fake_data_buf), rdata_payload, rlen_payload, fake_data_buf, &fake_size_mod)) { fake_data = fake_data_buf; break; @@ -1986,7 +2056,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint fake_data = fake_item->data; } fake_data += fake_item->offset; - fake_size = fake_item->size - fake_item->offset; + fake_size = fake_size_mod - fake_item->offset; pkt1_len = sizeof(pkt1); if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, htonl(sequence), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, @@ -3501,4 +3571,4 @@ static bool replay_queue(struct rawpacket_tailhead *q) } } return b; -} +} \ No newline at end of file diff --git a/nfq/nfqws.c b/nfq/nfqws.c index 5eaa48b7..71dbe49b 100644 --- a/nfq/nfqws.c +++ b/nfq/nfqws.c @@ -59,6 +59,126 @@ static bool bReload = false; bool bQuit = false; #endif +// altsni pool management functions +static struct altsni_pool* altsni_pool_create(void) +{ + struct altsni_pool *pool = malloc(sizeof(struct altsni_pool)); + if (!pool) return NULL; + pool->domains = NULL; + pool->count = 0; + pool->capacity = 0; + return pool; +} + +static bool altsni_pool_add(struct altsni_pool *pool, const char *domain) +{ + if (!pool || !domain || !*domain) return false; + + // Check domain length (max 127 to fit in sni field) + size_t len = strlen(domain); + if (len > 127) { + DLOG_ERR("altsni domain too long: %s\n", domain); + return false; + } + + // Expand array if needed + if (pool->count >= pool->capacity) { + size_t new_cap = pool->capacity ? pool->capacity * 2 : 16; + char **new_domains = realloc(pool->domains, new_cap * sizeof(char*)); + if (!new_domains) return false; + pool->domains = new_domains; + pool->capacity = new_cap; + } + + pool->domains[pool->count] = strdup(domain); + if (!pool->domains[pool->count]) return false; + pool->count++; + return true; +} + +static void altsni_pool_destroy(struct altsni_pool *pool) +{ + if (!pool) return; + for (size_t i = 0; i < pool->count; i++) + free(pool->domains[i]); + free(pool->domains); + free(pool); +} + +static const char* altsni_pool_get_random(const struct altsni_pool *pool) +{ + if (!pool || !pool->count) return NULL; + return pool->domains[random() % pool->count]; +} + +// Parse comma-separated domain list +static bool parse_altsni_domains(const char *opt, struct altsni_pool *pool) +{ + char *copy = strdup(opt); + if (!copy) return false; + + char *saveptr; + char *token = strtok_r(copy, ",", &saveptr); + while (token) { + // Skip leading spaces + while (*token == ' ') token++; + char *end = token + strlen(token) - 1; + while (end > token && *end == ' ') *end-- = 0; + + if (*token && !altsni_pool_add(pool, token)) { + free(copy); + return false; + } + token = strtok_r(NULL, ",", &saveptr); + } + + free(copy); + return pool->count > 0; +} + +// Load domains from file +static bool load_altsni_file(const char *filename, struct altsni_pool *pool) +{ + FILE *f = fopen(filename, "r"); + if (!f) { + DLOG_PERROR("fopen altsni file"); + return false; + } + + char line[256]; + int line_num = 0; + while (fgets(line, sizeof(line), f)) { + line_num++; + + // Remove newline + char *nl = strchr(line, '\n'); + if (nl) *nl = 0; + char *cr = strchr(line, '\r'); + if (cr) *cr = 0; + + // Skip leading whitespace + char *p = line; + while (*p == ' ' || *p == '\t') p++; + + // Skip empty lines and comments + if (!*p || *p == '#') continue; + + // Remove trailing whitespace + char *end = p + strlen(p) - 1; + while (end > p && (*end == ' ' || *end == '\t')) *end-- = 0; + + if (!altsni_pool_add(pool, p)) { + DLOG_ERR("altsni file %s line %d: failed to add domain\n", filename, line_num); + fclose(f); + return false; + } + } + + fclose(f); + DLOG_CONDUP("Loaded %zu altsni domains from %s\n", pool->count, filename); + return pool->count > 0; +} + static void onhup(int sig) { printf("HUP received ! Lists will be reloaded.\n"); @@ -1111,6 +1231,8 @@ static bool parse_tlsmod_list(char *opt, struct fake_tls_mod *tls_mod) strncpy(tls_mod->sni, e2 + 1, sizeof(tls_mod->sni) - 1); tls_mod->sni[sizeof(tls_mod->sni) - 1 - 1] = 0; } + else if (!strcmp(p, "altsni")) + tls_mod->mod |= FAKE_TLS_MOD_ALTSNI; else if (!strcmp(p, "padencap")) tls_mod->mod |= FAKE_TLS_MOD_PADENCAP; else if (!strcmp(p, "dupsid")) @@ -1395,7 +1517,7 @@ static bool onetime_tls_mod_blob(int profile_n, int fake_n, const struct fake_tl size_t extlen; modcache->extlen_offset = modcache->padlen_offset = 0; - if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI | FAKE_TLS_MOD_PADENCAP)) + if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI | FAKE_TLS_MOD_ALTSNI | FAKE_TLS_MOD_PADENCAP)) { if (!TLSFindExtLen(fake_tls, *fake_tls_size, &modcache->extlen_offset)) { @@ -1403,7 +1525,7 @@ static bool onetime_tls_mod_blob(int profile_n, int fake_n, const struct fake_tl return false; } DLOG("profile %d fake[%d] tls extensions length offset : %zu\n", profile_n, fake_n, modcache->extlen_offset); - if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI)) + if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI | FAKE_TLS_MOD_ALTSNI)) { size_t slen; if (!TLSFindExt(fake_tls, *fake_tls_size, 0, &ext, &extlen, false)) @@ -1540,6 +1662,13 @@ static bool onetime_tls_mod(struct desync_profile *dp) if (!(tls_mod->mod & ~FAKE_TLS_MOD_SAVE_MASK)) continue; + // Validate altsni: mod is set but no domains provided + if ((tls_mod->mod & FAKE_TLS_MOD_ALTSNI) && (!tls_mod->altsni || !tls_mod->altsni->count)) + { + DLOG_ERR("profile %d fake[%d] altsni mod set but no domains provided. Use --dpi-desync-fake-tls-altsni\n", dp->n, n); + return false; + } + if (!IsTLSClientHello(fake_tls->data, fake_tls->size, false) || (fake_tls->size < (44 + fake_tls->data[43]))) // has session id ? { DLOG("profile %d fake[%d] tls mod set but tls fake structure invalid.\n", dp->n, n); @@ -1892,7 +2021,8 @@ static void exithelp(void) " --dpi-desync-fake-tcp-mod=mod[,mod]\t\t\t; comma separated list of tcp fake mods. available mods : none,seq\n" " --dpi-desync-fake-http=[+ofs]@|0xHEX\t\t; fake http request\n" " --dpi-desync-fake-tls=[+ofs]@|0xHEX|![+offset] ; fake TLS ClientHello (for https)\n" - " --dpi-desync-fake-tls-mod=mod[,mod]\t\t\t; comma separated list of TLS fake mods. available mods : none,rnd,rndsni,sni=,dupsid,padencap\n" + " --dpi-desync-fake-tls-mod=mod[,mod]\t\t\t; comma separated list of TLS fake mods. available mods : none,rnd,rndsni,sni=,altsni,dupsid,padencap\n" + " --dpi-desync-fake-tls-altsni=dom1,dom2|@file\t\t; domains for altsni mod. @ prefix loads from file (one domain per line, # for comments)\n" " --dpi-desync-fake-unknown=[+ofs]@|0xHEX\t; unknown protocol fake payload\n" " --dpi-desync-fake-syndata=[+ofs]@|0xHEX\t; SYN data payload\n" " --dpi-desync-fake-quic=[+ofs]@|0xHEX\t\t; fake QUIC Initial\n" @@ -2084,6 +2214,7 @@ enum opt_indices { IDX_DPI_DESYNC_FAKE_HTTP, IDX_DPI_DESYNC_FAKE_TLS, IDX_DPI_DESYNC_FAKE_TLS_MOD, + IDX_DPI_DESYNC_FAKE_TLS_ALTSNI, IDX_DPI_DESYNC_FAKE_UNKNOWN, IDX_DPI_DESYNC_FAKE_SYNDATA, IDX_DPI_DESYNC_FAKE_QUIC, @@ -2225,6 +2356,7 @@ static const struct option long_options[] = { [IDX_DPI_DESYNC_FAKE_HTTP] = {"dpi-desync-fake-http", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_TLS] = {"dpi-desync-fake-tls", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_TLS_MOD] = {"dpi-desync-fake-tls-mod", required_argument, 0, 0}, + [IDX_DPI_DESYNC_FAKE_TLS_ALTSNI] = {"dpi-desync-fake-tls-altsni", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_UNKNOWN] = {"dpi-desync-fake-unknown", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_SYNDATA] = {"dpi-desync-fake-syndata", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_QUIC] = {"dpi-desync-fake-quic", required_argument, 0, 0}, @@ -3042,6 +3174,49 @@ int main(int argc, char **argv) if (dp->tls_fake_last) *(struct fake_tls_mod*)dp->tls_fake_last->extra2 = dp->tls_mod_last; break; + case IDX_DPI_DESYNC_FAKE_TLS_ALTSNI: + { + // Create pool if not already created + if (!dp->tls_mod_last.altsni) + { + dp->tls_mod_last.altsni = altsni_pool_create(); + if (!dp->tls_mod_last.altsni) + { + DLOG_ERR("out of memory for altsni pool\n"); + exit_clean(1); + } + } + + // Determine: file or comma-separated list + if (optarg[0] == '@') + { + // Load from file + if (!load_altsni_file(optarg + 1, dp->tls_mod_last.altsni)) + { + DLOG_ERR("Failed to load altsni file: %s\n", optarg + 1); + exit_clean(1); + } + } + else + { + // Parse comma-separated list + if (!parse_altsni_domains(optarg, dp->tls_mod_last.altsni)) + { + DLOG_ERR("Failed to parse altsni list: %s\n", optarg); + exit_clean(1); + } + } + DLOG("Loaded %zu altsni domains\n", dp->tls_mod_last.altsni->count); + + // Update tls_fake_last if it exists + if (dp->tls_fake_last) + { + struct fake_tls_mod *tls_mod = (struct fake_tls_mod*)dp->tls_fake_last->extra2; + if (tls_mod) + tls_mod->altsni = dp->tls_mod_last.altsni; + } + } + break; case IDX_DPI_DESYNC_FAKE_UNKNOWN: load_blob_to_collection(optarg, &dp->fake_unknown, FAKE_MAX_TCP, 0); break; @@ -3640,4 +3815,4 @@ ex: exiterr: result = 1; goto ex; -} +} \ No newline at end of file diff --git a/nfq/params.c b/nfq/params.c index ec8d4b92..58630dca 100644 --- a/nfq/params.c +++ b/nfq/params.c @@ -314,6 +314,16 @@ static void dp_clear_dynamic(struct desync_profile *dp) strlist_destroy(&dp->filter_ssid); #endif HostFailPoolDestroy(&dp->hostlist_auto_fail_counters); + // Clean up altsni pool if present + if (dp->tls_mod_last.altsni) + { + struct altsni_pool *pool = dp->tls_mod_last.altsni; + for (size_t i = 0; i < pool->count; i++) + free(pool->domains[i]); + free(pool->domains); + free(pool); + dp->tls_mod_last.altsni = NULL; + } struct blob_collection_head **fake,*fakes[] = {&dp->fake_http, &dp->fake_tls, &dp->fake_unknown, &dp->fake_unknown_udp, &dp->fake_quic, &dp->fake_wg, &dp->fake_dht, &dp->fake_discord, &dp->fake_stun, NULL}; for(fake=fakes;*fake;fake++) blob_collection_destroy(*fake); } @@ -382,4 +392,4 @@ void cleanup_params(struct params_s *params) #else free(params->user); params->user=NULL; #endif -} +} \ No newline at end of file diff --git a/nfq/params.h b/nfq/params.h index 0d30055c..9f3cfb23 100644 --- a/nfq/params.h +++ b/nfq/params.h @@ -62,6 +62,7 @@ #define FAKE_TLS_MOD_RND_SNI 0x40 #define FAKE_TLS_MOD_SNI 0x80 #define FAKE_TLS_MOD_PADENCAP 0x100 +#define FAKE_TLS_MOD_ALTSNI 0x200 #define FAKE_MAX_TCP 1460 #define FAKE_MAX_UDP 1472 @@ -76,9 +77,16 @@ struct fake_tls_mod_cache { size_t extlen_offset, padlen_offset; }; +struct altsni_pool +{ + char **domains; // array of domain strings + size_t count; // number of domains + size_t capacity; // allocated capacity +}; struct fake_tls_mod { char sni[128]; + struct altsni_pool *altsni; // pool of domains for random SNI selection uint32_t mod; }; struct hostfakesplit_mod @@ -272,4 +280,4 @@ int DLOG_ERR(const char *format, ...); int DLOG_PERROR(const char *s); int DLOG_CONDUP(const char *format, ...); int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...); -void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); +void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); \ No newline at end of file