An Interest In:
Web News this Week
- April 19, 2024
- April 18, 2024
- April 17, 2024
- April 16, 2024
- April 15, 2024
- April 14, 2024
- April 13, 2024
How to Write and Compile a Shellcode in Rust
Last week, we saw how to execute a shellcode from memory in Rust. What if we could write the actual shellcode in Rust?
Writing shellcodes is usually done directly in assembly. It gives you absolute control over what you are crafting, however, it comes with many, many drawbacks:
- It requires a lot of deep knowledge that is not transferable
- Shellcodes are not portable across different architectures
- Assembly is like regexps: (barely) easy to write, impossible to read
- Assembly code is a nightmare to compose and reuse
- It's extremely easy to introduce bugs that are hard to debug
- It's a nightmare to maintain over time and across teams of many developers
What if instead, we could write our shellcodes in a language that is high-level and thanks to a highly advanced compiler, gives us precise, low-level control. A language that would make our shellcodes portables across architectures and easy to reuse.
Sounds too good to be true?
Without further ado, here is how to write shellcodes in Rust, so you will be able to judge by yourself.
This post is an excerpt from my book Black Hat Rust
Here is the assembly equivalent of the "Hello world" shellcode that we are about to craft in Rust:
_start: jmp short stringcode: pop rsi xor rax, rax mov al, 1 mov rdi, rax mov rdx, rdi add rdx, 12 syscall xor rax, rax add rax, 60 xor rdi, rdi syscallstring: call code db 'hello world',0x0A
Rust shellcode
First, we need to configure the linker to produce a bloat-free binary:
shellcode.ld
ENTRY(_start);SECTIONS{ . = ALIGN(16); .text : { *(.text.prologue) *(.text) *(.rodata) } .data : { *(.data) } /DISCARD/ : { *(.interp) *(.comment) *(.debug_frame) }}
And tell cargo
to use this file:
shellcode/.cargo/config.toml
[build]rustflags = ["-C", "link-arg=-nostdlib", "-C", "link-arg=-static", "-C", "link-arg=-Wl,-T./shellcode.ld,--build-id=none", "-C", "relocation-model=pic"]
Then, we need to configure Rust to optimize the final binary for size:
shellcode/Cargo.toml
[package]name = "shellcode"version = "0.1.0"edition = "2018"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies][profile.dev]panic = "abort"[profile.release]panic = "abort"opt-level = "z"lto = truecodegen-units = 1
Now the configuration is done, we can start crafting the shellcode.
First, the boilerplate:
shellcode/main.rs
#![no_std]#![no_main]use core::arch::asm;#[panic_handler]fn panic(_: &core::panic::PanicInfo) -> ! { loop {}}const SYS_WRITE: usize = 1;const SYS_EXIT: usize = 60;const STDOUT: usize = 1;static MESSAGE: &str = "hello world
";
Then, we implement the syscalls. It's the only part that requires assembly and is not architecture-agnostic:
unsafe fn syscall1(syscall: usize, arg1: usize) -> usize { let ret: usize; asm!( "syscall", in("rax") syscall, in("rdi") arg1, out("rcx") _, out("r11") _, lateout("rax") ret, options(nostack), ); ret}unsafe fn syscall3(syscall: usize, arg1: usize, arg2: usize, arg3: usize) -> usize { let ret: usize; asm!( "syscall", in("rax") syscall, in("rdi") arg1, in("rsi") arg2, in("rdx") arg3, out("rcx") _, out("r11") _, lateout("rax") ret, options(nostack), ); ret}
And finally, we can write the entry point of the shellcode:
#[no_mangle]fn _start() { unsafe { syscall3( SYS_WRITE, STDOUT, MESSAGE.as_ptr() as usize, MESSAGE.len(), ); syscall1(SYS_EXIT, 0) };}
It can be compiled with:
.PHONY: build_shellcodebuild_shellcode: cd shellcode && cargo +nightly build --release strip -s shellcode/target/release/shellcode objcopy -O binary shellcode/target/release/shellcode shellcode.bin
and examined with:
.PHONY: dump_shellcodedump_shellcode: build_shellcode objdump -D -b binary -mi386 -Mx86-64 -Mintel -z shellcode.bin
$ make dump_shellcodeDisassembly of section .data:00000000 <.data>: 0: 48 8d 35 13 00 00 00 lea rsi,[rip+0x13] # 0x1a 7: 6a 01 push 0x1 9: 58 pop rax a: 6a 0c push 0xc c: 5a pop rdx d: 48 89 c7 mov rdi,rax 10: 0f 05 syscall 12: 6a 3c push 0x3c 14: 58 pop rax 15: 31 ff xor edi,edi 17: 0f 05 syscall 19: c3 ret 1a: 68 65 6c 6c 6f push 0x6f6c6c65 # "hello world
" 1f: 20 77 6f and BYTE PTR [rdi+0x6f],dh 22: 72 6c jb 0x90 24: 64 fs 25: 0a .byte 0xa
38 bytes! It's even better than our hand-crafted shellcode!
The only imperfection is the useless ret
instruction.
$ make execute_shellcodehello world
The code is on GitHub
As usual, you can find the code on GitHub: github.com/skerkour/kerkour.com (please don't forget to star the repo ).
Want to learn more Rust, Offensive Security and Applied Cryptography? Take a look at my book Black Hat Rust where, among other things, you will learn how to craft more advanced shellcodes with Rust.
Original Link: https://dev.to/sylvainkerkour/how-to-write-and-compile-a-shellcode-in-rust-3iip
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To