initial work on "unroller" (decrypt wtv encrypted pcap)

This commit is contained in:
zefie
2025-07-17 21:45:58 -04:00
parent dc99128a12
commit 8123158a8c
4 changed files with 165 additions and 0 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 = "<html><body><h1>Test</h1><p>This is a paragraph.</p></body></html>";
const compressedHtml = lzpf.Compress(htmlString);
const decompressedHtml = lzpf.Decompress(compressedHtml);
console.log("HTML match:", htmlString === decompressedHtml.toString());

View File

@@ -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.');
});