diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/ndpi_analyze.c | 39 | ||||
-rw-r--r-- | src/lib/ndpi_main.c | 9 | ||||
-rw-r--r-- | src/lib/protocols/http.c | 22 | ||||
-rw-r--r-- | src/lib/protocols/tls.c | 369 |
4 files changed, 430 insertions, 9 deletions
diff --git a/src/lib/ndpi_analyze.c b/src/lib/ndpi_analyze.c index c6132e429..176f4eeef 100644 --- a/src/lib/ndpi_analyze.c +++ b/src/lib/ndpi_analyze.c @@ -2141,3 +2141,42 @@ void ndpi_free_knn(ndpi_knn knn) { free_knn(knn, knn.n_samples); } void ndpi_free_btree(ndpi_btree *b) { free_tree((t_btree*)b); } +/* ********************************************************************************* */ + +/* It provides the Mahalanobis distance (https://en.wikipedia.org/wiki/Mahalanobis_distance) + between a point x and a distribution with mean u and inverted covariant matrix i_s. + Parameters: + x: input array (with dimension "size") + u: means array (with dimension "size") + i_s: inverted covariant matrix (with dimension "size" * "size") + + Bottom line: distance = sqrt([x - u] * [i_s] * [x - u]^T) +*/ +float ndpi_mahalanobis_distance(const u_int32_t *x, u_int32_t size, const float *u, const float *i_s) +{ + float md = 0; + float *diff; /* [x - u] */ + float *tmp; /* Result of [x - u] * [i_s] */ + u_int32_t i, j; + + /* Could we get rid of these allocations? */ + diff = ndpi_calloc(sizeof(float), size); + tmp = ndpi_calloc(sizeof(float), size); + if(diff && tmp) { + for (i = 0; i < size; i++) + diff[i] = x[i] - u[i]; + + /* Naive implementation of matrix multiplication(s) */ + for(i = 0; i < size; i++) { + for(j = 0; j < size; j++) { + tmp[i] += diff[j] * i_s[size * j + i]; + } + } + for(i = 0; i < size; i++) + md += tmp[i] * diff[i]; + } + ndpi_free(diff); + ndpi_free(tmp); + + return sqrt(md); +} diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c index b3b70de5a..e70fb87ec 100644 --- a/src/lib/ndpi_main.c +++ b/src/lib/ndpi_main.c @@ -198,7 +198,7 @@ static ndpi_risk_info ndpi_known_risks[] = { { NDPI_MALWARE_HOST_CONTACTED, NDPI_RISK_SEVERE, CLIENT_HIGH_RISK_PERCENTAGE, NDPI_CLIENT_ACCOUNTABLE }, { NDPI_BINARY_DATA_TRANSFER, NDPI_RISK_MEDIUM, CLIENT_FAIR_RISK_PERCENTAGE, NDPI_CLIENT_ACCOUNTABLE }, { NDPI_PROBING_ATTEMPT, NDPI_RISK_MEDIUM, CLIENT_FAIR_RISK_PERCENTAGE, NDPI_CLIENT_ACCOUNTABLE }, - { NDPI_OBFUSCATED_TRAFFIC, NDPI_RISK_HIGH, CLIENT_HIGH_RISK_PERCENTAGE, NDPI_BOTH_ACCOUNTABLE }, + { NDPI_OBFUSCATED_TRAFFIC, NDPI_RISK_HIGH, CLIENT_HIGH_RISK_PERCENTAGE, NDPI_BOTH_ACCOUNTABLE }, /* Leave this as last member */ { NDPI_MAX_RISK, NDPI_RISK_LOW, CLIENT_FAIR_RISK_PERCENTAGE, NDPI_NO_ACCOUNTABILITY } @@ -6769,6 +6769,9 @@ void ndpi_free_flow_data(struct ndpi_flow_struct* flow) { if(flow->flow_payload != NULL) ndpi_free(flow->flow_payload); + + if(flow->tls_quic.obfuscated_heur_state) + ndpi_free(flow->tls_quic.obfuscated_heur_state); } } @@ -8320,6 +8323,7 @@ static void ndpi_reset_packet_line_info(struct ndpi_packet_struct *packet) { packet->server_line.len = 0, packet->http_method.ptr = NULL, packet->http_method.len = 0, packet->http_response.ptr = NULL, packet->http_response.len = 0, packet->forwarded_line.ptr = NULL, packet->forwarded_line.len = 0; + packet->upgrade_line.ptr = NULL, packet->upgrade_line.len = 0; } /* ********************************************************************************* */ @@ -9026,6 +9030,7 @@ static void parse_single_packet_line(struct ndpi_detection_module_struct *ndpi_s { "Authorization:", &packet->authorization_line }, { NULL, NULL} }; struct header_line headers_u[] = { { "User-agent:", &packet->user_agent_line }, + { "Upgrade:", &packet->upgrade_line }, { NULL, NULL} }; struct header_line headers_c[] = { { "Content-Disposition:", &packet->content_disposition_line }, { "Content-type:", &packet->content_line }, @@ -11452,6 +11457,8 @@ static const struct cfg_param { { "tls", "certificate_expiration_threshold", "30", "0", "365", CFG_PARAM_INT, __OFF(tls_certificate_expire_in_x_days), NULL }, { "tls", "application_blocks_tracking", "disable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(tls_app_blocks_tracking_enabled), NULL }, + { "tls", "dpi.heuristics", "0x00", "0", "0x07", CFG_PARAM_INT, __OFF(tls_heuristics), NULL }, + { "tls", "dpi.heuristics.max_packets_extra_dissection", "25", "0", "255", CFG_PARAM_INT, __OFF(tls_heuristics_max_packets), NULL }, { "tls", "metadata.sha1_fingerprint", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(tls_sha1_fingerprint_enabled), NULL }, { "tls", "metadata.ja3c_fingerprint", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(tls_ja3c_fingerprint_enabled), NULL }, { "tls", "metadata.ja3s_fingerprint", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(tls_ja3s_fingerprint_enabled), NULL }, diff --git a/src/lib/protocols/http.c b/src/lib/protocols/http.c index bf26467da..57f71e2fe 100644 --- a/src/lib/protocols/http.c +++ b/src/lib/protocols/http.c @@ -148,7 +148,19 @@ static int ndpi_search_http_tcp_again(struct ndpi_detection_module_struct *ndpi_ #endif if(flow->extra_packets_func == NULL) { - return(0); /* We're good now */ + /* HTTP stuff completed */ + + /* Loook for TLS over websocket */ + if((ndpi_struct->cfg.tls_heuristics & NDPI_HEURISTICS_TLS_OBFUSCATED_HTTP) && /* Feature enabled */ + (flow->host_server_name[0] != '\0' && + flow->http.response_status_code != 0) && /* Bidirectional HTTP traffic */ + flow->http.websocket) { + + switch_extra_dissection_to_tls_obfuscated_heur(ndpi_struct, flow); + return(1); + } + + return(0); /* We are good now */ } /* Possibly more processing */ @@ -954,6 +966,12 @@ static void check_content_type_and_change_protocol(struct ndpi_detection_module_ } } + if(packet->upgrade_line.ptr != NULL) { + if(flow->http.response_status_code == 101 && + memcmp((char *)packet->upgrade_line.ptr, "websocket", 9) == 0) + flow->http.websocket = 1; + } + if(packet->server_line.ptr != NULL) { if(flow->http.server == NULL) { len = packet->server_line.len + 1; @@ -1577,7 +1595,7 @@ static void ndpi_search_http_tcp(struct ndpi_detection_module_struct *ndpi_struc ndpi_check_http_tcp(ndpi_struct, flow); if((ndpi_struct->cfg.http_parse_response_enabled && - flow->host_server_name[0] != '\0'&& + flow->host_server_name[0] != '\0' && flow->http.response_status_code != 0) || (!ndpi_struct->cfg.http_parse_response_enabled && (flow->host_server_name[0] != '\0' || diff --git a/src/lib/protocols/tls.c b/src/lib/protocols/tls.c index b413cd262..c8f880d49 100644 --- a/src/lib/protocols/tls.c +++ b/src/lib/protocols/tls.c @@ -152,6 +152,305 @@ static u_int32_t __get_master(struct ndpi_detection_module_struct *ndpi_struct, /* **************************************** */ +/* Heuristic to detect proxied/obfuscated TLS flows, based on + https://www.usenix.org/conference/usenixsecurity24/presentation/xue-fingerprinting. + Main differences between the paper and our implementation: + * only Mahalanobis Distance, no Chi-squared Test + * instead of 3-grams, we use 4-grams, always starting from the Client -> Server direction + * consecutive packets in the same direction always belong to the same burst/flight + + Core idea: + * the packets/bytes distribution of a TLS handshake is quite unique + * this fingerprint is still detectable if the handshake is + encrypted/proxied/obfuscated +*/ + +struct tls_obfuscated_heuristic_set { + u_int8_t stage; + u_int32_t bytes[4]; + u_int32_t pkts[4]; +}; + +struct tls_obfuscated_heuristic_state { + u_int8_t num_pkts; + + /* Burst/flight: consecutive packets in the same direction. + * Set: packet/bytes distribution of 4 consecutive bursts (always starting from C->S), + i.e. a 4-grams (using the paper terminology) + * We have up tp 2 sets contemporarily active. + * At the first pkt of a new C->S flight, we close the oldest set and open a new one */ + struct tls_obfuscated_heuristic_set sets[2]; +}; + +static int check_set(struct ndpi_detection_module_struct* ndpi_struct, + struct tls_obfuscated_heuristic_set *set) +{ +#ifdef NDPI_ENABLE_DEBUG_MESSAGES + struct ndpi_packet_struct* packet = &ndpi_struct->packet; +#endif + + /* Model: TLS 1.2; Firefox; No session resumption/0rtt */ + const float i_s_tls_12[4 * 4] = { 0.000292421113167604, 4.43677617831228E-07, -5.69966093492813E-05, -2.18124698406311E-06, + 4.43677617831228E-07, 5.98954952745268E-07, -3.59798436724817E-07, 5.71638172955893E-07, + -5.69966093492813E-05, -3.59798436724817E-07, 0.00076893788148309, 2.22278496185964E-05, + -2.18124698406311E-06, 5.71638172955893E-07, 2.22278496185964E-05, 5.72770077086287E-05 }; + const float average_tls_12[4] = { 212.883690341977, 4514.71195039459, 107.770762871101, 307.580232995115 }; + const float distance_tls_12 = 3.5; + + /* Model: TLS 1.3; Firefox; No session resumption/0rtt; no PQ; ECH(-grease) enabled */ + const float i_s_tls_13[4 * 4] = { 3.08030337925007E-05, 1.16179172096944E-07, 1.05356744968627E-07, 3.8862884355278E-08, + 1.16179172096944E-07, 6.93179117519316E-07, 2.77413220880937E-08, -3.63723200682445E-09, + 1.05356744968627E-07, 2.77413220880937E-08, 1.0260950589675E-06, -1.08769813590053E-08, + 3.88628843552779E-08, -3.63723200682445E-09, -1.08769813590053E-08, 8.63307792288604E-08 }; + const float average_tls_13[4] = { 640.657378447541, 4649.30338356554, 448.408302530566, 1094.2013079329}; + const float distance_tls_13 = 3.0; + + /* Model: TLS 1.2/1.3; Chrome; No session resumption/0rtt; PQ; ECH(-grease) enabled */ + const float i_s_chrome[4 * 4] = { 6.72374390966642E-06, -2.32109583941723E-08, 6.67140014394388E-08, 1.2526322628285E-08, + -2.32109583941723E-08, 5.64668947932086E-07, 4.58963631972597E-08, 6.41254684791958E-09, + 6.67140014394388E-08, 4.58963631972597E-08, 6.04057768431344E-07, -9.1507432597718E-10, + 1.2526322628285E-08, 6.41254684791958E-09, -9.1507432597718E-10, 1.01184796635481E-07 }; + const float average_chrome[4] = { 1850.43045387994, 4903.07735480722, 785.25280624695, 1051.22303562714 }; + const float distance_chrome = 3.0; + + /* TODO: * ECH/PQ are still under development/deployment -> re-evaluate these models + * Session resumptions/0rtt + * Non-web traffic */ + + /* This is the only logic about pkts distributions. + ClientHello shoudn't be splitted in too many fragments: usually 1; 2 with PQ */ + if(set->pkts[0] > 3) { + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: too many pkts in the first burst %d\n", set->pkts[0]); + return 0; + } + + if(ndpi_mahalanobis_distance(set->bytes, 4, average_chrome, i_s_chrome) < distance_chrome || + /* To avoid false positives: we didn't find TLS 1.3 CH smaller than 517 */ + (set->bytes[0] >= 517 && ndpi_mahalanobis_distance(set->bytes, 4, average_tls_13, i_s_tls_13) < distance_tls_13) || + ndpi_mahalanobis_distance(set->bytes, 4, average_tls_12, i_s_tls_12) < distance_tls_12) { + + NDPI_LOG_DBG(ndpi_struct, "TLS-Obf-Heur: md %f %f %f [%d/%d/%d/%d %d/%d/%d/%d] TCP? %d\n", + ndpi_mahalanobis_distance(set->bytes, 4, average_tls_12, i_s_tls_12), + ndpi_mahalanobis_distance(set->bytes, 4, average_tls_13, i_s_tls_13), + ndpi_mahalanobis_distance(set->bytes, 4, average_chrome, i_s_chrome), + set->pkts[0], set->pkts[1], set->pkts[2], set->pkts[3], + set->bytes[0], set->bytes[1], set->bytes[2], set->bytes[3], + !!packet->tcp); + return 1; + } + + return 0; +} + +static int tls_obfuscated_heur_search(struct ndpi_detection_module_struct* ndpi_struct, + struct ndpi_flow_struct* flow) { + struct ndpi_packet_struct* packet = &ndpi_struct->packet; + struct tls_obfuscated_heuristic_state *state = flow->tls_quic.obfuscated_heur_state; + struct tls_obfuscated_heuristic_set *set; + int i, j; + int is_tls_in_tls_heur = 0; + int byte_overhead; + + /* Stages: + 0: Unused/Start + 1: C->S : burst 1 + 2: S->C : burst 2 + 3: C->S : burst 3 + 4: S->C : burst 4 + 5: C->S : End + */ + + if(!state) + return 1; /* Exclude */ + + if(packet->payload_packet_len == 0) + return 0; /* Continue */ + + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: num_pkts %d\n", state->num_pkts); + + if(flow->extra_packets_func && + (flow->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS || + flow->detected_protocol_stack[1] == NDPI_PROTOCOL_TLS)) /* TLS-in-TLS heuristic */ + is_tls_in_tls_heur = 1; + + /* We try to keep into account the overhead (header/padding/mac/iv/nonce) of the + external layers (i.e. above the TLS hanshake we are trying to detect) */ + if(is_tls_in_tls_heur == 1) { + /* According to https://datatracker.ietf.org/doc/html/draft-mattsson-uta-tls-overhead-01 + the average packet overhead for TLS is 29 bytes. + TODO: this draft is OLD and about TLS 1.2 + Looking at real traffic, we found that we can have TLS packets 24 bytes long + */ + byte_overhead = 24; + } else { + /* The paper says that the overhead is usually quite small + ["typically ranging between 20 to 60 bytes"], without any citations. + From the tests, it seams that we can ignore it */ + byte_overhead = 0; + } + + if(packet->payload_packet_len < byte_overhead) { + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: packet too small. Stop.\n"); + return 1; /* Exclude */ + } + + if(is_tls_in_tls_heur == 1) { + /* We usually stop processing TLS handshake (and switch to this extra dissection + data path) after the FIRST Change-Cipher message. However, for this + heuristic, we need to ignore all packets before a Change-Cipher is sent in the + same direction */ + if(current_pkt_from_client_to_server(ndpi_struct, flow) && + flow->tls_quic.change_cipher_from_client == 0) { + if(packet->payload[0] == 0x14) { + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: Change-Cipher from client\n"); + flow->tls_quic.change_cipher_from_client = 1; + } + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: skip\n"); + return 0; /* Continue */ + } + if(current_pkt_from_server_to_client(ndpi_struct, flow) && + flow->tls_quic.change_cipher_from_server == 0) { + if(packet->payload[0] == 0x14) { + flow->tls_quic.change_cipher_from_server = 1; + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: Change-Cipher from server\n"); + } + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: skip\n"); + return 0; /* Continue */ + } + } + + if(state->num_pkts++ > ndpi_struct->cfg.tls_heuristics_max_packets) { + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: too many pkts. Stop\n"); + return 1; /* Exclude */ + } + + /* Update active sets */ + for(i = 0; i < 2; i ++) { + set = &state->sets[i]; + switch(set->stage) { + case 0: + /* This happen only at the beginning of the heuristic: after the first pkt + of the third (absolute) burst, we always have both sets used */ + if(i == 0 || state->sets[0].stage == 3) { + if(current_pkt_from_client_to_server(ndpi_struct, flow)) { + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: open set %d\n", i); + set->stage = 1; + break; + } else { + /* First packet should be always from client. + This shortcut makes detection harder if, before the obfuscated TLS hanshake + there is random traffic */ + return 1; /* Exclude */ + } + } + continue; + case 1: + if(current_pkt_from_server_to_client(ndpi_struct, flow)) + set->stage = 2; + break; + case 2: + if(current_pkt_from_client_to_server(ndpi_struct, flow)) + set->stage = 3; + break; + case 3: + if(current_pkt_from_server_to_client(ndpi_struct, flow)) + set->stage = 4; + break; + case 4: + if(current_pkt_from_client_to_server(ndpi_struct, flow)) + set->stage = 5; + break; + /* We cant have 5 here */ + } + + if(set->stage != 5) { + set->bytes[set->stage - 1] += (packet->payload_packet_len - byte_overhead); + set->pkts[set->stage - 1] += 1; + } + + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: set %d stage %d bytes %d/%d/%d/%d pkts %d/%d/%d/%d\n", + i, set->stage, + set->bytes[0], set->bytes[1], set->bytes[2], set->bytes[3], + set->pkts[0], set->pkts[1], set->pkts[2], set->pkts[3]); + + /* Check completed set */ + if(set->stage == 5) { + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: set %d completed\n", i); + if(check_set(ndpi_struct, set)) { + /* Heuristic match */ + return 2; /* Found */ + } else { + /* Close this set and open a new one... */ + set->stage = 1; + set->bytes[0] = packet->payload_packet_len - byte_overhead; + set->pkts[0] = 1; + for(j = 1; j < 4; j++) { + set->bytes[j] = 0; + set->pkts[j] = 0; + } + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: set %d closed and reused\n", i); + } + } + } + + return 0; /* Continue */ +} + +static int tls_obfuscated_heur_search_again(struct ndpi_detection_module_struct* ndpi_struct, + struct ndpi_flow_struct* flow) +{ + int rc; + + NDPI_LOG_DBG2(ndpi_struct, "TLS-Obf-Heur: extra dissection\n"); + + rc = tls_obfuscated_heur_search(ndpi_struct, flow); + if(rc == 0) + return 1; /* Keep working */ + if(rc == 2) { + NDPI_LOG_DBG(ndpi_struct, "TLS-Obf-Heur: found!\n"); + + /* Right now, if an heuritic matches, we set the classification/risk. + TODO: avoid false positives! + Some ideas: + * try to identify the servers: we wait for multiple sessions to the same server, + before to start marking the flows to that address + * consider the number of burst after TLS handshake (see Fig 8 paper) */ + + if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) { + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TLS, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI_AGGRESSIVE); + ndpi_set_risk(flow, NDPI_OBFUSCATED_TRAFFIC, "Obfuscated TLS traffic"); + } else { + flow->confidence = NDPI_CONFIDENCE_DPI_AGGRESSIVE; /* Update the value */ + if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS || + flow->detected_protocol_stack[1] == NDPI_PROTOCOL_TLS) + ndpi_set_risk(flow, NDPI_OBFUSCATED_TRAFFIC, "Obfuscated TLS-in-TLS traffic"); + else + ndpi_set_risk(flow, NDPI_OBFUSCATED_TRAFFIC, "Obfuscated TLS-in-HTTP-WebSocket traffic"); + } + + ndpi_protocol ret = { { __get_master(ndpi_struct, flow), NDPI_PROTOCOL_UNKNOWN }, NDPI_PROTOCOL_UNKNOWN /* unused */, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NULL}; + flow->category = ndpi_get_proto_category(ndpi_struct, ret); + } + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); /* Not necessary in extra-dissection data path, + but we need it with the plain heuristic */ + return 0; /* Stop */ +} + +void switch_extra_dissection_to_tls_obfuscated_heur(struct ndpi_detection_module_struct* ndpi_struct, + struct ndpi_flow_struct* flow) +{ + NDPI_LOG_DBG(ndpi_struct, "Switching to TLS Obfuscated heuristic\n"); + + flow->tls_quic.obfuscated_heur_state = ndpi_calloc(1, sizeof(struct tls_obfuscated_heuristic_state)); + + /* "* 2" to take into account ACKs. The "real" check is performend against + "tls_heuristics_max_packets" in tls_obfuscated_heur_search, as expected */ + flow->max_extra_packets_to_check = ndpi_struct->cfg.tls_heuristics_max_packets * 2; + flow->extra_packets_func = tls_obfuscated_heur_search_again; +} + +/* **************************************** */ + static int ndpi_search_tls_memory(const u_int8_t *payload, u_int16_t payload_len, u_int32_t seq, @@ -1018,7 +1317,6 @@ static int ndpi_search_tls_tcp(struct ndpi_detection_module_struct *ndpi_struct, https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-5 */ if(!(message->buffer[0] >= 20 && message->buffer[0] <= 26)) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); something_went_wrong = 1; } @@ -1072,6 +1370,11 @@ static int ndpi_search_tls_tcp(struct ndpi_detection_module_struct *ndpi_struct, #ifdef DEBUG_TLS printf("[TLS] Change Cipher Spec\n"); #endif + if(current_pkt_from_client_to_server(ndpi_struct, flow)) + flow->tls_quic.change_cipher_from_client = 1; + else + flow->tls_quic.change_cipher_from_server = 1; + ndpi_int_tls_add_connection(ndpi_struct, flow); flow->l4.tcp.tls.app_data_seen[packet->packet_direction] = 1; /* Further data is encrypted so we are not able to parse it without @@ -1215,6 +1518,16 @@ static int ndpi_search_tls_tcp(struct ndpi_detection_module_struct *ndpi_struct, ndpi_unset_risk(flow, NDPI_KNOWN_PROTOCOL_ON_NON_STANDARD_PORT); flow->extra_packets_func = NULL; return(0); /* That's all */ + /* Loook for TLS-in-TLS */ + } else if((ndpi_struct->cfg.tls_heuristics & NDPI_HEURISTICS_TLS_OBFUSCATED_TLS) && /* Feature enabled */ + (!something_went_wrong && + flow->tls_quic.certificate_processed == 1 && + flow->protos.tls_quic.client_hello_processed == 1 && + flow->protos.tls_quic.server_hello_processed == 1) && /* TLS handshake found without errors */ + flow->tls_quic.from_opportunistic_tls == 0 && /* No from plaintext Mails or FTP */ + !is_flow_addr_informative(flow) /* The proxy server is likely hosted on some cloud providers */ ) { + switch_extra_dissection_to_tls_obfuscated_heur(ndpi_struct, flow); + return(1); } else { flow->extra_packets_func = NULL; return(0); /* That's all */ @@ -1410,7 +1723,6 @@ static int ndpi_search_tls_udp(struct ndpi_detection_module_struct *ndpi_struct, packet->payload_packet_len = p_len; /* Restore */ if(no_dtls || change_cipher_found || flow->tls_quic.certificate_processed) { - NDPI_EXCLUDE_PROTO_EXT(ndpi_struct, flow, NDPI_PROTOCOL_DTLS); return(0); /* That's all */ } else { return(1); /* Keep working */ @@ -1446,6 +1758,8 @@ void switch_extra_dissection_to_tls(struct ndpi_detection_module_struct *ndpi_st ndpi_free(flow->tls_quic.message[1].buffer); memset(&flow->tls_quic.message[1], '\0', sizeof(flow->tls_quic.message[1])); + flow->tls_quic.from_opportunistic_tls = 1; + tlsInitExtraPacketProcessing(ndpi_struct, flow); } @@ -1466,6 +1780,14 @@ void switch_to_tls(struct ndpi_detection_module_struct *ndpi_struct, if(flow->tls_quic.message[1].buffer) ndpi_free(flow->tls_quic.message[1].buffer); memset(&flow->tls_quic.message[1], '\0', sizeof(flow->tls_quic.message[1])); + + /* We will not check obfuscated heuristic (anymore) because we have been + called from the STUN code, but we need to clear the previous state + (if any) to trigger "standard" TLS/DTLS code */ + if(flow->tls_quic.obfuscated_heur_state) { + ndpi_free(flow->tls_quic.obfuscated_heur_state); + flow->tls_quic.obfuscated_heur_state = NULL; + } } ndpi_search_tls_wrapper(ndpi_struct, flow); @@ -3031,6 +3353,7 @@ compute_ja3c: static void ndpi_search_tls_wrapper(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &ndpi_struct->packet; + int rc = 0; #ifdef DEBUG_TLS printf("==>> %s() %u [len: %u][version: %u]\n", @@ -3040,10 +3363,44 @@ static void ndpi_search_tls_wrapper(struct ndpi_detection_module_struct *ndpi_st flow->protos.tls_quic.ssl_version); #endif - if(packet->udp != NULL || flow->stun.maybe_dtls) - ndpi_search_tls_udp(ndpi_struct, flow); - else - ndpi_search_tls_tcp(ndpi_struct, flow); + /* It is not easy to handle "standard" TLS/DTLS detection and (plain) obfuscated + heuristic at the SAME time. Use a trivial logic: switch to heuristic + code only if the standard functions fail */ + + /* We might be in extra-dissection data-path here (if we have been + called from STUN or from Mails/FTP/...), but plain obfuscated heuristic + is always checked in "standard" data-path! */ + + if(flow->tls_quic.obfuscated_heur_state == NULL) { + if(packet->udp != NULL || flow->stun.maybe_dtls) + rc = ndpi_search_tls_udp(ndpi_struct, flow); + else + rc = ndpi_search_tls_tcp(ndpi_struct, flow); + + if(rc == 0) + flow->tls_quic.obfuscated_heur_state = ndpi_calloc(1, sizeof(struct tls_obfuscated_heuristic_state)); + } + + /* We should check for this TLS heuristic if: + * the feature is enabled + * this flow doesn't seem a real TLS/DTLS one + * we are not here from STUN code or from opportunistic tls path (mails/ftp) + * with TCP, we got the 3WHS (so that we can process the beginning of the flow) + */ + if(flow->tls_quic.obfuscated_heur_state && + (ndpi_struct->cfg.tls_heuristics & NDPI_HEURISTICS_TLS_OBFUSCATED_PLAIN) && + flow->stun.maybe_dtls == 0 && + flow->tls_quic.from_opportunistic_tls == 0 && + ((flow->l4_proto == IPPROTO_TCP && ndpi_seen_flow_beginning(flow)) || + flow->l4_proto == IPPROTO_UDP) && + !is_flow_addr_informative(flow) /* The proxy server is likely hosted on some cloud providers */ ) { + tls_obfuscated_heur_search_again(ndpi_struct, flow); + } else if(rc == 0) { + if(packet->udp != NULL || flow->stun.maybe_dtls) + NDPI_EXCLUDE_PROTO_EXT(ndpi_struct, flow, NDPI_PROTOCOL_DTLS); + else + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + } } /* **************************************** */ |