├── .gitignore ├── Cargo.lock ├── LICENSE ├── Cargo.toml ├── linker.ld ├── Makefile ├── .cargo └── config.toml ├── src ├── main.rs └── syscalls.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | shellcode.bin 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "shellcode" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shellcode" 3 | version = "1.0.0" 4 | edition = "2021" 5 | authors = ["James Johnson (jjohnsonjj1251@gmail.com)"] 6 | description = "Template for making shellcode in rust for various architectures" 7 | readme = "README.md" 8 | license = "WTFPL" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | 14 | [profile.dev] 15 | panic = "abort" 16 | 17 | [profile.release] 18 | panic = "abort" 19 | opt-level = "z" 20 | lto = true 21 | codegen-units = 1 22 | -------------------------------------------------------------------------------- /linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start); 2 | 3 | SECTIONS 4 | { 5 | . = ALIGN(16); 6 | 7 | .text : 8 | { 9 | *(.text.prologue) 10 | *(.text) 11 | *(.text.*) 12 | } 13 | 14 | . = ALIGN(16); 15 | 16 | .data : 17 | { 18 | *(.rodata) 19 | *(.rodata.*) 20 | *(.data) 21 | } 22 | 23 | .debug : 24 | { 25 | *(.interp) 26 | *(.comment) 27 | *(.debug_frame) 28 | *(.ARM.exidx) 29 | *(.MIPS.abiflags) 30 | *(.MIPS.options) 31 | *(.reginfo) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # FIXME: point this to bin directory of binutils 2 | CROSS_BINUTILS=/home/jaj/bin/aarch64-unknown-linux-gnu/bin 3 | 4 | STRIP=$(CROSS_BINUTILS)/strip 5 | OBJCOPY=$(CROSS_BINUTILS)/objcopy 6 | 7 | # FIXME: set this to the correct target triple 8 | TARGET=aarch64-unknown-none 9 | 10 | ELF_FILE=target/$(TARGET)/release/shellcode 11 | 12 | STRIP_OPTS=-s --strip-unneeded -x -X 13 | 14 | .PHONY: clean dis 15 | 16 | all: shellcode.bin 17 | 18 | shellcode.bin: $(ELF_FILE) 19 | $(STRIP) $(STRIP_OPTS) $(ELF_FILE) 20 | $(OBJCOPY) -O binary $(ELF_FILE) shellcode.bin 21 | 22 | $(ELF_FILE): src/* 23 | cargo build --release --target $(TARGET) 24 | 25 | dis: $(ELF_FILE) 26 | $(CROSS_BINUTILS)/objdump -d $(ELF_FILE) 27 | 28 | clean: 29 | cargo clean 30 | rm -f shellcode.bin 31 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | 3 | [target.x86_64-unknown-linux-gnu] 4 | rustflags = ["-C", "link-self-contained=no", "-C", "link-arg=-nostdlib", "-C", "link-arg=-nostartfiles", 5 | "-C", "link-arg=-Wl,-Tlinker.ld,--build-id=none", "-C", "link-arg=-static"] 6 | 7 | [target.i686-unknown-linux-gnu] 8 | rustflags = ["-C", "link-self-contained=no", "-C", "link-arg=-nostdlib", "-C", "link-arg=-nostartfiles", 9 | "-C", "link-arg=-Wl,-Tlinker.ld,--build-id=none", "-C", "link-arg=-static"] 10 | 11 | [target.armv7a-none-eabi] 12 | rustflags = ["-C", "link-self-contained=no", "-C", "link-arg=-nostdlib", 13 | "-C", "link-arg=-Tlinker.ld", "-C", "link-arg=--build-id=none", "-C", "link-arg=-static"] 14 | 15 | [target.aarch64-unknown-none] 16 | rustflags = ["-C", "link-self-contained=no", "-C", "link-arg=-nostdlib", 17 | "-C", "link-arg=-Tlinker.ld", "-C", "link-arg=--build-id=none", "-C", "link-arg=-static"] 18 | 19 | [target.mipsel-unknown-linux-musl] 20 | rustflags = ["-C", "link-self-contained=no", "-C", "linker-flavor=ld.lld", "-C", "link-arg=--nostdlib", 21 | "-C", "link-arg=-Tlinker.ld", "-C", "link-arg=--static", "-C", "link-arg=--build-id=none", 22 | "-C", "target-feature=+crt-static"] 23 | 24 | [target.mips64el-unknown-linux-muslabi64] 25 | rustflags = ["-C", "link-self-contained=no", "-C", "linker-flavor=ld.lld", "-C", "link-arg=--nostdlib", 26 | "-C", "link-arg=-Tlinker.ld", "-C", "link-arg=--static", "-C", "link-arg=--build-id=none", 27 | "-C", "target-feature=+crt-static"] 28 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! Template for writing shellcode in rust. 2 | //! 3 | //! The entrypoint of the shellcode is the `_start` function. It will be what is placed at the 4 | //! very beginning of the binary produced by the Makefile. Modify that function to create the 5 | //! shellcode that you want. Make sure to do an objdump of the binary to check and make sure 6 | //! that it is actually placed at the beginning of the shellcode. 7 | 8 | #![no_std] 9 | #![no_main] 10 | 11 | // This is only needed for the experimental asm architectures 12 | // See https://github.com/rust-lang/rust/issues/93335 13 | // Currently oly needed for mips of the suported architectures 14 | // #![feature(asm_experimental_arch)] 15 | 16 | mod syscalls; 17 | use crate::syscalls::*; 18 | 19 | /// Entry point of the shellcode 20 | /// 21 | /// This is the function that you want to modify. Other functions and modules can be added. 22 | /// Just make sure to check that everything is laid out properly in the binary. 23 | /// 24 | /// The size of the generated assembly will be affected by the return value of the function. 25 | /// If it will never return, change the return value to `!` and no function prologue or 26 | /// epilogue will be generated. 27 | /// 28 | /// The signature can be changed to whatever you want. It will take and return parameters in 29 | /// the standard sysv c abi. 30 | #[no_mangle] 31 | pub extern "C" fn _start() { 32 | let message: &str = "Hello, Shellcode!"; 33 | unsafe { 34 | let _ = write(1, message.as_bytes().as_ptr(), message.len()); 35 | exit(0); 36 | } 37 | } 38 | 39 | /// Panic handler 40 | /// 41 | /// The Cargo.toml file sets the panic behavior to abort so I don't think this functioin will 42 | /// be used. Just leave it so things compile. 43 | #[panic_handler] 44 | fn panic(_: &core::panic::PanicInfo) -> ! { 45 | loop {} 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Shellcode 2 | A template project for writing shellcode in rust. 3 | 4 | The goal of this template is to make it easy to write shellcode for the major architectures in rust without a lot of custom configuration. 5 | 6 | One of the drawbacks of this template is that the shellcode may not compile down to the smallest possible size as handmade or something 7 | more targeted to a specific architecture. x86_64 and aarch64 optimize to be quite small, but the others can get a little bloated. 8 | 9 | ## Getting Started 10 | ### Binutils 11 | To use this template, a copy of binutils for the target architecture must be installed. Compiling binutils from scratch is quite easy 12 | and the recommended method. 13 | 14 | Go to `https://mirrors.ocf.berkeley.edu/gnu/binutils/` and download the newest version of binutils (NOTE: This template was made using binutils version 2.38). 15 | Extract the tar archive. 16 | 17 | Run: 18 | ```bash 19 | ./configure --prefix=[your install prefix here] \ 20 | --target=[target triple here] \ 21 | --disable-static \ 22 | --disable-multilib \ 23 | --disable--werror \ 24 | --disable-nls 25 | 26 | make 27 | 28 | make install 29 | ``` 30 | 31 | You should use one of the following target triples: 32 | - x86_64-unknown-linux-gnu 33 | - i386-unknown-linux-gnu 34 | - aarch64-unknown-linux-gnu 35 | - arm-unknown-linux-gnu 36 | - mipsel-unknown-linux-gnu 37 | - mips64el-unknown-linux-gnu 38 | 39 | ### Configuration 40 | The `Makefile` will need to be configured to build for the proper architecture. You can grep for 'FIXME' to 41 | see what to change. The only things that should be changed are the `CROSS_BINUTILS` variable and the `TARGET` variable. 42 | 43 | `CROSS_BINUTILS` should be set the where binutils was compiled or installed to. `TARGET` should be the target triple of 44 | the architecture to compile for. See `.cargo/config.toml` for the available targets. 45 | 46 | ### Building 47 | Just run `make`. `shellcode.bin` will be built if there are no errors. I recommend running `objdump` from binutils on the binary just to 48 | sanity check the output. `make dis` will run objdump on the elf file generated by rustc with the debug information not stripped. That should 49 | match exactly the assembly in `shellcode.bin`. I have done limited testing of this template so ymmv. 50 | 51 | ## Architectures 52 | The currently supported architectures are: 53 | - x86_64-unknown-linux-gnu 54 | - i686-unknown-linux-gnu 55 | - armv7a-none-eabi 56 | - aarch64-unknown-none 57 | - mipsel-unknown-linux-musl 58 | - mips64el-unknown-linux-muslabi64 59 | 60 | It should be pretty easy to add support for other mips or arm targets. Just copy the target configuration and add a new target triple in the 61 | `.cargo/config.toml` file. It might require some configuration of linker flags or change to linker script but probably nothing too complicated. 62 | 63 | ## Contributing 64 | I'm happy to accept PRs to add new architectures or to help with code size. 65 | -------------------------------------------------------------------------------- /src/syscalls.rs: -------------------------------------------------------------------------------- 1 | //! Example write and exit syscall functions for various architectures 2 | 3 | use core::arch::asm; 4 | 5 | #[cfg(target_arch = "x86_64")] 6 | pub unsafe fn write(fd: usize, msg: *const u8, len: usize) -> Result { 7 | let sys_nr: usize = 1; 8 | let ret: isize; 9 | asm!( 10 | "syscall", 11 | in("rax") sys_nr, 12 | in("rdi") fd, 13 | in("rsi") msg, 14 | in("rdx") len, 15 | lateout("rax") ret, 16 | ); 17 | match ret { 18 | -1 => Err(()), 19 | _ => Ok(ret as usize), 20 | } 21 | } 22 | 23 | #[cfg(target_arch = "x86_64")] 24 | pub unsafe fn exit(ret: usize) -> ! { 25 | let sys_nr: usize = 60; 26 | asm!( 27 | "syscall", 28 | in("rax") sys_nr, 29 | in("rdi") ret, 30 | options(noreturn), 31 | ); 32 | } 33 | 34 | #[cfg(target_arch = "x86")] 35 | pub unsafe fn write(fd: usize, msg: *const u8, len: usize) -> Result { 36 | let sys_nr: usize = 1; 37 | let ret: isize; 38 | asm!( 39 | "syscall", 40 | in("eax") sys_nr, 41 | in("ebx") fd, 42 | in("ecx") msg, 43 | in("edx") len, 44 | lateout("eax") ret, 45 | ); 46 | match ret { 47 | -1 => Err(()), 48 | _ => Ok(ret as usize), 49 | } 50 | } 51 | 52 | #[cfg(target_arch = "x86")] 53 | pub unsafe fn exit(ret: usize) -> ! { 54 | let sys_nr: usize = 60; 55 | asm!( 56 | "syscall", 57 | in("eax") sys_nr, 58 | in("ebx") ret, 59 | options(noreturn), 60 | ); 61 | } 62 | 63 | #[cfg(target_arch = "arm")] 64 | pub unsafe fn write(fd: usize, msg: *const u8, len: usize) -> Result { 65 | let sys_nr: usize = 1; 66 | let ret: isize; 67 | asm!( 68 | "svc #0", 69 | in("r7") sys_nr, 70 | in("r0") fd, 71 | in("r1") msg, 72 | in("r2") len, 73 | lateout("r0") ret, 74 | ); 75 | match ret { 76 | -1 => Err(()), 77 | _ => Ok(ret as usize), 78 | } 79 | } 80 | 81 | #[cfg(target_arch = "arm")] 82 | pub unsafe fn exit(ret: usize) -> ! { 83 | let sys_nr: usize = 60; 84 | asm!( 85 | "svc #0", 86 | in("r7") sys_nr, 87 | in("r0") ret, 88 | options(noreturn), 89 | ); 90 | } 91 | 92 | #[cfg(target_arch = "aarch64")] 93 | pub unsafe fn write(fd: usize, msg: *const u8, len: usize) -> Result { 94 | let sys_nr: usize = 1; 95 | let ret: isize; 96 | asm!( 97 | "svc #0", 98 | in("x8") sys_nr, 99 | in("x0") fd, 100 | in("x1") msg, 101 | in("x2") len, 102 | lateout("x0") ret, 103 | ); 104 | match ret { 105 | -1 => Err(()), 106 | _ => Ok(ret as usize), 107 | } 108 | } 109 | 110 | #[cfg(target_arch = "aarch64")] 111 | pub unsafe fn exit(ret: usize) -> ! { 112 | let sys_nr: usize = 60; 113 | asm!( 114 | "svc #0", 115 | in("x8") sys_nr, 116 | in("x0") ret, 117 | options(noreturn), 118 | ); 119 | } 120 | 121 | #[cfg(any(target_arch = "mips", target_arch = "mips64"))] 122 | pub unsafe fn write(fd: usize, msg: *const u8, len: usize) -> Result { 123 | let sys_nr: usize = 1; 124 | let ret: isize; 125 | asm!( 126 | "syscall", 127 | in("$2") sys_nr, 128 | in("$4") fd, 129 | in("$5") msg, 130 | in("$6") len, 131 | lateout("$4") ret, 132 | ); 133 | match ret { 134 | -1 => Err(()), 135 | _ => Ok(ret as usize), 136 | } 137 | } 138 | 139 | #[cfg(any(target_arch = "mips", target_arch = "mips64"))] 140 | pub unsafe fn exit(ret: usize) -> ! { 141 | let sys_nr: usize = 60; 142 | asm!( 143 | "syscall", 144 | in("$2") sys_nr, 145 | in("$4") ret, 146 | options(noreturn), 147 | ); 148 | } 149 | --------------------------------------------------------------------------------