Skip to content

Commit

Permalink
Merge pull request #14 from StackOverflowExcept1on/master
Browse files Browse the repository at this point in the history
harder better faster smaller
  • Loading branch information
mcountryman authored Oct 28, 2022
2 parents e55a0b7 + ee77cf9 commit 864ffda
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 143 deletions.
13 changes: 10 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-2022, windows-2019, windows-2016]
os: [windows-2022, windows-2019]
if: "!contains(github.event.head_commit.message, '[ci skip]')"

steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Cache cargo
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.cargo
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.toml') }}
Expand All @@ -31,6 +33,11 @@ jobs:
toolchain: nightly
override: true

- name: Patch MSVC linker
run: |
cargo install anonlink
anonlink
- name: Build cargo
uses: actions-rs/cargo@v1
with:
Expand Down
27 changes: 2 additions & 25 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 1 addition & 17 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,5 @@ panic = "abort"
opt-level = "z"
codegen-units = 1

[dependencies.winapi]
version = "0.3"
features = ["ntdef", "winnt", "dbghelp", "minwindef", "impl-debug"]

[build-dependencies]
iced-x86 = { version = "1.11", default-features = false, features = ["std", "decoder"] }

[build-dependencies.winapi]
version = "0.3"
features = [
"ntdef",
"winnt",
"dbghelp",
"minwindef",
"libloaderapi",

"impl-debug",
]
iced-x86 = { version = "1.17", default-features = false, features = ["std", "decoder"] }
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ be used in production, more of a challenge. I'm in no ways an expert and
If you can go smaller let me know how you did it :grin:

### Results
`536b` :sunglasses:
`464b` :sunglasses:

```powershell
❯ cargo +nightly install anonlink
❯ anonlink
❯ cargo +nightly run --release
Hello World!
❯ cargo +nightly build --release && (Get-Item ".\target\release\min-sized-rust-windows.exe").Length
Compiling min-sized-rust-windows v0.1.0 (**\min-sized-rust-windows)
Finished release [optimized] target(s) in 1.33s
560
464
```

### Strategies
Expand All @@ -38,7 +40,7 @@ I'm excluding basic strategies here such as enabling lto and setting `opt-level
* Invoke `NtWriteFile`/`ZwWriteFile` using syscall `0x80`. [5][6]
1. This is undocumented behaviour in windows, syscalls change over time. [5]
2. I can't guarantee this will work on your edition of windows.. it's tested on
my local machine (W10) and on GH actions (windows-2019 and windows-2016) server
my local machine (W10) and on GH actions (windows-2022 and windows-2019) server
editions.
* Custom `LINK.exe` stub.
* A custom built stub created to remove `Rich PE` header. More information can be found [here](https://bytepointer.com/articles/the_microsoft_rich_header.htm).
Expand Down Expand Up @@ -69,3 +71,4 @@ I'm excluding basic strategies here such as enabling lto and setting `opt-level
* @Frago9876543210 - Brought binary size from `760b` -> `600b` :grin:
* @Frago9876543210 - Brought binary size from `600b` -> `560b` :grin:
* @ironhaven - Brought binary size from `560b` -> `536b` 😁
* @StackOverflowExcept1on - Brought binary size from `536b` -> `464b` 😁
9 changes: 7 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@
use iced_x86::{Code, Decoder, DecoderOptions, Mnemonic, OpKind, Register};
use std::env;

use core::ffi::{c_char, c_void};
use std::path::Path;
use std::slice::from_raw_parts;
use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA};

extern "system" {
pub fn GetProcAddress(hModule: *mut c_void, lpProcName: *const c_char) -> *mut c_void;
pub fn LoadLibraryA(lpFileName: *const c_char) -> *mut c_void;
}

