An i686 & x86_64 position independent implant template for Rust ๐ฆ
Rust Position Independent Shellcode (PIC) Template for i686 & x86_64 Linux & Windows
[!warning]
This is an experiment and I can personally guarantee it is unsafe. I describe below some of the unobvious (to me) issues I ended up facing.
I'm keen to hear of any possible workarounds for these issues, just open a PR.
This code is based on the following previous work:
- https://bruteratel.com/research/feature-update/2021/01/30/OBJEXEC/
- https://5pider.net/blog/2024/01/27/modern-shellcode-implant-design/
- https://github.com/wumb0/rust_bof
- https://kerkour.com/rust-position-independent-shellcode
- https://github.com/safedv/Rustic64
- Compiling
nostdfori686-pc-windows-gnuignorespanic=abort - [Inclusion of
-lkernel32and others when compiling#![no_std]for i686-pc-windows-gnu](https://users.rust-lang.org/t/inclusion-of-lkernel32-and-others-when-compiling-no-std-for-i686-pc-windows-gnu/121551) i686-w64-mingw32-gccand relative data addressing (PIC)
| Target | Payload Size | | ---------------- | ------------ | | i686-linux | 4141B | | x86_64-linux | 4167B | | i686-windows | 4141B | | x86_64-windows | 4120B |
[!NOTE]
Last tested withrustc 1.97.0-nightly (20de910db 2026-05-02)andcargo 1.97.0-nightly (4f9b52075 2026-05-01)
The following dependencies must be installed:
cargo install --force cargo-make
Cross-compiler for windows
sudo apt-get install mingw-w64
Cross-compiler for i686 linux
sudo apt-get install gcc-multilib
Linux rustup targets
rustup target add i686-unknown-linux-gnu
rustup target add x86_64-unknown-linux-gnu
Windows rustup targets
rustup target add i686-pc-windows-gnu
rustup target add x86_64-pc-windows-gn
Or you can build in the provided docker environment:
docker run --rm -it -v .:/build stardust-rs bash
To build one of these targets use cargo make -p $target build
Following is the current output of cargo make -p x86_64-linux run:
[LOADER]
[*] Allocate RW Memory
[*] Copy Shellcode Into RW Memory
[*] Set Memory RX
[*] Allocation Start Address: 0x700000000000
[*] Allocation End Address: 0x700000001047
[*] Allocation Size: 4167B
[STARDUST x86_64] [*] Hello Stardust! [*] Stardust Start Address: 0x700000000000 [*] Stardust Length: 4167 [*] Stardust Instance: 0x7f785645f000 [*] Hitting Breakpoint!
Problem #1 - format! macro e.g. &'static &str
Using the alloc::fmt::format! macro will result in a segementation fault due to absolute addresses to reference the pieces in Arguments { pieces, fmt: None, args }.
This results in the if !piece.is_empty() check failing within the following code @ https://github.com/rust-lang/rust/blob/master/library/core/src/fmt/mod.rs:
/ core::fmt::write () at core/src/fmt/mod.rs:1179 /
/ 1172 / match args.fmt {
/ 1173 / None => {
/ 1174 / // We can use default formatting parameters for all arguments.
/ 1175 / for (i, arg) in args.args.iter().enumerate() {
/ 1176 / // SAFETY: args.args and args.pieces come from the same Arguments,
/ 1177 / // which guarantees the indexes are always within bounds.
/ 1178 / let piece = unsafe { args.pieces.get_unchecked(i) };
/ 1179 / if !piece.is_empty() { // This is the check currently failing
/ 1180 / formatter.buf.write_str(*piece)?;
/ 1181 / }
/ 1182 /
/ 1183 / // SAFETY: There are no formatting parameters and hence no
/ 1184 / // count arguments.
/ 1185 / unsafe {
/ 1186 / arg.fmt(&mut formatter)?;
/ 1187 / }
/ 1188 / idx += 1;
/ 1189 / }
/ 1190 / }
This leads to a call being made to gccexcepttable which has been removed by linux.ld resulting in a segmentation fault.
[!note]
Patching the GOT appeared to get us a little further along before it crashes. YAY!๐ฅณ
Solution: None
(Solved) Problem #2 - Global Offset Table (GOT)
A bunch of stuff uses the GOT including calls to functions with the compiler_builtins crate, e.g. the following functions:
memcpymemmovememsetmemcmpbcmp
call made to a bad/absolute hard-coded memory address stored within the GOT and then referenced by a RIP-relative offset.
Similarly using extern "C" functions directly may result in the use the GOT.
The following code was used to ensure memcpy was required by the binary:
let src = alloc::string::String::from("SSECCUS\t\t:ypcmem gnitseT");
let dst: String = src.chars().rev().collect();
info!(&dst);
Patching the hardcoded addresses with GDB results in a successful execution as seen below:

Solution:
- Patch the GOT dynamically during runtime, this appears to allow the use of
compiler_builtins! - Don't directly call
externfunctions before patching, call them withinasm!macro
(Solved) Problem #3 - i686 Windows and -fPIC
You're best off reading this (or maybe you're not, won't get that time back) i686-w64-mingw32-gcc and relative data addressing (PIC).
Solution:
- Compile an i686 elf
- Patch the GOT dynamically during runtime
- Specify
stdcallwhere required. - "It's all just machine code in the end" - Me while justifying this mess
compilerbuiltins โ you cannot opt out without either going fully GOT-free (no statics, no compilerbuiltins functions). In this case fighting the compiler's codegen is harder than patching the GOT at runtime.