From a6de0298f2eeb7e344dbce0f3553b69bcb412680 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sun, 9 Feb 2025 20:47:51 +0100 Subject: [PATCH] static TLS works for the main exe!! --- README.md | 1 + shell.nix | 1 + src/emulated.rs | 33 ++++--- src/lib.rs | 171 ++++++++++++++++++++++++++++++++--- test/example_exe_tls_crt.exe | Bin 0 -> 11264 bytes test/example_exe_tls_crt.rs | 26 ++++++ test2/tls_exe.rs | 12 ++- 7 files changed, 214 insertions(+), 30 deletions(-) create mode 100644 test/example_exe_tls_crt.exe create mode 100644 test/example_exe_tls_crt.rs diff --git a/README.md b/README.md index fc84f95..e952de5 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,4 @@ a PE loader for educational purposes. - https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order - https://www.geoffchappell.com/studies/windows/win32/apisetschema/index.htm?tx=1 - http://www.nynaeve.net/?p=180 +- https://github.com/mingw-w64/mingw-w64 diff --git a/shell.nix b/shell.nix index fa4398f..8f77f41 100644 --- a/shell.nix +++ b/shell.nix @@ -1,3 +1,4 @@ { pkgs ? import { } }: pkgs.mkShell { nativeBuildInputs = with pkgs; [ lld_18 rustup ]; + packages = with pkgs; [ gef ]; } diff --git a/src/emulated.rs b/src/emulated.rs index 62f5d30..60cff5a 100644 --- a/src/emulated.rs +++ b/src/emulated.rs @@ -238,11 +238,15 @@ emulate!( emulate!( "api-ms-win-crt-runtime-l1-1-0.dll", mod api_ms_win_crt_runtime_l1_1_0 { - fn __p___argc() { - todo!("__p___argc") + fn __p___argc() -> *const u32 { + static ARGC: i32 = 1; + (&raw const ARGC).cast() } - fn __p___argv() { - todo!("__p___argv") + /// returns the address of argv + fn __p___argv() -> *const *const *const u8 { + static EMPTY_ARGS: [usize; 1] = [0]; + static ARGV: &[usize; 1] = &EMPTY_ARGS; + (&raw const ARGV).cast() } fn _c_exit() { todo!("_c_exit") @@ -259,8 +263,8 @@ emulate!( fn _exit() { todo!("_exit") } - fn _get_initial_narrow_environment() { - todo!("_get_initial_narrow_environment") + fn _get_initial_narrow_environment() -> *const () { + std::ptr::null() } fn _initialize_narrow_environment() { todo!("_initialize_narrow_environment") @@ -268,11 +272,11 @@ emulate!( fn _initialize_onexit_table() { todo!("_initialize_onexit_table") } - fn _initterm() { - todo!("_initterm") - } - fn _initterm_e() { - todo!("_initterm_e") + /// + fn _initterm(_start: *const (), _end: *const ()) {} + /// + fn _initterm_e(_start: *const (), _end: *const ()) -> u32 { + 0 } fn _register_onexit_function() { todo!("_register_onexit_function") @@ -286,8 +290,11 @@ emulate!( fn _set_app_type() { todo!("_set_app_type") } - fn exit() { - todo!("exit") + /// + fn exit(code: i32) -> ! { + tracing::info!("application requested exit with code {code}"); + // TODO: we need to do all kinds of cleanup + std::process::exit(code); } fn terminate() { todo!("terminate") diff --git a/src/lib.rs b/src/lib.rs index 2b654e1..4001afc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,11 +2,13 @@ mod emulated; mod sys; use std::{ + cell::RefCell, collections::HashMap, ffi::{CStr, CString}, fmt::Debug, ops::{Deref, DerefMut}, path::{Path, PathBuf}, + ptr, sync::{ atomic::{AtomicU64, Ordering}, LazyLock, Mutex, @@ -251,31 +253,89 @@ struct TlsDirectory { /// Note that this is a VA that should have been relocated earlier. raw_data_start_va: u64, /// The last byte of the TLS. - raw_data_env_va: u64, + raw_data_end_va: u64, address_of_index: u64, address_of_callbacks: u64, size_of_zero_fill: u32, characteristics: u32, } +#[expect(dead_code)] +const DLL_PROCESS_DETACH: u32 = 0; +const DLL_PROCESS_ATTACH: u32 = 1; +#[expect(dead_code)] +const DLL_THREAD_ATTACH: u32 = 2; +#[expect(dead_code)] +const DLL_THREAD_DETACH: u32 = 3; + +/// +type TlsCallback = + unsafe extern "win64" fn(dll_handle: *const (), reason: u32, reserved: *const ()); + const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; const IMAGE_FILE_MACHINE_ARM64: u16 = 0xaa64; pub fn execute(pe: &[u8], executable_path: &Path) { + let mut main_tls_slots = [ptr::null_mut(); 64]; + + let mut main_teb = ThreadEnvironmentBlock { + tib: ThreadInformationBlock { + exception_list: ptr::null(), + stack_base: ptr::null(), + stack_limit: ptr::null(), + sub_system_tib: ptr::null(), + fiber_data: ptr::null(), + arbitrary_user_pointer: ptr::null(), + this: ptr::null(), + }, + environment_pointer: ptr::null(), + client_id_unique_process: 0, + client_id_unique_thread: 0, + active_rpc_handle: ptr::null(), + thread_local_storage_pointer: &raw mut main_tls_slots, + }; + main_teb.tib.this = &raw const main_teb; + + THREAD_STATE.with(|state| state.state.borrow_mut().teb = &raw mut main_teb); + GLOBAL_STATE.state.lock().unwrap().executable_path = Some(executable_path.to_owned()); let image = load(pe, executable_path, false); let entrypoint = image.base + image.opt_header.address_of_entry_point as usize; tracing::debug!("YOLO to {:#x}", entrypoint); + setup_thread(&raw mut main_teb); + post_load(&image); + unsafe { let entrypoint = std::mem::transmute:: u32>(entrypoint); + setup_thread(&raw mut main_teb); let result = entrypoint(); tracing::info!("result: {result}"); }; } +fn post_load(image: &Image<'_>) { + tracing::debug!("call TLS callbacks"); + let Some(tls_directory) = bytemuck::cast_slice::( + &image[image.opt_header.tls_table.rva as usize..] + [..image.opt_header.tls_table.size as usize], + ) + .get(0) else { + return; + }; + + let mut ptr = tls_directory.address_of_callbacks as *const Option; + while let Some(cb) = unsafe { *ptr } { + tracing::debug!("calling TLS callback at {ptr:p}"); + unsafe { cb(image.base as _, DLL_PROCESS_ATTACH, ptr::null()) } + unsafe { + ptr = ptr.add(1); + } + } +} + #[derive(Clone)] struct Image<'pe> { base: usize, @@ -325,6 +385,14 @@ struct TheGlobalState { executable_path: Option, hmodule_to_dll: HashMap, next_emulated_hmodule_idx: AtomicU64, + tls_slots: Vec, +} + +enum TlsSlot { + Static { + #[expect(dead_code)] + init: &'static [u8], + }, } struct GlobalStateWrapper { @@ -344,6 +412,22 @@ impl GlobalStateWrapper { } } +struct ThreadState { + teb: *mut ThreadEnvironmentBlock, +} + +struct ThreadStateWrapper { + state: RefCell, +} + +std::thread_local! { + static THREAD_STATE: ThreadStateWrapper = ThreadStateWrapper { + state: RefCell::new(ThreadState { + teb: ptr::null_mut() + }), + }; +} + static GLOBAL_STATE: GlobalStateWrapper = GlobalStateWrapper { state: LazyLock::new(|| { Mutex::new(TheGlobalState { @@ -351,18 +435,34 @@ static GLOBAL_STATE: GlobalStateWrapper = GlobalStateWrapper { executable_path: None, hmodule_to_dll: HashMap::new(), next_emulated_hmodule_idx: AtomicU64::new(1), + tls_slots: Vec::new(), }) }), }; #[repr(C)] -struct ThreadEnvironmentBlock { - host_thread_ptr: *const (), - _pad: [u8; 80], - thing: *const (), - +struct ThreadInformationBlock { + exception_list: *const (), + stack_base: *const (), + stack_limit: *const (), + sub_system_tib: *const (), + fiber_data: *const (), + arbitrary_user_pointer: *const (), + this: *const ThreadEnvironmentBlock, } -const _: () = assert!(std::mem::offset_of!(ThreadEnvironmentBlock, thing) == 88); + +// https://github.com/wine-mirror/wine/blob/1aff1e6a370ee8c0213a0fd4b220d121da8527aa/include/winternl.h#L347 +#[repr(C)] +struct ThreadEnvironmentBlock { + tib: ThreadInformationBlock, + environment_pointer: *const (), + client_id_unique_process: u64, // handle + client_id_unique_thread: u64, // handle, + active_rpc_handle: *const (), + thread_local_storage_pointer: *mut [*mut (); 64], +} +const _: [(); 88] = + [(); std::mem::offset_of!(ThreadEnvironmentBlock, thread_local_storage_pointer)]; #[tracing::instrument(skip(pe, is_dll))] fn load<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image<'pe> { @@ -582,14 +682,44 @@ fn load_inner<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image } } - /* - not what's happening? - tracing::debug!("load TLS"); - let tls_directory = bytemuck::cast_slice::( - &image[opt_header.tls_table.rva as usize..][..opt_header.tls_table.size as usize], - ); - tracing::debug!(?tls_directory, "TLS directory"); - */ + // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-tls-section + + if opt_header.tls_table.size > 0 { + let tls_directory = bytemuck::cast_slice::( + &image[opt_header.tls_table.rva as usize..][..opt_header.tls_table.size as usize], + )[0]; + // Each module's data gets a slot in the TLS. + tracing::debug!("load TLS"); + let mut state = GLOBAL_STATE.state.lock().unwrap(); + let module_tls_slot_idx = state.tls_slots.len(); + // TODO: hi i think i need to do something here to assign the correct index so that the DLL understands what it's supposed to do. + tracing::debug!(?tls_directory, "TLS directory"); + + let size = tls_directory.raw_data_end_va - tls_directory.raw_data_start_va; + assert!(size > 0); + + let init = unsafe { + std::slice::from_raw_parts(tls_directory.raw_data_start_va as *const u8, size as usize) + }; + + state.tls_slots.push(TlsSlot::Static { init }); + drop(state); + + // TODO: alignment.. + let tls_data = unsafe { + std::alloc::alloc(std::alloc::Layout::from_size_align(size as usize, 8).unwrap()) + }; + assert!(!tls_data.is_null()); + + unsafe { ptr::copy_nonoverlapping(init.as_ptr(), tls_data, size as usize) }; + + THREAD_STATE.with(|state| unsafe { + (*(*state.state.borrow().teb).thread_local_storage_pointer)[module_tls_slot_idx] = + tls_data.cast::<()>() + }) + } else { + tracing::debug!("no TLS"); + } tracing::debug!("applying section protections"); for section in section_table { @@ -707,6 +837,8 @@ fn load_dll(dll_name: &str, executable_path: &Path) -> Option { let mmap = unsafe { &*(&**mmap as *const [u8]) }; let img: Image<'static> = load(&mmap, &path, true); + // TODO: we need to call DllMain!!! + // TODO: we need to call TLS callbacks!!! // Read the single export directory table from the front // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#export-directory-table @@ -833,3 +965,12 @@ fn find_dll(name: &str, executable_path: &Path) -> Option { None } + +#[cfg(target_arch = "x86_64")] +fn setup_thread(ptr: *mut ThreadEnvironmentBlock) { + // https://www.kernel.org/doc/html/next/x86/x86_64/fsgs.html + // requires fsgsbase which is_x86_feature_detected can't seem to detect? whatever. + unsafe { + std::arch::asm!("wrgsbase {}", in(reg) ptr); + } +} diff --git a/test/example_exe_tls_crt.exe b/test/example_exe_tls_crt.exe new file mode 100644 index 0000000000000000000000000000000000000000..62f9298cd9bd5be5ebfcef0518edb66ea9e33d23 GIT binary patch literal 11264 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 F0RRA+tK0wp literal 0 HcmV?d00001 diff --git a/test/example_exe_tls_crt.rs b/test/example_exe_tls_crt.rs new file mode 100644 index 0000000..74e49f7 --- /dev/null +++ b/test/example_exe_tls_crt.rs @@ -0,0 +1,26 @@ +#![feature(thread_local)] +#![no_std] +#![no_main] + +#[panic_handler] +fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! { + loop {} +} + +#[thread_local] +static mut A_THREAD_LOCAL: u32 = 50; +#[thread_local] +static mut ANOTHER_THREAD_LOCAL: u32 = 55; + +#[inline(never)] +fn set_tls(value: u32) { + unsafe { A_THREAD_LOCAL = value; } + unsafe { ANOTHER_THREAD_LOCAL = value; } +} + +#[no_mangle] +pub extern "stdcall" fn main() -> u32 { + // Use some indirection to actually force TLS to happen + set_tls(14); + unsafe { A_THREAD_LOCAL + ANOTHER_THREAD_LOCAL } +} diff --git a/test2/tls_exe.rs b/test2/tls_exe.rs index 39b9bce..7f96cb0 100644 --- a/test2/tls_exe.rs +++ b/test2/tls_exe.rs @@ -26,5 +26,13 @@ pub extern "stdcall" fn mainCRTStartup() -> u32 { unsafe { A_THREAD_LOCAL + ANOTHER_THREAD_LOCAL } } -#[no_mangle] -pub extern "stdcall" fn _tls_index() {} +/* +!!!!!!!!!!!!!!! +THIS IS WRONG. WE ARE NOT CREATING THE TLS DIRECTORY. THAT WOULD BE OUR JOB. +!!!!!!!!!!!!!! +*/ + + +extern "stdcall" { + static _tls_index: usize; +}