Welcome
This is a growing set of exercises in which simple Rust programs are compiled into binaries and analyzed step by step with reverse engineering tools.
The goal is to start from the simplest possible PE binaries and gradually move toward more complex Rust applications that showcase features such as:
- Basic console I/O
- Control flow (loops, branching, pattern matching)
- Functions and modules
- Error handling
- Structs, enums, traits
- Heap allocations and dynamic memory
- Generics and lifetimes
- Concurrency and async Rust
- Integration with external libraries
Environment Details
The exact output of compiled binaries will vary slightly depending on your compiler version, target architecture, and operating system. To make results reproducible, here are the specifics of the environment I’ll be using throughout this book:
rustc --version --verbose
rustc 1.88.0 (6b00bc388 2025-06-23)
binary: rustc
commit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc
commit-date: 2025-06-23
host: x86_64-pc-windows-msvc
release: 1.88.0
LLVM version: 20.1.5
The host field (x86_64-pc-windows-msvc) indicates the target platform: 64-bit Intel/AMD (x86_64), Windows OS (windows), and MSVC toolchain (msvc). This affects calling conventions, linking, and debug info (like PDB files)
systeminfo | findstr /B /C:"OS"
OS Name: Microsoft Windows 11 Home
OS Version: 10.0.26100 N/A Build 26100
OS Manufacturer: Microsoft Corporation
OS Configuration: Standalone Workstation
OS Build Type: Multiprocessor Free
The examples in this book are tool-agnostic. You can explore them with whatever reverse engineering workflow you prefer.
Personally, I’ll be experimenting with RetDec, Ghidra, IDA Pro, and x64dbg, but readers are encouraged to use their own favorite tools, whether that’s Radare2, Binary Ninja, Hopper, or anything else.
For quick PE analysis and disassembly, I’ve also created a lightweight Rust-based tool, peanalyse, which can show PE headers, image base, entrypoint, and disassemble code directly from a binary."
Exercises
git clone https://github.com/Jozefpodlecki/reverse-engineering-exercises
0-empty
Navigate to the project folder and compile with optimizations:
cd 0-empty
rustc -C opt-level=3 -C debuginfo=0 -o main-o3.exe main.rs
The binary under study is built from the source in this repository, which consists of nothing more than an empty Rust main function.
Despite the trivial source, the resulting executable still pulls in Rust’s standard runtime and the Windows CRT. This means the file contains initialization code, runtime setup, and exit handling beyond what the high-level program expresses.
In the following sections we’ll explore how the binary is structured, which subsystems it links against, and how control flows from the Windows loader into Rust’s runtime before eventually returning to the operating system.
Entry point of a PE executable:
peanalyse -i main.exe --entry
Architecture: Pe64
AddressOfEntryPoint (RVA): 0x00014280
ImageBase: 0x140000000
EntryPoint (VA): 0x140014280
Examine the first few instructions executed.
objdump -D main-o3.exe --start-address=0x140014280 --stop-address=0x140014290
main-o3.exe: file format pei-x86-64
Disassembly of section .text:
0000000140014280 <.text+0x13280>:
140014280: 48 83 ec 28 sub $0x28,%rsp
140014284: e8 e3 02 00 00 call 0x14001456c
140014289: 48 83 c4 28 add $0x28,%rsp
14001428d: e9 72 fe ff ff jmp 0x140014104
When decompiled in Ghidra, it appears as:
ulong __cdecl mainCRTStartup(void *param_1)
{
ulong uVar1;
__security_init_cookie();
uVar1 = __scrt_common_main_seh();
return uVar1;
}
First, __security_init_cookie
initializes the compiler's stack buffer overflow protection cookie by generating a pseudo-random value using the system time, thread ID, process ID, and performance counter; it ensures the cookie is never the default sentinel value and stores both the cookie and its complement for use in stack security checks.
void __cdecl __security_init_cookie(void)
{
DWORD DVar1;
_FILETIME local_res8;
LARGE_INTEGER local_res10;
_FILETIME local_18 [2];
if (__security_cookie == 0x2b992ddfa232) {
local_res8.dwLowDateTime = 0;
local_res8.dwHighDateTime = 0;
GetSystemTimeAsFileTime(&local_res8);
local_18[0] = local_res8;
DVar1 = GetCurrentThreadId();
local_18[0] = (_FILETIME)((ulonglong)local_18[0] ^ (ulonglong)DVar1);
DVar1 = GetCurrentProcessId();
local_18[0] = (_FILETIME)((ulonglong)local_18[0] ^ (ulonglong)DVar1);
QueryPerformanceCounter(&local_res10);
__security_cookie =
((ulonglong)local_res10.s.LowPart << 0x20 ^
CONCAT44(local_res10.s.HighPart,local_res10.s.LowPart) ^ (ulonglong)local_18[0] ^
(ulonglong)local_18) & 0xffffffffffff;
if (__security_cookie == 0x2b992ddfa232) {
__security_cookie = 0x2b992ddfa233;
}
}
__security_cookie_complement = ~__security_cookie;
return;
}
__scrt_common_main_seh
initializes the C runtime, acquires a startup lock to ensure thread-safe setup, runs global constructors via _initterm_e
and _initterm
, executes dynamic TLS init and destructor callbacks if present, retrieves argc
, argv
, and the environment, calls main()
with these values, and then performs CRT and OS cleanup by calling _cexit()
or exit()
depending on whether the application is managed.
__scrt_fastfail
triggers an immediate program termination using a low-level fast-fail mechanism. It checks for CPU support for the fast fail
instruction, optionally invokes the debugger hook, captures the CPU context, performs stack unwinding if possible, and invokes UnhandledExceptionFilter
. If no debugger handles the exception, it ensures the process terminates without normal cleanup, signaling a critical unrecoverable error.
__scrt_initialize_crt
sets up CRT state for the module, marks DLL initialization if needed, detects CPU features via __isa_available_init
, and determines whether the runtime environment should be initialized.
int __cdecl __scrt_common_main_seh(void)
{
char **_Argv;
bool bVar1;
bool bVar2;
int iVar3;
_func___cdecl_void_void_ptr_ulong_void_ptr **pp_Var4;
char **_Env;
undefined8 *puVar5;
int *piVar6;
bVar1 = __scrt_initialize_crt(exe);
if (!bVar1) {
/* WARNING: Subroutine does not return */
__scrt_fastfail(7);
}
bVar1 = false;
bVar2 = __scrt_acquire_startup_lock();
if (__scrt_current_native_startup_state == initializing) {
/* WARNING: Subroutine does not return */
__scrt_fastfail(7);
}
if (__scrt_current_native_startup_state == uninitialized) {
__scrt_current_native_startup_state = initializing;
iVar3 = _initterm_e(&__xi_a,&__xi_z);
if (iVar3 != 0) {
return 0xff;
}
_initterm(&__xc_a,&__xc_z);
__scrt_current_native_startup_state = initialized;
}
else {
bVar1 = true;
}
__scrt_release_startup_lock(bVar2);
pp_Var4 = __scrt_get_dyn_tls_init_callback();
if ((*pp_Var4 != (_func___cdecl_void_void_ptr_ulong_void_ptr *)0x0) &&
(bVar2 = __scrt_is_nonwritable_in_current_image(pp_Var4), bVar2)) {
_guard_dispatch_icall_nop();
}
pp_Var4 = __scrt_get_dyn_tls_dtor_callback();
if ((*pp_Var4 != (_func___cdecl_void_void_ptr_ulong_void_ptr *)0x0) &&
(bVar2 = __scrt_is_nonwritable_in_current_image(pp_Var4), bVar2)) {
_register_thread_local_exe_atexit_callback(*pp_Var4);
}
_Env = (char **)_get_initial_narrow_environment();
puVar5 = (undefined8 *)__p___argv();
_Argv = (char **)*puVar5;
piVar6 = (int *)__p___argc();
iVar3 = main(*piVar6,_Argv,_Env);
bVar2 = __scrt_is_managed_app();
if (bVar2) {
if (!bVar1) {
_cexit();
}
__scrt_uninitialize_crt(true,false);
return iVar3;
}
exit(iVar3);
}
int __cdecl main(int _Argc,char **_Argv,char **_Env)
{
int extraout_EAX;
std::rt::lang_start_internal();
return extraout_EAX;
}
Summary
The PE entry -> mainCRTStartup
-> __scrt_common_main_seh
-> lang_start_internal
-> main()
chain handles runtime initialization, CRT setup, and exception handling before executing the user-defined main()
.
1-no-std
This exercise explores a minimal Windows executable written in Rust. The program is compiled without the standard library (#![no_std]) and does not link against the C runtime.
All functionality — writing to the console and exiting the process — is implemented manually using Windows API calls.
cd no-std
cargo build --release
peanalyse -i no-std.exe --entry
Architecture: Pe64
AddressOfEntryPoint (RVA): 0x00001000
ImageBase: 0x140000000
EntryPoint (VA): 0x140001000
The program’s entry point is at 0x140001000.
Disassembly
cd target/release
peanalyse -i no-std.exe --disassemble-from-addr=0x140001000
0x140001000: sub rsp, 0x28
0x140001004: mov ecx, 0xfffffff5
0x140001009: call qword ptr [rip + 0x1009] ; target = 0x140002018
0x14000100F: mov qword ptr [rsp + 0x20], 0
0x140001018: lea rdx, [rip + 0x1009] ; target = 0x140002028
0x14000101F: mov rcx, rax
0x140001022: mov r8d, 0xc
0x140001028: xor r9d, r9d
0x14000102B: call qword ptr [rip + 0xfcf] ; target = 0x140002000
0x140001031: mov ecx, 0x2710
0x140001036: call qword ptr [rip + 0xfcc] ; target = 0x140002008
0x14000103C: xor ecx, ecx
0x14000103E: call qword ptr [rip + 0xfcc] ; target = 0x140002010
0x140001044: int3
0x140001000: sub rsp, 0x28
Reserve 0x28 bytes (40 bytes) on the stack for local variables / alignment.
0x140001004: mov ecx, 0xfffffff5
pub const STD_OUTPUT_HANDLE: STD_HANDLE = 4294967285u32;
0xfffffff5
is the signed 32-bit representation of 4294967285u32.
RIP-relative addressing for calls
Calls use RIP-relative addressing to locate the thunk table:
0x140001009: call qword ptr [rip + 0x1009]
Resolving:
0x140001009 + 6 + 0x1009 = 0x140002018
Thunk table contents
peanalyse -i no-std.exe --read-addr=0x140002018
0x140002018 - 0x2228
Each thunk entry holds the RVA of the corresponding IAT slot:
IAT entries patched by the loader
peanalyse -i no-std.exe --iat-entries
DLL: kernel32.dll
WriteConsoleA @ RVA 0x00002238 VA 0x140002238 (hint 1296)
Sleep @ RVA 0x00002248 VA 0x140002248 (hint 1192)
ExitProcess @ RVA 0x00002250 VA 0x140002250 (hint 274)
GetStdHandle @ RVA 0x00002228 VA 0x140002228 (hint 621)
Example flow: GetStdHandle
0x140001009: call qword ptr [rip + 0x1001]
→ resolves to 0x140002010 (thunk entry, fixed at compile time)
→ thunk entry contains pointer to 0x140002218 (true IAT slot)
→ loader patches [0x140002218] with &kernel32!GetStdHandle
→ final call jumps into kernel32.dll
Lastly
0x14000100F: mov qword ptr [rsp + 0x20], 0
0x140001018: lea rdx, [rip + 0x1009] ; target = 0x140002028
0x14000101F: mov rcx, rax
0x140001022: mov r8d, 0xc
0x140001028: xor r9d, r9d
0x14000102B: call qword ptr [rip + 0xfcf] ; target = 0x140002000
Microsoft x64 calling convention. rcx → 1st argument rdx → 2nd argument r8 → 3rd r9 → 4th Additional stack space (shadow space) is already reserved at [rsp..rsp+0x20].
It is the caller's responsibility to allocate 32 bytes of "shadow space" on the stack right before calling the function (regardless of the actual number of parameters used), and to pop the stack after the call.
Resources
magic-string
Very simple password checker
peanalyse -i magic-string.exe --entry
Architecture: Pe64
AddressOfEntryPoint (RVA): 0x000177E0
ImageBase: 0x140000000
EntryPoint (VA): 0x1400177E0
mainCRTStartup 0x1400177E0
sub rsp,28
call <magic-string.__security_init_cookie>
add rsp,28
jmp <magic-string.__scrt_common_main_seh> # 0x140017664
__scrt_common_main_seh 0x140017664
mov qword ptr ss:[rsp+8],rbx
mov qword ptr ss:[rsp+10],rsi
push rdi
sub rsp,30
mov ecx,1
call <magic-string.__scrt_initialize_crt>
test al,al
...
mov r8,rdi
mov rdx,rbx
mov ecx,dword ptr ds:[rax]
call <magic-string.main> # 0x140001310
...
add rsp,30
pop rdi
ret
Initializes CRT and calls main..
Standard SEH (Structured Exception Handling) setup.
main 0x140001310
sub rsp,38
mov r9,rdx
movsxd r8,ecx
lea rax,qword ptr ds:[<sub_7FF6AB8E1100>] ; function pointer to Rust closure
mov qword ptr ss:[rsp+30],rax
mov byte ptr ss:[rsp+20],0
lea rdx,qword ptr ds:[7FF6AB8FA3A0]
lea rcx,qword ptr ss:[rsp+30]
call <magic-string.std::rt::lang_start_internal::h5830dd6b2696abe8> # 0x140003080
nop
add rsp,38
ret
Converts main to a trait object (dyn Fn() -> i32)
.
Calls lang_start_internal with a fat pointer.
lang_start_internal 0x140003080
push rbp
push rsi
push rdi
sub rsp,A0
lea rbp,qword ptr ss:[rsp+80]
mov qword ptr ss:[rbp],FFFFFFFFFFFFFFFE
mov rsi,rdx
mov rdi,rcx
...
mov eax,dword ptr ds:[<_tls_index>]
mov rdx,qword ptr gs:[58]
mov rax,qword ptr ds:[rdx+rax*8]
mov qword ptr ds:[rax+8],rcx
mov qword ptr ds:[7FF6AB904220],rcx
mov rcx,rdi
call qword ptr ds:[rsi+28]
...
ret
Implements Rust runtime startup and panic handling.
Calls the main closure via vtable ([rsi+28]).
Fat pointer (dyn Fn() -> i32 + Sync)
sub rsp,28
mov rcx,qword ptr ds:[rcx]
call <magic-string.sub_7FF6AB8E1020> # 0x140001020
xor eax,eax
add rsp,28
ret
First stub: loads data pointer from fat pointer.
0x140001020
sub rsp,28
call rcx # 0x140001100
nop
add rsp,28
ret
Second stub: trampoline calls the real rust_main
.
rust_main
push rbp
sub rsp,80
lea rbp,qword ptr ss:[rsp+80]
mov qword ptr ss:[rbp-8],FFFFFFFFFFFFFFFE
mov qword ptr ss:[rbp-28],0
mov qword ptr ss:[rbp-20],1
mov qword ptr ss:[rbp-18],0
call <magic-string.std::io::stdio::stdin::hb37ee29cfde5d31f>
mov qword ptr ss:[rbp-10],rax
lea rcx,qword ptr ss:[rbp-10]
lea rdx,qword ptr ss:[rbp-28]
call <magic-string.std::io::stdio::Stdin::read_line::h9f941692afe5412c>
test al,1
jne magic-string.7FF6AB8E1219
cmp qword ptr ss:[rbp-18],14
jne magic-string.7FF6AB8E1181
mov rax,qword ptr ss:[rbp-20]
movdqu xmm0,xmmword ptr ds:[rax]
movd xmm1,dword ptr ds:[rax+10]
pcmpeqb xmm1,xmmword ptr ds:[<__xmm@00000000000000000000000077626e41>]
pcmpeqb xmm0,xmmword ptr ds:[<__xmm@233d7138577448503841673436394c75>]
pand xmm0,xmm1
pmovmskb eax,xmm0
cmp eax,FFFF
je magic-string.7FF6AB8E11EA
lea rax,qword ptr ds:[7FF6AB8FA460]
mov qword ptr ss:[rbp-58],rax
mov qword ptr ss:[rbp-50],1
mov qword ptr ss:[rbp-48],8
pxor xmm0,xmm0
movdqu xmmword ptr ss:[rbp-40],xmm0
lea rcx,qword ptr ss:[rbp-58]
call <magic-string.std::io::stdio::_print::h033ee6824b02a35e>
call <magic-string.std::io::stdio::stdin::hb37ee29cfde5d31f>
mov qword ptr ss:[rbp-10],rax
lea rcx,qword ptr ss:[rbp-10]
lea rdx,qword ptr ss:[rbp-28]
call <magic-string.std::io::stdio::Stdin::read_line::h9f941692afe5412c>
test al,1
jne magic-string.7FF6AB8E1247
mov rdx,qword ptr ss:[rbp-28]
test rdx,rdx
je magic-string.7FF6AB8E11E0
mov rcx,qword ptr ss:[rbp-20]
mov r8d,1
call <magic-string.__rustc::__rust_dealloc>
nop
add rsp,80
pop rbp
ret
Read Line
lea rcx,qword ptr ss:[rbp-10]
lea rdx,qword ptr ss:[rbp-28]
call <magic-string.std::io::stdio::Stdin::read_line::h9f941692afe5412c>
test al,1
jne magic-string.7FF6AB8E1219
read_line
writes the input directly into the stack-allocated buffer at[rbp-28]
- The number of bytes read is stored at
[rbp-18]
.
Input Length Check
cmp qword ptr ss:[rbp-18],14
jne magic-string.7FF6AB8E1181
- rbp-18 stores the number of bytes read from stdin.
- Compares it against 0x14 (20 decimal).
- If input length ≠ 20 → jump to “Denied” handler.
String Comparison (SIMD)
mov rax,[rbp-20] ; pointer to buffer
movdqu xmm0,[rax] ; first 16 bytes
movd xmm1,[rax+16] ; next 4 bytes
pcmpeqb xmm1,[__xmm@000000…] ; compare bytes with constant1
pcmpeqb xmm0,[__xmm@233d…] ; compare bytes with constant2
pand xmm0,xmm1
pmovmskb eax,xmm0
cmp eax,FFFF
Load input into XMM registers
- xmm0 = first 16 bytes of user input.
- xmm1 = next 4 bytes of user input (loaded with movd).
Compare bytes against constants
- pcmpeqb xmm0, [pattern2] → sets each byte in xmm0 to 0xFF if equal, 0x00 if not.
- pcmpeqb xmm1, [pattern1] → same for xmm1.
Combine results
- pand xmm0, xmm1 → bitwise AND of the results.
Ensures all bytes must match across both chunks.
Extract mask
- pmovmskb eax, xmm0 → extract the MSB of each byte into a 16-bit mask in eax.
Check full match
- cmp eax, 0xFFFF → all 16 bytes in xmm0 matched.
If not all match → jump to Denied handler.
Label | Value |
---|---|
__xmm@000...77626e41 | "uL964gA8PHtW8q=#" |
__xmm@233d...34639… | "Anbw" |
Full target string | "uL964gA8PHtW8q=#Anbw" |
Resources
- https://binarydefense.com/resources/blog/digging-through-rust-to-find-gold-extracting-secrets-from-rust-malware/
string-clone
This program demonstrates the use of the rand crate for random string generation and includes a String cloning operation, both of which we will examine during reverse engineering.
0x140000240
Function Prologue / Stack Setup
00007FF70D261240 | 55 | push rbp
00007FF70D261241 | 41:57 | push r15
00007FF70D261243 | 41:56 | push r14
00007FF70D261245 | 41:55 | push r13
00007FF70D261247 | 41:54 | push r12
00007FF70D261249 | 56 | push rsi
00007FF70D26124A | 57 | push rdi
00007FF70D26124B | 53 | push rbx
00007FF70D26124C | 48:81EC 98000000 | sub rsp,98
00007FF70D261253 | 48:8DAC24 80000000 | lea rbp,qword ptr ss:[rsp+80]
Local Variable Initialization
00007FF70D26125B | 48:C745 10 FEFFFFFF | mov qword ptr ss:[rbp+10],FFFFFFFFFFFFF
00007FF70D261263 | 48:C745 F8 00000000 | mov qword ptr ss:[rbp-8],0
00007FF70D26126B | 48:C745 00 08000000 | mov qword ptr ss:[rbp],8
00007FF70D261273 | 48:C745 08 00000000 | mov qword ptr ss:[rbp+8],0
Allocates 152 bytes (0x98) on the stack for:
- Vec internals (collector)
- String temporaries (str in the loop)
- RNG state / loop counters
- Establishes rbp as a base pointer to simplify access to local variables.
RNG Initialization
00007FF70D26127B | E8 40040000 | call <string-clone._$LT$rand..rngs..thr
00007FF70D261280 | 48:89C6 | mov rsi,rax
00007FF70D261283 | 48:8945 C8 | mov qword ptr ss:[rbp-38],rax
rsi
holds RNG state for later random sampling.
Buffer check
00007FF70D261287 | 48:8D78 10 | lea rdi,qword ptr ds:[rax+10]
00007FF70D26128B | 48:8B88 50010000 | mov rcx,qword ptr ds:[rax+150]
00007FF70D261292 | 48:83F9 40 | cmp rcx,40
00007FF70D261296 | 72 39 | jb string-clone.7FF70D2612D1
RNG Refill / Update
00007FF70D261298 | 48:8D8E 10010000 | lea rcx,qword ptr ds:[rsi+110]
00007FF70D26129F | 48:8B86 48010000 | mov rax,qword ptr ds:[rsi+148]
00007FF70D2612A6 | 48:85C0 | test rax,rax
00007FF70D2612A9 | 7E 1C | jle string-clone.7FF70D2612C7
00007FF70D2612AB | 48:05 00FFFFFF | add rax,FFFFFFFFFFFFFF00
00007FF70D2612B1 | 48:8986 48010000 | mov qword ptr ds:[rsi+148],rax
00007FF70D2612B8 | BA 06000000 | mov edx,6
00007FF70D2612BD | 49:89F8 | mov r8,rdi
00007FF70D2612C0 | E8 6B040000 | call <string-clone.rand_chacha::guts::refill_wide::hc43b38d823048efe>
00007FF70D2612C5 | EB 08 | jmp string-clone.7FF70D2612CF
00007FF70D2612C7 | 48:89FA | mov rdx,rdi
00007FF70D2612CA | E8 E1FDFFFF | call <string-clone.sub_7FF70D2610B0>
RNG Value Fetch / Scaling
00007FF70D2612CF | 31C9 | xor ecx,ecx
00007FF70D2612D1 | 8B548E 10 | mov edx,dword ptr ds:[rsi+rcx*4+10]
00007FF70D2612D5 | 48:8D41 01 | lea rax,qword ptr ds:[rcx+1]
00007FF70D2612D9 | 48:8986 50010000 | mov qword ptr ds:[rsi+150],rax
00007FF70D2612E0 | 48:8D1C92 | lea rbx,qword ptr ds:[rdx+rdx*4]
00007FF70D2612E4 | 49:89DF | mov r15,rbx
00007FF70D2612E7 | 49:C1EF 20 | shr r15,20
Scales RNG value to desired range
shr and lea are used for modulo/rejection to avoid bias
RNG Rejection / Bias Check
00007FF70D2612EB | 83FB FC | cmp ebx,FFFFFFFC
00007FF70D2612EE | 72 5E | jb string-clone.7FF70D26134E
Loop Counter / Iteration Setup
00007FF70D2612F0 | 48:83F9 3F | cmp rcx,3F
00007FF70D2612F4 | 75 3C | jne string-clone.7FF70D261332
00007FF70D2612F6 | 48:89F1 | mov rcx,rsi
00007FF70D2612F9 | 48:81C1 10010000 | add rcx,110
00007FF70D261300 | 48:8B86 48010000 | mov rax,qword ptr ds:[rsi+148]
00007FF70D261307 | 48:85C0 | test rax,rax
00007FF70D26130A | 7E 1C | jle string-clone.7FF70D261328
00007FF70D26130C | 48:05 00FFFFFF | add rax,FFFFFFFFFFFFFF00
00007FF70D261312 | 48:8986 48010000 | mov qword ptr ds:[rsi+148],rax
00007FF70D261319 | BA 06000000 | mov edx,6
00007FF70D26131E | 49:89F8 | mov r8,rdi
00007FF70D261321 | E8 0A040000 | call <string-clone.rand_chacha::guts::refill_wide::hc43b38d823048efe>
00007FF70D261326 | EB 08 | jmp string-clone.7FF70D261330
00007FF70D261328 | 48:89FA | mov rdx,rdi
00007FF70D26132B | E8 80FDFFFF | call <string-clone.sub_7FF70D2610B0>
00007FF70D261330 | 31C0 | xor eax,eax
00007FF70D261332 | 8B4C86 10 | mov ecx,dword ptr ds:[rsi+rax*4+10]
00007FF70D261336 | 48:FFC0 | inc rax
00007FF70D261339 | 48:8986 50010000 | mov qword ptr ds:[rsi+150],rax
00007FF70D261340 | 48:8D0489 | lea rax,qword ptr ds:[rcx+rcx*4]
00007FF70D261344 | 48:C1E8 20 | shr rax,20
00007FF70D261348 | 01C3 | add ebx,eax
00007FF70D26134A | 49:83D7 00 | adc r15,0
00007FF70D26134E | 48:8B45 C8 | mov rax,qword ptr ss:[rbp-38]
00007FF70D261352 | 48:FF08 | dec qword ptr ds:[rax]
00007FF70D261355 | 75 09 | jne string-clone.7FF70D261360
00007FF70D261357 | 48:8D4D C8 | lea rcx,qword ptr ss:[rbp-38]
00007FF70D26135B | E8 40030000 | call <string-clone.alloc::rc::Rc$LT$T$C
00007FF70D261360 | 41:83C7 05 | add r15d,5
00007FF70D261364 | 45:31E4 | xor r12d,r12d
00007FF70D261367 | BE 08000000 | mov esi,8
00007FF70D26136C | 48:8D7D B0 | lea rdi,qword ptr ss:[rbp-50]
00007FF70D261370 | 48:8D5D C8 | lea rbx,qword ptr ss:[rbp-38]
00007FF70D261374 | 4C:8D6D A0 | lea r13,qword ptr ss:[rbp-60]
00007FF70D261378 | 45:31F6 | xor r14d,r14d
00007FF70D26137B | EB 06 | jmp string-clone.7FF70D261383
00007FF70D26137D | 0F1F00 | nop dword ptr ds:[rax],eax
This exercise explores how Rust represents function trait objects at a low level. Specifically, we examine:
fn_trait: &(dyn Fn() -> i32 + Sync + RefUnwindSafe)
This is a trait object reference: a pointer to a function-like value that is thread-safe (Sync) and panic-safe (RefUnwindSafe).
Setting up the Trait Object
00007FF7C1D914B0 | 48:83EC 38 | sub rsp,38 ; allocate 56 bytes stack
00007FF7C1D914B4 | 48:8D4C24 36 | lea rcx,[rsp+36] ; closure environment pointer (data)
00007FF7C1D914B9 | 48:8D15 687F0100 | lea rdx,[vtable] ; vtable pointer
00007FF7C1D914D2 | E8 69FEFFFF | call call_fn_trait ; call the wrapper function
Loading the fat pointer
00007FF7C1D91425 | 48:8B4C24 20 | mov rcx,qword ptr ss:[rsp+20]
00007FF7C1D9142A | 48:8B5424 28 | mov rdx,qword ptr ss:[rsp+28]
00007FF7C1D9142F | FF52 28 | call qword ptr ds:[rdx+28] |
RCX = closure environment pointer (data) RDX = vtable pointer (vtable) call [rdx+28] = calls FnOnce::call_once via vtable.
Trait Object Layout
struct FnVtable {
void* drop_in_place; // offset 0x00
unsigned long long size; // offset 0x08
unsigned long long align;// offset 0x10
void* call; // offset 0x18 (Fn::call)
void* call_mut; // offset 0x20 (FnMut::call_mut)
void* call_once; // offset 0x28 (FnOnce::call_once)
};
struct FnTraitObject {
void* data; // 0x00 (closure environment)
void* vtable; // 0x08 (pointer to FnVtable)
};
Observing in x64dbg
Struct tab → Parse Header → Display Type
Define FnVtable
Map RCX → data and RDX → vtable.
Step into call_once via vtable to see the constant returned.
Return value appears in RAX:
Closure Execution void* call
00007FF7C1D911C0 | 48:83EC 38 | sub rsp,38 | function.rs:250
00007FF7C1D911C4 | 48:894C24 30 | mov qword ptr ss:[rsp+30],rcx |
00007FF7C1D911C9 | E8 32000000 | call <fn_trait_object.core::ops::function::FnOnce::call_once |
00007FF7C1D911CE | 90 | nop |
00007FF7C1D911CF | 48:83C4 38 | add rsp,38 |
00007FF7C1D911D3 | C3 | ret | |
Closure Execution void* call_mut
, void* call_once
00007FF7C1D91500 | 50 | push rax | main.rs:18
00007FF7C1D91501 | 48:890C24 | mov qword ptr ss:[rsp],rcx |
00007FF7C1D91505 | B8 87D61200 | mov eax,12D687 |
00007FF7C1D9150A | 59 | pop rcx |
00007FF7C1D9150B | C3 | ret | |
#![allow(unused)] fn main() { let closure = || 1234567; }
The closure simply loads the constant 1234567 into RAX
Captured variables example
#![allow(unused)] fn main() { let a = 1234567i32; let b = 99u64; let closure = || { a + b as i32 }; }
It compiles the closure into a struct-like environment containing these captured values.
This environment is passed as a pointer (RCX) to the generated closure code when called via a trait object.
00007FF7FA7914C0 | 48:83EC 48 | sub rsp,48 ; allocate 72 bytes for closure
00007FF7FA7914C4 | C74424 2C 87D61200 | mov dword ptr [rsp+2C],12D687 ; store 'a' (1234567)
00007FF7FA7914CC | 48:C74424 30 63000000| mov qword ptr [rsp+30],63 ; store 'b' (99)
00007FF7FA7914D5 | 48:8D4424 2C | lea rax,[rsp+2C] ; load address of 'a'
00007FF7FA7914DA | 48:894424 38 | mov [rsp+38],rax ; save pointer in closure env
00007FF7FA7914DF | 48:8D4424 30 | lea rax,[rsp+30] ; load address of 'b'
00007FF7FA7914E4 | 48:894424 40 | mov [rsp+40],rax ; save pointer in closure env
[rsp+38] = pointer to a
[rsp+40] = pointer to b
This closure environment is what RCX will point to when the closure is called.
Inside the Closure
00007FF7FA791544 | 48:894C24 30 | mov [rsp+30], rcx
Store RCX, which points to the closure environment, on the stack at [rsp+30].
00007FF7FA791549 | 48:8B4424 30 | mov rax, [rsp+30]
00007FF7FA79154E | 48:8B00 | mov rax, [rax]
00007FF7FA791551 | 48:894424 38 | mov [rsp+38], rax
Load the closure environment pointer from [rsp+30] into RAX.
Dereference it ([rax]) to get the first vtable pointer (often drop_in_place).
Store this in [rsp+38] — this is now the first field of the fat pointer/closure struct.
00007FF7FA791556 | 48:8B4424 30 | mov rax, [rsp+30]
00007FF7FA79155B | 48:8B40 08 | mov rax, [rax+8]
00007FF7FA79155F | 48:894424 40 | mov [rsp+40], rax
Reload closure env pointer from [rsp+30] into RAX.
Dereference [rax+8] — second field of the environment (often the pointer to captured data).
Store at [rsp+40].
00007FF7FA791564 | 48:8B01 | mov rax,[rcx] ; load pointer to 'a'
00007FF7FA791567 | 8B00 | mov eax,[rax] ; load value of 'a'
00007FF7FA791569 | 48:8B49 08 | mov rcx,[rcx+8] ; load pointer to 'b'
00007FF7FA79156D | 48:8B09 | mov rcx,[rcx] ; load value of 'b'
00007FF7FA791570 | 01C8 | add eax,ecx ; perform addition a + b
RCX points to the closure environment.
[RCX] = pointer to a, [RCX+8] = pointer to b.
mov eax,[rax] and mov rcx,[rcx] dereference the pointers to get the actual values.
add eax, ecx computes the closure result.
This exercise explores how
Function Prologue
00007FF7C3EE8C70 | 55 | push rbp
...
00007FF7C3EE8C7C | 48:81EC B8020000 | sub rsp,2B8
Allocate 696 bytes on stack for
- Local variables (like buffer, windivert, tx, etc.)
- Rust runtime bookkeeping (Vec metadata, thread closure captures)
Zeroing / Initializing memory
00007FF7C3EE8CC1 | 0F57C0 | xorps xmm0,xmm0
00007FF7C3EE8CC4 | 0F2983 80000000 | movaps xmmword ptr ds:[rbx+80],xmm0
00007FF7C3EE8CCB | 0F2983 00010000 | movaps xmmword ptr ds:[rbx+100],xmm0
00007FF7C3EE8CD2 | 66:C783 80010000 0000 | mov word ptr ds:[rbx+180],0
00007FF7C3EE8CDB | 48:C783 88010000 00000000 | mov qword ptr ds:[rbx+188],0
Stack alignment & frame pointers
00007FF7C3EE8C83 | 48:8DAC24 80000000 | lea rbp,qword ptr ss:[rsp+80]
00007FF7C3EE8C8B | 48:83E4 80 | and rsp,FFFFFFFFFFFFFF80
00007FF7C3EE8C8F | 48:89E3 | mov rbx,rsp
Memory allocation
00007FF7C3EE8D15 | 0FB605 25D40200 | movzx eax,byte ptr ds:[<__rust_no_alloc_shim_is_unstable>]
00007FF7C3EE8D1C | B9 00020000 | mov ecx,200
00007FF7C3EE8D21 | BA 80000000 | mov edx,80
00007FF7C3EE8D26 | E8 A5260000 | call <windivert.__rustc::__rust_alloc>
00007FF7C3EE8D2B | 48:85C0 | test rax,rax
Rust runtime is calling __rust_alloc to allocate heap memory:
rcx, rdx = allocation size and alignment.
rax = pointer to allocated memory.
test rax, rax checks if allocation failed (None if null).
Copying strings / data
00007FF7C3EE8D37 | 48:8D93 80000000 | lea rdx,qword ptr ds:[rbx+80]
00007FF7C3EE8D44 | 48:89C1 | mov rcx,rax
00007FF7C3EE8D47 | E8 10BE0100 | call <windivert.memcpy>
Channel setup and threads
00007FF7C3EE8D99 | 4C:8D43 68 | lea r8,qword ptr ds:[rbx+68]
00007FF7C3EE8D9D | E8 CE130000 | call <windivert.std::thread::Builder::spawn_unchecked>
This is spawning a thread in Rust.
Arguments: stack pointers, local buffer, and channel sender (tx) are passed as thread closure environment.
Rust runtime handles closure captures in memory and passes a pointer to spawn_unchecked.
00007FF7C3EE83E0 | 55 | push rbp |
00007FF7C3EE83E1 | 41:57 | push r15 |
00007FF7C3EE83E3 | 41:56 | push r14 |
00007FF7C3EE83E5 | 41:55 | push r13 |
00007FF7C3EE83E7 | 41:54 | push r12 |
00007FF7C3EE83E9 | 56 | push rsi |
00007FF7C3EE83EA | 57 | push rdi |
00007FF7C3EE83EB | 53 | push rbx |
00007FF7C3EE83EC | 48:81EC 28010000 | sub rsp,128 |
00007FF7C3EE83F3 | 48:8DAC24 80000000 | lea rbp,qword ptr ss:[rsp+80] |
00007FF7C3EE83FB | 48:C785 A0000000 FEFFFFFF | mov qword ptr ss:[rbp+A0],FFFFFFFFFFFFFFFE |
00007FF7C3EE8406 | 48:894D 68 | mov qword ptr ss:[rbp+68],rcx |
00007FF7C3EE840A | 48:8D41 10 | lea rax,qword ptr ds:[rcx+10] | rax:&"tcp.SrcPort == "
00007FF7C3EE840E | 48:8945 40 | mov qword ptr ss:[rbp+40],rax |
00007FF7C3EE8412 | 48:8D05 77B60100 | lea rax,qword ptr ds:[<core::fmt::num::imp::_$LT$impl$u20$core..fmt..Display$u20$for$u20$i32 | rax:&"tcp.SrcPort == "
00007FF7C3EE8419 | 48:8945 48 | mov qword ptr ss:[rbp+48],rax | [rbp+48]:core::fmt::num::imp::_$LT$impl$u20$core..fmt..Display$u20$for$u20$i32$GT$::fmt::h2816672a2b9a7a5a
00007FF7C3EE841D | 48:8D05 84FD0100 | lea rax,qword ptr ds:[7FF7C3F081A8] | rax:&"tcp.SrcPort == ", 00007FF7C3F081A8:&"tcp.SrcPort == "
00007FF7C3EE8424 | 48:8945 C0 | mov qword ptr ss:[rbp-40],rax | [rbp-40]:&"tcp.SrcPort == "
00007FF7C3EE8428 | 48:C745 C8 01000000 | mov qword ptr ss:[rbp-38],1 |
00007FF7C3EE8430 | 48:C745 E0 00000000 | mov qword ptr ss:[rbp-20],0 |
00007FF7C3EE8438 | 48:8D45 40 | lea rax,qword ptr ss:[rbp+40] |
00007FF7C3EE843C | 48:8945 D0 | mov qword ptr ss:[rbp-30],rax |
00007FF7C3EE8440 | 48:C745 D8 01000000 | mov qword ptr ss:[rbp-28],1 |
00007FF7C3EE8448 | 48:8D8D 80000000 | lea rcx,qword ptr ss:[rbp+80] |
00007FF7C3EE844F | 48:8D55 C0 | lea rdx,qword ptr ss:[rbp-40] | [rbp-40]:&"tcp.SrcPort == "
00007FF7C3EE8453 | E8 886F0100 | call <windivert.alloc::fmt::format::format_inner::hfc6b6ff323357fe5> |
00007FF7C3EE8458 | 0F1085 80000000 | movups xmm0,xmmword ptr ss:[rbp+80] |
00007FF7C3EE845F | 0F2945 40 | movaps xmmword ptr ss:[rbp+40],xmm0 |
00007FF7C3EE8463 | 48:8B85 90000000 | mov rax,qword ptr ss:[rbp+90] |
00007FF7C3EE846A | 48:8945 50 | mov qword ptr ss:[rbp+50],rax |
00007FF7C3EE846E | 48:8D8D 80000000 | lea rcx,qword ptr ss:[rbp+80] |
00007FF7C3EE8475 | 48:8D55 40 | lea rdx,qword ptr ss:[rbp+40] |
00007FF7C3EE8479 | 41:B9 05000000 | mov r9d,5 |
00007FF7C3EE847F | 45:31C0 | xor r8d,r8d |
00007FF7C3EE8482 | E8 391B0000 | call <windivert.windivert::divert::WinDivert$LT$windivert..layer..NetworkLayer$GT$::network: |
00007FF7C3EE8487 | 48:B8 0700000000000080 | mov rax,8000000000000007 | rax:&"tcp.SrcPort == "
00007FF7C3EE8491 | 48:3985 80000000 | cmp qword ptr ss:[rbp+80],rax |
00007FF7C3EE8498 | 0F85 A5020000 | jne windivert.7FF7C3EE8743 |
00007FF7C3EE849E | 48:8B85 88000000 | mov rax,qword ptr ss:[rbp+88] |
00007FF7C3EE84A5 | 8B8D 90000000 | mov ecx,dword ptr ss:[rbp+90] |
00007FF7C3EE84AB | 48:8945 B0 | mov qword ptr ss:[rbp-50],rax |
00007FF7C3EE84AF | 894D B8 | mov dword ptr ss:[rbp-48],ecx |
00007FF7C3EE84B2 | 0FB605 88DC0200 | movzx eax,byte ptr ds:[<__rust_no_alloc_shim_is_unstable>] |
00007FF7C3EE84B9 | B9 FFFF0000 | mov ecx,FFFF |
00007FF7C3EE84BE | BA 01000000 | mov edx,1 |
00007FF7C3EE84C3 | E8 382F0000 | call <windivert.__rustc::__rust_alloc_zeroed> |
00007FF7C3EE84C8 | 48:8945 70 | mov qword ptr ss:[rbp+70],rax |
00007FF7C3EE84CC | 48:85C0 | test rax,rax | rax:&"tcp.SrcPort == "
00007FF7C3EE84CF | 75 1B | jne windivert.7FF7C3EE84EC |
00007FF7C3EE84D1 | 4C:8D05 08FD0100 | lea r8,qword ptr ds:[7FF7C3F081E0] | 00007FF7C3F081E0:&"src\\main.rs"
00007FF7C3EE84D8 | B9 01000000 | mov ecx,1 |
00007FF7C3EE84DD | BA FFFF0000 | mov edx,FFFF |
00007FF7C3EE84E2 | E8 6CDB0100 | call <windivert.alloc::raw_vec::handle_error::h5d55154af761dff4> |
00007FF7C3EE84E7 | E9 B1020000 | jmp windivert.7FF7C3EE879D |
00007FF7C3EE84EC | 48:8D75 C0 | lea rsi,qword ptr ss:[rbp-40] | [rbp-40]:&"tcp.SrcPort == "
00007FF7C3EE84F0 | 48:8D7D B0 | lea rdi,qword ptr ss:[rbp-50] |
00007FF7C3EE84F4 | 49:BD 0100000000000080 | mov r13,8000000000000001 |
00007FF7C3EE84FE | 48:8D9D 80000000 | lea rbx,qword ptr ss:[rbp+80] |
00007FF7C3EE8505 | 41:BC 01000000 | mov r12d,1 |
00007FF7C3EE850B | EB 14 | jmp windivert.7FF7C3EE8521 |
00007FF7C3EE850D | 0F1F00 | nop dword ptr ds:[rax],eax |
00007FF7C3EE8510 | 48:8D0455 00000000 | lea rax,qword ptr ds:[rdx*2] | rax:&"tcp.SrcPort == "
00007FF7C3EE8518 | 48:85C0 | test rax,rax | rax:&"tcp.SrcPort == "
00007FF7C3EE851B | 0F85 5F010000 | jne windivert.7FF7C3EE8680 |
00007FF7C3EE8521 | 41:B9 FFFF0000 | mov r9d,FFFF |
00007FF7C3EE8527 | 48:89F1 | mov rcx,rsi |
00007FF7C3EE852A | 48:89FA | mov rdx,rdi |
00007FF7C3EE852D | 4C:8B45 70 | mov r8,qword ptr ss:[rbp+70] |
00007FF7C3EE8531 | E8 EA2E0000 | call <windivert.windivert::divert::blocking::_$LT$impl$u20$windivert..divert..WinDivert$LT$w |
00007FF7C3EE8536 | 48:8B45 C0 | mov rax,qword ptr ss:[rbp-40] | [rbp-40]:&"tcp.SrcPort == "
00007FF7C3EE853A | 4C:39E8 | cmp rax,r13 | rax:&"tcp.SrcPort == "
00007FF7C3EE853D | 0F84 51010000 | je windivert.7FF7C3EE8694 |
00007FF7C3EE8543 | 48:8945 60 | mov qword ptr ss:[rbp+60],rax |
00007FF7C3EE8547 | 48:8B45 C8 | mov rax,qword ptr ss:[rbp-38] |
00007FF7C3EE854B | 48:8945 78 | mov qword ptr ss:[rbp+78],rax |
00007FF7C3EE854F | 4C:8B7D D0 | mov r15,qword ptr ss:[rbp-30] |
00007FF7C3EE8553 | 4D:85FF | test r15,r15 |
00007FF7C3EE8556 | 0F88 27020000 | js windivert.7FF7C3EE8783 |
00007FF7C3EE855C | 74 22 | je windivert.7FF7C3EE8580 |
00007FF7C3EE855E | 0FB605 DCDB0200 | movzx eax,byte ptr ds:[<__rust_no_alloc_shim_is_unstable>] |
00007FF7C3EE8565 | BA 01000000 | mov edx,1 |
00007FF7C3EE856A | 4C:89F9 | mov rcx,r15 |
00007FF7C3EE856D | E8 5E2E0000 | call <windivert.__rustc::__rust_alloc> |
00007FF7C3EE8572 | 48:85C0 | test rax,rax | rax:&"tcp.SrcPort == "
00007FF7C3EE8575 | 0F84 0D020000 | je windivert.7FF7C3EE8788 |
00007FF7C3EE857B | 49:89C6 | mov r14,rax | rax:&"tcp.SrcPort == "
00007FF7C3EE857E | EB 06 | jmp windivert.7FF7C3EE8586 |
00007FF7C3EE8580 | 41:BE 01000000 | mov r14d,1 |
00007FF7C3EE8586 | 4C:89F1 | mov rcx,r14 |
00007FF7C3EE8589 | 48:8B55 78 | mov rdx,qword ptr ss:[rbp+78] |
00007FF7C3EE858D | 4D:89F8 | mov r8,r15 |
00007FF7C3EE8590 | E8 C7C50100 | call <windivert.memcpy> |
00007FF7C3EE8595 | 48:8B4D 68 | mov rcx,qword ptr ss:[rbp+68] |
00007FF7C3EE8599 | 48:8B01 | mov rax,qword ptr ds:[rcx] | rax:&"tcp.SrcPort == "
00007FF7C3EE859C | 48:8B51 08 | mov rdx,qword ptr ds:[rcx+8] |
00007FF7C3EE85A0 | 48:83F8 02 | cmp rax,2 | rax:&"tcp.SrcPort == "
00007FF7C3EE85A4 | 74 3A | je windivert.7FF7C3EE85E0 |
00007FF7C3EE85A6 | 83F8 01 | cmp eax,1 |
00007FF7C3EE85A9 | 75 65 | jne windivert.7FF7C3EE8610 |
00007FF7C3EE85AB | 4C:89BD 80000000 | mov qword ptr ss:[rbp+80],r15 |
00007FF7C3EE85B2 | 4C:89B5 88000000 | mov qword ptr ss:[rbp+88],r14 |
00007FF7C3EE85B9 | 4C:89BD 90000000 | mov qword ptr ss:[rbp+90],r15 |
00007FF7C3EE85C0 | C74424 20 00CA9A3B | mov dword ptr ss:[rsp+20],3B9ACA00 |
00007FF7C3EE85C8 | 48:89F1 | mov rcx,rsi |
00007FF7C3EE85CB | 49:89D8 | mov r8,rbx |
00007FF7C3EE85CE | E8 2D9DFFFF | call <windivert.std::sync::mpmc::list::Channel$LT$T$GT$::send::hccb9013442662364> |
00007FF7C3EE85D3 | EB 63 | jmp windivert.7FF7C3EE8638 |
00007FF7C3EE85D5 | 66662E:0F1F8400 00000000 | nop word ptr ds:[rax+rax],ax |
00007FF7C3EE85E0 | 4C:89BD 80000000 | mov qword ptr ss:[rbp+80],r15 |
00007FF7C3EE85E7 | 4C:89B5 88000000 | mov qword ptr ss:[rbp+88],r14 |
00007FF7C3EE85EE | 4C:89BD 90000000 | mov qword ptr ss:[rbp+90],r15 |
00007FF7C3EE85F5 | C74424 20 00CA9A3B | mov dword ptr ss:[rsp+20],3B9ACA00 |
00007FF7C3EE85FD | 48:89F1 | mov rcx,rsi |
00007FF7C3EE8600 | 49:89D8 | mov r8,rbx |
00007FF7C3EE8603 | E8 28D3FFFF | call <windivert.std::sync::mpmc::zero::Channel$LT$T$GT$::send::h6f8bab36c27d44da> |
00007FF7C3EE8608 | EB 2E | jmp windivert.7FF7C3EE8638 |
00007FF7C3EE860A | 66:0F1F4400 00 | nop word ptr ds:[rax+rax],ax |
00007FF7C3EE8610 | 4C:89BD 80000000 | mov qword ptr ss:[rbp+80],r15 |
00007FF7C3EE8617 | 4C:89B5 88000000 | mov qword ptr ss:[rbp+88],r14 |
00007FF7C3EE861E | 4C:89BD 90000000 | mov qword ptr ss:[rbp+90],r15 |
00007FF7C3EE8625 | C74424 20 00CA9A3B | mov dword ptr ss:[rsp+20],3B9ACA00 |
00007FF7C3EE862D | 48:89F1 | mov rcx,rsi |
00007FF7C3EE8630 | 49:89D8 | mov r8,rbx |
00007FF7C3EE8633 | E8 C8ADFFFF | call <windivert.std::sync::mpmc::array::Channel$LT$T$GT$::send::h3f0d389a1efe5b98> |
00007FF7C3EE8638 | 48:8B45 C0 | mov rax,qword ptr ss:[rbp-40] | [rbp-40]:&"tcp.SrcPort == "
00007FF7C3EE863C | 48:83F8 02 | cmp rax,2 | rax:&"tcp.SrcPort == "
00007FF7C3EE8640 | 48:8B55 60 | mov rdx,qword ptr ss:[rbp+60] |
00007FF7C3EE8644 | 0F84 C6FEFFFF | je windivert.7FF7C3EE8510 |
00007FF7C3EE864A | 48:8B4D C8 | mov rcx,qword ptr ss:[rbp-38] |
00007FF7C3EE864E | A8 01 | test al,1 |
00007FF7C3EE8650 | 0F84 C3000000 | je windivert.7FF7C3EE8719 |
00007FF7C3EE8656 | 48:8D45 C8 | lea rax,qword ptr ss:[rbp-38] |
00007FF7C3EE865A | 0F1040 08 | movups xmm0,xmmword ptr ds:[rax+8] | rax+08:anon.95dd94260625f633aaffbc1dd64e9bca.29.llvm.18142701166963205077+3F8
00007FF7C3EE865E | 0F2985 80000000 | movaps xmmword ptr ss:[rbp+80],xmm0 |
00007FF7C3EE8665 | 48:89C8 | mov rax,rcx | rax:&"tcp.SrcPort == "
00007FF7C3EE8668 | 48:F7D8 | neg rax | rax:&"tcp.SrcPort == "
00007FF7C3EE866B | 0F80 9FFEFFFF | jo windivert.7FF7C3EE8510 |
00007FF7C3EE8671 | EB 6A | jmp windivert.7FF7C3EE86DD |
00007FF7C3EE8673 | 666666662E:0F1F8400 00000000 | nop word ptr ds:[rax+rax],ax |
00007FF7C3EE8680 | 41:B8 01000000 | mov r8d,1 |
00007FF7C3EE8686 | 48:8B4D 78 | mov rcx,qword ptr ss:[rbp+78] |
00007FF7C3EE868A | E8 512D0000 | call <windivert.__rustc::__rust_dealloc> |
00007FF7C3EE868F | E9 8DFEFFFF | jmp windivert.7FF7C3EE8521 |
00007FF7C3EE8694 | 48:8D45 C8 | lea rax,qword ptr ss:[rbp-38] |
00007FF7C3EE8698 | 0F1000 | movups xmm0,xmmword ptr ds:[rax] | rax:&"tcp.SrcPort == "
00007FF7C3EE869B | 0F1048 10 | movups xmm1,xmmword ptr ds:[rax+10] | rax+10:"src\\main.rs"
00007FF7C3EE869F | 0F298D 90000000 | movaps xmmword ptr ss:[rbp+90],xmm1 |
00007FF7C3EE86A6 | 0F2985 80000000 | movaps xmmword ptr ss:[rbp+80],xmm0 |
00007FF7C3EE86AD | 48:8D05 44FB0100 | lea rax,qword ptr ds:[7FF7C3F081F8] | rax:&"tcp.SrcPort == ", 00007FF7C3F081F8:&"src\\main.rs"
00007FF7C3EE86B4 | 48:894424 20 | mov qword ptr ss:[rsp+20],rax |
00007FF7C3EE86B9 | 48:8D0D 90F70100 | lea rcx,qword ptr ds:[7FF7C3F07E50] | 00007FF7C3F07E50:"called `Result::unwrap()` on an `Err` value"
00007FF7C3EE86C0 | 4C:8D0D 69F70100 | lea r9,qword ptr ds:[7FF7C3F07E30] |
00007FF7C3EE86C7 | 4C:8D85 80000000 | lea r8,qword ptr ss:[rbp+80] |
00007FF7C3EE86CE | BA 2B000000 | mov edx,2B | 2B:'+'
00007FF7C3EE86D3 | E8 28DE0100 | call <windivert.core::result::unwrap_failed::h70751bb42e9051bd> |
00007FF7C3EE86D8 | E9 C0000000 | jmp windivert.7FF7C3EE879D |
00007FF7C3EE86DD | 48:894D C0 | mov qword ptr ss:[rbp-40],rcx | [rbp-40]:&"tcp.SrcPort == "
00007FF7C3EE86E1 | 0F2885 80000000 | movaps xmm0,xmmword ptr ss:[rbp+80] |
00007FF7C3EE86E8 | 0F1145 C8 | movups xmmword ptr ss:[rbp-38],xmm0 |
00007FF7C3EE86EC | 48:8D05 1DFB0100 | lea rax,qword ptr ds:[7FF7C3F08210] | rax:&"tcp.SrcPort == ", 00007FF7C3F08210:&"src\\main.rs"
00007FF7C3EE86F3 | 48:894424 20 | mov qword ptr ss:[rsp+20],rax |
00007FF7C3EE86F8 | 48:8D0D 51F70100 | lea rcx,qword ptr ds:[7FF7C3F07E50] | 00007FF7C3F07E50:"called `Result::unwrap()` on an `Err` value"
00007FF7C3EE86FF | 4C:8D0D 7AF70100 | lea r9,qword ptr ds:[<&sub_7FF7C3EE7D30>] |
00007FF7C3EE8706 | 4C:8D45 C0 | lea r8,qword ptr ss:[rbp-40] | [rbp-40]:&"tcp.SrcPort == "
00007FF7C3EE870A | BA 2B000000 | mov edx,2B | 2B:'+'
00007FF7C3EE870F | E8 ECDD0100 | call <windivert.core::result::unwrap_failed::h70751bb42e9051bd> |
00007FF7C3EE8714 | E9 84000000 | jmp windivert.7FF7C3EE879D |
00007FF7C3EE8719 | 48:8945 30 | mov qword ptr ss:[rbp+30],rax |
00007FF7C3EE871D | 48:894D 28 | mov qword ptr ss:[rbp+28],rcx |
00007FF7C3EE8721 | 48:8B45 D0 | mov rax,qword ptr ss:[rbp-30] |
00007FF7C3EE8725 | 48:8945 38 | mov qword ptr ss:[rbp+38],rax | [rbp+38]:std::thread::spawnhook::ChildSpawnHooks::run+17C
00007FF7C3EE8729 | 48:8D0D 60FE0100 | lea rcx,qword ptr ds:[<anon.70de80080aa0578d8e2c0b1b642816f1.8.llvm.9175985875024411532>] | 00007FF7C3F08590:"internal error: entered unreachable code"
00007FF7C3EE8730 | 4C:8D05 29FF0100 | lea r8,qword ptr ds:[<anon.70de80080aa0578d8e2c0b1b642816f1.11.llvm.9175985875024411532>] | 00007FF7C3F08660:&"C:\\Users\\jozef\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\\lib/rustlib/src/rust\\library\\std\\src\\sync\\mpmc\\mod.rs"
00007FF7C3EE8737 | BA 28000000 | mov edx,28 | 28:'('
00007FF7C3EE873C | E8 3FDB0100 | call <windivert.core::panicking::panic::h0cc6c70cb61d6977> |
00007FF7C3EE8741 | EB 5A | jmp windivert.7FF7C3EE879D |
00007FF7C3EE8743 | 0F1085 80000000 | movups xmm0,xmmword ptr ss:[rbp+80] |
00007FF7C3EE874A | 0F108D 90000000 | movups xmm1,xmmword ptr ss:[rbp+90] |
00007FF7C3EE8751 | 0F294D D0 | movaps xmmword ptr ss:[rbp-30],xmm1 |
00007FF7C3EE8755 | 0F2945 C0 | movaps xmmword ptr ss:[rbp-40],xmm0 |
00007FF7C3EE8759 | 48:8D05 68FA0100 | lea rax,qword ptr ds:[7FF7C3F081C8] | rax:&"tcp.SrcPort == ", 00007FF7C3F081C8:&"src\\main.rs"
00007FF7C3EE8760 | 48:894424 20 | mov qword ptr ss:[rsp+20],rax |
00007FF7C3EE8765 | 48:8D0D E4F60100 | lea rcx,qword ptr ds:[7FF7C3F07E50] | 00007FF7C3F07E50:"called `Result::unwrap()` on an `Err` value"
00007FF7C3EE876C | 4C:8D0D BDF60100 | lea r9,qword ptr ds:[7FF7C3F07E30] |
00007FF7C3EE8773 | 4C:8D45 C0 | lea r8,qword ptr ss:[rbp-40] | [rbp-40]:&"tcp.SrcPort == "
00007FF7C3EE8777 | BA 2B000000 | mov edx,2B | 2B:'+'
00007FF7C3EE877C | E8 7FDD0100 | call <windivert.core::result::unwrap_failed::h70751bb42e9051bd> |
00007FF7C3EE8781 | EB 1A | jmp windivert.7FF7C3EE879D |
00007FF7C3EE8783 | 45:31E4 | xor r12d,r12d |
00007FF7C3EE8786 | EB 03 | jmp windivert.7FF7C3EE878B |
00007FF7C3EE8788 | 4D:89FE | mov r14,r15 |
00007FF7C3EE878B | 4C:8D05 96F90100 | lea r8,qword ptr ds:[7FF7C3F08128] | 00007FF7C3F08128:&"C:\\Users\\jozef\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\\lib/rustlib/src/rust\\library\\alloc\\src\\slice.rs"
00007FF7C3EE8792 | 4C:89E1 | mov rcx,r12 |
00007FF7C3EE8795 | 4C:89F2 | mov rdx,r14 |
00007FF7C3EE8798 | E8 B6D80100 | call <windivert.alloc::raw_vec::handle_error::h5d55154af761dff4> |
00007FF7C3EE879D | 0F0B | ud2 |
00007FF7C3EE879F | 90 | nop |
00007FF7C3EE87A0 | 48:895424 10 | mov qword ptr ss:[rsp+10],rdx |
00007FF7C3EE87A5 | 55 | push rbp |
00007FF7C3EE87A6 | 41:57 | push r15 |
00007FF7C3EE87A8 | 41:56 | push r14 |
00007FF7C3EE87AA | 41:55 | push r13 |
00007FF7C3EE87AC | 41:54 | push r12 |
00007FF7C3EE87AE | 56 | push rsi |
00007FF7C3EE87AF | 57 | push rdi |
00007FF7C3EE87B0 | 53 | push rbx |
00007FF7C3EE87B1 | 48:83EC 28 | sub rsp,28 |
00007FF7C3EE87B5 | 48:8DAA 80000000 | lea rbp,qword ptr ds:[rdx+80] |
00007FF7C3EE87BC | 48:8B4D 68 | mov rcx,qword ptr ss:[rbp+68] |
00007FF7C3EE87C0 | 48:8B01 | mov rax,qword ptr ds:[rcx] | rax:&"tcp.SrcPort == "
00007FF7C3EE87C3 | 48:83C1 08 | add rcx,8 |
00007FF7C3EE87C7 | 48:85C0 | test rax,rax | rax:&"tcp.SrcPort == "
00007FF7C3EE87CA | 74 0C | je windivert.7FF7C3EE87D8 |
00007FF7C3EE87CC | 83F8 01 | cmp eax,1 |
00007FF7C3EE87CF | 75 6B | jne windivert.7FF7C3EE883C |
00007FF7C3EE87D1 | E8 AAE7FFFF | call <windivert.std::sync::mpmc::counter::Sender$LT$C$GT$::release::h556aa944d0fa04c6> |
00007FF7C3EE87D6 | EB 69 | jmp windivert.7FF7C3EE8841 |
00007FF7C3EE87D8 | 48:8B31 | mov rsi,qword ptr ds:[rcx] |
00007FF7C3EE87DB | F048:FF8E 00020000 | lock dec qword ptr ds:[rsi+200] |
00007FF7C3EE87E3 | 75 5C | jne windivert.7FF7C3EE8841 |
00007FF7C3EE87E5 | 48:8B86 80000000 | mov rax,qword ptr ds:[rsi+80] | rax:&"tcp.SrcPort == "
00007FF7C3EE87EC | 48:8B8E 90010000 | mov rcx,qword ptr ds:[rsi+190] |
00007FF7C3EE87F3 | 666666662E:0F1F8400 00000000 | nop word ptr ds:[rax+rax],ax |
00007FF7C3EE8800 | 48:89C2 | mov rdx,rax | rax:&"tcp.SrcPort == "
00007FF7C3EE8803 | 48:09CA | or rdx,rcx |
00007FF7C3EE8806 | F048:0FB196 80000000 | lock cmpxchg qword ptr ds:[rsi+80],rdx |
00007FF7C3EE880F | 75 EF | jne windivert.7FF7C3EE8800 |
00007FF7C3EE8811 | 48:8586 90010000 | test qword ptr ds:[rsi+190],rax | rax:&"tcp.SrcPort == "
00007FF7C3EE8818 | 75 0C | jne windivert.7FF7C3EE8826 |
00007FF7C3EE881A | 48:8D8E 40010000 | lea rcx,qword ptr ds:[rsi+140] |
00007FF7C3EE8821 | E8 CAB2FFFF | call <windivert.std::sync::mpmc::waker::SyncWaker::disconnect::h6ad45fdb3df37a47 (.llvm.9322 |
00007FF7C3EE8826 | B0 01 | mov al,1 |
00007FF7C3EE8828 | 8686 10020000 | xchg byte ptr ds:[rsi+210],al |
00007FF7C3EE882E | 84C0 | test al,al |
00007FF7C3EE8830 | 74 0F | je windivert.7FF7C3EE8841 |
00007FF7C3EE8832 | 48:89F1 | mov rcx,rsi |
00007FF7C3EE8835 | E8 E6EDFFFF | call <windivert.core::ptr::drop_in_place$LT$alloc..boxed..Box$LT$std..sync..mpmc..counter..C |
00007FF7C3EE883A | EB 05 | jmp windivert.7FF7C3EE8841 |
00007FF7C3EE883C | E8 8FE8FFFF | call <windivert.std::sync::mpmc::counter::Sender$LT$C$GT$::release::h9ff499d21cc3324b> |
00007FF7C3EE8841 | 90 | nop |
00007FF7C3EE8842 | 48:83C4 28 | add rsp,28 |
00007FF7C3EE8846 | 5B | pop rbx |
00007FF7C3EE8847 | 5F | pop rdi |
00007FF7C3EE8848 | 5E | pop rsi |
00007FF7C3EE8849 | 41:5C | pop r12 |
00007FF7C3EE884B | 41:5D | pop r13 |
00007FF7C3EE884D | 41:5E | pop r14 |
00007FF7C3EE884F | 41:5F | pop r15 |
00007FF7C3EE8851 | 5D | pop rbp |
00007FF7C3EE8852 | C3 | ret |
peanalyse -i calling-conventions.exe --entry
Architecture: Pe64
AddressOfEntryPoint (RVA): 0x000168A0
ImageBase: 0x140000000
EntryPoint (VA): 0x1400168A0
peanalyse -i calling-conventions.exe --disassemble-from-addr=0x1400016B0
0x1400016EA: mov dword ptr [rsp + 0x38], eax
0x1400016EE: mov ecx, 1
0x1400016F3: mov edx, 2
0x1400016F8: mov r8d, 3
0x1400016FE: mov r9d, 4
0x140001704: mov dword ptr [rsp + 0x20], 5
0x14000170C: mov dword ptr [rsp + 0x28], 6
0x140001714: mov dword ptr [rsp + 0x30], 7
0x14000171C: call 0x1400012c0
Windows x64 calling convention:
- Registers: RCX, RDX, R8, R9 for the first 4 parameters.
- Stack: parameters 5, 6, 7 go on the stack at offsets from RSP.
0x14000188F: mov dword ptr [rsp + 0x44], eax
0x140001893: mov dword ptr [rsp + 0x48], 1
0x14000189B: mov dword ptr [rsp + 0x4c], 2
0x1400018A3: mov dword ptr [rsp + 0x50], 3
0x1400018AB: mov dword ptr [rsp + 0x54], 4
0x1400018B3: mov dword ptr [rsp + 0x58], 5
0x1400018BB: mov dword ptr [rsp + 0x5c], 6
0x1400018C3: mov dword ptr [rsp + 0x60], 7
0x1400018CB: mov dword ptr [rsp + 0x64], 8
0x1400018D3: mov rax, qword ptr [rsp + 0x48]
0x1400018D8: mov qword ptr [rsp + 0x1a8], rax
0x1400018E0: mov rax, qword ptr [rsp + 0x50]
0x1400018E5: mov qword ptr [rsp + 0x1b0], rax
0x1400018ED: mov rax, qword ptr [rsp + 0x58]
0x1400018F2: mov qword ptr [rsp + 0x1b8], rax
0x1400018FA: mov rax, qword ptr [rsp + 0x60]
0x1400018FF: mov qword ptr [rsp + 0x1c0], rax
0x140001907: lea rcx, [rsp + 0x1a8]
0x14000190F: call 0x1400011b0
On x86-64 Windows (MSVC calling convention):
- If the struct fits in 1–2 registers (≤16 bytes), it is passed in registers.
- If the struct is larger than 16 bytes, it is passed by reference: the caller allocates space on the stack and passes a pointer.
- The callee then reads fields via that pointer.
BigStruct
is 32 bytes, so rust will pass a pointer to struct rather than copying all fields into registers. --lea rcx, [rsp + ...] or mov rcx, &struct_on_stack
before the call.
This program generates a random creature variant and prints its name using a precomputed lookup table. The Rust enum is transmuted from a random byte, and a match on the enum is replaced by a table-based string lookup in release mode.
call rand::rngs::thread::rng
mov rsi, rax
mov [rbp+local_20], rax
Initializes ThreadRng and stores its pointer in rsi and a local variable.
mov rcx, [rsi + 0x150]
cmp rcx, 0x40
jc SHORT LAB_BUFFER_OK
Loads the RNG buffer index and checks if it needs a refill.
lea r8, [rax + 0x10]
add rax, 0x110
mov rcx, [rsi + 0x148]
test rcx, rcx
jle SHORT LAB_REPLENISH_FALLBACK
add rcx, -0x100
mov [rsi + 0x148], rcx
mov rcx, rax
mov edx, 0x6
call rand_chacha::guts::refill_wide
jmp SHORT LAB_CONTINUE
LAB_REPLENISH_FALLBACK:
mov rcx, rax
mov rdx, r8
call FUN_140001050
Refills the ChaCha RNG buffer if exhausted. Fallback path for insufficient buffer, calls internal RNG helper.
lea rcx, [DAT_STRING_OFFSET_TABLE]
movsxd rdx, [rcx + rax*4]
add rdx, rcx
lea rcx, [DAT_STRING_PTR_TABLE]
mov rax, [rcx + rax*8]
mov [rbp + local_30], rdx
mov [rbp + local_28], rax
Maps the enum variant to a string pointer using precomputed offset and pointer tables.
[rcx + rax*4]
- The offset table uses 32-bit entries (4 bytes each).
- rax holds the random enum variant (0–31).
- Multiplying rax by 4 gives the byte offset for the correct entry.
- MOVSXD rdx, [rcx + rax*4] loads the 32-bit offset for the variant and sign-extends it to 64-bit.
- This offset is relative to the base of the string table, so adding rdx + rcx produces the absolute string address.
[rcx + rax*8]
- The pointer table uses 64-bit entries (8 bytes each).
- Again, rax is the variant index.
- Multiplying by 8 gives the byte offset of the correct pointer.
- MOV rax, [rcx + rax*8] loads the full 64-bit pointer directly, no sign-extension needed.
lea rax, [rbp + local_30]
mov [rbp + local_40], rax
lea rax, [LAB_140001030]
mov [rbp + local_38], rax
lea rax, [DAT_CONST_1]
mov [rbp + local_70], rax
mov [rbp + local_68], 2
mov [rbp + local_50], 0
lea rax, [rbp + local_40]
mov [rbp + local_60], rax
mov [rbp + local_58], 1
lea rcx, [rbp + local_70]
call std::io::stdio::_print
Prepares arguments and calls _print to output the creature name.
mov rax, [rbp + local_20]
dec [rax]
jz SHORT LAB_DROP
add rsp, 0x88
pop rsi
pop rbp
ret
mov rax, [rbp + local_20]
Loads the pointer to the ThreadRng instance, which is stored in local_20.
dec [rax]
Decrements the reference count of the ThreadRng. ThreadRng is behind an Rc (reference-counted smart pointer).
jz SHORT LAB_DROP
LAB_DROP:
lea rcx, [rbp + local_20]
call alloc::rc::Rc<T,A>::drop_slow
If the reference count reached zero, jump to drop_slow to free the memory. If not zero, skip and just clean the stack.
add rsp, 0x88; pop rsi; pop rbp; ret
Standard stack cleanup and return from main if the RNG instance is still alive (ref count > 0).
This exercise tauri app template.
tauri::plugin::Builder<R,C>::new
_ZN5tauri6plugin20Builder$LT$R$C$C$GT$3new
140096e10 e8 5b 4d 4f 00 CALL std::hash::random::impl$0::new::KEYS::constant
140096e15 49 89 c6 MOV R14,RAX
140096e18 80 78 10 01 CMP byte ptr [RAX+0x10],0x1
- Part of HashMap/FxHasher initialization.
- Returns a pointer (RAX → R14) to the random seed/keys used internally by the plugin builder.
140096e22 49 8b 06 MOV RAX,qword ptr [R14]
140096e25 49 8b 56 08 MOV RDX,qword ptr [R14+0x8]
140096e29 48 8d 48 01 LEA RCX,[RAX+1]
140096e2d 49 89 0e MOV qword ptr [R14],RCX
- Increments an internal counter stored in the random::KEYS structure.
- Likely used to ensure unique identifiers for each plugin instance.
_ZN83_$LT$tauri_runtime_wry..Wry$LT$T$GT$$u20$_<>::run
1400fc4a0 MOV RSI, qword ptr [RCX + 0x50]
1400fc4a4 INC.LOCK qword ptr [RSI]
1400fc4a8 JLE LAB_1400fc4c8
- Loads a pointer from the object (RCX points to self).
- Atomically increments the value pointed to by that pointer.
- Jumps if less than or equal to zero (JLE) to LAB_1400fc4c8.
1400fc4aa MOV RDI, qword ptr [RCX + 0x18]
1400fc4ae INC.LOCK qword ptr [RDI]
1400fc4b2 JLE LAB_1400fc4c8
1400fc4b4 MOV RBX, qword ptr [RCX + 0x48]
1400fc4b8 INC.LOCK qword ptr [RBX]
1400fc4bc JLE LAB_1400fc4c8
1400fc4be MOV RAX, qword ptr [RCX + 0x58]
1400fc4c2 INC.LOCK qword ptr [RAX]
1400fc4c6 JG LAB_1400fc4ca
- Each load gets a pointer from the struct and performs an atomic increment.
- If any of these increments result in <= 0, the code jumps to a UD2 instruction (illegal instruction) at LAB_1400fc4c8.
- The last increment uses JG (jump if greater) to continue normal execution.
Misc
C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um
ntdll.LdrGetProcedureAddressForCaller
- Internal loader function that resolves function pointers in DLLs.
- Equivalent to GetProcAddress, but with extra security / caller tracking.
- Used during import table processing or when dynamic function lookups occur.
ntdll.RtlDeleteElementGenericTableAvlEx
- Deletes an element from an AVL-based generic table.
- Windows often uses these tables internally to track loaded modules, TLS slots, or runtime resources.
- Seeing this in a loader trace usually indicates cleanup or deallocation during module setup or teardown.
kernelbase.GetApplicationRestartSettings
- Retrieves settings related to Windows application restart / recovery.
- Called internally during process initialization to see if the OS wants to auto-restart a crashed application.
ntdll.RtlEncodeRemotePointer
- Obfuscates or encodes pointers that may be shared across processes or stored in global tables.
- Prevents accidental or malicious misuse of raw pointers (security measure).
- Usually happens after resolving function addresses or storing thread-local data.
ntdll.RtlAnsiStringToUnicodeString
- Converts an ANSI string to a Unicode (UTF-16) string.
- Used during DLL loading or manifest/registry string parsing, because many Windows APIs expect Unicode internally.
ntdll.RtlAcquireSRWLockExclusive
- Acquires a Slim Reader/Writer lock exclusively.
- Prevents multiple threads from modifying shared resources at the same time.
- Often used in runtime and loader code for: -- AVL tables -- Loader metadata -- Heap or TLS structures
ntdll.RtlUTF8ToUnicode
- Converts a UTF-8 string to Unicode (UTF-16).
ntdll.TpCallbackMayRunLong
- Part of Windows Thread Pool (Tp) API.
- Called internally when a thread pool callback might take a long time to execute.
- The runtime uses this to decide whether to inject additional threads or adjust scheduling.
RtlInsertElementGenericTableFullAvl
- Inserts an element into a generic AVL tree in memory.
- The “FullAvl” version handles both insertion and balancing automatically.
ntdll.RtlRaiseStatus
- Raises a Windows NTSTATUS exception.
- Converts a status code (like an error or system signal) into an exception that can be caught by SEH handlers.
ntdll.RtlUserThreadStart
- Entry point for new user-mode threads.
- Windows sets up a thread so that when it begins execution, it runs RtlUserThreadStart.
- Responsible for: -- Setting up thread-local storage (TLS) -- Calling your thread function (LPTHREAD_START_ROUTINE) -- Handling structured exception handling (SEH) for the thread
kernel32.BaseThreadInitThunk
- Called by the OS when a thread starts.
- Sets up the thread environment before calling RtlUserThreadStart.
- Handles: -- Register initialization -- Stack setup -- Exception frame setup
ntdll.NtReleaseWorkerFactoryWorker
ntdll.RtlGetReturnAddressHijackTarget
ntdll.ZwWaitForWorkViaWorkerFactory
ntdll.NtWaitForSingleObject
LdrpInitialize
LdrpInitializeProcess
LdtpInitializeThread
TestAlert
NtContinue
ntdll.NtMapViewOfSection
NTSYSAPI NTSTATUS ZwMapViewOfSection(
[in] HANDLE SectionHandle,
[in] HANDLE ProcessHandle,
[in, out] PVOID *BaseAddress,
[in] ULONG_PTR ZeroBits,
[in] SIZE_T CommitSize,
[in, out, optional] PLARGE_INTEGER SectionOffset,
[in, out] PSIZE_T ViewSize,
[in] SECTION_INHERIT InheritDisposition,
[in] ULONG AllocationType,
[in] ULONG Win32Protect
);
Maps a view of a section into the virtual address space of a subject process.
ntdll.LdrGetDllHandleByMapping
Given a mapped memory region, tries to resolve if it corresponds to a loaded DLL.
Helps the loader avoid re-loading the same module.
ntdll.RtlDeactivateActivationContextUnsafeFast
Cleans up activation contexts (side-by-side assemblies, manifests, COM contexts).
You see this around DLL load/unload sequences.
"UnsafeFast" = lightweight version without full safety checks.
ntdll.LdrGetDllHandleEx
Higher-level helper for finding a DLL handle by name or characteristics.
Often used before LdrLoadDll.
ntdll.LdrLoadDll
Main function to load a DLL (after checking handles/mappings).
Calls NtMapViewOfSection internally if the DLL isn’t already mapped.
Also processes imports, TLS callbacks, and entrypoints (DllMain).
ntdll.RtlFindClearBitsAndSet
A low-level runtime routine that manipulates a bitmap (finds a sequence of 0s, flips them to 1s).
Used internally by the loader and memory manager to track free/used slots (e.g., TLS slots, heap allocations).
ntdll.EtwEventWriteNoRegistration
Event Tracing for Windows (ETW).
Writes events even if no provider is registered.
Usually system bookkeeping — not critical to program flow, but shows the loader/system is logging activity.
ntdll.LdrInitializeThunk
Runs when a new thread starts (especially the first thread of a process). Final loader setup: initializes loader state, processes TLS, resolves imports. Then jumps to program’s real entrypoint (e.g., main, WinMain, or DllMain for DLL entry).
What Happens When You Double-Click an Executable in Windows
1. User Action and Shell Event
- The user double-clicks the
.exe
file icon in Windows Explorer. - Windows Explorer interprets this as a request to “open” the file using the default verb.
- Internally, Explorer calls the
ShellExecuteEx
API to handle the action:
Registry
Computer\HKEY_CLASSES_ROOT\exefile
FriendlyTypeName @%SystemRoot%\System32\shell32.dll,-10156
Api
ShellExecuteEx(&sei);
typedef struct _SHELLEXECUTEINFOA {
DWORD cbSize;
ULONG fMask;
HWND hwnd;
LPCSTR lpVerb;
LPCSTR lpFile;
LPCSTR lpParameters;
LPCSTR lpDirectory;
int nShow;
HINSTANCE hInstApp;
void *lpIDList;
LPCSTR lpClass;
HKEY hkeyClass;
DWORD dwHotKey;
union {
HANDLE hIcon;
HANDLE hMonitor;
} DUMMYUNIONNAME;
HANDLE hProcess;
} SHELLEXECUTEINFOA, *LPSHELLEXECUTEINFOA;
EditFlags
EditFlags for exefile
38 07 00 00
3 0x08 FTA_NoEdit Cannot rename in Explorer
4 0x10 FTA_NoRemove Cannot delete
5 0x20 FTA_NoNewVerb Hidden from “New” menu
8 0x100 FTA_NoEditDesc Cannot edit description
9 0x200 FTA_NoEditIcon Cannot edit icon
10 0x400 FTA_NoEditDflt Cannot edit default verb