Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mcountryman/min-sized-rust-windows

:crab: 464b rust binary on windows
https://github.com/mcountryman/min-sized-rust-windows

rust windows

Last synced: 5 days ago
JSON representation

:crab: 464b rust binary on windows

Awesome Lists containing this project

README

        

# Minimum Binary Size Windows
[![CI](https://github.com/mcountryman/min-sized-rust-windows/actions/workflows/ci.yml/badge.svg)](https://github.com/mcountryman/min-sized-rust-windows/actions/workflows/ci.yml)

The smallest hello world I could get on win10 x64 in rust. This isn't something meant to
be used in production, more of a challenge. I'm in no ways an expert and
[I have seen windows binaries get smaller on windows](https://github.com/pts/pts-tinype). [2]
If you can go smaller let me know how you did it :grin:

### Results
`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
464
```

### Strategies
I'm excluding basic strategies here such as enabling lto and setting `opt-level = 'z'`. [0]

* [`no_std`](https://github.com/johnthagen/min-sized-rust#removing-libstd-with-no_std)
* [`no_main`](https://github.com/johnthagen/min-sized-rust#remove-corefmt-with-no_main-and-careful-usage-of-libstd)
* Merge `.rdata` and `.pdata` sections into `.text` section linker flag. [1]
* Using the LINK.exe [`/MERGE`](https://docs.microsoft.com/en-us/cpp/build/reference/merge-combine-sections?view=msvc-160)
flag found at the bottom of `main.rs`.
* Section definitions add more junk to the final output, and I _believe_ they have a
min-size. For this example we really don't care about readonly data (`.rdata`) or
exception handlers (`.pdata`) so we "merge" these empty sections into the `.text`
sections.
* No imports.
* To avoid having an extra `.idata` section (more bytes and cannot be merged into
`.text` section using `LINK.exe`) we do the following.
* Resolve stdout handle from `PEB`'s process parameters (thanks ChrisSD). [3][4]
* 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-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).
* Credits to @Frago9876543210 for finding, and implementing this.
* Drop debug info in pe header.
* Add `/EMITPOGOPHASEINFO /DEBUG:NONE` flags.
* Credits to @Frago9876543210 for finding, and implementing this.


### Future
* Using strategies shown in [[2]](https://github.com/pts/pts-tinype) we _could_ post process
the exe and merge headers to get closer to the 600-500b mark although we start straying
away from the goal of this project.
* Provided the call signature of `ZwWriteFile` I could use `build.rs` to make a script to
dynamically resolve the syscall number from `ntdll` using something like [iced-x86](https://crates.io/crates/iced-x86).
* Go pure assembly (drop type definitions for PEB).

### References
0. https://github.com/johnthagen/min-sized-rust
1. www.catch22.net/tuts/win32/reducing-executable-size#use-the-right-linker-settings
2. https://github.com/pts/pts-tinype
3. https://news.ycombinator.com/item?id=25266892 (Thank you anonunivgrad & ChrisSD!)
4. https://processhacker.sourceforge.io/doc/struct___r_t_l___u_s_e_r___p_r_o_c_e_s_s___p_a_r_a_m_e_t_e_r_s.html
5. https://j00ru.vexillium.org/syscalls/nt/64/
6. https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntwritefile

### Credits
* @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` 😁