aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Nardi <12729895+IvanNardi@users.noreply.github.com>2023-12-06 10:24:26 +0100
committerGitHub <noreply@github.com>2023-12-06 10:24:26 +0100
commitf74cf16c361018fc98d796978013df4ca0c6050f (patch)
tree47b08a2ff93f4313c5c2175fe4663099f38bfd49 /src
parentad20846fad48d5da8f21fcb7cc9703048b0c02db (diff)
OpenVPN: rework detection (#2199)
Close #1873
Diffstat (limited to 'src')
-rw-r--r--src/include/ndpi_typedefs.h3
-rw-r--r--src/lib/protocols/openvpn.c265
2 files changed, 170 insertions, 98 deletions
diff --git a/src/include/ndpi_typedefs.h b/src/include/ndpi_typedefs.h
index 06e55adca..718fec834 100644
--- a/src/include/ndpi_typedefs.h
+++ b/src/include/ndpi_typedefs.h
@@ -1454,8 +1454,7 @@ struct ndpi_flow_struct {
/* NDPI_PROTOCOL_OPENVPN */
- u_int8_t ovpn_session_id[8];
- u_int8_t ovpn_counter;
+ u_int8_t ovpn_session_id[2][8];
/* NDPI_PROTOCOL_TINC */
u_int8_t tinc_state;
diff --git a/src/lib/protocols/openvpn.c b/src/lib/protocols/openvpn.c
index 127214ea1..24b8810de 100644
--- a/src/lib/protocols/openvpn.c
+++ b/src/lib/protocols/openvpn.c
@@ -26,6 +26,7 @@
#include "ndpi_api.h"
#include "ndpi_private.h"
+
/*
* OpenVPN TCP / UDP Detection - 128/160 hmac
*
@@ -34,10 +35,6 @@
* - packet ID
* - session ID
*
- * Two (good) packets are needed to perform detection.
- * - First packet from client: save session ID
- * - Second packet from server: report saved session ID
- *
* TODO
* - Support PSK only mode (instead of TLS)
* - Support PSK + TLS mode (PSK used for early authentication)
@@ -46,37 +43,82 @@
*/
#define P_CONTROL_HARD_RESET_CLIENT_V1 (0x01 << 3)
-#define P_CONTROL_HARD_RESET_CLIENT_V2 (0x07 << 3)
#define P_CONTROL_HARD_RESET_SERVER_V1 (0x02 << 3)
+#define P_CONTROL_V1 (0x04 << 3)
+#define P_ACK_V1 (0x05 << 3)
+#define P_CONTROL_HARD_RESET_CLIENT_V2 (0x07 << 3)
#define P_CONTROL_HARD_RESET_SERVER_V2 (0x08 << 3)
+#define P_CONTROL_HARD_RESET_CLIENT_V3 (0x0A << 3)
+#define P_CONTROL_WKC_V1 (0x0B << 3)
+
#define P_OPCODE_MASK 0xF8
#define P_SHA1_HMAC_SIZE 20
#define P_HMAC_128 16 // (RSA-)MD5, (RSA-)MD4, ..others
#define P_HMAC_160 20 // (RSA-|DSA-)SHA(1), ..others, SHA1 is openvpn default
+#define P_HMAC_NONE 0 // No HMAC
#define P_HARD_RESET_PACKET_ID_OFFSET(hmac_size) (9 + hmac_size)
-#define P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size) (P_HARD_RESET_PACKET_ID_OFFSET(hmac_size) + 8)
-#define P_HARD_RESET_CLIENT_MAX_COUNT 5
-
-static
-#ifndef WIN32
-inline
-#endif
-u_int32_t get_packet_id(const u_int8_t * payload, u_int8_t hms) {
+#define P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size) (P_HARD_RESET_PACKET_ID_OFFSET(hmac_size) + 8 * (!!(hmac_size)))
+
+
+
+static int is_opcode_valid(u_int8_t opcode)
+{
+ /* Ignore:
+ * P_DATA_V1/2: they don't have any (useful) info in the header
+ * P_CONTROL_SOFT_RESET_V1: it is used to key renegotiation -> it is not at the beginning of the session
+ */
+ return opcode == P_CONTROL_HARD_RESET_CLIENT_V1 ||
+ opcode == P_CONTROL_HARD_RESET_SERVER_V1 ||
+ opcode == P_CONTROL_V1 ||
+ opcode == P_ACK_V1 ||
+ opcode == P_CONTROL_HARD_RESET_CLIENT_V2 ||
+ opcode == P_CONTROL_HARD_RESET_SERVER_V2 ||
+ opcode == P_CONTROL_HARD_RESET_CLIENT_V3 ||
+ opcode == P_CONTROL_WKC_V1;
+}
+
+static u_int32_t get_packet_id(const u_int8_t * payload, u_int8_t hms) {
return(ntohl(*(u_int32_t*)(payload + P_HARD_RESET_PACKET_ID_OFFSET(hms))));
}
-static
-#ifndef WIN32
-inline
-#endif
-int8_t check_pkid_and_detect_hmac_size(const u_int8_t * payload) {
+/* From wireshark */
+/* We check the leading 4 byte of a suspected hmac for 0x00 bytes,
+ if more than 1 byte out of the 4 provided contains 0x00, the
+ hmac is considered not valid, which suggests that no tls auth is used.
+ unfortunatly there is no other way to detect tls auth on the fly */
+static int check_for_valid_hmac(u_int32_t hmac)
+{
+ int c = 0;
+
+ if((hmac & 0x000000FF) == 0x00000000)
+ c++;
+ if((hmac & 0x0000FF00) == 0x00000000)
+ c++;
+ if ((hmac & 0x00FF0000) == 0x00000000)
+ c++;
+ if ((hmac & 0xFF000000) == 0x00000000)
+ c++;
+ if (c > 1)
+ return 0;
+ return 1;
+}
+
+static int8_t detect_hmac_size(const u_int8_t *payload, int payload_len) {
// try to guess
- if(get_packet_id(payload, P_HMAC_160) == 1)
+ if((payload_len >= P_HARD_RESET_PACKET_ID_OFFSET(P_HMAC_160) + 4) &&
+ get_packet_id(payload, P_HMAC_160) == 1)
return P_HMAC_160;
- if(get_packet_id(payload, P_HMAC_128) == 1)
+ if((payload_len >= P_HARD_RESET_PACKET_ID_OFFSET(P_HMAC_128) + 4) &&
+ get_packet_id(payload, P_HMAC_128) == 1)
return P_HMAC_128;
-
+
+ /* Heuristic from Wireshark, to detect no-HMAC flows (i.e. tls-crypt) */
+ if(payload_len >= 14 &&
+ !(payload[9] > 0 &&
+ check_for_valid_hmac(ntohl(*(u_int32_t*)(payload + 9)))))
+ return P_HMAC_NONE;
+
return(-1);
}
@@ -90,89 +132,120 @@ static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct
int8_t hmac_size;
int8_t failed = 0;
/* No u_ */int16_t ovpn_payload_len = packet->payload_packet_len;
+ int dir = packet->packet_direction;
+
+ /* Detection:
+ * (1) server and client resets matching (via session id -> remote session id)
+ * (2) consecutive packets (in both directions) with the same session id
+ * (3) asymmetric traffic
+ */
+
+ if(ovpn_payload_len < 14 + 2 * (packet->tcp != NULL)) {
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
- if(ovpn_payload_len >= 40) {
- // skip openvpn TCP transport packet size
- if(packet->tcp != NULL)
- ovpn_payload += 2, ovpn_payload_len -= 2;;
-
- opcode = ovpn_payload[0] & P_OPCODE_MASK;
-
- if(packet->udp) {
-#ifdef DEBUG
- printf("[packet_id: %u][opcode: %u][Packet ID: %d][%u <-> %u][len: %u]\n",
- flow->num_processed_pkts,
- opcode, check_pkid_and_detect_hmac_size(ovpn_payload),
- htons(packet->udp->source), htons(packet->udp->dest), ovpn_payload_len);
-#endif
-
- if(
- (flow->num_processed_pkts == 1)
- && (
- ((ovpn_payload_len == 112)
- && ((opcode == 168) || (opcode == 192))
- )
- || ((ovpn_payload_len == 80)
- && ((opcode == 184) || (opcode == 88) || (opcode == 160) || (opcode == 168) || (opcode == 200)))
- )) {
- NDPI_LOG_INFO(ndpi_struct,"found openvpn\n");
+ /* Skip openvpn TCP transport packet size */
+ if(packet->tcp != NULL)
+ ovpn_payload += 2, ovpn_payload_len -= 2;
+
+ opcode = ovpn_payload[0] & P_OPCODE_MASK;
+ if(!is_opcode_valid(opcode)) {
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+ /* Maybe a strong assumption... */
+ if((ovpn_payload[0] & ~P_OPCODE_MASK) != 0) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid key id\n");
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+ if(flow->packet_direction_counter[dir] == 1 &&
+ !(opcode == P_CONTROL_HARD_RESET_CLIENT_V1 ||
+ opcode == P_CONTROL_HARD_RESET_CLIENT_V2 ||
+ opcode == P_CONTROL_HARD_RESET_SERVER_V1 ||
+ opcode == P_CONTROL_HARD_RESET_SERVER_V2 ||
+ opcode == P_CONTROL_HARD_RESET_CLIENT_V3)) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid first packet\n");
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+ if(flow->packet_direction_counter[dir] == 1 &&
+ packet->tcp &&
+ ntohs(*(u_int16_t *)(packet->payload)) != ovpn_payload_len) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid tcp len on reset\n");
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+
+ NDPI_LOG_DBG2(ndpi_struct, "[packets %d/%d][opcode: %u][len: %u]\n",
+ flow->packet_direction_counter[dir],
+ flow->packet_direction_counter[!dir],
+ opcode, ovpn_payload_len);
+
+ if(flow->packet_direction_counter[dir] > 1) {
+ if(memcmp(flow->ovpn_session_id[dir], ovpn_payload + 1, 8) != 0) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid session id on two consecutive pkts in the same dir\n");
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+ if(flow->packet_direction_counter[dir] >= 2 &&
+ flow->packet_direction_counter[!dir] >= 2) {
+ /* (2) */
+ NDPI_LOG_INFO(ndpi_struct,"found openvpn (session ids match on both direction)\n");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
return;
}
+ if(flow->packet_direction_counter[dir] >= 4 &&
+ flow->packet_direction_counter[!dir] == 0) {
+ /* (3) */
+ NDPI_LOG_INFO(ndpi_struct,"found openvpn (asymmetric)\n");
+ ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
+ return;
}
-
- if(flow->ovpn_counter < P_HARD_RESET_CLIENT_MAX_COUNT && (opcode == P_CONTROL_HARD_RESET_CLIENT_V1 ||
- opcode == P_CONTROL_HARD_RESET_CLIENT_V2)) {
- if(check_pkid_and_detect_hmac_size(ovpn_payload) > 0) {
- memcpy(flow->ovpn_session_id, ovpn_payload+1, 8);
-
- NDPI_LOG_DBG2(ndpi_struct,
- "session key: %02x%02x%02x%02x%02x%02x%02x%02x\n",
- flow->ovpn_session_id[0], flow->ovpn_session_id[1], flow->ovpn_session_id[2], flow->ovpn_session_id[3],
- flow->ovpn_session_id[4], flow->ovpn_session_id[5], flow->ovpn_session_id[6], flow->ovpn_session_id[7]);
+ } else {
+ memcpy(flow->ovpn_session_id[dir], ovpn_payload + 1, 8);
+ NDPI_LOG_DBG2(ndpi_struct, "Session key [%d]: 0x%lx\n", dir,
+ ndpi_ntohll(*(u_int64_t *)flow->ovpn_session_id[dir]));
+ }
+
+ /* (1) */
+ if(flow->packet_direction_counter[!dir] > 0 &&
+ (opcode == P_CONTROL_HARD_RESET_SERVER_V1 ||
+ opcode == P_CONTROL_HARD_RESET_SERVER_V2)) {
+
+ hmac_size = detect_hmac_size(ovpn_payload, ovpn_payload_len);
+ NDPI_LOG_DBG2(ndpi_struct, "hmac size %d\n", hmac_size);
+ failed = 1;
+ if(hmac_size >= 0 &&
+ P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size) < ovpn_payload_len) {
+ u_int16_t offset = P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size);
+
+ alen = ovpn_payload[offset];
+
+ if(alen > 0) {
+ offset += 1 + alen * 4;
+
+ if((offset + 8) <= ovpn_payload_len) {
+ session_remote = &ovpn_payload[offset];
+
+ if(memcmp(flow->ovpn_session_id[!dir], session_remote, 8) == 0) {
+ NDPI_LOG_INFO(ndpi_struct,"found openvpn\n");
+ ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
+ return;
+ } else {
+ NDPI_LOG_DBG2(ndpi_struct, "key mismatch 0x%lx\n", ndpi_ntohll(*(u_int64_t *)session_remote));
+ }
+ }
+ } else {
+ failed = 0; /* Server reset without remote session id field */
}
- } else if(flow->ovpn_counter >= 1 && flow->ovpn_counter <= P_HARD_RESET_CLIENT_MAX_COUNT &&
- (opcode == P_CONTROL_HARD_RESET_SERVER_V1 || opcode == P_CONTROL_HARD_RESET_SERVER_V2)) {
-
- hmac_size = check_pkid_and_detect_hmac_size(ovpn_payload);
-
- if(hmac_size > 0) {
- u_int16_t offset = P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size);
-
- alen = ovpn_payload[offset];
-
- if (alen > 0) {
- offset += 1 + alen * 4;
-
- if((offset+8) <= ovpn_payload_len) {
- session_remote = &ovpn_payload[offset];
-
- if(memcmp(flow->ovpn_session_id, session_remote, 8) == 0) {
- NDPI_LOG_INFO(ndpi_struct,"found openvpn\n");
- ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
- return;
- } else {
- NDPI_LOG_DBG2(ndpi_struct,
- "key mismatch: %02x%02x%02x%02x%02x%02x%02x%02x\n",
- session_remote[0], session_remote[1], session_remote[2], session_remote[3],
- session_remote[4], session_remote[5], session_remote[6], session_remote[7]);
- failed = 1;
- }
- } else
- failed = 1;
- } else
- failed = 1;
- } else
- failed = 1;
- } else
- failed = 1;
-
- flow->ovpn_counter++;
-
- if(failed)
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ }
}
+ if(failed)
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+
if(flow->packet_counter > 5)
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}