From 8123158a8c2264249f185af4418039216c235874 Mon Sep 17 00:00:00 2001 From: zefie Date: Thu, 17 Jul 2025 21:45:58 -0400 Subject: [PATCH] initial work on "unroller" (decrypt wtv encrypted pcap) --- zefie_wtvp_minisrv/package-lock.json | 17 ++++ zefie_wtvp_minisrv/package.json | 2 + zefie_wtvp_minisrv/test_lzpf.js | 18 ++++ zefie_wtvp_minisrv/unroll_rc4.js | 128 +++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 zefie_wtvp_minisrv/test_lzpf.js create mode 100644 zefie_wtvp_minisrv/unroll_rc4.js diff --git a/zefie_wtvp_minisrv/package-lock.json b/zefie_wtvp_minisrv/package-lock.json index 8767f855..b5c8f1b3 100644 --- a/zefie_wtvp_minisrv/package-lock.json +++ b/zefie_wtvp_minisrv/package-lock.json @@ -17,10 +17,12 @@ "express": "^4.21.2", "follow-redirects": "^1.15.6", "html-entities": "^2.5.2", + "http-string-parser": "^0.0.6", "iconv-lite": "^0.6.3", "mime-types": "^2.1.35", "newsie": "1.2.1", "nntp-server-zefie": "^3.1.0", + "pcap-parser": "^0.2.1", "php-serialize": "^5.0.1", "proxy-agent": "^6.4.0", "rc4-crypto": "^1.5.0", @@ -1912,6 +1914,12 @@ "node": ">= 14" } }, + "node_modules/http-string-parser": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/http-string-parser/-/http-string-parser-0.0.6.tgz", + "integrity": "sha512-sngOeBkIL32kum4Z+FulU+3Ve41B3js1IKfel0WAhwLqDJiUPC1UTqFRBr2/IDw9dbks6B4xSIYgPiJU7ivxww==", + "license": "MIT" + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -2495,6 +2503,15 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/pcap-parser": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/pcap-parser/-/pcap-parser-0.2.1.tgz", + "integrity": "sha512-+1t1GiMpEHI+MFub/mpCmfpyU4oVOyn4h71Zp5GqC/2uv0yteM6MghazKBQMkNXgmmsCPT1JUMfqsF03cYjnyw==", + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/php-serialize": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/php-serialize/-/php-serialize-5.1.0.tgz", diff --git a/zefie_wtvp_minisrv/package.json b/zefie_wtvp_minisrv/package.json index 9664ecdf..cb71e72d 100644 --- a/zefie_wtvp_minisrv/package.json +++ b/zefie_wtvp_minisrv/package.json @@ -35,10 +35,12 @@ "express": "^4.21.2", "follow-redirects": "^1.15.6", "html-entities": "^2.5.2", + "http-string-parser": "^0.0.6", "iconv-lite": "^0.6.3", "mime-types": "^2.1.35", "newsie": "1.2.1", "nntp-server-zefie": "^3.1.0", + "pcap-parser": "^0.2.1", "php-serialize": "^5.0.1", "proxy-agent": "^6.4.0", "rc4-crypto": "^1.5.0", diff --git a/zefie_wtvp_minisrv/test_lzpf.js b/zefie_wtvp_minisrv/test_lzpf.js new file mode 100644 index 00000000..71929903 --- /dev/null +++ b/zefie_wtvp_minisrv/test_lzpf.js @@ -0,0 +1,18 @@ +const WTVLzpf = require("./includes/classes/WTVLzpf.js") +const lzpf = new WTVLzpf(); + +// Test with a simple string +const testString = "This is a test string to compress and decompress"; +const compressed = lzpf.Compress(testString); +const decompressed = lzpf.Decompress(compressed); + +console.log("Original:", testString); +console.log("Decompressed:", decompressed.toString()); +console.log("Match:", testString === decompressed.toString()); + +// Test with HTML-like data (which LZPF was optimized for) +const htmlString = "

Test

This is a paragraph.

