183 lines
5.3 KiB
C++
183 lines
5.3 KiB
C++
/*
|
|
* openvpn.c
|
|
*
|
|
* Copyright (C) 2011-22 - ntop.org
|
|
*
|
|
*
|
|
* nDPI is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* nDPI is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with nDPI. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#include <stdint.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "app_l7_protocol.h"
|
|
/*
|
|
* OpenVPN TCP / UDP Detection - 128/160 hmac
|
|
*
|
|
* Detection based upon these openvpn protocol properties:
|
|
* - opcode
|
|
* - 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)
|
|
* - TLS certificate extraction
|
|
*
|
|
*/
|
|
|
|
#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_HARD_RESET_SERVER_V2 (0x08 << 3)
|
|
#define P_ACK_V1 (0x05 << 3)
|
|
#define P_CONTROL_V1 (0x04 << 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
|
|
#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 inline uint32_t get_packet_id(const uint8_t * payload, uint8_t hms) {
|
|
return(ntohl(*(uint32_t*)(payload + P_HARD_RESET_PACKET_ID_OFFSET(hms))));
|
|
}
|
|
|
|
static inline int8_t check_pkid_and_detect_hmac_size(const uint8_t * payload) {
|
|
// try to guess
|
|
if((int)get_packet_id(payload, P_HMAC_160) != 0)
|
|
return P_HMAC_160;
|
|
|
|
if((int)get_packet_id(payload, P_HMAC_128) != 0 )
|
|
return P_HMAC_128;
|
|
|
|
if((int)get_packet_id(payload, P_HMAC_NONE) != 0)
|
|
return P_HMAC_NONE;
|
|
|
|
return(-1);
|
|
}
|
|
|
|
int app_identify_guess_openvpn(const unsigned char *payload, int payload_len, int c2s_pkt_cnt, int s2c_pkt_cnt, struct ovpn_ctx *ovpn, int l4_is_tcp, int curdir_is_c2s)
|
|
{
|
|
const uint8_t * session_remote;
|
|
uint8_t opcode;
|
|
uint8_t alen;
|
|
int8_t hmac_size;
|
|
int8_t failed = 0;
|
|
|
|
if (c2s_pkt_cnt > 5)
|
|
{
|
|
if (ovpn->contain_local_session_id_pkt_num == P_HARD_RESET_CLIENT_MAX_COUNT - 1 && ovpn->valid_opcode_cnt == P_HARD_RESET_CLIENT_MAX_COUNT)
|
|
return 1;
|
|
return -1;
|
|
}
|
|
|
|
if (payload_len >= 40)
|
|
{
|
|
// skip openvpn TCP transport packet size
|
|
if (l4_is_tcp)
|
|
payload += 2, payload_len -= 2;
|
|
|
|
opcode = payload[0] & P_OPCODE_MASK;
|
|
if (curdir_is_c2s == 1 &&
|
|
(opcode == P_CONTROL_HARD_RESET_CLIENT_V1 ||
|
|
opcode == P_CONTROL_HARD_RESET_CLIENT_V2 ||
|
|
opcode == P_ACK_V1 ||
|
|
opcode == P_CONTROL_V1))
|
|
{
|
|
ovpn->valid_opcode_cnt++;
|
|
}
|
|
// for UDP, trying to identify by opcode and first packet length
|
|
if (l4_is_tcp == 0)
|
|
{
|
|
if ((c2s_pkt_cnt == 1 || s2c_pkt_cnt == 1) && (((payload_len == 112) && ((opcode == 168) || (opcode == 192))) || ((payload_len == 80) && ((opcode == 184) || (opcode == 88) || (opcode == 160) || (opcode == 168) || (opcode == 200)))))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if(s2c_pkt_cnt >= 1 && curdir_is_c2s == 0)return 0;
|
|
|
|
if (curdir_is_c2s == 1 && c2s_pkt_cnt <= P_HARD_RESET_CLIENT_MAX_COUNT && (opcode == P_CONTROL_HARD_RESET_CLIENT_V1 || opcode == P_CONTROL_HARD_RESET_CLIENT_V2 || opcode == P_ACK_V1 || opcode == P_CONTROL_V1))
|
|
{
|
|
if (check_pkid_and_detect_hmac_size(payload) >= 0)
|
|
{
|
|
if (c2s_pkt_cnt > 1)
|
|
{
|
|
if (memcmp(&ovpn->local_session_id, payload + 1, 8) == 0)
|
|
{
|
|
ovpn->contain_local_session_id_pkt_num++;
|
|
}
|
|
}
|
|
memcpy(&ovpn->local_session_id, payload + 1, 8);
|
|
}
|
|
}
|
|
else if (c2s_pkt_cnt >= 1 && c2s_pkt_cnt <= 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(payload);
|
|
|
|
if (hmac_size >= 0)
|
|
{
|
|
u_int16_t offset = P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size);
|
|
|
|
alen = payload[offset];
|
|
|
|
if (alen > 0)
|
|
{
|
|
offset += 1 + alen * 4;
|
|
|
|
if ((offset + 8) <= payload_len)
|
|
{
|
|
session_remote = &payload[offset];
|
|
|
|
if (memcmp(&ovpn->local_session_id, session_remote, 8) == 0)
|
|
{
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
|
|
failed = 1;
|
|
}
|
|
}
|
|
else
|
|
failed = 1;
|
|
}
|
|
else
|
|
failed = 1;
|
|
}
|
|
else
|
|
failed = 1;
|
|
}
|
|
else
|
|
failed = 1;
|
|
|
|
if (failed)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
} |