Add support for bruteforce mode of parsing SNI from Client Hello.

This commit is contained in:
Vadim Vetrov 2024-08-15 02:31:48 +03:00
parent 7f340fb033
commit 044801efb9
No known key found for this signature in database
GPG Key ID: E8A308689D7A73A5
5 changed files with 109 additions and 3 deletions

View File

@ -142,6 +142,8 @@ Available flags:
- `--fk-winsize=<winsize>` Specifies window size for the fragmented TCP packet. Applicable if you want for response to be fragmented. May slowdown connection initialization.
- `--sni-detection={parse|brute}` Specifies how to detect SNI. Parse will normally detect it by parsing the Client Hello message. Brute will go through the entire message and check possibility of SNI occurrence. Please note, that when `--sni-domains` option is not all brute will be O(nm) time complexity where n stands for length of the message and m is number of domains. Defaults to parse.
- `--seg2delay=<delay>` This flag forces **youtubeUnblock** to wait a little bit before send the 2nd part of the split packet.
- `--silent` Disables verbose mode.

19
args.c
View File

@ -19,6 +19,8 @@ struct config_t config = {
.fake_sni = 1,
.fake_sni_seq_len = 1,
.sni_detection = SNI_DETECTION_PARSE,
#ifdef SEG2_DELAY
.seg2_delay = SEG2_DELAY,
#else
@ -56,6 +58,7 @@ struct config_t config = {
#define OPT_FK_WINSIZE 14
#define OPT_TRACE 15
#define OPT_QUIC_DROP 16
#define OPT_SNI_DETECTION 17
#define OPT_SEG2DELAY 5
#define OPT_THREADS 6
#define OPT_SILENT 7
@ -77,6 +80,7 @@ static struct option long_opt[] = {
{"frag-sni-faked", 1, 0, OPT_FRAG_SNI_FAKED},
{"fk-winsize", 1, 0, OPT_FK_WINSIZE},
{"quic-drop", 0, 0, OPT_QUIC_DROP},
{"sni-detection", 1, 0, OPT_SNI_DETECTION},
{"seg2delay", 1, 0, OPT_SEG2DELAY},
{"threads", 1, 0, OPT_THREADS},
{"silent", 0, 0, OPT_SILENT},
@ -126,6 +130,7 @@ void print_usage(const char *argv0) {
printf("\t--frag-sni-faked={0|1}\n");
printf("\t--fk-winsize=<winsize>\n");
printf("\t--quic-drop\n");
printf("\t--sni-detection={parse|brute}\n");
printf("\t--seg2delay=<delay>\n");
printf("\t--threads=<threads number>\n");
printf("\t--silent\n");
@ -166,6 +171,16 @@ int parse_args(int argc, char *argv[]) {
config.domains_str = optarg;
config.domains_strlen = strlen(config.domains_str);
break;
case OPT_SNI_DETECTION:
if (strcmp(optarg, "parse") == 0) {
config.sni_detection = SNI_DETECTION_PARSE;
} else if (strcmp(optarg, "brute") == 0) {
config.sni_detection = SNI_DETECTION_BRUTE;
} else {
goto invalid_opt;
}
break;
case OPT_FRAG:
if (strcmp(optarg, "tcp") == 0) {
@ -346,6 +361,10 @@ void print_welcome() {
printf("All QUIC packets will be dropped\n");
}
if (config.sni_detection == SNI_DETECTION_BRUTE) {
printf("Server Name Extension will be parsed in the bruteforce mode\n");
}
if (config.all_domains) {
printf("All Client Hello will be targetted by youtubeUnblock!\n");
}

View File

@ -30,6 +30,9 @@ struct config_t {
#define VERBOSE_TRACE 2
int verbose;
int quic_drop;
#define SNI_DETECTION_PARSE 0
#define SNI_DETECTION_BRUTE 1
int sni_detection;
/* In milliseconds */
unsigned int seg2_delay;
const char *domains_str;
@ -89,7 +92,7 @@ extern struct config_t config;
// The Maximum Transmission Unit size for rawsocket
// Larger packets will be fragmented. Applicable for Chrome's kyber.
#define AVAILABLE_MTU 1384
#define AVAILABLE_MTU 1500
#define DEFAULT_QUEUE_NUM 537

View File

@ -428,7 +428,25 @@ int post_fake_sni(const struct iphdr *iph, unsigned int iph_len,
return 0;
}
void z_function(const char *str, int *zbuf, size_t len) {
zbuf[0] = len;
ssize_t lh = 0, rh = 1;
for (ssize_t i = 1; i < len; i++) {
zbuf[i] = 0;
if (i < rh) {
zbuf[i] = min(zbuf[i - lh], rh - i);
}
while (i + zbuf[i] < len && str[zbuf[i]] == str[i + zbuf[i]])
zbuf[i]++;
if (i + zbuf[i] > rh) {
lh = i;
rh = i + zbuf[i];
}
}
}
#define TLS_CONTENT_TYPE_HANDSHAKE 0x16
#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01
@ -463,12 +481,16 @@ struct tls_verdict analyze_tls_data(
uint16_t message_length = ntohs(*(uint16_t *)(msgData + 3));
const uint8_t *message_length_ptr = msgData + 3;
if (tls_vmajor != 0x03) goto nextMessage;
if (i + 5 + message_length > dlen) break;
if (i + 5 > dlen) break;
if (tls_content_type != TLS_CONTENT_TYPE_HANDSHAKE)
goto nextMessage;
if (config.sni_detection == SNI_DETECTION_BRUTE) {
goto brute;
}
const uint8_t *handshakeProto = msgData + 5;
@ -506,7 +528,7 @@ struct tls_verdict analyze_tls_data(
const uint8_t *extensionsPtr = msgPtr;
const uint8_t *extensions_end = extensionsPtr + extensionsLen;
if (extensions_end > data_end) break;
if (extensions_end > data_end) extensions_end = data_end;
while (extensionsPtr < extensions_end) {
const uint8_t *extensionPtr = extensionsPtr;
@ -590,8 +612,54 @@ nextMessage:
out:
return vrd;
brute:
if (config.all_domains) {
vrd.target_sni = 1;
vrd.sni_len = 0;
vrd.sni_offset = dlen / 2;
goto out;
}
unsigned int j = 0;
for (unsigned int i = 0; i <= config.domains_strlen; i++) {
if ( i > j &&
(i == config.domains_strlen ||
config.domains_str[i] == '\0' ||
config.domains_str[i] == ',' ||
config.domains_str[i] == '\n' )) {
uint8_t buf[MAX_PACKET_SIZE];
int zbuf[MAX_PACKET_SIZE];
unsigned int domain_len = (i - j);
const char *domain_startp = config.domains_str + j;
if (domain_len + dlen + 1> MAX_PACKET_SIZE) continue;
memcpy(buf, domain_startp, domain_len);
memcpy(buf + domain_len, "#", 1);
memcpy(buf + domain_len + 1, data, dlen);
z_function((char *)buf, zbuf, domain_len + 1 + dlen);
for (unsigned int k = 0; k < dlen; k++) {
if (zbuf[k] == domain_len) {
vrd.target_sni = 1;
vrd.sni_len = domain_len;
vrd.sni_offset = (k - domain_len - 1);
goto out;
}
}
j = i + 1;
}
}
goto out;
}
int gen_fake_sni(const struct iphdr *iph, const struct tcphdr *tcph,
uint8_t *buf, uint32_t *buflen) {

14
types.h
View File

@ -44,4 +44,18 @@ typedef __i64 int64_t;
#include <netinet/udp.h> // IWYU pragma: export
#endif
#define max(a,b)__extension__\
({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; \
})
#define min(a,b)__extension__\
({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; \
})
#endif /* TYPES_H */