diff --git a/README.md b/README.md index 132e384..767070f 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,4 @@ PoC's showing how rust can do stuff people often critiquize it about. # Members - `rust-teeny`: A rust std binary only **8.0 KiB** big, achieves so by `abort()`ing instead of having graceful error logging, panics shouldn't have any source information nor should there be `Debug` implementations, no debug symbols and any potential path reference to `$PWD` is renamed. It carries a shellcode payload loaded into memory and executed from memory. +- `rust-teeny-nostd`: Same as avobe but `no_std`, **4.1 KiB**. diff --git a/rust-teeny-nostd/.gitignore b/rust-teeny-nostd/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/rust-teeny-nostd/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rust-teeny-nostd/Cargo.lock b/rust-teeny-nostd/Cargo.lock new file mode 100644 index 0000000..1c0e4bf --- /dev/null +++ b/rust-teeny-nostd/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "rust-teeny" +version = "0.1.0" +dependencies = [ + "libc", +] diff --git a/rust-teeny-nostd/Cargo.toml b/rust-teeny-nostd/Cargo.toml new file mode 100644 index 0000000..6d8e84f --- /dev/null +++ b/rust-teeny-nostd/Cargo.toml @@ -0,0 +1,16 @@ +cargo-features = ["panic-immediate-abort"] + +[package] +name = "rust-teeny" +version = "0.1.0" +edition = "2024" + +[profile.release] +opt-level = "z" +strip = "symbols" +panic = "immediate-abort" +lto = true +codegen-units = 1 + +[dependencies] +libc = "0" diff --git a/rust-teeny-nostd/README.md b/rust-teeny-nostd/README.md new file mode 100644 index 0000000..e143140 --- /dev/null +++ b/rust-teeny-nostd/README.md @@ -0,0 +1 @@ +Same as [../rust-teeny/] but `no_std`, takes 4.1 KiB. diff --git a/rust-teeny-nostd/justfile b/rust-teeny-nostd/justfile new file mode 100644 index 0000000..c26c900 --- /dev/null +++ b/rust-teeny-nostd/justfile @@ -0,0 +1,39 @@ +alias b := build +alias s := stat +alias z := stat-size + +target := "x86_64-unknown-linux-gnu" + +build: + RUSTFLAGS="\ + -Zunstable-options \ + \ + -Zlocation-detail=none -Zfmt-debug=none \ + \ + -Clinker=clang -Clink-arg=-fuse-ld=lld \ + -Clink-arg=-Wl,--gc-sections -Clink-arg=-Wl,--as-needed -Clink-arg=-Wl,--strip-all -Clink-arg=-Wl,--build-id=none \ + \ + -Cpanic=immediate-abort --remap-path-prefix=$PWD=[src] \ + " cargo \ + \ + -Zbuild-std=core,alloc,std,panic_abort \ + -Zbuild-std-features="optimize_for_size" \ + \ + b -r --target {{ target }} + + # Base, does like nothing + strip --strip-all --strip-unneeded target/{{ target }}/release/rust-teeny + + # BS, I don't want these + strip -R .comment -R .note.\* target/{{ target }}/release/rust-teeny + + # Only OK because we don't do unwinding + strip -R .eh_frame\* target/{{ target }}/release/rust-teeny + + # Not sure why .gnu.version and .gnu.hash don't break if I remove them in my machine, but that's very relative and its related to dynamic loading, but they could reduce size by `0.2 KiB` + +stat: + stat target/{{ target }}/release/rust-teeny + +stat-size: + stat target/{{ target }}/release/rust-teeny -c "%s" | numfmt --to=iec-i diff --git a/rust-teeny-nostd/rust-toolchain.toml b/rust-teeny-nostd/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-teeny-nostd/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/rust-teeny-nostd/src/main.rs b/rust-teeny-nostd/src/main.rs new file mode 100644 index 0000000..5425f59 --- /dev/null +++ b/rust-teeny-nostd/src/main.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] +use core::ffi::{c_int, c_void}; + +use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE, off_t, size_t}; + +// SAFETY: Shellcodes should have FFI behavior equivalent to an `unsafe extern "C" fn() -> ()` +#[cfg(target_arch = "x86_64")] +static SHELLCODE: &[u8] = b"\x31\xC0\xFF\xC0\x89\xC7\x48\x8D\x35\x08\x00\x00\x00\xBA\x0E\x00\x00\x00\x0F\x05\xC3Hello, World!\n"; + +#[link(name = "c")] +unsafe extern "C" { + fn mmap( + addr: *mut c_void, + len: size_t, + prot: c_int, + flags: c_int, + fd: c_int, + offset: off_t, + ) -> *mut c_void; + + fn mprotect(addr: *mut c_void, len: size_t, prot: c_int) -> c_int; + + fn exit(status: c_int) -> !; +} + +/// Leaks memory +fn alloc_shellcode(content: &[u8]) -> unsafe extern "C" fn() -> () { + let exec_mem = unsafe { + mmap( + core::ptr::null_mut(), + content.len(), + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0, + ) + }; + + assert_ne!(exec_mem, libc::MAP_FAILED, "Map failed"); + + unsafe { core::ptr::copy_nonoverlapping(content.as_ptr(), exec_mem.cast(), content.len()) }; + + let mprotect_status = unsafe { mprotect(exec_mem, content.len(), PROT_READ | PROT_EXEC) }; + assert_eq!(mprotect_status, 0, "Mprotect failed"); + + unsafe { core::mem::transmute(exec_mem) } +} + +#[unsafe(no_mangle)] +fn main() { + let exec_mem = alloc_shellcode(SHELLCODE); + unsafe { exec_mem() }; + + unsafe { exit(0) } +} diff --git a/rust-teeny/src/main.rs b/rust-teeny/src/main.rs index c43c0b4..5425f59 100644 --- a/rust-teeny/src/main.rs +++ b/rust-teeny/src/main.rs @@ -1,13 +1,33 @@ -use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE}; +#![no_std] +#![no_main] +use core::ffi::{c_int, c_void}; + +use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE, off_t, size_t}; // SAFETY: Shellcodes should have FFI behavior equivalent to an `unsafe extern "C" fn() -> ()` #[cfg(target_arch = "x86_64")] static SHELLCODE: &[u8] = b"\x31\xC0\xFF\xC0\x89\xC7\x48\x8D\x35\x08\x00\x00\x00\xBA\x0E\x00\x00\x00\x0F\x05\xC3Hello, World!\n"; +#[link(name = "c")] +unsafe extern "C" { + fn mmap( + addr: *mut c_void, + len: size_t, + prot: c_int, + flags: c_int, + fd: c_int, + offset: off_t, + ) -> *mut c_void; + + fn mprotect(addr: *mut c_void, len: size_t, prot: c_int) -> c_int; + + fn exit(status: c_int) -> !; +} + /// Leaks memory fn alloc_shellcode(content: &[u8]) -> unsafe extern "C" fn() -> () { let exec_mem = unsafe { - libc::mmap( + mmap( core::ptr::null_mut(), content.len(), PROT_READ | PROT_WRITE, @@ -21,13 +41,16 @@ fn alloc_shellcode(content: &[u8]) -> unsafe extern "C" fn() -> () { unsafe { core::ptr::copy_nonoverlapping(content.as_ptr(), exec_mem.cast(), content.len()) }; - let mprotect_status = unsafe { libc::mprotect(exec_mem, content.len(), PROT_READ | PROT_EXEC) }; + let mprotect_status = unsafe { mprotect(exec_mem, content.len(), PROT_READ | PROT_EXEC) }; assert_eq!(mprotect_status, 0, "Mprotect failed"); unsafe { core::mem::transmute(exec_mem) } } +#[unsafe(no_mangle)] fn main() { let exec_mem = alloc_shellcode(SHELLCODE); unsafe { exec_mem() }; + + unsafe { exit(0) } }