initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
*/target
|
||||
@@ -0,0 +1,7 @@
|
||||
Rust for the haters.
|
||||
|
||||
PoC's showing how rust can do stuff people often critiquize it about.
|
||||
|
||||
# Members
|
||||
|
||||
- `rust-teeny`: A rust std binary only **8.6 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.
|
||||
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+16
@@ -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",
|
||||
]
|
||||
@@ -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"
|
||||
@@ -0,0 +1,15 @@
|
||||
Tiny stripped rust binary (8.6 KiB, x86_64, dynamic libc) that loads and executes a shellcode payload built into it.
|
||||
|
||||
## Building
|
||||
|
||||
Just `just build`. Mostly uses CLI flags, but most could be moved into the `Cargo.toml`, needs a nightly compiler as of rust 1.96.0, the `rust-toolchain.toml` should take care of that though.
|
||||
|
||||
It assumes building for `x86_64-unknown-linux-gnu` as I cannot manage to get a musl build and I don't know other architectures which need manual shellcode addition. But this can be extended to other architectures.
|
||||
|
||||
## Properties
|
||||
|
||||
The code is completely correct, checks `mmap` and `mprotect` succeeded, the code could theoretically be smaller but less correct by:
|
||||
|
||||
- Memory mapping a RWX page directly, some protected kernel builds require `RW` -> `RX` transition though.
|
||||
- Assuming mmap and mprotect succeeded, no modern kernel should fail on `mmap` even if out of memory, the mapping is done regardless and memory actually reserved once used. `mprotect` shouldn't fail either if done correctly (man page mentions failures because no memory, which I said shouldn't happen, invalid permission combinations and invalid, which don't happen if you do correct permissions and valid pointers, or rare permission cases).
|
||||
- `no_std`, this barely needs std, could just hardcode the 3-4 syscalls present and don't use `std` at all. This code also hardcoded `libc` values before so that's not needed either, but I think `libc` has `no_std` support either way.
|
||||
@@ -0,0 +1,28 @@
|
||||
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,--strip-all \
|
||||
\
|
||||
-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 }}
|
||||
|
||||
stat:
|
||||
stat target/{{ target }}/release/rust-teeny
|
||||
|
||||
stat-size:
|
||||
stat target/{{ target }}/release/rust-teeny -c "%s" | numfmt --to=iec-i
|
||||
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
@@ -0,0 +1,33 @@
|
||||
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE};
|
||||
|
||||
// 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";
|
||||
|
||||
/// Leaks memory
|
||||
fn alloc_shellcode(content: &[u8]) -> unsafe extern "C" fn() -> () {
|
||||
let exec_mem = unsafe {
|
||||
libc::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 { libc::mprotect(exec_mem, content.len(), PROT_READ | PROT_EXEC) };
|
||||
assert_eq!(mprotect_status, 0, "Mprotect failed");
|
||||
|
||||
unsafe { core::mem::transmute(exec_mem) }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let exec_mem = alloc_shellcode(SHELLCODE);
|
||||
unsafe { exec_mem() };
|
||||
}
|
||||
Reference in New Issue
Block a user