Add nDPI protocol analyzer and integrate with handshake analysis
This commit is contained in:
18
Dockerfile
18
Dockerfile
@@ -6,8 +6,22 @@ RUN apt-get update && apt-get install -y \
|
|||||||
iproute2 \
|
iproute2 \
|
||||||
iptables \
|
iptables \
|
||||||
tcpdump \
|
tcpdump \
|
||||||
|
build-essential \
|
||||||
|
git \
|
||||||
|
libpcap-dev \
|
||||||
|
libjson-c-dev \
|
||||||
|
pkg-config \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& 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
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy pubspec files first (for dependency caching)
|
# Copy pubspec files first (for dependency caching)
|
||||||
@@ -18,6 +32,10 @@ RUN dart pub get
|
|||||||
|
|
||||||
# Copy source code (invalidates cache from here)
|
# Copy source code (invalidates cache from here)
|
||||||
COPY lib/ ./lib/
|
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
|
# Compile the application
|
||||||
RUN dart compile exe lib/main.dart -o waylume_server
|
RUN dart compile exe lib/main.dart -o waylume_server
|
||||||
|
|||||||
@@ -358,55 +358,84 @@ class ProtocolBlockingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _analyzeHandshake(String handshakeData, Connection conn) {
|
static Future<void> _analyzeHandshake(String handshakeData, Connection conn) async {
|
||||||
print('════════════════ HANDSHAKE SIGNATURE ANALYSIS ════════════════');
|
print('════════════════ nDPI PROTOCOL ANALYSIS ════════════════');
|
||||||
print('📍 Connection: $conn');
|
print('📍 Connection: $conn');
|
||||||
|
|
||||||
// Extract raw bytes from tcpdump hex output
|
// Extract hex bytes from tcpdump output
|
||||||
final hexBytes = _extractHexBytes(handshakeData);
|
final hexBytes = _extractHexBytes(handshakeData);
|
||||||
final asciiData = _extractAsciiFromHex(hexBytes);
|
|
||||||
|
|
||||||
print('📊 Raw Data Length: ${handshakeData.length} chars');
|
if (hexBytes.isEmpty) {
|
||||||
print('🔢 Hex Bytes (first 64): ${hexBytes.take(64).join(' ')}');
|
print('❌ No hex data found in tcpdump output');
|
||||||
print('📝 ASCII Representation: ${asciiData.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}');
|
print('══════════════════════════════════════════════════════════════');
|
||||||
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))}');
|
return;
|
||||||
|
|
||||||
// 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 (detectedProtocol != null) {
|
// Convert hex bytes to single hex string for C analyzer
|
||||||
print('🎯 PROTOCOL IDENTIFIED: $detectedProtocol');
|
final hexString = hexBytes.join('');
|
||||||
print('📋 Signature: $signature');
|
print('🔢 Analyzing ${hexBytes.length} bytes of packet data');
|
||||||
} else {
|
|
||||||
print('❓ UNKNOWN PROTOCOL');
|
try {
|
||||||
print('💡 Pattern not recognized - logging for analysis');
|
// 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('══════════════════════════════════════════════════════════════');
|
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<String> _extractHexBytes(String tcpdumpOutput) {
|
static List<String> _extractHexBytes(String tcpdumpOutput) {
|
||||||
final hexPattern = RegExp(r'0x[0-9a-f]+:\s*([0-9a-f\s]+)', caseSensitive: false);
|
final hexPattern = RegExp(r'0x[0-9a-f]+:\s*([0-9a-f\s]+)', caseSensitive: false);
|
||||||
final matches = hexPattern.allMatches(tcpdumpOutput);
|
final matches = hexPattern.allMatches(tcpdumpOutput);
|
||||||
|
|||||||
121
protocol_analyzer.c
Normal file
121
protocol_analyzer.c
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <pcap.h>
|
||||||
|
#include <ndpi_api.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/ip.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <netinet/udp.h>
|
||||||
|
|
||||||
|
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 <hex_packet_data>\n", argv[0]);
|
||||||
|
printf("Example: %s \"4500003c...\"\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_ndpi();
|
||||||
|
analyze_packet_from_hex(argv[1]);
|
||||||
|
cleanup_ndpi();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user