WireMCP

by 0xKoda
Verified
// index.js - WireMCP Server const axios = require('axios'); const { exec } = require('child_process'); const { promisify } = require('util'); const which = require('which'); const fs = require('fs').promises; const execAsync = promisify(exec); const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const { z } = require('zod'); // Redirect console.log to stderr const originalConsoleLog = console.log; console.log = (...args) => console.error(...args); // Dynamically locate tshark async function findTshark() { try { const tsharkPath = await which('tshark'); console.error(`Found tshark at: ${tsharkPath}`); return tsharkPath; } catch (err) { console.error('which failed to find tshark:', err.message); const fallbacks = process.platform === 'win32' ? ['C:\\Program Files\\Wireshark\\tshark.exe', 'C:\\Program Files (x86)\\Wireshark\\tshark.exe'] : ['/usr/bin/tshark', '/usr/local/bin/tshark', '/opt/homebrew/bin/tshark', '/Applications/Wireshark.app/Contents/MacOS/tshark']; for (const path of fallbacks) { try { await execAsync(`${path} -v`); console.error(`Found tshark at fallback: ${path}`); return path; } catch (e) { console.error(`Fallback ${path} failed: ${e.message}`); } } throw new Error('tshark not found. Please install Wireshark (https://www.wireshark.org/download.html) and ensure tshark is in your PATH.'); } } // Initialize MCP server const server = new McpServer({ name: 'wiremcp', version: '1.0.0', }); // Tool 1: Capture live packet data server.tool( 'capture_packets', 'Capture live traffic and provide raw packet data as JSON for LLM analysis', { interface: z.string().optional().default('en0').describe('Network interface to capture from (e.g., eth0, en0)'), duration: z.number().optional().default(5).describe('Capture duration in seconds'), }, async (args) => { try { const tsharkPath = await findTshark(); const { interface, duration } = args; const tempPcap = 'temp_capture.pcap'; console.error(`Capturing packets on ${interface} for ${duration}s`); await execAsync( `${tsharkPath} -i ${interface} -w ${tempPcap} -a duration:${duration}`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); const { stdout, stderr } = await execAsync( `${tsharkPath} -r "${tempPcap}" -T json -e frame.number -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e tcp.flags -e frame.time -e http.request.method -e http.response.code`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); if (stderr) console.error(`tshark stderr: ${stderr}`); let packets = JSON.parse(stdout); const maxChars = 720000; let jsonString = JSON.stringify(packets); if (jsonString.length > maxChars) { const trimFactor = maxChars / jsonString.length; const trimCount = Math.floor(packets.length * trimFactor); packets = packets.slice(0, trimCount); jsonString = JSON.stringify(packets); console.error(`Trimmed packets from ${packets.length} to ${trimCount} to fit ${maxChars} chars`); } await fs.unlink(tempPcap).catch(err => console.error(`Failed to delete ${tempPcap}: ${err.message}`)); return { content: [{ type: 'text', text: `Captured packet data (JSON for LLM analysis):\n${jsonString}`, }], }; } catch (error) { console.error(`Error in capture_packets: ${error.message}`); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }; } } ); // Tool 2: Capture and provide summary statistics server.tool( 'get_summary_stats', 'Capture live traffic and provide protocol hierarchy statistics for LLM analysis', { interface: z.string().optional().default('en0').describe('Network interface to capture from (e.g., eth0, en0)'), duration: z.number().optional().default(5).describe('Capture duration in seconds'), }, async (args) => { try { const tsharkPath = await findTshark(); const { interface, duration } = args; const tempPcap = 'temp_capture.pcap'; console.error(`Capturing summary stats on ${interface} for ${duration}s`); await execAsync( `${tsharkPath} -i ${interface} -w ${tempPcap} -a duration:${duration}`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); const { stdout, stderr } = await execAsync( `${tsharkPath} -r "${tempPcap}" -qz io,phs`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); if (stderr) console.error(`tshark stderr: ${stderr}`); await fs.unlink(tempPcap).catch(err => console.error(`Failed to delete ${tempPcap}: ${err.message}`)); return { content: [{ type: 'text', text: `Protocol hierarchy statistics for LLM analysis:\n${stdout}`, }], }; } catch (error) { console.error(`Error in get_summary_stats: ${error.message}`); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }; } } ); // Tool 3: Capture and provide conversation stats server.tool( 'get_conversations', 'Capture live traffic and provide TCP/UDP conversation statistics for LLM analysis', { interface: z.string().optional().default('en0').describe('Network interface to capture from (e.g., eth0, en0)'), duration: z.number().optional().default(5).describe('Capture duration in seconds'), }, async (args) => { try { const tsharkPath = await findTshark(); const { interface, duration } = args; const tempPcap = 'temp_capture.pcap'; console.error(`Capturing conversations on ${interface} for ${duration}s`); await execAsync( `${tsharkPath} -i ${interface} -w ${tempPcap} -a duration:${duration}`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); const { stdout, stderr } = await execAsync( `${tsharkPath} -r "${tempPcap}" -qz conv,tcp`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); if (stderr) console.error(`tshark stderr: ${stderr}`); await fs.unlink(tempPcap).catch(err => console.error(`Failed to delete ${tempPcap}: ${err.message}`)); return { content: [{ type: 'text', text: `TCP/UDP conversation statistics for LLM analysis:\n${stdout}`, }], }; } catch (error) { console.error(`Error in get_conversations: ${error.message}`); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }; } } ); // Tool 4: Capture traffic and check threats against URLhaus server.tool( 'check_threats', 'Capture live traffic and check IPs against URLhaus blacklist', { interface: z.string().optional().default('en0').describe('Network interface to capture from (e.g., eth0, en0)'), duration: z.number().optional().default(5).describe('Capture duration in seconds'), }, async (args) => { try { const tsharkPath = await findTshark(); const { interface, duration } = args; const tempPcap = 'temp_capture.pcap'; console.error(`Capturing traffic on ${interface} for ${duration}s to check threats`); await execAsync( `${tsharkPath} -i ${interface} -w ${tempPcap} -a duration:${duration}`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); const { stdout } = await execAsync( `${tsharkPath} -r "${tempPcap}" -T fields -e ip.src -e ip.dst`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); const ips = [...new Set(stdout.split('\n').flatMap(line => line.split('\t')).filter(ip => ip && ip !== 'unknown'))]; console.error(`Captured ${ips.length} unique IPs: ${ips.join(', ')}`); const urlhausUrl = 'https://urlhaus.abuse.ch/downloads/text/'; console.error(`Fetching URLhaus blacklist from ${urlhausUrl}`); let urlhausData; let urlhausThreats = []; try { const response = await axios.get(urlhausUrl); console.error(`URLhaus response status: ${response.status}, length: ${response.data.length} chars`); console.error(`URLhaus raw data (first 200 chars): ${response.data.slice(0, 200)}`); const ipRegex = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/; urlhausData = [...new Set(response.data.split('\n') .map(line => { const match = line.match(ipRegex); return match ? match[0] : null; }) .filter(ip => ip))]; console.error(`URLhaus lookup successful: ${urlhausData.length} blacklist IPs fetched`); console.error(`Sample URLhaus IPs: ${urlhausData.slice(0, 5).join(', ') || 'None'}`); urlhausThreats = ips.filter(ip => urlhausData.includes(ip)); console.error(`Checked IPs against URLhaus: ${urlhausThreats.length} threats found - ${urlhausThreats.join(', ') || 'None'}`); } catch (e) { console.error(`Failed to fetch URLhaus data: ${e.message}`); urlhausData = []; } const outputText = `Captured IPs:\n${ips.join('\n')}\n\n` + `Threat check against URLhaus blacklist:\n${ urlhausThreats.length > 0 ? `Potential threats: ${urlhausThreats.join(', ')}` : 'No threats detected in URLhaus blacklist.' }`; await fs.unlink(tempPcap).catch(err => console.error(`Failed to delete ${tempPcap}: ${err.message}`)); return { content: [{ type: 'text', text: outputText }], }; } catch (error) { console.error(`Error in check_threats: ${error.message}`); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }; } } ); // Tool 5: Check a specific IP against URLhaus IOCs server.tool( 'check_ip_threats', 'Check a given IP address against URLhaus blacklist for IOCs', { ip: z.string().regex(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/).describe('IP address to check (e.g., 192.168.1.1)'), }, async (args) => { try { const { ip } = args; console.error(`Checking IP ${ip} against URLhaus blacklist`); const urlhausUrl = 'https://urlhaus.abuse.ch/downloads/text/'; console.error(`Fetching URLhaus blacklist from ${urlhausUrl}`); let urlhausData; let isThreat = false; try { const response = await axios.get(urlhausUrl); console.error(`URLhaus response status: ${response.status}, length: ${response.data.length} chars`); console.error(`URLhaus raw data (first 200 chars): ${response.data.slice(0, 200)}`); const ipRegex = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/; urlhausData = [...new Set(response.data.split('\n') .map(line => { const match = line.match(ipRegex); return match ? match[0] : null; }) .filter(ip => ip))]; console.error(`URLhaus lookup successful: ${urlhausData.length} blacklist IPs fetched`); console.error(`Sample URLhaus IPs: ${urlhausData.slice(0, 5).join(', ') || 'None'}`); isThreat = urlhausData.includes(ip); console.error(`IP ${ip} checked against URLhaus: ${isThreat ? 'Threat found' : 'No threat found'}`); } catch (e) { console.error(`Failed to fetch URLhaus data: ${e.message}`); urlhausData = []; } const outputText = `IP checked: ${ip}\n\n` + `Threat check against URLhaus blacklist:\n${ isThreat ? 'Potential threat detected in URLhaus blacklist.' : 'No threat detected in URLhaus blacklist.' }`; return { content: [{ type: 'text', text: outputText }], }; } catch (error) { console.error(`Error in check_ip_threats: ${error.message}`); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }; } } ); // Tool 6: Analyze an existing PCAP file for general context server.tool( 'analyze_pcap', 'Analyze a PCAP file and provide general packet data as JSON for LLM analysis', { pcapPath: z.string().describe('Path to the PCAP file to analyze (e.g., ./demo.pcap)'), }, async (args) => { try { const tsharkPath = await findTshark(); const { pcapPath } = args; console.error(`Analyzing PCAP file: ${pcapPath}`); // Check if file exists await fs.access(pcapPath); // Extract broad packet data const { stdout, stderr } = await execAsync( `${tsharkPath} -r "${pcapPath}" -T json -e frame.number -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e udp.srcport -e udp.dstport -e http.host -e http.request.uri -e frame.protocols`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); if (stderr) console.error(`tshark stderr: ${stderr}`); const packets = JSON.parse(stdout); const ips = [...new Set(packets.flatMap(p => [ p._source?.layers['ip.src']?.[0], p._source?.layers['ip.dst']?.[0] ]).filter(ip => ip))]; console.error(`Found ${ips.length} unique IPs: ${ips.join(', ')}`); const urls = packets .filter(p => p._source?.layers['http.host'] && p._source?.layers['http.request.uri']) .map(p => `http://${p._source.layers['http.host'][0]}${p._source.layers['http.request.uri'][0]}`); console.error(`Found ${urls.length} URLs: ${urls.join(', ') || 'None'}`); const protocols = [...new Set(packets.map(p => p._source?.layers['frame.protocols']?.[0]))].filter(p => p); console.error(`Found protocols: ${protocols.join(', ') || 'None'}`); const maxChars = 720000; let jsonString = JSON.stringify(packets); if (jsonString.length > maxChars) { const trimFactor = maxChars / jsonString.length; const trimCount = Math.floor(packets.length * trimFactor); packets.splice(trimCount); jsonString = JSON.stringify(packets); console.error(`Trimmed packets from ${packets.length} to ${trimCount} to fit ${maxChars} chars`); } const outputText = `Analyzed PCAP: ${pcapPath}\n\n` + `Unique IPs:\n${ips.join('\n')}\n\n` + `URLs:\n${urls.length > 0 ? urls.join('\n') : 'None'}\n\n` + `Protocols:\n${protocols.join('\n') || 'None'}\n\n` + `Packet Data (JSON for LLM):\n${jsonString}`; return { content: [{ type: 'text', text: outputText }], }; } catch (error) { console.error(`Error in analyze_pcap: ${error.message}`); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }; } } ); // Tool 7: Extract credentials from a PCAP file server.tool( 'extract_credentials', 'Extract potential credentials (HTTP Basic Auth, FTP, Telnet) from a PCAP file for LLM analysis', { pcapPath: z.string().describe('Path to the PCAP file to analyze (e.g., ./demo.pcap)'), }, async (args) => { try { const tsharkPath = await findTshark(); const { pcapPath } = args; console.error(`Extracting credentials from PCAP file: ${pcapPath}`); await fs.access(pcapPath); // Extract plaintext credentials const { stdout: plaintextOut } = await execAsync( `${tsharkPath} -r "${pcapPath}" -T fields -e http.authbasic -e ftp.request.command -e ftp.request.arg -e telnet.data -e frame.number`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); // Extract Kerberos credentials const { stdout: kerberosOut } = await execAsync( `${tsharkPath} -r "${pcapPath}" -T fields -e kerberos.CNameString -e kerberos.realm -e kerberos.cipher -e kerberos.type -e kerberos.msg_type -e frame.number`, { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } } ); const lines = plaintextOut.split('\n').filter(line => line.trim()); const packets = lines.map(line => { const [authBasic, ftpCmd, ftpArg, telnetData, frameNumber] = line.split('\t'); return { authBasic: authBasic || '', ftpCmd: ftpCmd || '', ftpArg: ftpArg || '', telnetData: telnetData || '', frameNumber: frameNumber || '' }; }); const credentials = { plaintext: [], encrypted: [] }; // Process HTTP Basic Auth packets.forEach(p => { if (p.authBasic) { const [username, password] = Buffer.from(p.authBasic, 'base64').toString().split(':'); credentials.plaintext.push({ type: 'HTTP Basic Auth', username, password, frame: p.frameNumber }); } }); // Process FTP packets.forEach(p => { if (p.ftpCmd === 'USER') { credentials.plaintext.push({ type: 'FTP', username: p.ftpArg, password: '', frame: p.frameNumber }); } if (p.ftpCmd === 'PASS') { const lastUser = credentials.plaintext.findLast(c => c.type === 'FTP' && !c.password); if (lastUser) lastUser.password = p.ftpArg; } }); // Process Telnet packets.forEach(p => { if (p.telnetData) { const telnetStr = p.telnetData.trim(); if (telnetStr.toLowerCase().includes('login:') || telnetStr.toLowerCase().includes('password:')) { credentials.plaintext.push({ type: 'Telnet Prompt', data: telnetStr, frame: p.frameNumber }); } else if (telnetStr && !telnetStr.match(/[A-Z][a-z]+:/) && !telnetStr.includes(' ')) { const lastPrompt = credentials.plaintext.findLast(c => c.type === 'Telnet Prompt'); if (lastPrompt && lastPrompt.data.toLowerCase().includes('login:')) { credentials.plaintext.push({ type: 'Telnet', username: telnetStr, password: '', frame: p.frameNumber }); } else if (lastPrompt && lastPrompt.data.toLowerCase().includes('password:')) { const lastUser = credentials.plaintext.findLast(c => c.type === 'Telnet' && !c.password); if (lastUser) lastUser.password = telnetStr; else credentials.plaintext.push({ type: 'Telnet', username: '', password: telnetStr, frame: p.frameNumber }); } } } }); // Process Kerberos credentials const kerberosLines = kerberosOut.split('\n').filter(line => line.trim()); kerberosLines.forEach(line => { const [cname, realm, cipher, type, msgType, frameNumber] = line.split('\t'); if (cipher && type) { let hashFormat = ''; // Format hash based on message type if (msgType === '10' || msgType === '30') { // AS-REQ or TGS-REQ hashFormat = '$krb5pa$23$'; if (cname) hashFormat += `${cname}$`; if (realm) hashFormat += `${realm}$`; hashFormat += cipher; } else if (msgType === '11') { // AS-REP hashFormat = '$krb5asrep$23$'; if (cname) hashFormat += `${cname}@`; if (realm) hashFormat += `${realm}$`; hashFormat += cipher; } if (hashFormat) { credentials.encrypted.push({ type: 'Kerberos', hash: hashFormat, username: cname || 'unknown', realm: realm || 'unknown', frame: frameNumber, crackingMode: msgType === '11' ? 'hashcat -m 18200' : 'hashcat -m 7500' }); } } }); console.error(`Found ${credentials.plaintext.length} plaintext and ${credentials.encrypted.length} encrypted credentials`); const outputText = `Analyzed PCAP: ${pcapPath}\n\n` + `Plaintext Credentials:\n${credentials.plaintext.length > 0 ? credentials.plaintext.map(c => c.type === 'Telnet Prompt' ? `${c.type}: ${c.data} (Frame ${c.frame})` : `${c.type}: ${c.username}:${c.password} (Frame ${c.frame})` ).join('\n') : 'None'}\n\n` + `Encrypted/Hashed Credentials:\n${credentials.encrypted.length > 0 ? credentials.encrypted.map(c => `${c.type}: User=${c.username} Realm=${c.realm} (Frame ${c.frame})\n` + `Hash=${c.hash}\n` + `Cracking Command: ${c.crackingMode}\n` ).join('\n') : 'None'}\n\n` + `Note: Encrypted credentials can be cracked using tools like John the Ripper or hashcat.\n` + `For Kerberos hashes:\n` + `- AS-REQ/TGS-REQ: hashcat -m 7500 or john --format=krb5pa-md5\n` + `- AS-REP: hashcat -m 18200 or john --format=krb5asrep`; return { content: [{ type: 'text', text: outputText }], }; } catch (error) { console.error(`Error in extract_credentials: ${error.message}`); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }; } } ); // Add prompts for each tool server.prompt( 'capture_packets_prompt', { interface: z.string().optional().describe('Network interface to capture from'), duration: z.number().optional().describe('Duration in seconds to capture'), }, ({ interface = 'en0', duration = 5 }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please analyze the network traffic on interface ${interface} for ${duration} seconds and provide insights about: 1. The types of traffic observed 2. Any notable patterns or anomalies 3. Key IP addresses and ports involved 4. Potential security concerns` } }] }) ); server.prompt( 'summary_stats_prompt', { interface: z.string().optional().describe('Network interface to capture from'), duration: z.number().optional().describe('Duration in seconds to capture'), }, ({ interface = 'en0', duration = 5 }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please provide a summary of network traffic statistics from interface ${interface} over ${duration} seconds, focusing on: 1. Protocol distribution 2. Traffic volume by protocol 3. Notable patterns in protocol usage 4. Potential network health indicators` } }] }) ); server.prompt( 'conversations_prompt', { interface: z.string().optional().describe('Network interface to capture from'), duration: z.number().optional().describe('Duration in seconds to capture'), }, ({ interface = 'en0', duration = 5 }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please analyze network conversations on interface ${interface} for ${duration} seconds and identify: 1. Most active IP pairs 2. Conversation durations and data volumes 3. Unusual communication patterns 4. Potential indicators of network issues` } }] }) ); server.prompt( 'check_threats_prompt', { interface: z.string().optional().describe('Network interface to capture from'), duration: z.number().optional().describe('Duration in seconds to capture'), }, ({ interface = 'en0', duration = 5 }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please analyze traffic on interface ${interface} for ${duration} seconds and check for security threats: 1. Compare captured IPs against URLhaus blacklist 2. Identify potential malicious activity 3. Highlight any concerning patterns 4. Provide security recommendations` } }] }) ); server.prompt( 'check_ip_threats_prompt', { ip: z.string().describe('IP address to check'), }, ({ ip }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please analyze the following IP address (${ip}) for potential security threats: 1. Check against URLhaus blacklist 2. Evaluate the IP's reputation 3. Identify any known malicious activity 4. Provide security recommendations` } }] }) ); server.prompt( 'analyze_pcap_prompt', { pcapPath: z.string().describe('Path to the PCAP file'), }, ({ pcapPath }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please analyze the PCAP file at ${pcapPath} and provide insights about: 1. Overall traffic patterns 2. Unique IPs and their interactions 3. Protocols and services used 4. Notable events or anomalies 5. Potential security concerns` } }] }) ); server.prompt( 'extract_credentials_prompt', { pcapPath: z.string().describe('Path to the PCAP file'), }, ({ pcapPath }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please analyze the PCAP file at ${pcapPath} for potential credential exposure: 1. Look for plaintext credentials (HTTP Basic Auth, FTP, Telnet) 2. Identify Kerberos authentication attempts 3. Extract any hashed credentials 4. Provide security recommendations for credential handling` } }] }) ); // Start the server server.connect(new StdioServerTransport()) .then(() => console.error('WireMCP Server is running...')) .catch(err => { console.error('Failed to start WireMCP:', err); process.exit(1); });
ID: d63qil4l1s