aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/ndpi_analyze.c39
-rw-r--r--src/lib/ndpi_main.c9
-rw-r--r--src/lib/protocols/http.c22
-rw-r--r--src/lib/protocols/tls.c369
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);
+ }
}
/* **************************************** */