"; +const compressedHtml = lzpf.Compress(htmlString); +const decompressedHtml = lzpf.Decompress(compressedHtml); + +console.log("HTML match:", htmlString === decompressedHtml.toString()); diff --git a/zefie_wtvp_minisrv/unroll_rc4.js b/zefie_wtvp_minisrv/unroll_rc4.js new file mode 100644 index 00000000..8d05af07 --- /dev/null +++ b/zefie_wtvp_minisrv/unroll_rc4.js @@ -0,0 +1,128 @@ +const fs = require('fs'); +const pcap = require('pcap-parser'); +const WTVSec = require('./includes/classes/WTVSec.js'); +const WTVLZPF = require('./includes/classes/WTVLzpf.js'); +const httpHeaderParser = require('http-string-parser'); + +const server_ip = '192.168.11.26'; // šŸ” Replace with actual IP +const connections = new Map(); // (key: `${srcIP}:${srcPort}<->${dstIP}:${dstPort}`) + +function connectionKey(src, sport, dst, dport) { + return `${src}:${sport}<->${dst}:${dport}`; +} + +class ConnectionState { + constructor() { + this.packets = []; + this.buffer = Buffer.alloc(0); + this.rc4 = null; + this.secure = false; + this.wtv = null; + this.incarnation = null; + } + + feed(data) { + this.buffer = Buffer.concat([this.buffer, data]); + this.packets.push(data); + + // Parse headers if not done + const text = this.buffer.toString(); + if (!this.wtv && text.includes('wtv-initial-key')) { + const headers = httpHeaderParser.parseResponse(text).headers; + if (headers['wtv-initial-key'] && headers['wtv-challenge']) { + const initialKey = headers['wtv-initial-key'].trim(); + const challenge = headers['wtv-challenge'].trim(); + + this.wtv = new WTVSec({ + config: { + keys: { + initial_shared_key: initialKey, + }, + debug_flags: { + debug: false + } + } + }); + + this.wtv.ProcessChallenge(challenge); + } + } + + if (!this.secure && text.includes('SECURE ON')) { + const headers = httpHeaderParser.parseRequest(text).headers; + let incarnationHeader = Object.keys(headers).find(k => k.toLowerCase() === 'wtv-incarnation'); + let incarnationValue = incarnationHeader ? headers[incarnationHeader].trim() : "1"; + + this.incarnation = parseInt(incarnationValue); + if (this.wtv) { + this.wtv.set_incarnation(this.incarnation); + this.secure = true; + console.log("šŸ” SECURE ON -- Starting decryption (incarnation =", this.incarnation + ")"); + } + } + } + + decrypt(data) { + if (this.secure && this.wtv) { + try { + return this.wtv.Decrypt(0, data); // Use key1 by default + } catch (e) { + console.error("Decryption failed:", e); + return data; + } + } + return data; + } +} + +// Main PCAP processing +const parser = pcap.parse(fs.createReadStream('pcap.pcap')); + +parser.on('packet', packet => { + const data = packet.data; + const ethType = data.readUInt16BE(12); + if (ethType !== 0x0800) return; // Not IPv4 + + const ipHeader = data.slice(14, 34); + const protocol = ipHeader[9]; + if (protocol !== 6) return; // Not TCP + + const srcIP = ipHeader.slice(12, 16).join('.'); + const dstIP = ipHeader.slice(16, 20).join('.'); + const tcpHeaderStart = 34; + const srcPort = data.readUInt16BE(tcpHeaderStart); + const dstPort = data.readUInt16BE(tcpHeaderStart + 2); + const tcpHeaderLen = (data[tcpHeaderStart + 12] >> 4) * 4; + const tcpPayloadOffset = tcpHeaderStart + tcpHeaderLen; + + const payload = data.slice(tcpPayloadOffset); + if (payload.length === 0) return; + + const isServer = srcIP === server_ip; + const connKey = connectionKey(srcIP, srcPort, dstIP, dstPort); + + if (!connections.has(connKey)) { + connections.set(connKey, new ConnectionState()); + } + + const state = connections.get(connKey); + state.feed(payload); + + const decrypted = state.decrypt(payload); + + /* TODO + if (decrypted.includes("wtv-lzpf")) { + const headers = decrypted.toString('utf8').split("\n\n")[0]; + const lzpf_data = decrypted.slice(headers.length + 2); + const lzpf = new WTVLZPF(); + process.stdout.write(headers); + const decomp_data = lzpf.Decompress(lzpf_data); + process.stdout.write(decomp_data); + } +*/ + process.stdout.write(decrypted); +}); + +parser.on('end', () => { + console.log('\nāœ… Done parsing PCAP.'); +});