diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/.gitignore b/.gitignore index 1d6d67b..0be2ae9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ *.exe *.pdb +*.dll +*.dll.lib !/test/*.exe diff --git a/Cargo.lock b/Cargo.lock index 7912ab6..fb11b9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,7 @@ version = "0.1.0" dependencies = [ "bitflags", "bytemuck", + "libc", "memmap2", "windows", ] diff --git a/Cargo.toml b/Cargo.toml index f58059f..5ec3bb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,6 @@ memmap2 = "0.9.5" [target.'cfg(windows)'.dependencies] windows = { version = "0.59.0", features = ["Win32_System_Memory", "Win32_Security", "Win32_System_SystemInformation"] } + +[target.'cfg(unix)'.dependencies] +libc = "0.2.169" diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..fa4398f --- /dev/null +++ b/shell.nix @@ -0,0 +1,3 @@ +{ pkgs ? import { } }: pkgs.mkShell { + nativeBuildInputs = with pkgs; [ lld_18 rustup ]; +} diff --git a/src/lib.rs b/src/lib.rs index c150f5f..18973bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,11 @@ mod emulated; mod sys; -use std::{ffi::CStr, fmt::Debug, path::PathBuf}; +use std::{ + ffi::CStr, + fmt::Debug, + path::{Path, PathBuf}, +}; #[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod)] #[repr(C)] @@ -197,7 +201,7 @@ struct ImportDirectoryTableEntry { const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; const IMAGE_FILE_MACHINE_ARM64: u16 = 0xaa64; -pub fn execute(pe: &[u8]) { +pub fn execute(pe: &[u8], executable_path: &Path) { let (header, after_header) = parse_header(pe); match (std::env::consts::ARCH, header.machine) { @@ -258,14 +262,12 @@ pub fn execute(pe: &[u8]) { assert_eq!(base & (allocation_granularity - 1), 0); - let total_size = section_table.last().unwrap().virtual_address as usize; + let last_section = section_table.last().unwrap(); + let total_size = (last_section.virtual_address as usize + last_section.virtual_size as usize) + .next_multiple_of(allocation_granularity); let a = unsafe { - crate::sys::anon_write_map( - total_size.next_multiple_of(allocation_granularity), - std::ptr::with_exposed_provenance(base), - ) - .unwrap() + crate::sys::anon_write_map(total_size, std::ptr::with_exposed_provenance(base)).unwrap() }; // allocate the sections. @@ -273,11 +275,10 @@ pub fn execute(pe: &[u8]) { if section.virtual_size > section.size_of_raw_data { todo!("zero padding") } + eprintln!("mapping section {:?}", section.name); let section_a = &mut a[section.virtual_address as usize..]; - dbg!(section); - section_a[..section.size_of_raw_data as usize].copy_from_slice( &pe[section.pointer_to_raw_data as usize..][..section.size_of_raw_data as usize], ); @@ -289,19 +290,21 @@ pub fn execute(pe: &[u8]) { ) .to_vec(); + eprintln!("checking imports"); for import_directory in import_directory_table { dbg!(import_directory); let dll_name = CStr::from_bytes_until_nul(&a[import_directory.name_rva as usize..]) .unwrap() .to_owned(); + let dll_name = dll_name.to_str().unwrap(); if dll_name.is_empty() { // Trailing null import directory. break; } - dbg!(&dll_name); + eprintln!("loading dll {dll_name}"); - let dll = find_dll(&dll_name); + let dll = find_dll(&dll_name, executable_path); match dll { Some(DllLocation::Emulated) => eprintln!(" emulating {dll_name:?}"), Some(DllLocation::Found(path)) => todo!("unsupported, loading dll at {path:?}"), @@ -329,11 +332,10 @@ pub fn execute(pe: &[u8]) { let func_name = CStr::from_bytes_until_nul(&a[hint_name_table_rva as usize + 2..]).unwrap(); eprintln!(" import by name: hint={hint} name={func_name:?}"); - let resolved_va = - emulated::emulate(dll_name.to_str().unwrap(), func_name.to_str().unwrap()) - .unwrap_or_else(|| { - panic!("could not find function {func_name:?} in dll {dll_name:?}") - }); + let resolved_va = emulated::emulate(dll_name, func_name.to_str().unwrap()) + .unwrap_or_else(|| { + panic!("could not find function {func_name:?} in dll {dll_name:?}") + }); assert_eq!(size_of::(), size_of::()); a[import_directory.import_address_table_rva as usize..][i * size_of::()..] @@ -343,6 +345,7 @@ pub fn execute(pe: &[u8]) { } } + eprintln!("applying section protections"); for section in section_table { let mode = if section .characteristics @@ -368,13 +371,13 @@ pub fn execute(pe: &[u8]) { .unwrap(); } - eprintln!("YOLO"); + let entrypoint = + optional_header.image_base as usize + optional_header.address_of_entry_point as usize; + eprintln!("YOLO to {:#x}", entrypoint); unsafe { - let entrypoint = std::mem::transmute:: !>( - optional_header.address_of_entry_point as usize, - ); - entrypoint(); + let result = sys::call_entrypoint_via_stdcall(entrypoint); + eprintln!("result: {result}"); }; } @@ -406,18 +409,39 @@ enum DllLocation { Found(PathBuf), } -fn find_dll(name: &CStr) -> Option { +fn find_dll(name: &str, executable_path: &Path) -> Option { // https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order - let name = name.to_str().unwrap(); if name.starts_with("api-") && emulated::supports_dll(name) { // This is an API set, essentially a virtual alias // https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets return Some(DllLocation::Emulated); } - if emulated::supports_dll(name) { return Some(DllLocation::Emulated); } + let name_lowercase = name.to_lowercase(); + + let probe_path = |path: &Path| -> Option { + std::fs::read_dir(path) + .ok()? + .find(|entry| { + entry + .as_ref() + .map(|entry| { + entry + .file_name() + .to_str() + .is_some_and(|name| name.to_lowercase() == name_lowercase) + }) + .unwrap_or(false) + }) + .map(|entry| entry.unwrap().path()) + }; + + if let Some(path) = probe_path(executable_path.parent().unwrap()) { + return Some(DllLocation::Found(path)); + } + None } diff --git a/src/main.rs b/src/main.rs index 647eada..3be6eb4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #[cfg(windows)] use std::os::windows::fs::OpenOptionsExt; +use std::path::Path; fn main() { let mut opts = std::fs::OpenOptions::new(); @@ -10,14 +11,11 @@ fn main() { windows::Win32::Foundation::GENERIC_EXECUTE.0 | windows::Win32::Foundation::GENERIC_READ.0, ); - let file = opts - .open( - std::env::args() - .nth(1) - .unwrap_or_else(|| "test/example_exe.exe".into()), - ) - .unwrap(); + let path = std::env::args() + .nth(1) + .unwrap_or_else(|| "test/example_exe.exe".into()); + let file = opts.open(&path).unwrap(); let map = unsafe { memmap2::Mmap::map(&file).unwrap() }; - portability::execute(&map); + portability::execute(&map, Path::new(&path)); } diff --git a/src/sys.rs b/src/sys.rs index 1979122..1893b1d 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -14,7 +14,8 @@ mod imp { Foundation::INVALID_HANDLE_VALUE, System::{ Memory::{ - FILE_MAP_EXECUTE, FILE_MAP_WRITE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS, PAGE_READONLY, PAGE_READWRITE + FILE_MAP_EXECUTE, FILE_MAP_WRITE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, + PAGE_PROTECTION_FLAGS, PAGE_READONLY, PAGE_READWRITE, }, SystemInformation::SYSTEM_INFO, }, @@ -51,8 +52,6 @@ mod imp { None, )?; - eprintln!("created {address:p} {size:x}"); - debug_assert_eq!(address.addr() & (allocation_granularity() - 1), 0); debug_assert_eq!(size & (allocation_granularity() - 1), 0); @@ -78,7 +77,7 @@ mod imp { pub(crate) fn protect(address: *const (), size: usize, mode: Mode) -> io::Result<()> { debug_assert_eq!(address.addr() & (page_size() - 1), 0); - let mut old= PAGE_PROTECTION_FLAGS::default(); + let mut old = PAGE_PROTECTION_FLAGS::default(); unsafe { windows::Win32::System::Memory::VirtualProtect( address.cast::(), @@ -93,11 +92,81 @@ mod imp { .map_err(Into::into) } } + + pub(crate) unsafe fn call_entrypoint_via_stdcall(fnptr: *const ()) -> u32 { + let fnptr = unsafe { std::mem::transmute::<_, unsafe extern "stdcall" fn() -> u32>(fnptr) }; + unsafe { fnptr() } + } } #[cfg(unix)] mod imp { - compile_error!("no unix yet lol skill issue"); + use std::io; + + use super::Mode; + + pub(crate) fn allocation_granularity() -> usize { + unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } + } + + pub(crate) fn page_size() -> usize { + allocation_granularity() + } + + pub(crate) unsafe fn anon_write_map<'a>( + size: usize, + address: *const (), + ) -> io::Result<&'a mut [u8]> { + debug_assert_eq!(address.addr() & (allocation_granularity() - 1), 0); + debug_assert_eq!(size & (allocation_granularity() - 1), 0); + + let ret = libc::mmap( + address as _, + size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + if ret == libc::MAP_FAILED { + Err(io::Error::last_os_error()) + } else if ret.addr() != address.addr() { + Err(io::Error::new( + io::ErrorKind::AlreadyExists, + "address already taken".to_owned(), + )) + } else { + Ok(std::slice::from_raw_parts_mut( + address.cast_mut().cast(), + size, + )) + } + } + + pub(crate) fn protect(address: *const (), size: usize, mode: super::Mode) -> io::Result<()> { + debug_assert_eq!(address.addr() & (page_size() - 1), 0); + let prot = match mode { + Mode::Read => libc::PROT_READ, + Mode::Write => libc::PROT_READ | libc::PROT_WRITE, + Mode::Execute => libc::PROT_READ | libc::PROT_EXEC, + }; + let ret = unsafe { libc::mprotect(address as _, size, prot) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + + pub(crate) unsafe fn call_entrypoint_via_stdcall(fnptr: usize) -> u32 { + // todo this might be correct or not idk??? is it close enough in this case maybe?? use asm probably. + let fnptr = unsafe { + std::mem::transmute::<*const (), unsafe extern "C" fn() -> u32>( + std::ptr::with_exposed_provenance(fnptr), + ) + }; + unsafe { fnptr() } + } } pub(crate) use imp::*; diff --git a/test/build.sh b/test/build.sh new file mode 100755 index 0000000..a726a47 --- /dev/null +++ b/test/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +rustc example_exe.rs --target x86_64-pc-windows-msvc -Cpanic=abort -Clinker=lld-link -Clink-arg=/entry:my_main -Clink-arg=/NODEFAULTLIB -Cdebuginfo=0 diff --git a/test/example_exe.exe b/test/example_exe.exe old mode 100644 new mode 100755 index 903f3c5..1cf32cd Binary files a/test/example_exe.exe and b/test/example_exe.exe differ diff --git a/test/example_exe.rs b/test/example_exe.rs index e293a9a..c0962f8 100644 --- a/test/example_exe.rs +++ b/test/example_exe.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![windows_subsystem = "console"] #[panic_handler] fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! { @@ -7,4 +8,6 @@ fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! { } #[no_mangle] -pub fn main() {} +pub extern "stdcall" fn my_main() -> u32 { + 42 +} diff --git a/test/example_exe_crt.exe b/test/example_exe_crt.exe new file mode 100644 index 0000000..903f3c5 Binary files /dev/null and b/test/example_exe_crt.exe differ diff --git a/test/example_exe_crt.rs b/test/example_exe_crt.rs new file mode 100644 index 0000000..fbb27bc --- /dev/null +++ b/test/example_exe_crt.rs @@ -0,0 +1,10 @@ +#![no_std] +#![no_main] + +#[panic_handler] +fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! { + loop {} +} + +#[no_mangle] +pub extern "stdcall" fn my_main() {} diff --git a/test2/Makefile b/test2/Makefile new file mode 100644 index 0000000..0faa087 --- /dev/null +++ b/test2/Makefile @@ -0,0 +1,16 @@ +RUSTC = rustc --target x86_64-pc-windows-msvc -Cpanic=abort -Clinker=lld-link -Clink-arg=/NODEFAULTLIB -Clink-arg=/debug:none -Cdebuginfo=0 + +build: empty_exe.exe one_dll.exe + +empty_exe.exe: empty_exe.rs + $(RUSTC) empty_exe.rs + +one_dll.exe: one_dll.rs small_dll.dll + $(RUSTC) one_dll.rs + +small_dll.dll: small_dll.rs + $(RUSTC) small_dll.rs + +.PHONY: clean +clean: + rm *.exe *.pdb *.dll diff --git a/test2/empty_exe.rs b/test2/empty_exe.rs new file mode 100644 index 0000000..4c5a380 --- /dev/null +++ b/test2/empty_exe.rs @@ -0,0 +1,13 @@ +#![no_std] +#![no_main] +#![windows_subsystem = "console"] + +#[panic_handler] +fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! { + loop {} +} + +#[no_mangle] +pub extern "stdcall" fn mainCRTStartup() -> u32 { + 42 +} diff --git a/test2/one_dll.rs b/test2/one_dll.rs new file mode 100644 index 0000000..503bc9a --- /dev/null +++ b/test2/one_dll.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] +#![windows_subsystem = "console"] + +#[link(name = "small_dll", kind = "raw-dylib")] +unsafe extern "C" { + safe fn my_export() -> u32; +} + +#[panic_handler] +fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! { + loop {} +} + +#[no_mangle] +pub extern "stdcall" fn mainCRTStartup() -> u32 { + my_export() +} diff --git a/test2/small_dll.rs b/test2/small_dll.rs new file mode 100644 index 0000000..494dac0 --- /dev/null +++ b/test2/small_dll.rs @@ -0,0 +1,18 @@ +#![no_std] +#![crate_type = "cdylib"] +#![windows_subsystem = "console"] + +#[panic_handler] +fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! { + loop {} +} + +#[no_mangle] +pub extern "C" fn my_export() -> u32 { + 42 +} + +#[no_mangle] +pub extern "stdcall" fn _DllMainCRTStartup() -> u32 { + 0 +}