Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
December 12, 2022 02:00 pm GMT

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

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To