Add files via upload

This commit is contained in:
Jay
2025-03-17 17:31:42 -04:00
committed by GitHub
commit 15ab72bf96
23 changed files with 1408 additions and 0 deletions
Generated
+25
View File
@@ -0,0 +1,25 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "cc"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "stardust"
version = "0.1.0"
dependencies = [
"cc",
]
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "stardust"
version = "0.1.0"
edition = "2021"
authors = ["Stardust Team"]
description = "A modern and easy to use 32/64-bit shellcode template in Rust"
[lib]
name = "stardust"
crate-type = ["staticlib", "cdylib"]
[build-dependencies]
cc = "1.0"
[features]
default = []
debug = []
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true
[profile.dev]
panic = "abort"
+23
View File
@@ -0,0 +1,23 @@
FROM ubuntu:22.04
# Install dependencies
RUN apt-get update && apt-get install -y \
curl \
build-essential \
mingw-w64 \
nasm \
binutils-mingw-w64 \
&& rm -rf /var/lib/apt/lists/*
# Install Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
# Set up Rust targets for cross-compilation
RUN rustup target add x86_64-pc-windows-gnu i686-pc-windows-gnu
# Create working directory
WORKDIR /stardust
# Set default command
CMD ["bash"]
+56
View File
@@ -0,0 +1,56 @@
NAME := stardust
# Docker commands
DOCKER_BUILD := docker build -t stardust-rust .
DOCKER_RUN := docker run --rm -v $(PWD):/stardust stardust-rust
# Cargo commands
CARGO := cargo
# Build directories
BIN_DIR := bin
OBJ_DIR := bin/obj
all: x64 x86
debug: x64-debug x86-debug
# Create necessary directories
dirs:
@mkdir -p $(BIN_DIR) $(OBJ_DIR)
# Build Docker image
docker-build:
$(DOCKER_BUILD)
# x64 builds
x64: dirs docker-build
@echo "Compiling x64 project"
$(DOCKER_RUN) $(CARGO) build --release --target x86_64-pc-windows-gnu
@echo "Extracting shellcode"
$(DOCKER_RUN) x86_64-w64-mingw32-objcopy --dump-section .text=$(BIN_DIR)/$(NAME).x64.bin target/x86_64-pc-windows-gnu/release/stardust.dll
x64-debug: dirs docker-build
@echo "Compiling x64 project (debug)"
$(DOCKER_RUN) $(CARGO) build --features debug --target x86_64-pc-windows-gnu
@echo "Extracting shellcode"
$(DOCKER_RUN) x86_64-w64-mingw32-objcopy --dump-section .text=$(BIN_DIR)/$(NAME).x64.bin target/x86_64-pc-windows-gnu/debug/stardust.dll
# x86 builds
x86: dirs docker-build
@echo "Compiling x86 project"
$(DOCKER_RUN) $(CARGO) build --release --target i686-pc-windows-gnu
@echo "Extracting shellcode"
$(DOCKER_RUN) i686-w64-mingw32-objcopy --dump-section .text=$(BIN_DIR)/$(NAME).x86.bin target/i686-pc-windows-gnu/release/stardust.dll
x86-debug: dirs docker-build
@echo "Compiling x86 project (debug)"
$(DOCKER_RUN) $(CARGO) build --features debug --target i686-pc-windows-gnu
@echo "Extracting shellcode"
$(DOCKER_RUN) i686-w64-mingw32-objcopy --dump-section .text=$(BIN_DIR)/$(NAME).x86.bin target/i686-pc-windows-gnu/debug/stardust.dll
clean:
@rm -rf target
@rm -rf $(BIN_DIR)/*.bin
@echo "Cleaned build artifacts"
.PHONY: all debug x64 x86 x64-debug x86-debug clean dirs docker-build
+170
View File
@@ -0,0 +1,170 @@
# Rusty-Stardust
A modern and easy to use 32/64-bit shellcode template, ported to Rust from the original [Stardust](https://github.com/Cracked5pider/Stardust) project by [C5pider](https://twitter.com/C5pider).
## About
This project is a Rust implementation of the Stardust design, which provides a modern approach to position-independent code development. The original Stardust project introduced several key innovations in shellcode design:
- Separation of code and data into distinct sections
- Global instance access in position-independent code
- Raw string embedding capabilities
- Compile-time string hashing for stealth
- Exception handling and debug information stripping
Rusty-Stardust maintains these core features while leveraging Rust's safety guarantees and modern tooling. This implementation serves as both a practical tool and an educational resource for those interested in learning about position-independent code development in Rust.
## Features
- Raw string embedding in shellcode
- Compile-time string hashing with fnv1a for both function and module resolution
- No_std implementation for minimal footprint
- Cross-compilation for both x86 and x64 Windows targets
- Docker-based build environment for cross-platform development
- Position-independent code using RIP-relative addressing
- Exception handling and debug information stripping for minimal footprint
- Proper MinGW toolchain integration for reliable shellcode extraction
## Building
### Prerequisites
- Docker (for cross-compilation)
- Rust (for local development)
- NASM (assembled automatically in Docker)
- MinGW toolchain (provided in Docker environment)
### Building with Docker
Build for release:
```shell
$ make
```
Build for debug mode (with DbgPrint support):
```shell
$ make debug
```
### Build Outputs
Output files will be placed in the `bin` directory:
- `stardust.x64.bin`: 64-bit shellcode (586 bytes)
- `stardust.x86.bin`: 32-bit shellcode (8.2KB)
The shellcode is extracted using MinGW's objcopy tool and contains:
- Stack setup code
- DLL loading functionality
- Function resolution code
- Architecture-specific instructions
### Build Configuration
The project uses several key configurations for reliable shellcode generation:
1. **Linker Script** (`scripts/linker.ld`):
- Discards exception handling sections (`.pdata`, `.xdata`)
- Removes debug information (`.debug*`)
- Strips unnecessary sections (`.eh_frame`, `.note*`, `.comment*`)
2. **Cargo Config** (`.cargo/config.toml`):
- Position Independent Code (PIC) enabled
- Structured Exception Handling (SEH) disabled
- Windows subsystem configuration
- Custom linker settings for both architectures
3. **Assembly Integration**:
- Assembly files are compiled with NASM
- RIP-relative addressing for position independence
- Proper section alignment and linking
## Usage Example
### Module Resolution
Resolving modules from PEB:
```rust
// Resolve modules from PEB using hash
let ntdll_hash = hash_str!("ntdll.dll");
let ntdll_handle = resolve::module(ntdll_hash);
let kernel32_hash = hash_str!("kernel32.dll");
let kernel32_handle = resolve::module(kernel32_hash);
```
### API Resolution
Resolving function APIs:
```rust
// Resolve LoadLibraryA from kernel32
let load_library_fn: *mut FnLoadLibraryA = resolve::api(
kernel32_handle,
hash_str!("LoadLibraryA") as usize
);
// Load user32.dll
let user32 = unsafe { (*load_library_fn)(symbol("user32.dll".as_ptr())) };
// Resolve MessageBoxA from user32
let msgbox_fn: *mut FnMessageBoxA = resolve::api(
user32 as usize,
hash_str!("MessageBoxA") as usize
);
// Display message box
unsafe {
(*msgbox_fn)(
core::ptr::null_mut(),
symbol("Hello world".as_ptr()),
symbol("caption".as_ptr()),
MB_OK
);
}
```
### Debugging Output
When built in debug mode:
```rust
#[cfg(feature = "debug")]
{
dbg_printf!(instance, "Shellcode @ %p [%d bytes]\n", base_addr, size);
}
```
## Testing
A test program called "stomper" is included to load and execute the shellcode:
```shell
$ cd tests
$ cargo build
$ ./target/debug/stomper ../bin/stardust.x64.bin
```
## Architecture
This Rust port maintains the same architecture as the original C++ version:
- Assembly files are kept intact and linked using build.rs
- Memory layouts match the original for PE file parsing
- Windows structures are defined in Rust to match their C/C++ counterparts
- Function resolution uses the same hash-based approach for stealth
- MinGW toolchain integration for reliable shellcode extraction
- Exception handling and debug information stripping for minimal footprint
## Credits
This project is a Rust implementation of the original Stardust project. Special thanks to:
- [C5pider](https://twitter.com/C5pider) for the original Stardust design and implementation
- [Modexp](https://twitter.com/modexpblog) for his work on Windows PIC
- [Austin Hudson](https://twitter.com/ilove2pwn_) for his work on titanldr-ng
- [Kyle Avery](https://twitter.com/kyleavery_) for his work on AceLdr
- [x86matthew](https://twitter.com/x86matthew) for assembly guidance
- [mrexodia](https://twitter.com/mrexodia) for linker script insights
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
The original Stardust project and its design concepts are the work of [C5pider](https://github.com/Cracked5pider/Stardust). This Rust implementation is provided as an educational resource and alternative implementation for those interested in learning about position-independent code development in Rust.
+27
View File
@@ -0,0 +1,27 @@
[BITS 64]
DEFAULT REL
EXTERN entry
GLOBAL stardust
GLOBAL RipStart
[SECTION .text$A]
stardust:
push rsi
mov rsi, rsp
and rsp, 0FFFFFFFFFFFFFFF0h
sub rsp, 020h
call entry
mov rsp, rsi
pop rsi
ret
RipStart:
call RipPtr
ret
RipPtr:
mov rax, [rsp]
sub rax, 0x1b
ret
+15
View File
@@ -0,0 +1,15 @@
[BITS 64]
DEFAULT REL
GLOBAL RipData
[SECTION .text$C]
RipData:
call RetPtrData
ret
RetPtrData:
mov rax, [rsp]
sub rax, 0x5
ret
+25
View File
@@ -0,0 +1,25 @@
[BITS 32]
DEFAULT REL
EXTERN _entry
GLOBAL _stardust
GLOBAL _RipStart
[SECTION .text$A]
_stardust:
push ebp
mov ebp, esp
call _entry
mov esp, ebp
pop ebp
ret
_RipStart:
call _RipPtr
ret
_RipPtr:
mov eax, [esp]
sub eax, 0x11
ret
+15
View File
@@ -0,0 +1,15 @@
[BITS 32]
DEFAULT REL
GLOBAL _RipData
[SECTION .text$C]
_RipData:
call _RetPtrData
ret
_RetPtrData:
mov eax, [esp]
sub eax, 0x5
ret
Binary file not shown.
Binary file not shown.
+93
View File
@@ -0,0 +1,93 @@
use std::env;
use std::path::Path;
use std::process::Command;
fn main() {
let target = env::var("TARGET").unwrap();
let out_dir = env::var("OUT_DIR").unwrap();
// Compile x64 or x86 assembly files based on target
if target.contains("x86_64") {
// Assemble x64 files
let entry_obj = format!("{}/entry.o", out_dir);
let utils_obj = format!("{}/utils.o", out_dir);
let status = Command::new("nasm")
.args(&[
"-f", "win64",
"asm/x64/entry.asm",
"-o", &entry_obj
])
.status()
.expect("Failed to assemble entry.x64.asm");
if !status.success() {
panic!("Failed to assemble entry.x64.asm");
}
let status = Command::new("nasm")
.args(&[
"-f", "win64",
"asm/x64/utils.asm",
"-o", &utils_obj
])
.status()
.expect("Failed to assemble utils.x64.asm");
if !status.success() {
panic!("Failed to assemble utils.x64.asm");
}
// Use cc to link the object files
cc::Build::new()
.object(&entry_obj)
.object(&utils_obj)
.compile("asm");
} else if target.contains("i686") {
// Assemble x86 files
let entry_obj = format!("{}/entry.o", out_dir);
let utils_obj = format!("{}/utils.o", out_dir);
let status = Command::new("nasm")
.args(&[
"-f", "win32",
"asm/x86/entry.asm",
"-o", &entry_obj
])
.status()
.expect("Failed to assemble entry.x86.asm");
if !status.success() {
panic!("Failed to assemble entry.x86.asm");
}
let status = Command::new("nasm")
.args(&[
"-f", "win32",
"asm/x86/utils.asm",
"-o", &utils_obj
])
.status()
.expect("Failed to assemble utils.x86.asm");
if !status.success() {
panic!("Failed to assemble utils.x86.asm");
}
// Use cc to link the object files
cc::Build::new()
.object(&entry_obj)
.object(&utils_obj)
.compile("asm");
} else {
panic!("Unsupported target: {}", target);
}
// Tell cargo to rerun if assembly files change
println!("cargo:rerun-if-changed=asm/x64/entry.asm");
println!("cargo:rerun-if-changed=asm/x64/utils.asm");
println!("cargo:rerun-if-changed=asm/x86/entry.asm");
println!("cargo:rerun-if-changed=asm/x86/utils.asm");
println!("cargo:rerun-if-changed=scripts/linker.ld");
}
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
set -e
# This script can be used for local testing with a native Rust installation
# It won't generate the actual cross-compiled Windows shellcode but helps validate code
echo "Building Rust code with native compiler..."
cargo build
echo "Testing code structure..."
cargo check --all-features
echo "Checking for errors..."
cargo clippy
echo "All tests passed!"
echo "Note: This is only a code quality test - for actual Windows shellcode generation,"
echo "you need to use the Docker-based build with cross-compilation."
+21
View File
@@ -0,0 +1,21 @@
SECTIONS
{
.text :
{
*( .text$A );
*( .text$B );
*( .rdata* );
*( .text$C );
}
/* Discard exception handling and debug sections */
/DISCARD/ :
{
*(.pdata);
*(.xdata);
*(.debug*);
*(.eh_frame);
*(.note*);
*(.comment*);
}
}
+18
View File
@@ -0,0 +1,18 @@
#![allow(dead_code)]
// FNV1a hash constants
pub const FNV1A_PRIME: u32 = 0x01000193;
pub const FNV1A_BASIS: u32 = 0x811c9dc5;
// Constants for shellcode
#[cfg(target_arch = "x86_64")]
pub const END_OFFSET: usize = 0x10;
#[cfg(target_arch = "x86")]
pub const END_OFFSET: usize = 0x10;
// Windows constants
pub const IMAGE_DOS_SIGNATURE: u16 = 0x5A4D;
pub const IMAGE_NT_SIGNATURE: u32 = 0x00004550;
pub const IMAGE_DIRECTORY_ENTRY_EXPORT: usize = 0;
pub const MB_OK: u32 = 0x00000000;
+77
View File
@@ -0,0 +1,77 @@
use crate::constants::{FNV1A_BASIS, FNV1A_PRIME};
// Runtime hash function for strings
pub unsafe fn hash_string(string: *const u8) -> u32 {
let mut hash = FNV1A_BASIS;
let mut ptr = string;
while *ptr != 0 {
let mut byte = *ptr;
// Convert to uppercase if lowercase
if byte >= b'a' {
byte -= 0x20;
}
hash ^= byte as u32;
hash = hash.wrapping_mul(FNV1A_PRIME);
ptr = ptr.add(1);
}
hash
}
// Runtime hash function for wide strings
pub unsafe fn hash_string_wide(string: *const u16) -> u32 {
let mut hash = FNV1A_BASIS;
let mut ptr = string;
while *ptr != 0 {
let mut byte = (*ptr & 0xFF) as u8;
// Convert to uppercase if lowercase
if byte >= b'a' {
byte -= 0x20;
}
hash ^= byte as u32;
hash = hash.wrapping_mul(FNV1A_PRIME);
ptr = ptr.add(1);
}
hash
}
// Compile-time hash function for const evaluation
#[allow(dead_code)]
pub const fn hash_const(s: &str) -> u32 {
let bytes = s.as_bytes();
let mut hash = FNV1A_BASIS;
let mut i = 0;
while i < bytes.len() {
let mut byte = bytes[i];
// Convert to uppercase if lowercase
if byte >= b'a' {
byte -= 0x20;
}
hash ^= byte as u32;
hash = hash.wrapping_mul(FNV1A_PRIME);
i += 1;
}
hash
}
// Macro for creating compile-time hashes
#[macro_export]
macro_rules! hash_str {
($s:expr) => {
$crate::hash::hash_const($s)
};
}
+193
View File
@@ -0,0 +1,193 @@
use crate::constants::{END_OFFSET, MB_OK};
use crate::hash_str;
use crate::memory::symbol;
use crate::resolve;
use crate::windows::*;
use core::ffi::c_void;
// Type aliases for Windows API functions
type FnLoadLibraryA = unsafe extern "system" fn(lpLibFileName: PSTR) -> HMODULE;
type FnGetProcAddress = unsafe extern "system" fn(hModule: HMODULE, lpProcName: PSTR) -> PVOID;
type FnMessageBoxA = unsafe extern "system" fn(hWnd: PVOID, lpText: PSTR, lpCaption: PSTR, uType: u32) -> i32;
#[cfg(feature = "debug")]
type FnDbgPrint = unsafe extern "system" fn(Format: PCH, ...) -> i32;
// Keep resolve_import macro but mark it as #[allow(unused_macros)] since it might be needed later
#[allow(unused_macros)]
macro_rules! resolve_import {
($instance:expr, $module:ident) => {
{
let base_addr = $instance.$module.handle;
if base_addr != 0 {
$(
// For each API in the module struct, resolve it using its hash
$instance.$module.$api = core::mem::transmute(
resolve::_api(base_addr, $instance.$module.$api as usize)
);
)*
}
}
};
}
// Debug print macro
#[cfg(feature = "debug")]
#[macro_export]
macro_rules! dbg_printf {
($instance:expr, $fmt:expr, $($arg:expr),*) => {
{
unsafe {
if !$instance.ntdll.DbgPrint.is_null() {
// Format like the original: [DEBUG::function::line] message
let prefix = symbol::<PCH>(concat!("[DEBUG::", file!(), "::", line!(), "] ").as_ptr());
let msg = symbol::<PCH>($fmt.as_ptr());
($instance.ntdll.DbgPrint)(prefix, msg, $($arg),*);
}
}
}
};
}
#[cfg(not(feature = "debug"))]
#[macro_export]
macro_rules! dbg_printf {
($instance:expr, $fmt:expr, $($arg:expr),*) => {
{}
};
}
// Main instance structure
pub struct Instance {
// Base address and size
pub base: BaseInfo,
// Modules
pub ntdll: NtdllModule,
pub kernel32: Kernel32Module,
}
// Base information
pub struct BaseInfo {
pub address: usize,
pub length: usize,
}
// Ntdll module
pub struct NtdllModule {
pub handle: usize,
#[cfg(feature = "debug")]
pub DbgPrint: *mut FnDbgPrint,
}
// Kernel32 module
pub struct Kernel32Module {
pub handle: usize,
pub LoadLibraryA: *mut FnLoadLibraryA,
pub GetProcAddress: *mut FnGetProcAddress,
}
impl Instance {
// Initialize the instance
pub fn new() -> Self {
unsafe {
// Create instance with empty values
let mut instance = Instance {
base: BaseInfo {
address: 0,
length: 0,
},
ntdll: NtdllModule {
handle: 0,
#[cfg(feature = "debug")]
DbgPrint: core::ptr::null_mut(),
},
kernel32: Kernel32Module {
handle: 0,
LoadLibraryA: core::ptr::null_mut(),
GetProcAddress: core::ptr::null_mut(),
},
};
// Calculate shellcode base address + size
instance.base.address = crate::memory::RipStart();
instance.base.length = (crate::memory::RipData() - instance.base.address) + END_OFFSET;
// Resolve modules from PEB
instance.ntdll.handle = resolve::module(hash_str!("ntdll.dll"));
if instance.ntdll.handle == 0 {
return instance;
}
instance.kernel32.handle = resolve::module(hash_str!("kernel32.dll"));
if instance.kernel32.handle == 0 {
return instance;
}
// Resolve imports
#[cfg(feature = "debug")]
{
instance.ntdll.DbgPrint = core::mem::transmute(
resolve::_api(instance.ntdll.handle, hash_str!("DbgPrint") as usize)
);
}
instance.kernel32.LoadLibraryA = core::mem::transmute(
resolve::_api(instance.kernel32.handle, hash_str!("LoadLibraryA") as usize)
);
instance.kernel32.GetProcAddress = core::mem::transmute(
resolve::_api(instance.kernel32.handle, hash_str!("GetProcAddress") as usize)
);
instance
}
}
// Start the shellcode execution
pub unsafe fn start(&self, _arg: *mut c_void) {
// Load user32.dll
let user32 = if !self.kernel32.LoadLibraryA.is_null() {
unsafe { (*self.kernel32.LoadLibraryA)(symbol("user32.dll".as_ptr())) }
} else {
core::ptr::null_mut()
};
#[cfg(feature = "debug")]
{
if !user32.is_null() {
dbg_printf!(self, "oh wow look we loaded user32.dll -> %p\n", user32);
} else {
dbg_printf!(self, "okay something went wrong. failed to load user32 :/\n");
}
let peb = NtCurrentPeb();
dbg_printf!(self, "running from %ls (Pid: %d)\n",
(*(*peb).ProcessParameters).ImagePathName.Buffer,
(*NtCurrentTeb()).ClientId.UniqueProcess);
dbg_printf!(self, "shellcode @ %p [%d bytes]\n", self.base.address, self.base.length);
}
// Resolve MessageBoxA and display message
if !user32.is_null() {
let msgbox: *mut FnMessageBoxA = core::mem::transmute(
resolve::_api(user32 as usize, hash_str!("MessageBoxA") as usize)
);
if !msgbox.is_null() {
unsafe {
(*msgbox)(
core::ptr::null_mut(),
symbol("Hello world".as_ptr()),
symbol("caption".as_ptr()),
MB_OK
);
}
}
}
}
}
+50
View File
@@ -0,0 +1,50 @@
#![no_std]
#![no_main]
#![allow(non_snake_case)]
// Import modules
mod constants;
mod hash;
mod instance;
mod memory;
mod resolve;
mod windows;
// Re-export key components but exclude memory to avoid shadowing
pub use constants::*;
pub use hash::*;
pub use instance::*;
// Don't re-export memory to avoid shadowing the assembly functions
pub use resolve::*;
pub use windows::*;
use core::ffi::c_void;
use core::panic::PanicInfo;
// External assembly functions (defined in our assembly files)
// These are now properly declared only here
#[link(name = "asm")]
extern "C" {
pub fn RipStart() -> usize;
pub fn RipData() -> usize;
}
// Shellcode entry point - matches the assembly entry point
#[no_mangle]
pub unsafe extern "C" fn entry(args: *mut c_void) {
// Create instance and start execution
let instance = instance::Instance::new();
instance.start(args);
}
// Panic handler that does nothing
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
// Exit handler for #![no_std]
#[no_mangle]
pub unsafe extern "C" fn __aeabi_unwind_cpp_pr0() {
loop {}
}
+79
View File
@@ -0,0 +1,79 @@
//! Memory manipulation functions
// Zero memory (Rust's equivalent of RtlSecureZeroMemory)
pub unsafe fn zero(memory: *mut u8, length: u32) {
for i in 0..length {
*memory.offset(i as isize) = 0;
}
}
// Memory copy function
pub unsafe fn copy(destination: *mut u8, source: *const u8, length: u32) -> *mut u8 {
for i in 0..length {
*destination.offset(i as isize) = *source.offset(i as isize);
}
destination
}
// Memory compare function
pub unsafe fn compare(memory1: *const u8, memory2: *const u8, length: usize) -> u32 {
let mut a = memory1;
let mut b = memory2;
let mut len = length;
while len > 0 {
let val1 = *a;
let val2 = *b;
if val1 != val2 {
return (val1 as i32 - val2 as i32) as u32;
}
a = a.offset(1);
b = b.offset(1);
len -= 1;
}
0
}
// Get string from RIP-relative addressing
pub unsafe fn symbol<T>(s: *const u8) -> T {
let rip_data = external_rip_data();
let offset = (s as usize).wrapping_sub(rip_data_fn_addr() as usize);
let absolute_addr = rip_data.wrapping_sub(offset);
core::mem::transmute_copy(&absolute_addr)
}
// Use the external RipStart/RipData functions from lib.rs
extern "C" {
// These are now public functions
pub fn RipStart() -> usize;
pub fn RipData() -> usize;
}
// Helper functions to get addresses
fn rip_data_fn_addr() -> usize {
RipData as usize
}
fn external_rip_data() -> usize {
unsafe { RipData() }
}
// Macro to define the RangeHeadList loop, improved version
#[macro_export]
macro_rules! range_head_list {
($head_list:expr, $type:ty, |$current:ident| $body:block) => {
{
let head_ptr = $head_list as *const LIST_ENTRY;
let mut $current = (*head_ptr).Flink as $type;
while $current as *const _ != head_ptr as *const _ {
$body
$current = (*$current).InLoadOrderLinks.Flink as $type;
}
}
};
}
+89
View File
@@ -0,0 +1,89 @@
use crate::constants::{IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE};
use crate::hash::{hash_string, hash_string_wide};
use crate::range_head_list;
use crate::windows::*;
/// Finds a module by its hash in the PEB's loaded module list
pub unsafe fn module(library_hash: u32) -> usize {
let peb = NtCurrentPeb();
let ldr = (*peb).Ldr;
let module_list = &(*ldr).InLoadOrderModuleList;
let mut result = 0;
// Use the fixed range_head_list macro instead of manual iteration
range_head_list!(module_list, PLDR_DATA_TABLE_ENTRY, |current| {
if library_hash == 0 {
result = (*current).OriginalBase as usize;
break;
}
if hash_string_wide((*current).BaseDllName.Buffer) == library_hash {
result = (*current).OriginalBase as usize;
break;
}
});
result
}
/// Finds an API function by its hash in the specified module
pub unsafe fn _api(module_base: usize, symbol_hash: usize) -> usize {
if module_base == 0 || symbol_hash == 0 {
return 0;
}
let mut address = 0;
// Get DOS header
let dos_header = module_base as *mut IMAGE_DOS_HEADER;
if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE {
return 0;
}
// Get NT headers
let nt_headers = (module_base + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS;
if (*nt_headers).Signature != IMAGE_NT_SIGNATURE {
return 0;
}
// Get export directory
let export_dir_rva = (*nt_headers).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
let export_dir = (module_base + export_dir_rva as usize) as *mut IMAGE_EXPORT_DIRECTORY;
// Get export tables
let names = (module_base + (*export_dir).AddressOfNames as usize) as *mut u32;
let functions = (module_base + (*export_dir).AddressOfFunctions as usize) as *mut u32;
let ordinals = (module_base + (*export_dir).AddressOfNameOrdinals as usize) as *mut u16;
// Find the export by hash
for i in 0..(*export_dir).NumberOfNames {
let name_rva = *names.offset(i as isize);
let name = (module_base + name_rva as usize) as *const u8;
if hash_string(name) == symbol_hash as u32 {
let ordinal = *ordinals.offset(i as isize) as isize;
let function_rva = *functions.offset(ordinal);
address = module_base + function_rva as usize;
break;
}
}
address
}
/// Generic wrapper for _api that returns a typed function pointer
pub unsafe fn api<T>(module_base: usize, symbol_hash: usize) -> *mut T {
_api(module_base, symbol_hash) as *mut T
}
// Macro to resolve an API by name
#[macro_export]
macro_rules! resolve_api {
($module:expr, $name:ident) => {
$crate::resolve::api::<unsafe extern "system" fn()>(
$module,
$crate::hash_str!(stringify!($name)) as usize
) as *const unsafe extern "system" fn()
};
}
+300
View File
@@ -0,0 +1,300 @@
#![allow(non_snake_case, non_camel_case_types)]
use core::ffi::c_void;
// Windows basic types
pub type BYTE = u8;
pub type WORD = u16;
pub type DWORD = u32;
pub type LONG = i32;
pub type ULONG = u32;
pub type ULONG_PTR = usize;
pub type PVOID = *mut c_void;
pub type LPVOID = *mut c_void;
pub type HANDLE = PVOID;
pub type HMODULE = HANDLE;
pub type BOOL = i32;
pub type PSTR = *mut u8;
pub type PWSTR = *mut u16;
pub type PCH = *const i8;
pub type LPCSTR = *const i8;
pub type LPCWSTR = *const u16;
pub type UINT = u32;
pub type WCHAR = u16;
// Windows structures
#[repr(C)]
pub struct LIST_ENTRY {
pub Flink: *mut LIST_ENTRY,
pub Blink: *mut LIST_ENTRY,
}
pub type PLIST_ENTRY = *mut LIST_ENTRY;
#[repr(C)]
pub struct UNICODE_STRING {
pub Length: u16,
pub MaximumLength: u16,
pub Buffer: *mut WCHAR,
}
pub type PUNICODE_STRING = *mut UNICODE_STRING;
#[repr(C)]
pub struct CLIENT_ID {
pub UniqueProcess: HANDLE,
pub UniqueThread: HANDLE,
}
#[repr(C)]
pub struct RTL_USER_PROCESS_PARAMETERS {
// Simplified - only what we need
pub Reserved1: [BYTE; 16],
pub Reserved2: [PVOID; 10],
pub ImagePathName: UNICODE_STRING,
pub CommandLine: UNICODE_STRING,
}
pub type PRTL_USER_PROCESS_PARAMETERS = *mut RTL_USER_PROCESS_PARAMETERS;
#[repr(C)]
pub struct PEB_LDR_DATA {
pub Length: ULONG,
pub Initialized: BOOL,
pub SsHandle: HANDLE,
pub InLoadOrderModuleList: LIST_ENTRY,
pub InMemoryOrderModuleList: LIST_ENTRY,
pub InInitializationOrderModuleList: LIST_ENTRY,
}
pub type PPEB_LDR_DATA = *mut PEB_LDR_DATA;
#[repr(C)]
pub struct LDR_DATA_TABLE_ENTRY {
pub InLoadOrderLinks: LIST_ENTRY,
pub InMemoryOrderLinks: LIST_ENTRY,
pub InInitializationOrderLinks: LIST_ENTRY,
pub OriginalBase: PVOID,
pub EntryPoint: PVOID,
pub SizeOfImage: ULONG,
pub FullDllName: UNICODE_STRING,
pub BaseDllName: UNICODE_STRING,
pub Flags: ULONG,
pub LoadCount: u16,
pub TlsIndex: u16,
pub HashLinks: LIST_ENTRY,
pub TimeDateStamp: ULONG,
}
pub type PLDR_DATA_TABLE_ENTRY = *mut LDR_DATA_TABLE_ENTRY;
#[repr(C)]
pub struct PEB {
pub InheritedAddressSpace: BYTE,
pub ReadImageFileExecOptions: BYTE,
pub BeingDebugged: BYTE,
pub BitField: BYTE,
pub Mutant: HANDLE,
pub ImageBaseAddress: PVOID,
pub Ldr: *mut PEB_LDR_DATA,
// ... other fields not needed for our purposes
}
pub type PPEB = *mut PEB;
#[repr(C)]
pub struct TEB {
pub Reserved1: [BYTE; 12],
pub ProcessEnvironmentBlock: PPEB,
pub Reserved2: [BYTE; 399],
pub ClientId: CLIENT_ID,
// Simplified - truncated remaining fields
}
pub type PTEB = *mut TEB;
// PE Format structures
#[repr(C)]
pub struct IMAGE_DOS_HEADER {
pub e_magic: WORD,
pub e_cblp: WORD,
pub e_cp: WORD,
pub e_crlc: WORD,
pub e_cparhdr: WORD,
pub e_minalloc: WORD,
pub e_maxalloc: WORD,
pub e_ss: WORD,
pub e_sp: WORD,
pub e_csum: WORD,
pub e_ip: WORD,
pub e_cs: WORD,
pub e_lfarlc: WORD,
pub e_ovno: WORD,
pub e_res: [WORD; 4],
pub e_oemid: WORD,
pub e_oeminfo: WORD,
pub e_res2: [WORD; 10],
pub e_lfanew: LONG,
}
pub type PIMAGE_DOS_HEADER = *mut IMAGE_DOS_HEADER;
#[repr(C)]
pub struct IMAGE_DATA_DIRECTORY {
pub VirtualAddress: DWORD,
pub Size: DWORD,
}
pub type PIMAGE_DATA_DIRECTORY = *mut IMAGE_DATA_DIRECTORY;
#[repr(C)]
pub struct IMAGE_OPTIONAL_HEADER64 {
pub Magic: WORD,
pub MajorLinkerVersion: BYTE,
pub MinorLinkerVersion: BYTE,
pub SizeOfCode: DWORD,
pub SizeOfInitializedData: DWORD,
pub SizeOfUninitializedData: DWORD,
pub AddressOfEntryPoint: DWORD,
pub BaseOfCode: DWORD,
pub ImageBase: u64,
pub SectionAlignment: DWORD,
pub FileAlignment: DWORD,
pub MajorOperatingSystemVersion: WORD,
pub MinorOperatingSystemVersion: WORD,
pub MajorImageVersion: WORD,
pub MinorImageVersion: WORD,
pub MajorSubsystemVersion: WORD,
pub MinorSubsystemVersion: WORD,
pub Win32VersionValue: DWORD,
pub SizeOfImage: DWORD,
pub SizeOfHeaders: DWORD,
pub CheckSum: DWORD,
pub Subsystem: WORD,
pub DllCharacteristics: WORD,
pub SizeOfStackReserve: u64,
pub SizeOfStackCommit: u64,
pub SizeOfHeapReserve: u64,
pub SizeOfHeapCommit: u64,
pub LoaderFlags: DWORD,
pub NumberOfRvaAndSizes: DWORD,
pub DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
}
#[repr(C)]
pub struct IMAGE_OPTIONAL_HEADER32 {
pub Magic: WORD,
pub MajorLinkerVersion: BYTE,
pub MinorLinkerVersion: BYTE,
pub SizeOfCode: DWORD,
pub SizeOfInitializedData: DWORD,
pub SizeOfUninitializedData: DWORD,
pub AddressOfEntryPoint: DWORD,
pub BaseOfCode: DWORD,
pub BaseOfData: DWORD,
pub ImageBase: DWORD,
pub SectionAlignment: DWORD,
pub FileAlignment: DWORD,
pub MajorOperatingSystemVersion: WORD,
pub MinorOperatingSystemVersion: WORD,
pub MajorImageVersion: WORD,
pub MinorImageVersion: WORD,
pub MajorSubsystemVersion: WORD,
pub MinorSubsystemVersion: WORD,
pub Win32VersionValue: DWORD,
pub SizeOfImage: DWORD,
pub SizeOfHeaders: DWORD,
pub CheckSum: DWORD,
pub Subsystem: WORD,
pub DllCharacteristics: WORD,
pub SizeOfStackReserve: DWORD,
pub SizeOfStackCommit: DWORD,
pub SizeOfHeapReserve: DWORD,
pub SizeOfHeapCommit: DWORD,
pub LoaderFlags: DWORD,
pub NumberOfRvaAndSizes: DWORD,
pub DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
}
#[repr(C)]
pub struct IMAGE_FILE_HEADER {
pub Machine: WORD,
pub NumberOfSections: WORD,
pub TimeDateStamp: DWORD,
pub PointerToSymbolTable: DWORD,
pub NumberOfSymbols: DWORD,
pub SizeOfOptionalHeader: WORD,
pub Characteristics: WORD,
}
#[cfg(target_arch = "x86_64")]
#[repr(C)]
pub struct IMAGE_NT_HEADERS {
pub Signature: DWORD,
pub FileHeader: IMAGE_FILE_HEADER,
pub OptionalHeader: IMAGE_OPTIONAL_HEADER64,
}
#[cfg(target_arch = "x86")]
#[repr(C)]
pub struct IMAGE_NT_HEADERS {
pub Signature: DWORD,
pub FileHeader: IMAGE_FILE_HEADER,
pub OptionalHeader: IMAGE_OPTIONAL_HEADER32,
}
pub type PIMAGE_NT_HEADERS = *mut IMAGE_NT_HEADERS;
#[repr(C)]
pub struct IMAGE_EXPORT_DIRECTORY {
pub Characteristics: DWORD,
pub TimeDateStamp: DWORD,
pub MajorVersion: WORD,
pub MinorVersion: WORD,
pub Name: DWORD,
pub Base: DWORD,
pub NumberOfFunctions: DWORD,
pub NumberOfNames: DWORD,
pub AddressOfFunctions: DWORD,
pub AddressOfNames: DWORD,
pub AddressOfNameOrdinals: DWORD,
}
pub type PIMAGE_EXPORT_DIRECTORY = *mut IMAGE_EXPORT_DIRECTORY;
// Helper functions for accessing TEB/PEB
#[cfg(target_arch = "x86_64")]
#[inline(always)]
pub unsafe fn NtCurrentTeb() -> PTEB {
let teb: PTEB;
core::arch::asm!(
"mov {}, gs:[0x30]",
out(reg) teb,
options(nostack, preserves_flags)
);
teb
}
#[cfg(target_arch = "x86")]
#[inline(always)]
pub unsafe fn NtCurrentTeb() -> PTEB {
let teb: PTEB;
core::arch::asm!(
"mov {}, fs:[0x18]",
out(reg) teb,
options(nostack, preserves_flags)
);
teb
}
#[cfg(target_arch = "x86_64")]
pub unsafe fn NtCurrentPeb() -> *mut PEB {
let peb: *mut PEB;
core::arch::asm!(
"mov {}, gs:[0x60]",
out(reg) peb,
options(nostack, preserves_flags)
);
peb
}
#[cfg(target_arch = "x86")]
pub unsafe fn NtCurrentPeb() -> *mut PEB {
let peb: *mut PEB;
core::arch::asm!(
"mov {}, fs:[0x30]",
out(reg) peb,
options(nostack, preserves_flags)
);
peb
}
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "stomper"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "stomper"
path = "stomper.rs"
[dependencies]
windows = { version = "0.48", features = ["Win32_System_Memory"] }
+76
View File
@@ -0,0 +1,76 @@
use std::fs;
use std::mem;
use std::path::Path;
use std::ptr;
use std::env;
use windows::Win32::System::Memory::{
VirtualAlloc, VirtualProtect, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE, PAGE_READWRITE
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Determine architecture
let args: Vec<String> = env::args().collect();
let bin_path = if args.len() > 1 {
args[1].clone()
} else {
#[cfg(target_arch = "x86_64")]
let bin_name = "stardust.x64.bin";
#[cfg(target_arch = "x86")]
let bin_name = "stardust.x86.bin";
format!("../bin/{}", bin_name)
};
println!("Loading shellcode from: {}", bin_path);
// Read shellcode from file
let shellcode = fs::read(Path::new(&bin_path))?;
let shellcode_len = shellcode.len();
println!("Shellcode size: {} bytes", shellcode_len);
unsafe {
// Allocate memory for the shellcode
let memory = VirtualAlloc(
ptr::null(),
shellcode_len,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
if memory.is_null() {
return Err("Failed to allocate memory".into());
}
println!("Memory allocated at: {:p}", memory);
// Copy shellcode to allocated memory
ptr::copy_nonoverlapping(
shellcode.as_ptr(),
memory.cast::<u8>(),
shellcode_len
);
// Make memory executable
let mut old_protect = PAGE_READWRITE;
VirtualProtect(
memory,
shellcode_len,
PAGE_EXECUTE_READWRITE,
&mut old_protect
)?;
println!("Memory protection changed to PAGE_EXECUTE_READWRITE");
// Create function pointer and execute shellcode
let shellcode_fn: extern "C" fn(arg: *const u8) = mem::transmute(memory);
println!("Executing shellcode...");
shellcode_fn(ptr::null());
println!("Shellcode execution completed");
}
Ok(())
}