Hilbish/readline/examples/commands.go

316 lines
11 KiB
Go

package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"github.com/jessevdk/go-flags"
"github.com/maxlandon/readline"
)
// This file declares a go-flags parser and a few commands.
var (
// commandParser - The command parser used by the example console.
commandParser = flags.NewNamedParser("example", flags.IgnoreUnknown)
)
func bindCommands() (err error) {
// core console
// ----------------------------------------------------------------------------------------
ex, err := commandParser.AddCommand("exit", // Command string
"Exit from the client/server console", // Description (completions, help usage)
"", // Long description
&Exit{}) // Command implementation
ex.Aliases = []string{"core"}
cd, err := commandParser.AddCommand("cd",
"Change client working directory",
"",
&ChangeClientDirectory{})
cd.Aliases = []string{"core"}
ls, err := commandParser.AddCommand("ls",
"List directory contents",
"",
&ListClientDirectories{})
ls.Aliases = []string{"core"}
// Log
log, err := commandParser.AddCommand("log",
"Manage log levels of one or more components",
"",
&Log{})
log.Aliases = []string{"core"}
// Implant generation
// ----------------------------------------------------------------------------------------
g, err := commandParser.AddCommand("generate",
"Configure and compile an implant (staged or stager)",
"",
&Generate{})
g.Aliases = []string{"builds"}
g.SubcommandsOptional = true
_, err = g.AddCommand("stager",
"Generate a stager shellcode payload using MSFVenom, (to file: --save, to stdout: --format",
"",
&GenerateStager{})
r, err := commandParser.AddCommand("regenerate",
"Recompile an implant by name, passed as argument (completed)",
"",
&Regenerate{})
r.Aliases = []string{"builds"}
// Add choices completions (and therefore completions) to some of these commands.
loadArgumentCompletions(commandParser)
return
}
// Exit - Kill the current client console
type Exit struct{}
// Execute - Run
func (e *Exit) Execute(args []string) (err error) {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Confirm exit (Y/y): ")
text, _ := reader.ReadString('\n')
answer := strings.TrimSpace(text)
if (answer == "Y") || (answer == "y") {
os.Exit(0)
}
fmt.Println()
return
}
// ChangeClientDirectory - Change the working directory of the client console
type ChangeClientDirectory struct {
Positional struct {
Path string `description:"local path" required:"1-1"`
} `positional-args:"yes" required:"yes"`
}
// Execute - Handler for ChangeDirectory
func (cd *ChangeClientDirectory) Execute(args []string) (err error) {
dir, err := expand(cd.Positional.Path)
err = os.Chdir(dir)
if err != nil {
fmt.Printf(CommandError+"%s \n", err)
} else {
fmt.Printf(Info+"Changed directory to %s \n", dir)
}
return
}
// ListClientDirectories - List directory contents
type ListClientDirectories struct {
Positional struct {
Path []string `description:"local directory/file"`
} `positional-args:"yes"`
}
// Execute - Command
func (ls *ListClientDirectories) Execute(args []string) error {
base := []string{"ls", "--color", "-l"}
if len(ls.Positional.Path) == 0 {
ls.Positional.Path = []string{"."}
}
fullPaths := []string{}
for _, path := range ls.Positional.Path {
full, _ := expand(path)
fullPaths = append(fullPaths, full)
}
base = append(base, fullPaths...)
// Print output
out, err := shellExec(base[0], base[1:])
if err != nil {
fmt.Printf(CommandError+"%s \n", err.Error())
return nil
}
// Print output
fmt.Println(out)
return nil
}
// shellExec - Execute a program
func shellExec(executable string, args []string) (string, error) {
path, err := exec.LookPath(executable)
if err != nil {
return "", err
}
cmd := exec.Command(path, args...)
// Load OS environment
cmd.Env = os.Environ()
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
return strings.Trim(string(out), "/"), nil
}
// Generate - Configure and compile an implant
type Generate struct {
StageOptions // Command makes use of full stage options
}
// StageOptions - All these options, regrouped by area, are used by any command that needs full
// configuration information for a stage Sliver implant.
type StageOptions struct {
// CoreOptions - All options about OS/arch, files to save, debugs, etc.
CoreOptions struct {
OS string `long:"os" short:"o" description:"target host operating system" default:"windows" value-name:"stage OS"`
Arch string `long:"arch" short:"a" description:"target host CPU architecture" default:"amd64" value-name:"stage architectures"`
Format string `long:"format" short:"f" description:"output formats (exe, shared (DLL), service (see 'psexec' for info), shellcode (Windows only)" default:"exe" value-name:"stage formats"`
Profile string `long:"profile-name" description:"implant profile name to use (use with generate-profile)"`
Name string `long:"name" short:"N" description:"implant name to use (overrides random name generation)"`
Save string `long:"save" short:"s" description:"directory/file where to save binary"`
Debug bool `long:"debug" short:"d" description:"enable debug features (incompatible with obfuscation, and prevailing)"`
} `group:"core options"`
// TransportOptions - All options pertaining to transport/RPC matters
TransportOptions struct {
MTLS []string `long:"mtls" short:"m" description:"mTLS C2 domain(s), comma-separated (ex: mtls://host:port)" env-delim:","`
DNS []string `long:"dns" short:"n" description:"DNS C2 domain(s), comma-separated (ex: dns://mydomain.com)" env-delim:","`
HTTP []string `long:"http" short:"h" description:"HTTP(S) C2 domain(s)" env-delim:","`
NamedPipe []string `long:"named-pipe" short:"p" description:"Named pipe transport strings, comma-separated" env-delim:","`
TCPPivot []string `long:"tcp-pivot" short:"i" description:"TCP pivot transport strings, comma-separated" env-delim:","`
Reconnect int `long:"reconnect" short:"j" description:"attempt to reconnect every n second(s)" default:"60"`
MaxErrors int `long:"max-errors" short:"k" description:"max number of transport errors" default:"10"`
} `group:"transport options"`
// SecurityOptions - All security-oriented options like restrictions.
SecurityOptions struct {
LimitDatetime string `long:"limit-datetime" short:"w" description:"limit execution to before datetime"`
LimitDomain bool `long:"limit-domain-joined" short:"D" description:"limit execution to domain joined machines"`
LimitUsername string `long:"limit-username" short:"U" description:"limit execution to specified username"`
LimitHosname string `long:"limit-hostname" short:"H" description:"limit execution to specified hostname"`
LimitFileExits string `long:"limit-file-exists" short:"F" description:"limit execution to hosts with this file in the filesystem"`
} `group:"security options"`
// EvasionOptions - All proactive security options (obfuscation, evasion, etc)
EvasionOptions struct {
Canary []string `long:"canary" short:"c" description:"DNS canary domain strings, comma-separated" env-delim:","`
SkipSymbols bool `long:"skip-obfuscation" short:"b" description:"skip binary/symbol obfuscation"`
Evasion bool `long:"evasion" short:"e" description:"enable evasion features"`
} `group:"evasion options"`
}
// Execute - Configure and compile an implant
func (g *Generate) Execute(args []string) (err error) {
save := g.CoreOptions.Save
if save == "" {
save, _ = os.Getwd()
}
fmt.Println("Executed 'generate' command. ")
return
}
// Regenerate - Recompile an implant by name, passed as argument (completed)
type Regenerate struct {
Positional struct {
ImplantName string `description:"Name of Sliver implant to recompile" required:"1-1"`
} `positional-args:"yes" required:"yes"`
Save string `long:"save" short:"s" description:"Directory/file where to save binary"`
}
// Execute - Recompile an implant with a given profile
func (r *Regenerate) Execute(args []string) (err error) {
fmt.Println("Executed 'regenerate' command. ")
return
}
// GenerateStager - Generate a stager payload using MSFVenom
type GenerateStager struct {
PayloadOptions struct {
OS string `long:"os" short:"o" description:"target host operating system" default:"windows" value-name:"stage OS"`
Arch string `long:"arch" short:"a" description:"target host CPU architecture" default:"amd64" value-name:"stage architectures"`
Format string `long:"msf-format" short:"f" description:"output format (MSF Venom formats). List is auto-completed" default:"raw" value-name:"MSF Venom transform formats"`
BadChars string `long:"badchars" short:"b" description:"bytes to exclude from stage shellcode"`
Save string `long:"save" short:"s" description:"directory to save the generated stager to"`
} `group:"payload options"`
TransportOptions struct {
LHost string `long:"lhost" short:"l" description:"listening host address" required:"true"`
LPort int `long:"lport" short:"p" description:"listening host port" default:"8443"`
Protocol string `long:"protocol" short:"P" description:"staging protocol (tcp/http/https)" default:"tcp" value-name:"stager protocol"`
} `group:"transport options"`
}
// Execute - Generate a stager payload using MSFVenom
func (g *GenerateStager) Execute(args []string) (err error) {
fmt.Println("Executed 'generate stager' subcommand. ")
return
}
// Log - Log management commands. Sets log level by default.
type Log struct {
Positional struct {
Level string `description:"log level to filter by" required:"1-1"`
Components []string `description:"components on which to apply log filter" required:"1"`
} `positional-args:"yes" required:"true"`
}
// Execute - Set the log level of one or more components
func (l *Log) Execute(args []string) (err error) {
fmt.Println("Executed 'log' command. ")
return
}
var (
Info = fmt.Sprintf("%s[-]%s ", readline.BLUE, readline.RESET)
Warn = fmt.Sprintf("%s[!]%s ", readline.YELLOW, readline.RESET)
Error = fmt.Sprintf("%s[!]%s ", readline.RED, readline.RESET)
Success = fmt.Sprintf("%s[*]%s ", readline.GREEN, readline.RESET)
Infof = fmt.Sprintf("%s[-] ", readline.BLUE) // Infof - formatted
Warnf = fmt.Sprintf("%s[!] ", readline.YELLOW) // Warnf - formatted
Errorf = fmt.Sprintf("%s[!] ", readline.RED) // Errorf - formatted
Sucessf = fmt.Sprintf("%s[*] ", readline.GREEN) // Sucessf - formatted
RPCError = fmt.Sprintf("%s[RPC Error]%s ", readline.RED, readline.RESET)
CommandError = fmt.Sprintf("%s[Command Error]%s ", readline.RED, readline.RESET)
ParserError = fmt.Sprintf("%s[Parser Error]%s ", readline.RED, readline.RESET)
DBError = fmt.Sprintf("%s[DB Error]%s ", readline.RED, readline.RESET)
)
// expand will expand a path with ~ to the $HOME of the current user.
func expand(path string) (string, error) {
if path == "" {
return path, nil
}
home := os.Getenv("HOME")
if home == "" {
usr, err := user.Current()
if err != nil {
return "", err
}
home = usr.HomeDir
}
return filepath.Abs(strings.Replace(path, "~", home, 1))
}