TLDR You can clone and modify this example repository.
Add the rust target to compile for plain x86_64:
rustup target add x86_64-unknown-none
Create a new binary crate:
cargo new --bin shellcode
Add to your Cargo.toml the following configurations for the release profile to make the shellcode as small as possible.
[profile.release]
panic = "abort" # don't unwind the stack on panic
opt-level = "z" # optimize for size
debug=0 # no debug info
debug-assertions = false #
overflow-checks = false # optional
strip="symbols" # remove symbols
The main.rs for our shellcode looks like this:
#![no_std]
#![no_main]
#![feature(core_intrinsics)]
const N: u64 = 100;
// our shellcode
#[no_mangle]
pub fn _start() {
let mut buffer = [0; 1024];
let mut sum = 0;
for i in 0..N {
let start = rdtsc();
buffer[i as usize] = 0;
let end = rdtsc();
sum += end - start;
}
print!(sum / N);
}
/// Handle the panics, by default abort
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
core::intrinsics::abort()
}
// simple macro to call the write syscall with an integer
macro_rules! print {
($value:expr) => {
unsafe{
core::arch::asm!(
"push {input}", // put the value to print on stack
"mov rdx, 8", // len = 8
"mov rsi, rsp", //char *buf = rsp
"mov rdi, 1", // fd = stdout
"mov rax, 1", // write
"syscall",
input = in(reg) $value,
out("rdx") _,
out("rsi") _,
out("rdi") _,
out("rax") _,
);
}
};
}
// safe wrapper, this might fails only if we are not on x86_64
pub fn rdtsc() -> u64 {
unsafe{core::arch::x86_64::_rdtsc()}
}
Finally, we can compile the shellcode with:
RUSTFLAGS="-C relocation-model=pie" cargo build --release --target="x86_64-unknown-none"
If you want your shellcode to exploit AVX and other instructions you can specify your target CPU with -C target-cpu=native.
This will create a file ./target/x86_64-unknown-none/release/shellcode which .text segment contains the shellcode.
objdump -D ./target/x86_64-unknown-none/release/shellcode -M intel -j .text
./target/x86_64-unknown-none/release/shellcode: file format elf64-x86-64
Disassembly of section .text:
000000000000120d <.text>:
120d: 6a 64 push 0x64
120f: 5f pop rdi
1210: 31 c9 xor ecx,ecx
1212: 48 83 ef 01 sub rdi,0x1
1216: 72 1d jb 0x1235
1218: 0f 31 rdtsc
121a: 48 89 d6 mov rsi,rdx
121d: 48 c1 e6 20 shl rsi,0x20
1221: 48 09 c6 or rsi,rax
1224: 0f 31 rdtsc
1226: 48 c1 e2 20 shl rdx,0x20
122a: 48 09 c2 or rdx,rax
122d: 48 29 f1 sub rcx,rsi
1230: 48 01 d1 add rcx,rdx
1233: eb dd jmp 0x1212
1235: 50 push rax
1236: 6a 64 push 0x64
1238: 5e pop rsi
1239: 48 89 c8 mov rax,rcx
123c: 31 d2 xor edx,edx
123e: 48 f7 f6 div rsi
1241: 48 89 c1 mov rcx,rax
1244: 51 push rcx
1245: 48 c7 c2 08 00 00 00 mov rdx,0x8
124c: 48 89 e6 mov rsi,rsp
124f: 48 c7 c7 01 00 00 00 mov rdi,0x1
1256: 48 c7 c0 01 00 00 00 mov rax,0x1
125d: 0f 05 syscall
125f: 58 pop rax
1260: c3 ret
To dump the shellcode to a binary file use objcopy to dump the whole text segment
objcopy -O binary ./target/x86_64-unknown-none/release/shellcode shellcode.bin -j .text
Caveats: Since we are dumping the .text segment, we can't use static or global variables because these would go in the sections .bss, .rodata, and .data which we won't load.
The dumped shellcode can be examined with:
objdump -D -b binary -mi386 -Mx86-64 -Mintel ./shellcode.bin
./shellcode.bin: file format binary
Disassembly of section .data:
00000000 <.data>:
0: 6a 64 push 0x64
2: 5f pop rdi
3: 31 c9 xor ecx,ecx
5: 48 83 ef 01 sub rdi,0x1
9: 72 1d jb 0x28
b: 0f 31 rdtsc
d: 48 89 d6 mov rsi,rdx
10: 48 c1 e6 20 shl rsi,0x20
14: 48 09 c6 or rsi,rax
17: 0f 31 rdtsc
19: 48 c1 e2 20 shl rdx,0x20
1d: 48 09 c2 or rdx,rax
20: 48 29 f1 sub rcx,rsi
23: 48 01 d1 add rcx,rdx
26: eb dd jmp 0x5
28: 50 push rax
29: 6a 64 push 0x64
2b: 5e pop rsi
2c: 48 89 c8 mov rax,rcx
2f: 31 d2 xor edx,edx
31: 48 f7 f6 div rsi
34: 48 89 c1 mov rcx,rax
37: 51 push rcx
38: 48 c7 c2 08 00 00 00 mov rdx,0x8
3f: 48 89 e6 mov rsi,rsp
42: 48 c7 c7 01 00 00 00 mov rdi,0x1
49: 48 c7 c0 01 00 00 00 mov rax,0x1
50: 0f 05 syscall
52: 58 pop rax
53: c3 ret
If the code editor keeps warning about duplciated panic handler, just create the file .cargo/config with:
[build]
target = "x86_64-unknown-none"