From 7f28184857e3212e3dfa953d5a19af135d38d344 Mon Sep 17 00:00:00 2001 From: ImBenji Date: Fri, 29 Aug 2025 01:29:06 +0100 Subject: [PATCH] Add nDPI protocol analyzer and integrate with handshake analysis --- Dockerfile | 18 +++ lib/services/protocol_blocking_service.dart | 107 ++++++++++------- protocol_analyzer.c | 121 ++++++++++++++++++++ 3 files changed, 207 insertions(+), 39 deletions(-) create mode 100644 protocol_analyzer.c diff --git a/Dockerfile b/Dockerfile index 8dfcf6c..53c73f4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,22 @@ RUN apt-get update && apt-get install -y \ iproute2 \ iptables \ tcpdump \ + build-essential \ + git \ + libpcap-dev \ + libjson-c-dev \ + pkg-config \ && rm -rf /var/lib/apt/lists/* +# Build and install nDPI +RUN git clone https://github.com/ntop/nDPI.git /tmp/nDPI && \ + cd /tmp/nDPI && \ + ./autogen.sh && \ + make && \ + make install && \ + ldconfig && \ + rm -rf /tmp/nDPI + WORKDIR /app # Copy pubspec files first (for dependency caching) @@ -18,6 +32,10 @@ RUN dart pub get # Copy source code (invalidates cache from here) COPY lib/ ./lib/ +COPY protocol_analyzer.c ./ + +# Compile the C protocol analyzer +RUN gcc -o protocol_analyzer protocol_analyzer.c -lndpi -lpcap # Compile the application RUN dart compile exe lib/main.dart -o waylume_server diff --git a/lib/services/protocol_blocking_service.dart b/lib/services/protocol_blocking_service.dart index 653f61d..3cc686c 100644 --- a/lib/services/protocol_blocking_service.dart +++ b/lib/services/protocol_blocking_service.dart @@ -358,55 +358,84 @@ class ProtocolBlockingService { } } - static void _analyzeHandshake(String handshakeData, Connection conn) { - print('════════════════ HANDSHAKE SIGNATURE ANALYSIS ════════════════'); + static Future _analyzeHandshake(String handshakeData, Connection conn) async { + print('════════════════ nDPI PROTOCOL ANALYSIS ════════════════'); print('📍 Connection: $conn'); - // Extract raw bytes from tcpdump hex output + // Extract hex bytes from tcpdump output final hexBytes = _extractHexBytes(handshakeData); - final asciiData = _extractAsciiFromHex(hexBytes); - print('📊 Raw Data Length: ${handshakeData.length} chars'); - print('🔢 Hex Bytes (first 64): ${hexBytes.take(64).join(' ')}'); - print('📝 ASCII Representation: ${asciiData.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); - print('🔍 First 32 bytes as string: ${String.fromCharCodes(hexBytes.take(32).map((h) => int.tryParse(h, radix: 16) ?? 0).where((b) => b >= 32 && b <= 126))}'); - - // Protocol detection with signature details - final data = handshakeData.toLowerCase(); - String? detectedProtocol; - String signature = ''; - - if (data.contains('bittorrent protocol') || hexBytes.join('').contains('13426974546f7272656e742070726f746f636f6c')) { - detectedProtocol = 'BitTorrent'; - signature = 'BitTorrent handshake signature detected'; - } else if (data.contains('ssh-2.0') || data.contains('ssh-1.')) { - detectedProtocol = 'SSH'; - signature = 'SSH protocol version string'; - } else if (data.contains('get ') || data.contains('post ') || data.contains('http/')) { - detectedProtocol = 'HTTP'; - signature = 'HTTP request headers'; - } else if (data.contains('220 ') && conn.remotePort == 25) { - detectedProtocol = 'SMTP'; - signature = 'SMTP welcome message'; - } else if (data.contains('220 ') && conn.remotePort == 21) { - detectedProtocol = 'FTP'; - signature = 'FTP welcome message'; - } else if (hexBytes.isNotEmpty && hexBytes.first == '16' && hexBytes.length > 5) { - // TLS detection - detectedProtocol = 'TLS/SSL'; - signature = 'TLS ClientHello/ServerHello (0x16 record type)'; + if (hexBytes.isEmpty) { + print('❌ No hex data found in tcpdump output'); + print('══════════════════════════════════════════════════════════════'); + return; } - if (detectedProtocol != null) { - print('🎯 PROTOCOL IDENTIFIED: $detectedProtocol'); - print('📋 Signature: $signature'); - } else { - print('❓ UNKNOWN PROTOCOL'); - print('💡 Pattern not recognized - logging for analysis'); + // Convert hex bytes to single hex string for C analyzer + final hexString = hexBytes.join(''); + print('🔢 Analyzing ${hexBytes.length} bytes of packet data'); + + try { + // Call our C nDPI analyzer + final result = await Process.run('./protocol_analyzer', [hexString]); + + if (result.exitCode == 0) { + print('✅ nDPI Analysis Results:'); + print(result.stdout.toString().trim()); + + // Parse JSON output to extract protocol info + try { + final jsonStr = result.stdout.toString().trim(); + // Simple protocol extraction - look for protocol field + final protocolMatch = RegExp(r'"protocol":\s*"([^"]+)"').firstMatch(jsonStr); + final categoryMatch = RegExp(r'"category":\s*"([^"]+)"').firstMatch(jsonStr); + + if (protocolMatch != null) { + final protocol = protocolMatch.group(1) ?? 'Unknown'; + final category = categoryMatch?.group(1) ?? 'Unknown'; + + print('🎯 DETECTED: $protocol (Category: $category)'); + + // Check if this is a protocol we want to block + if (_shouldBlockProtocol(protocol, category)) { + print('🚫 BLOCKING PROTOCOL: $protocol'); + // TODO: Implement blocking logic here + } else { + print('✅ ALLOWING PROTOCOL: $protocol'); + } + } + } catch (e) { + print('⚠️ Error parsing nDPI results: $e'); + } + + } else { + print('❌ nDPI analyzer failed:'); + print(' Exit code: ${result.exitCode}'); + print(' Error: ${result.stderr}'); + } + } catch (e) { + print('❌ Error running nDPI analyzer: $e'); } + print('══════════════════════════════════════════════════════════════'); } + static bool _shouldBlockProtocol(String protocol, String category) { + // Define protocols/categories to block + final blockedProtocols = { + 'BitTorrent', 'uTorrent', 'Transmission', 'qBittorrent', + 'eMule', 'KaZaA', 'Gnutella', 'DirectConnect', + 'Skype_Call', // Block Skype calls but allow chat + }; + + final blockedCategories = { + 'Download', 'P2P', 'FileSharing' + }; + + return blockedProtocols.contains(protocol) || + blockedCategories.contains(category); + } + static List _extractHexBytes(String tcpdumpOutput) { final hexPattern = RegExp(r'0x[0-9a-f]+:\s*([0-9a-f\s]+)', caseSensitive: false); final matches = hexPattern.allMatches(tcpdumpOutput); diff --git a/protocol_analyzer.c b/protocol_analyzer.c new file mode 100644 index 0000000..902dfb7 --- /dev/null +++ b/protocol_analyzer.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ndpi_detection_module_struct *ndpi_struct = NULL; +struct ndpi_flow_struct *ndpi_flow = NULL; +struct ndpi_id_struct *src_id = NULL, *dst_id = NULL; + +void init_ndpi() { + NDPI_PROTOCOL_BITMASK all; + + ndpi_struct = ndpi_init_detection_module(); + if (ndpi_struct == NULL) { + printf("ERROR: ndpi_init_detection_module failed\n"); + exit(1); + } + + // Enable all protocols + NDPI_BITMASK_SET_ALL(all); + ndpi_set_protocol_detection_bitmask2(ndpi_struct, &all); + + ndpi_finalize_initialization(ndpi_struct); + + // Allocate flow and ID structures + ndpi_flow = calloc(1, NDPI_DETECTION_ONLY_IPV4_FLOW_SIZE); + src_id = calloc(1, NDPI_ID_SIZE); + dst_id = calloc(1, NDPI_ID_SIZE); +} + +void cleanup_ndpi() { + if (ndpi_flow) free(ndpi_flow); + if (src_id) free(src_id); + if (dst_id) free(dst_id); + if (ndpi_struct) ndpi_exit_detection_module(ndpi_struct); +} + +void analyze_packet_from_hex(const char* hex_data) { + // Convert hex string to binary data + size_t hex_len = strlen(hex_data); + if (hex_len % 2 != 0) { + printf("ERROR: Invalid hex data length\n"); + return; + } + + size_t bin_len = hex_len / 2; + unsigned char *packet_data = malloc(bin_len); + + for (size_t i = 0; i < bin_len; i++) { + sscanf(hex_data + 2*i, "%2hhx", &packet_data[i]); + } + + // Basic IP header parsing + struct iphdr *ip_header = (struct iphdr*)packet_data; + if (bin_len < sizeof(struct iphdr) || ip_header->version != 4) { + printf("ERROR: Not a valid IPv4 packet\n"); + free(packet_data); + return; + } + + uint32_t src_ip = ntohl(ip_header->saddr); + uint32_t dst_ip = ntohl(ip_header->daddr); + uint16_t src_port = 0, dst_port = 0; + + // Extract ports for TCP/UDP + if (ip_header->protocol == IPPROTO_TCP) { + struct tcphdr *tcp_header = (struct tcphdr*)(packet_data + (ip_header->ihl * 4)); + src_port = ntohs(tcp_header->source); + dst_port = ntohs(tcp_header->dest); + } else if (ip_header->protocol == IPPROTO_UDP) { + struct udphdr *udp_header = (struct udphdr*)(packet_data + (ip_header->ihl * 4)); + src_port = ntohs(udp_header->source); + dst_port = ntohs(udp_header->dest); + } + + // Reset flow for new analysis + memset(ndpi_flow, 0, NDPI_DETECTION_ONLY_IPV4_FLOW_SIZE); + + // Perform nDPI detection + ndpi_protocol protocol = ndpi_detection_process_packet( + ndpi_struct, ndpi_flow, packet_data, bin_len, + 0, /* timestamp */ + src_id, dst_id + ); + + // Output results in JSON format for easy parsing + printf("{\n"); + printf(" \"src_ip\": \"%u.%u.%u.%u\",\n", + (src_ip >> 24) & 0xFF, (src_ip >> 16) & 0xFF, + (src_ip >> 8) & 0xFF, src_ip & 0xFF); + printf(" \"dst_ip\": \"%u.%u.%u.%u\",\n", + (dst_ip >> 24) & 0xFF, (dst_ip >> 16) & 0xFF, + (dst_ip >> 8) & 0xFF, dst_ip & 0xFF); + printf(" \"src_port\": %u,\n", src_port); + printf(" \"dst_port\": %u,\n", dst_port); + printf(" \"protocol\": \"%s\",\n", ndpi_protocol2name(ndpi_struct, protocol, NULL, 0)); + printf(" \"category\": \"%s\",\n", ndpi_category_get_name(ndpi_struct, protocol.category)); + printf(" \"confidence\": %u\n", protocol.confidence); + printf("}\n"); + + free(packet_data); +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + printf("Example: %s \"4500003c...\"\n", argv[0]); + return 1; + } + + init_ndpi(); + analyze_packet_from_hex(argv[1]); + cleanup_ndpi(); + + return 0; +} \ No newline at end of file