/*
*
* Copyright (c) 2011-2016 The University of Waikato, Hamilton, New Zealand.
* All rights reserved.
*
* This file is part of libprotoident.
*
* This code has been developed by the University of Waikato WAND
* research group. For further information please see http://www.wand.net.nz/
*
* libprotoident 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.
*
* libprotoident 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 this program. If not, see .
*
*
*/
#include
#ifdef __APPLE__
#include
#define bswap32 OSSwapInt32
#endif
#ifdef __FreeBSD__
#include
#endif
#ifdef __linux__
#include
#define bswap32 __bswap_32
#endif
#include "libprotoident.h"
#include "proto_common.h"
bool match_str_either(lpi_data_t *data, const char *string) {
if (MATCHSTR(data->payload[0], string))
return true;
if (MATCHSTR(data->payload[1], string))
return true;
return false;
}
bool match_str_both(lpi_data_t *data, const char *string1,
const char *string2) {
if (MATCHSTR(data->payload[0], string1) &&
MATCHSTR(data->payload[1], string2))
return true;
if (MATCHSTR(data->payload[1], string1) &&
MATCHSTR(data->payload[0], string2))
return true;
return false;
}
bool match_chars_either(lpi_data_t *data, char a, char b, char c,
char d) {
if (MATCH(data->payload[0], a, b, c, d))
return true;
if (MATCH(data->payload[1], a, b, c, d))
return true;
return false;
}
bool match_payload_length(uint32_t payload, uint32_t payload_len) {
uint32_t header = 0;
header = ntohl(payload);
/* See if the length in the (presumed) header matches the
* length of the rest of the packet minus the header itself (4 bytes).
*
* Watch out for the case of a 4 byte packet containing just
* 00 00 00 00! */
if (payload_len > 4 && header == payload_len - 4)
return true;
return false;
}
bool match_ip_address_both(lpi_data_t *data) {
uint8_t matches = 0;
if (data->ips[0] == 0 || data->ips[1] == 0)
return false;
if (data->payload_len[0] == 0)
matches += 1;
else if (data->payload[0] == data->ips[0])
matches += 1;
else if (data->payload[0] == data->ips[1])
matches += 1;
if (data->payload_len[1] == 0)
matches += 1;
else if (data->payload[1] == data->ips[0])
matches += 1;
else if (data->payload[1] == data->ips[1])
matches += 1;
if (matches == 2)
return true;
else
return false;
}
/* Multiple protocols use HTTP-style requests */
bool match_http_request(uint32_t payload, uint32_t len) {
/* HTTP requests - some of these are MS-specific extensions */
if (len == 0)
return true;
if (MATCHSTR(payload, "GET ")) return true;
if (len == 1 && MATCH(payload, 'G', 0x00, 0x00, 0x00))
return true;
if (len == 2 && MATCH(payload, 'G', 'E', 0x00, 0x00))
return true;
if (len == 3 && MATCH(payload, 'G', 'E', 'T', 0x00))
return true;
if (MATCHSTR(payload, "POST")) return true;
if (MATCHSTR(payload, "HEAD")) return true;
if (MATCHSTR(payload, "PUT ")) return true;
if (MATCHSTR(payload, "DELE")) return true;
if (MATCHSTR(payload, "auth")) return true;
/* SVN? */
if (MATCHSTR(payload, "REPO")) return true;
/* Webdav */
if (MATCHSTR(payload, "LOCK")) return true;
if (MATCHSTR(payload, "UNLO")) return true;
if (MATCHSTR(payload, "OPTI")) return true;
if (MATCHSTR(payload, "PROP")) return true;
if (MATCHSTR(payload, "MKCO")) return true;
if (MATCHSTR(payload, "POLL")) return true;
if (MATCHSTR(payload, "SEAR")) return true;
/* Ntrip - some differential GPS system using modified HTTP */
if (MATCHSTR(payload, "SOUR")) return true;
return false;
}
/* File headers are not specific to any particular protocol */
bool match_file_header(uint32_t payload) {
/* RIFF is a meta-format for storing AVI and WAV files */
if (MATCHSTR(payload, "RIFF"))
return true;
/* MZ is a .exe file */
if (MATCH(payload, 'M', 'Z', ANY, 0x00))
return true;
/* Ogg files */
if (MATCHSTR(payload, "OggS"))
return true;
/* ZIP files */
if (MATCH(payload, 'P', 'K', 0x03, 0x04))
return true;
/* MPEG files */
if (MATCH(payload, 0x00, 0x00, 0x01, 0xba))
return true;
/* RAR files */
if (MATCHSTR(payload, "Rar!"))
return true;
/* EBML */
if (MATCH(payload, 0x1a, 0x45, 0xdf, 0xa3))
return true;
/* JPG */
if (MATCH(payload, 0xff, 0xd8, ANY, ANY))
return true;
/* GIF */
if (MATCHSTR(payload, "GIF8"))
return true;
/* I'm also going to include PHP scripts in here */
if (MATCH(payload, 0x3c, 0x3f, 0x70, 0x68))
return true;
/* Unix scripts */
if (MATCH(payload, 0x23, 0x21, 0x2f, 0x62))
return true;
/* PDFs */
if (MATCHSTR(payload, "%PDF"))
return true;
/* PNG */
if (MATCH(payload, 0x89, 'P', 'N', 'G'))
return true;
/* HTML */
if (MATCHSTR(payload, "server_port == 80 || data->client_port == 80)
return true;
if (data->server_port == 8080 || data->client_port == 8080)
return true;
if (data->server_port == 8081 || data->client_port == 8081)
return true;
/* If port 443 responds, we want it to be counted as genuine
* HTTP, rather than a bad port scenario */
if (data->server_port == 443 || data->client_port == 443) {
if (data->payload_len[0] > 0 && data->payload_len[1] > 0)
return true;
}
return false;
}
/* 16 03 00 X is an SSLv3 handshake */
static inline bool match_ssl3_handshake(uint32_t payload, uint32_t len) {
if (len == 0)
return true;
if (len == 1 && MATCH(payload, 0x16, 0x00, 0x00, 0x00))
return true;
if (MATCH(payload, 0x16, 0x03, 0x00, ANY))
return true;
return false;
}
/* 16 03 01 X is an TLS handshake */
static inline bool match_tls_handshake(uint32_t payload, uint32_t len) {
if (len == 0)
return true;
if (len == 1 && MATCH(payload, 0x16, 0x00, 0x00, 0x00))
return true;
if (MATCH(payload, 0x16, 0x03, 0x01, ANY))
return true;
if (MATCH(payload, 0x16, 0x03, 0x02, ANY))
return true;
if (MATCH(payload, 0x16, 0x03, 0x03, ANY))
return true;
return false;
}
/* SSLv2 handshake - the ANY byte in the 0x80 payload is actually the length
* of the payload - 2.
*
* XXX This isn't always true - consecutive packets may be merged it seems :(
*/
static inline bool match_ssl2_handshake(uint32_t payload, uint32_t len) {
if (MATCH(payload, 0x80, ANY, 0x01, 0x03))
return true;
if (MATCH(payload, 0x81, ANY, 0x01, 0x03))
return true;
return false;
}
static inline bool match_tls_alert(uint32_t payload, uint32_t len) {
if (MATCH(payload, 0x15, 0x03, 0x01, ANY))
return true;
if (MATCH(payload, 0x15, 0x03, 0x02, ANY))
return true;
if (MATCH(payload, 0x15, 0x03, 0x03, ANY))
return true;
/* Alerts are also possible under SSL 3.0 */
if (MATCH(payload, 0x15, 0x03, 0x00, ANY))
return true;
return false;
}
static inline bool match_tls_change(uint32_t payload, uint32_t len) {
if (MATCH(payload, 0x14, 0x03, 0x01, ANY))
return true;
if (MATCH(payload, 0x14, 0x03, 0x02, ANY))
return true;
if (MATCH(payload, 0x14, 0x03, 0x03, ANY))
return true;
return false;
}
static inline bool match_tls_content(uint32_t payload, uint32_t len) {
if (MATCH(payload, 0x17, 0x03, 0x01, ANY))
return true;
if (MATCH(payload, 0x17, 0x03, 0x02, ANY))
return true;
if (MATCH(payload, 0x17, 0x03, 0x03, ANY))
return true;
return false;
}
bool match_ssl(lpi_data_t *data) {
if (match_ssl3_handshake(data->payload[0], data->payload_len[0]) &&
match_ssl3_handshake(data->payload[1], data->payload_len[1]))
return true;
if (match_tls_handshake(data->payload[0], data->payload_len[0]) &&
match_tls_handshake(data->payload[1], data->payload_len[1]))
return true;
if (match_ssl3_handshake(data->payload[0], data->payload_len[0]) &&
match_tls_handshake(data->payload[1], data->payload_len[1]))
return true;
if (match_tls_handshake(data->payload[0], data->payload_len[0]) &&
match_ssl3_handshake(data->payload[1], data->payload_len[1]))
return true;
/* Seems we can sometimes skip the full handshake and start on the data
* right away (as indicated by 0x17) - for now, I've only done this for TLS */
if (match_tls_handshake(data->payload[0], data->payload_len[0]) &&
match_tls_content(data->payload[1], data->payload_len[1]))
return true;
if (match_tls_handshake(data->payload[1], data->payload_len[1]) &&
match_tls_content(data->payload[0], data->payload_len[0]))
return true;
/* Need to check for TLS alerts (errors) too */
if (match_tls_handshake(data->payload[0], data->payload_len[0]) &&
match_tls_alert(data->payload[1], data->payload_len[1]))
return true;
if (match_tls_handshake(data->payload[1], data->payload_len[1]) &&
match_tls_alert(data->payload[0], data->payload_len[0]))
return true;
if (match_ssl3_handshake(data->payload[0], data->payload_len[0]) &&
match_tls_alert(data->payload[1], data->payload_len[1]))
return true;
if (match_ssl3_handshake(data->payload[1], data->payload_len[1]) &&
match_tls_alert(data->payload[0], data->payload_len[0]))
return true;
/* Need to check for cipher changes too */
if (match_tls_handshake(data->payload[0], data->payload_len[0]) &&
match_tls_change(data->payload[1], data->payload_len[1]))
return true;
if (match_tls_handshake(data->payload[1], data->payload_len[1]) &&
match_tls_change(data->payload[0], data->payload_len[0]))
return true;
/* Some HTTPS servers respond with unencrypted content, presumably
* when somebody invalid attempts a connection */
if (match_tls_handshake(data->payload[0], data->payload_len[0]) &&
MATCHSTR(data->payload[1], "payload_len[0] != 0)
return true;
if (match_tls_handshake(data->payload[1], data->payload_len[1]) &&
MATCHSTR(data->payload[0], "payload_len[1] != 0)
return true;
/* Allow TLS content in both directions -- could be multi-path TCP?
* Or some form of picking up where a previous connection left off?
*/
if (match_tls_content(data->payload[0], data->payload_len[0]) &&
match_tls_content(data->payload[1], data->payload_len[1]))
return true;
if (match_tls_content(data->payload[1], data->payload_len[1]) &&
match_tls_content(data->payload[0], data->payload_len[0]))
return true;
if ((match_tls_handshake(data->payload[0], data->payload_len[0]) ||
match_ssl3_handshake(data->payload[0], data->payload_len[0])) &&
match_ssl2_handshake(data->payload[1], data->payload_len[1]))
return true;
if ((match_tls_handshake(data->payload[1], data->payload_len[1]) ||
match_ssl3_handshake(data->payload[1], data->payload_len[1])) &&
match_ssl2_handshake(data->payload[0], data->payload_len[0]))
return true;
if (data->payload_len[0] == 0 && match_ssl2_handshake(data->payload[1], data->payload_len[1]))
return true;
if (data->payload_len[1] == 0 && match_ssl2_handshake(data->payload[0], data->payload_len[0]))
return true;
return false;
}
static bool dns_req(uint32_t payload) {
/* The flags / rcode on requests are usually all zero.
*
* Exceptions: CD and RD may be set
*
* Remember BYTE ORDER!
*/
payload = htonl(payload);
if ((payload & 0x0000ffff) == 0x00000000)
return true;
/* Check for CD */
if ((payload & 0x0000ffff) == 0x00000010)
return true;
/* Check for RD */
if ((payload & 0x0000ffff) == 0x00000100)
return true;
return false;
}
static bool dns_backscatter(uint32_t payload) {
/* Let's see if we can identify unsolicited DNS responses */
/* Last byte seems to be always 0x00 - third is either 0x84 or 0x85 */
payload = htonl(payload);
if ((payload & 0x0000ffff) == 0x00008500)
return true;
if ((payload & 0x0000ffff) == 0x00008580)
return true;
if ((payload & 0x0000ffff) == 0x00008400)
return true;
if ((payload & 0x0000ffff) == 0x00008480)
return true;
if ((payload & 0x0000ffff) == 0x00008483)
return true;
if ((payload & 0x0000ffff) == 0x00008403)
return true;
if ((payload & 0x0000ffff) == 0x00008000)
return true;
return false;
}
bool match_dns(lpi_data_t *data) {
if (data->payload_len[0] == 0 || data->payload_len[1] == 0) {
/* No response, so we have a bit of a hard time - however,
* most requests have a pretty standard set of flags.
*
* We'll also use the port here to help out */
if (data->server_port != 53 && data->client_port != 53)
return false;
if (data->payload_len[0] > 12 && dns_req(data->payload[0]))
return true;
if (data->payload_len[1] > 12 && dns_req(data->payload[1]))
return true;
if (data->payload_len[0] > 12 &&
dns_backscatter(data->payload[0]))
return true;
if (data->payload_len[1] > 12 &&
dns_backscatter(data->payload[1]))
return true;
return false;
}
if (((htonl(data->payload[0])) & 0xffff7800) !=
((htonl(data->payload[1])) & 0xffff7800))
return false;
if ((htonl(data->payload[0]) & 0x00008000) ==
(htonl(data->payload[1]) & 0x00008000))
return false;
return true;
}
bool match_tds_request(uint32_t payload, uint32_t len) {
uint32_t stated_len = 0;
stated_len = (ntohl(payload) & 0xffff);
if (stated_len != len)
return false;
if (MATCH(payload, 0x12, 0x01, ANY, ANY))
return true;
if (MATCH(payload, 0x10, 0x01, ANY, ANY))
return true;
return false;
}
bool match_8000_payload(uint32_t payload, uint32_t len) {
if (len == 0)
return true;
if (MATCH(payload, 0x3b, 0x00, 0x00, 0x00)) {
return true;
}
if (MATCH(payload, 0x3c, 0x00, 0x00, 0x00)) {
return true;
}
if (MATCH(payload, 0x3d, 0x00, 0x00, 0x00)) {
return true;
}
if (MATCH(payload, 0x3e, 0x00, 0x00, 0x00)) {
return true;
}
return false;
}
bool match_emule(lpi_data_t *data) {
/* Check that payload begins with e3 or c5 in both directions before
* classifying as eMule */
/* (I noticed that most emule(probably) flows began with "e3 xx 00 00"
* or "c5 xx 00 00", perhaps is worth looking into... Although I
* couldn't find anything about emule packets) */
if (data->payload_len[0] < 4 && data->payload_len[1] < 4)
return false;
if (MATCH(data->payload[0], 0xe3, ANY, 0x00, 0x00) &&
MATCH(data->payload[1], 0xe3, ANY, 0x00, 0x00))
return true;
if (MATCH(data->payload[0], 0xe3, ANY, 0x00, 0x00) &&
MATCH(data->payload[1], 0xc5, ANY, 0x00, 0x00))
return true;
/* XXX I haven't seen any obviously legit emule that starts with c5
* in both directions */
/*
if (MATCH(data->payload[0], 0xc5, ANY, ANY, ANY) &&
MATCH(data->payload[1], 0xc5, ANY, ANY, ANY))
return true;
*/
if (MATCH(data->payload[0], 0xc5, ANY, 0x00, 0x00) &&
MATCH(data->payload[1], 0xe3, ANY, 0x00, 0x00))
return true;
if (MATCH(data->payload[0], 0xe3, ANY, 0x00, 0x00) &&
data->payload_len[1] == 0)
return true;
if (MATCH(data->payload[1], 0xe3, ANY, 0x00, 0x00) &&
data->payload_len[0] == 0)
return true;
return false;
}
static inline bool match_kaspersky_ke(uint32_t payload, uint32_t len) {
if (len == 0)
return true;
if (MATCH(payload, 'K', 'E', 0x00, 0x00))
return true;
if (MATCH(payload, 'K', 'E', 0x00, 0x02))
return true;
if (MATCH(payload, 'K', 'E', 0x00, 0x07))
return true;
return false;
}
static inline bool match_kaspersky_ks(uint32_t payload, uint32_t len) {
if (len == 0)
return true;
if (MATCH(payload, 'K', 'S', 0x00, 0x00))
return true;
return false;
}
bool match_kaspersky(lpi_data_t *data) {
/* Traffic is either on TCP port 443 or UDP port 2001.
*
* One of the endpoints is always in either a Kaspersky range or
* an old PSInet range */
if (match_str_both(data, "PI\x00\x00", "PI\x00\x00")) {
if (data->payload_len[0] == 2 && data->payload_len[1] == 2)
return true;
}
if (match_kaspersky_ke(data->payload[0], data->payload_len[0])) {
if (match_kaspersky_ke(data->payload[1], data->payload_len[1]))
return true;
}
if (match_kaspersky_ks(data->payload[0], data->payload_len[0])) {
if (match_kaspersky_ks(data->payload[1], data->payload_len[1]))
return true;
}
return false;
}
bool match_youku_payload(uint32_t pload, uint32_t len) {
if (len == 0)
return true;
if (MATCH(pload, 0x4b, 0x55, 0x00, 0x01))
return true;
if (MATCH(pload, 0x4b, 0x55, 0x00, 0x02))
return true;
if (MATCH(pload, 0x4b, 0x55, 0x00, 0x03))
return true;
if (MATCH(pload, 0x4b, 0x55, 0x00, 0x04))
return true;
return false;
}
bool match_tpkt(uint32_t payload, uint32_t len) {
uint32_t stated_len = 0;
/*
* TPKT header is 03 00 + 2 bytes of length (including the TPKT header)
*/
if (!MATCH(payload, 0x03, 0x00, ANY, ANY))
return false;
stated_len = ntohl(payload) & 0xffff;
if (stated_len != len)
return false;
return true;
}
bool match_qqlive_payload(uint32_t payload, uint32_t len) {
uint32_t swap;
/* This appears to have a 3 byte header. First byte is always 0xfe.
* Second and third bytes are the length (minus the 3 byte header).
*/
if (len == 0)
return true;
swap = htonl(payload);
swap = (swap & 0xffff00) >> 8;
if (ntohs(swap) != len - 3)
return false;
/* Interestingly, the third and fourth byte always match */
swap = htonl(payload);
if ((swap & 0xff) != ((swap & 0xff00) >> 8))
return false;
if (MATCH(payload, 0xfe, ANY, ANY, ANY))
return true;
return false;
}
bool match_yy_payload(uint32_t payload, uint32_t len) {
/* The first four bytes are a length field, but using the
* wrong byte order...
*/
if (!MATCH(payload, ANY, ANY, 0x00, 0x00))
return false;
#if BYTE_ORDER == BIG_ENDIAN
if (bswap32(payload) == len)
return true;
#else
if (payload == len)
return true;
#endif
return false;
}
/* Byte swapping functions for various inttypes */
uint64_t byteswap64(uint64_t num)
{
return (byteswap32((num&0xFFFFFFFF00000000ULL)>>32))
|((uint64_t)byteswap32(num&0x00000000FFFFFFFFULL)<<32);
}
uint32_t byteswap32(uint32_t num)
{
return ((num&0x000000FFU)<<24)
| ((num&0x0000FF00U)<<8)
| ((num&0x00FF0000U)>>8)
| ((num&0xFF000000U)>>24);
}
uint16_t byteswap16(uint16_t num)
{
return ((num<<8)&0xFF00)|((num>>8)&0x00FF);
}