This commit is contained in:
nora 2025-02-01 14:37:46 +01:00
parent 091e833acf
commit dc6ef2108d
17 changed files with 221 additions and 39 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

2
.gitignore vendored
View file

@ -2,5 +2,7 @@
*.exe *.exe
*.pdb *.pdb
*.dll
*.dll.lib
!/test/*.exe !/test/*.exe

1
Cargo.lock generated
View file

@ -52,6 +52,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytemuck", "bytemuck",
"libc",
"memmap2", "memmap2",
"windows", "windows",
] ]

View file

@ -10,3 +10,6 @@ memmap2 = "0.9.5"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows = { version = "0.59.0", features = ["Win32_System_Memory", "Win32_Security", "Win32_System_SystemInformation"] } windows = { version = "0.59.0", features = ["Win32_System_Memory", "Win32_Security", "Win32_System_SystemInformation"] }
[target.'cfg(unix)'.dependencies]
libc = "0.2.169"

3
shell.nix Normal file
View file

@ -0,0 +1,3 @@
{ pkgs ? import <nixpkgs> { } }: pkgs.mkShell {
nativeBuildInputs = with pkgs; [ lld_18 rustup ];
}

View file

@ -1,7 +1,11 @@
mod emulated; mod emulated;
mod sys; 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)] #[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod)]
#[repr(C)] #[repr(C)]
@ -197,7 +201,7 @@ struct ImportDirectoryTableEntry {
const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664;
const IMAGE_FILE_MACHINE_ARM64: u16 = 0xaa64; 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); let (header, after_header) = parse_header(pe);
match (std::env::consts::ARCH, header.machine) { match (std::env::consts::ARCH, header.machine) {
@ -258,14 +262,12 @@ pub fn execute(pe: &[u8]) {
assert_eq!(base & (allocation_granularity - 1), 0); 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 { let a = unsafe {
crate::sys::anon_write_map( crate::sys::anon_write_map(total_size, std::ptr::with_exposed_provenance(base)).unwrap()
total_size.next_multiple_of(allocation_granularity),
std::ptr::with_exposed_provenance(base),
)
.unwrap()
}; };
// allocate the sections. // allocate the sections.
@ -273,11 +275,10 @@ pub fn execute(pe: &[u8]) {
if section.virtual_size > section.size_of_raw_data { if section.virtual_size > section.size_of_raw_data {
todo!("zero padding") todo!("zero padding")
} }
eprintln!("mapping section {:?}", section.name);
let section_a = &mut a[section.virtual_address as usize..]; let section_a = &mut a[section.virtual_address as usize..];
dbg!(section);
section_a[..section.size_of_raw_data as usize].copy_from_slice( 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], &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(); .to_vec();
eprintln!("checking imports");
for import_directory in import_directory_table { for import_directory in import_directory_table {
dbg!(import_directory); dbg!(import_directory);
let dll_name = CStr::from_bytes_until_nul(&a[import_directory.name_rva as usize..]) let dll_name = CStr::from_bytes_until_nul(&a[import_directory.name_rva as usize..])
.unwrap() .unwrap()
.to_owned(); .to_owned();
let dll_name = dll_name.to_str().unwrap();
if dll_name.is_empty() { if dll_name.is_empty() {
// Trailing null import directory. // Trailing null import directory.
break; 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 { match dll {
Some(DllLocation::Emulated) => eprintln!(" emulating {dll_name:?}"), Some(DllLocation::Emulated) => eprintln!(" emulating {dll_name:?}"),
Some(DllLocation::Found(path)) => todo!("unsupported, loading dll at {path:?}"), Some(DllLocation::Found(path)) => todo!("unsupported, loading dll at {path:?}"),
@ -329,11 +332,10 @@ pub fn execute(pe: &[u8]) {
let func_name = let func_name =
CStr::from_bytes_until_nul(&a[hint_name_table_rva as usize + 2..]).unwrap(); CStr::from_bytes_until_nul(&a[hint_name_table_rva as usize + 2..]).unwrap();
eprintln!(" import by name: hint={hint} name={func_name:?}"); eprintln!(" import by name: hint={hint} name={func_name:?}");
let resolved_va = let resolved_va = emulated::emulate(dll_name, func_name.to_str().unwrap())
emulated::emulate(dll_name.to_str().unwrap(), func_name.to_str().unwrap()) .unwrap_or_else(|| {
.unwrap_or_else(|| { panic!("could not find function {func_name:?} in dll {dll_name:?}")
panic!("could not find function {func_name:?} in dll {dll_name:?}") });
});
assert_eq!(size_of::<usize>(), size_of::<u64>()); assert_eq!(size_of::<usize>(), size_of::<u64>());
a[import_directory.import_address_table_rva as usize..][i * size_of::<u64>()..] a[import_directory.import_address_table_rva as usize..][i * size_of::<u64>()..]
@ -343,6 +345,7 @@ pub fn execute(pe: &[u8]) {
} }
} }
eprintln!("applying section protections");
for section in section_table { for section in section_table {
let mode = if section let mode = if section
.characteristics .characteristics
@ -368,13 +371,13 @@ pub fn execute(pe: &[u8]) {
.unwrap(); .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 { unsafe {
let entrypoint = std::mem::transmute::<usize, unsafe fn() -> !>( let result = sys::call_entrypoint_via_stdcall(entrypoint);
optional_header.address_of_entry_point as usize, eprintln!("result: {result}");
);
entrypoint();
}; };
} }
@ -406,18 +409,39 @@ enum DllLocation {
Found(PathBuf), Found(PathBuf),
} }
fn find_dll(name: &CStr) -> Option<DllLocation> { fn find_dll(name: &str, executable_path: &Path) -> Option<DllLocation> {
// https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order // 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) { if name.starts_with("api-") && emulated::supports_dll(name) {
// This is an API set, essentially a virtual alias // This is an API set, essentially a virtual alias
// https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets // https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets
return Some(DllLocation::Emulated); return Some(DllLocation::Emulated);
} }
if emulated::supports_dll(name) { if emulated::supports_dll(name) {
return Some(DllLocation::Emulated); return Some(DllLocation::Emulated);
} }
let name_lowercase = name.to_lowercase();
let probe_path = |path: &Path| -> Option<PathBuf> {
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 None
} }

View file

@ -1,5 +1,6 @@
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::fs::OpenOptionsExt; use std::os::windows::fs::OpenOptionsExt;
use std::path::Path;
fn main() { fn main() {
let mut opts = std::fs::OpenOptions::new(); 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, windows::Win32::Foundation::GENERIC_EXECUTE.0 | windows::Win32::Foundation::GENERIC_READ.0,
); );
let file = opts let path = std::env::args()
.open( .nth(1)
std::env::args() .unwrap_or_else(|| "test/example_exe.exe".into());
.nth(1) let file = opts.open(&path).unwrap();
.unwrap_or_else(|| "test/example_exe.exe".into()),
)
.unwrap();
let map = unsafe { memmap2::Mmap::map(&file).unwrap() }; let map = unsafe { memmap2::Mmap::map(&file).unwrap() };
portability::execute(&map); portability::execute(&map, Path::new(&path));
} }

View file

@ -14,7 +14,8 @@ mod imp {
Foundation::INVALID_HANDLE_VALUE, Foundation::INVALID_HANDLE_VALUE,
System::{ System::{
Memory::{ 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, SystemInformation::SYSTEM_INFO,
}, },
@ -51,8 +52,6 @@ mod imp {
None, None,
)?; )?;
eprintln!("created {address:p} {size:x}");
debug_assert_eq!(address.addr() & (allocation_granularity() - 1), 0); debug_assert_eq!(address.addr() & (allocation_granularity() - 1), 0);
debug_assert_eq!(size & (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<()> { pub(crate) fn protect(address: *const (), size: usize, mode: Mode) -> io::Result<()> {
debug_assert_eq!(address.addr() & (page_size() - 1), 0); debug_assert_eq!(address.addr() & (page_size() - 1), 0);
let mut old= PAGE_PROTECTION_FLAGS::default(); let mut old = PAGE_PROTECTION_FLAGS::default();
unsafe { unsafe {
windows::Win32::System::Memory::VirtualProtect( windows::Win32::System::Memory::VirtualProtect(
address.cast::<c_void>(), address.cast::<c_void>(),
@ -93,11 +92,81 @@ mod imp {
.map_err(Into::into) .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)] #[cfg(unix)]
mod imp { 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::*; pub(crate) use imp::*;

3
test/build.sh Executable file
View file

@ -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

BIN
test/example_exe.exe Normal file → Executable file

Binary file not shown.

View file

@ -1,5 +1,6 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![windows_subsystem = "console"]
#[panic_handler] #[panic_handler]
fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! { fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! {
@ -7,4 +8,6 @@ fn handle_panic(_: &core::panic::PanicInfo<'_>) -> ! {
} }
#[no_mangle] #[no_mangle]
pub fn main() {} pub extern "stdcall" fn my_main() -> u32 {
42
}

BIN
test/example_exe_crt.exe Normal file

Binary file not shown.

10
test/example_exe_crt.rs Normal file
View file

@ -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() {}

16
test2/Makefile Normal file
View file

@ -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

13
test2/empty_exe.rs Normal file
View file

@ -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
}

18
test2/one_dll.rs Normal file
View file

@ -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()
}

18
test2/small_dll.rs Normal file
View file

@ -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
}