Browse Source

Implement altsni functionality for random SNI rotation in TLS fakes

pull/1997/head
Semyon Saprykin 5 months ago
parent
commit
b5a5ff2f4f
  1. 82
      nfq/desync.c
  2. 183
      nfq/nfqws.c
  3. 12
      nfq/params.c
  4. 10
      nfq/params.h

82
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;
}
}

183
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]@<filename>|0xHEX\t\t; fake http request\n"
" --dpi-desync-fake-tls=[+ofs]@<filename>|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=<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=<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]@<filename>|0xHEX\t; unknown protocol fake payload\n"
" --dpi-desync-fake-syndata=[+ofs]@<filename>|0xHEX\t; SYN data payload\n"
" --dpi-desync-fake-quic=[+ofs]@<filename>|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;
}
}

12
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
}
}

10
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);
Loading…
Cancel
Save