diff options
author | Ivan Nardi <12729895+IvanNardi@users.noreply.github.com> | 2023-12-06 10:24:26 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-06 10:24:26 +0100 |
commit | f74cf16c361018fc98d796978013df4ca0c6050f (patch) | |
tree | 47b08a2ff93f4313c5c2175fe4663099f38bfd49 /src | |
parent | ad20846fad48d5da8f21fcb7cc9703048b0c02db (diff) |
OpenVPN: rework detection (#2199)
Close #1873
Diffstat (limited to 'src')
-rw-r--r-- | src/include/ndpi_typedefs.h | 3 | ||||
-rw-r--r-- | src/lib/protocols/openvpn.c | 265 |
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); } |