/// Converts string literal into a `LPCSTR`
macro_rules! l {
Expand Down Expand Up @@ -74,7 +79,7 @@ unsafe fn get_syscall_id(library: *const i8, name: *const i8) -> Option<u32> {
// Find instruction that mov's syscall id into eax register
// `mov eax, ?`
if instr.op0_register() == Register::EAX {
id = if instr.op_kind(1) == OpKind::Immediate32 {
id = if let Ok(OpKind::Immediate32) = instr.try_op_kind(1) {
Some(instr.immediate32())
} else {
None
Expand Down
173 changes: 80 additions & 93 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@
#![no_main]
#![feature(asm_const)]
#![windows_subsystem = "console"]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]

use core::arch::asm;
use core::mem::MaybeUninit;
use core::panic::PanicInfo;
use winapi::shared::ntdef::{BOOLEAN, LIST_ENTRY, NTSTATUS, PULONG, ULONG};
use winapi::um::winnt::{HANDLE, PVOID};

// Blow up if we try to compile without msvc, x64 arch, or windows.
#[cfg(not(all(target_env = "msvc", target_arch = "x86_64", target_os = "windows")))]
Expand All @@ -18,30 +13,22 @@ compile_error!("Platform not supported!");
// Includes syscall constant.
include!(concat!(env!("OUT_DIR"), "/syscall.rs"));

/// The `Hello World!` utf8 buffer.
pub const BUF: &[u8] = b"Hello World!\n";
macro_rules! buf {
() => {
"Hello World!\n"
};
}

// Actually this function returns u32 (xor eax, eax; ret)
#[no_mangle]
extern "C" fn mainCRTStartup() -> u32 {
let peb: *mut PEB;
let mut status: MaybeUninit<IO_STATUS_BLOCK> = MaybeUninit::uninit();
extern "C" fn mainCRTStartup() {
unsafe {
// Get PEB from reserved register `GS`
asm!(
"mov {}, gs:[0x60]",
out(reg) peb,
options(pure, nomem, nostack)
);

// Get STDOUT handle from PEB
let handle = (*(*peb).ProcessParameters).StandardOutput;

asm!(
// ZwWriteFile
// NtWriteFile (see https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntwritefile)
//
// num | type | name | register | desc
// -----|------------------|---------------|----------|---------------------
// 1 | HANDLE | FileHandle | rcx |
// 1 | HANDLE | FileHandle | r10 |
// 2 | HANDLE | Event | rdx | unused
// 3 | PIO_APC_ROUTINE | ApcRoutine | r8 | unused
// 4 | PVOID | ApcContext | r9 | unused
Expand All @@ -52,84 +39,84 @@ extern "C" fn mainCRTStartup() -> u32 {
// 9 | PULONG | Key | rsp+0x48 | should be 0 at time of syscall
//

// move status ptr into stack
"mov qword ptr [rsp+0x28], {0}",
// move buffer ptr into stack
"mov qword ptr [rsp+0x30], {1}",
// move buffer len into stack
"mov dword ptr [rsp+0x38], {2}",

//allocate memory
//stack size = 80 (see https://github.com/JustasMasiulis/inline_syscall/blob/master/include/inline_syscall.inl)
//If I understand correctly, then the stack size is calculated like this:
//1. 8 bytes for "pseudo ret address"
//2. NtWriteFile has 9 args, 9 * 8 = 72 bytes (first 32 bytes is shadow space)
//3. stack alignment by 16, in our case is nothing to align
"sub rsp, 80",

//arg 1, r10 = NtCurrentTeb()->ProcessParameters->hStdOutput
//most useful structs is described in wine source code
//see: https://github.com/wine-mirror/wine/blob/master/include/winternl.h
//r10 = 0x60 (offset to PEB)
"push 0x60",
"pop r10",

//r10 = PEB*
"mov r10, gs:[r10]",
//0x20 is RTL_USER_PROCESS_PARAMETERS offset
"mov r10, [r10 + 0x20]",
//0x28 is hStdOutput offset
"mov r10, [r10 + 0x28]",

//arg 2, rdx = 0
"xor edx, edx",

//arg 3, r8 = 0, not necessary
//"xor r8, r8",

//arg 4, r9 = 0, not necessary
//"xor r9, r9",

//arg 5, [rsp + 0x28]
//this is not quite correct, but we will just overwrite the memory location
//called "stack shadow space"
//see: https://stackoverflow.com/questions/30190132/what-is-the-shadow-space-in-x64-assembly
//memory from rsp to [rsp + sizeof(IO_STATUS_BLOCK)] will be overwritten after syscall
//sizeof(IO_STATUS_BLOCK) = 16 bytes
"mov [rsp + 0x28], rsp",

//arg 6, [rsp + 0x30]
//this is dirty hack to save bytes and push string to register rax
//call instruction will push address of hello world string to the stack and jumps to label 1
//so, we can store address of string using pop instruction
//label "1", f - forward (see https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels)
"call 1f",
concat!(".ascii \"", buf!(), "\""),
"1: pop rax",
"mov [rsp + 0x30], rax",

//arg 7, [rsp + 0x38]
"mov dword ptr [rsp + 0x38], {1}",

//arg 8, [rsp + 0x40], not necessary
//"mov qword ptr [rsp + 0x40], 0",

//arg 9, [rsp + 0x48], not necessary
//"mov qword ptr [rsp + 0x48], 0",

//eax = NT_WRITE_FILE_SYSCALL_ID
"push {0}",
"pop rax",

//make syscall
"syscall",

// arg 5
in(reg) &mut status,
// arg 6
in(reg) BUF.as_ptr(),
// arg 7
const BUF.len() as u32,

// syscall id
in("eax") NT_WRITE_FILE_SYSCALL_ID,

// arg 1
//in("rcx") handle,
// on windows 10 the kernel reads from r10 not rcx
in("r10") handle,
// arg 2
in("rdx") 0,
);
//eax = 0 (exit code)
"xor eax, eax",

0
//deallocate memory
"add rsp, 80",
const NT_WRITE_FILE_SYSCALL_ID,
const buf!().len(),
options(nomem, nostack),
);
}
}

#[panic_handler]
fn panic(_: &PanicInfo) -> ! {
loop {}
}

#[repr(C)]
pub struct PEB {
pub InheritedAddressSpace: BOOLEAN,
pub ReadImageFileExecOptions: BOOLEAN,
pub BeingDebugged: BOOLEAN,
pub BitField: BOOLEAN,
pub Mutant: HANDLE,
pub ImageBaseAddress: PVOID,
pub Ldr: *mut PEB_LDR_DATA,
pub ProcessParameters: *mut RTL_USER_PROCESS_PARAMETERS,
}

#[repr(C)]
pub struct PEB_LDR_DATA {
pub Length: ULONG,
pub Initialized: BOOLEAN,
pub SsHandle: HANDLE,
pub InLoadOrderModuleList: LIST_ENTRY,
// ...
}

#[repr(C)]
pub struct RTL_USER_PROCESS_PARAMETERS {
pub MaximumLength: ULONG,
pub Length: ULONG,
pub Flags: ULONG,
pub DebugFlags: ULONG,
pub ConsoleHandle: HANDLE,
pub ConsoleFlags: ULONG,
pub StandardInput: HANDLE,
pub StandardOutput: HANDLE,
pub StandardError: HANDLE,
}

#[repr(C)]
pub struct IO_STATUS_BLOCK {
_1: IO_STATUS_BLOCK_u,
_2: PULONG,
}

#[repr(C)]
pub union IO_STATUS_BLOCK_u {
_1: NTSTATUS,
_2: PVOID,
}

0 comments on commit 864ffda

Please sign in to comment.