/* * * 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); }