This commit is contained in:
nora 2025-02-01 21:01:00 +01:00
parent 402efaee61
commit 303eb47de5
6 changed files with 222 additions and 38 deletions

23
Cargo.lock generated
View file

@ -88,6 +88,27 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "naked-function"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b8d5fca6ab1e6215b010aefd3b9ac5aae369dae0faea3a7f34f296cc9f719ac"
dependencies = [
"cfg-if",
"naked-function-macro",
]
[[package]]
name = "naked-function-macro"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b4123e70df5fe0bb370cff166ae453b9c5324a2cfc932c0f7e55498147a0475"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -122,8 +143,10 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytemuck", "bytemuck",
"cfg-if",
"libc", "libc",
"memmap2", "memmap2",
"naked-function",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"windows", "windows",

View file

@ -6,7 +6,9 @@ edition = "2021"
[dependencies] [dependencies]
bitflags = { version = "2.8.0", features = ["bytemuck"] } bitflags = { version = "2.8.0", features = ["bytemuck"] }
bytemuck = { version = "1.21.0", features = ["derive"] } bytemuck = { version = "1.21.0", features = ["derive"] }
cfg-if = "1.0.0"
memmap2 = "0.9.5" memmap2 = "0.9.5"
naked-function = "0.1.5"
tracing = { version = "0.1.41", features = ["attributes"] } tracing = { version = "0.1.41", features = ["attributes"] }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }

View file

@ -1,3 +1,28 @@
mod base_defs {
pub(crate) type HANDLE = usize;
#[repr(transparent)]
pub(crate) struct LPCWSTR(pub *const u16);
impl std::fmt::Debug for LPCWSTR {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unsafe {
let mut p = self.0;
let mut v = p.read();
while v != 0 {
let c = std::char::decode_utf16([v])
.map(Result::unwrap)
.collect::<String>();
f.write_str(&c)?;
p = p.add(1);
v = p.read();
}
}
Ok(())
}
}
}
macro_rules! define_emulation_entry { macro_rules! define_emulation_entry {
($($name:ident,)*) => { ($($name:ident,)*) => {
pub(crate) fn supports_dll(dll_name: &str) -> bool { pub(crate) fn supports_dll(dll_name: &str) -> bool {
@ -32,7 +57,8 @@ define_emulation_entry!(
macro_rules! emulate { macro_rules! emulate {
($dllname:literal, mod $modname:ident { ($dllname:literal, mod $modname:ident {
$( $(
fn $name:ident($($args:tt)*) $(-> $ret:ty)? { $(#[$attr:meta])*
fn $name:ident($($argname:ident: $argty:ty),* $(,)?) $(-> $ret:ty)? {
$($body:tt)* $($body:tt)*
} }
)* )*
@ -40,6 +66,9 @@ macro_rules! emulate {
mod $modname { mod $modname {
pub(super) const DLL_NAME: &str = $dllname; pub(super) const DLL_NAME: &str = $dllname;
pub(super) fn emulate(dll_name: &str, function_name: &str) -> Option<usize> { pub(super) fn emulate(dll_name: &str, function_name: &str) -> Option<usize> {
#[allow(unused_imports)]
use crate::emulated::base_defs::*;
if dll_name.to_lowercase() != $dllname { if dll_name.to_lowercase() != $dllname {
return None; return None;
} }
@ -47,7 +76,8 @@ macro_rules! emulate {
$( $(
if function_name == stringify!($name) { if function_name == stringify!($name) {
unsafe { unsafe {
return Some(std::mem::transmute($name as extern "system" fn())); // NOTE: The ABI string is a lie.....
return Some(std::mem::transmute($name::trampoline as unsafe extern "C" fn($($argty),*)));
} }
} }
)* )*
@ -56,10 +86,42 @@ macro_rules! emulate {
} }
$( $(
// TODO: Windows API adapter...
#[allow(non_snake_case)] #[allow(non_snake_case)]
extern "system" fn $name($($args)*) $(-> $ret)? { mod $name {
$($body)* #[allow(unused_imports)]
use crate::emulated::base_defs::*;
// https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions
cfg_if::cfg_if! {
if #[cfg(windows)] {
pub(super) unsafe extern "C" fn trampoline($($argname: $argty)*) {
unsafe { inner($($argname),*) }
}
} else if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] {
#[naked_function::naked]
#[export_name = concat!("portability_callconv_trampoline_", stringify!($modname), "__", stringify!($name))]
pub(super) unsafe extern "C" fn trampoline($($argname: $argty),*) {
// Map Windows arguments to System V arguments.
// For now we only support 4 integer arguments.
asm!(
"mov rdi, rcx", // arg 1
"mov rsi, rdx", // arg 2
"mov rdx, r8", // arg 3
"mov rcx, r9", // arg 4
"jmp {}",
sym inner,
);
}
} else {
compile_error!("unsupported architecture or operating system");
}
}
$(#[$attr])*
unsafe extern "C" fn inner($($argname: $argty),*) $(-> $ret)? {
#[allow(unused_unsafe)]
unsafe { $($body)* }
}
} }
)* )*
} }
@ -331,14 +393,22 @@ emulate!(
fn GetCurrentProcess() { fn GetCurrentProcess() {
todo!("GetCurrentProcess") todo!("GetCurrentProcess")
} }
fn GetCurrentProcessId() { /// <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocessid>
todo!("GetCurrentProcessId") fn GetCurrentProcessId() -> u32 {
std::process::id()
} }
fn GetCurrentThread() { fn GetCurrentThread() {
todo!("GetCurrentThread") todo!("GetCurrentThread")
} }
fn GetCurrentThreadId() { // <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadid>
todo!("GetCurrentThreadId") fn GetCurrentThreadId() -> u32 {
use std::sync::atomic;
static THREAD_ID_COUNTER: atomic::AtomicU32 = atomic::AtomicU32::new(0);
std::thread_local! {
static THREAD_ID: u32 = THREAD_ID_COUNTER.fetch_add(1, atomic::Ordering::Relaxed);
}
THREAD_ID.with(|id| *id)
} }
fn GetDateFormatW() { fn GetDateFormatW() {
todo!("GetDateFormatW") todo!("GetDateFormatW")
@ -439,8 +509,17 @@ emulate!(
fn GetSystemTime() { fn GetSystemTime() {
todo!("GetSystemTime") todo!("GetSystemTime")
} }
fn GetSystemTimeAsFileTime() { /// <https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>
todo!("GetSystemTimeAsFileTime") fn GetSystemTimeAsFileTime(lpSystemTimeAsFileTime: *mut std::ffi::c_void) {
#[repr(C)]
struct _FILETIME {
dwLowDateTime: u32,
dwHighDateTime: u32,
}
lpSystemTimeAsFileTime.cast::<_FILETIME>().write(_FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
});
} }
fn GetSystemTimePreciseAsFileTime() { fn GetSystemTimePreciseAsFileTime() {
todo!("GetSystemTimePreciseAsFileTime") todo!("GetSystemTimePreciseAsFileTime")
@ -544,8 +623,8 @@ emulate!(
fn LoadLibraryExA() { fn LoadLibraryExA() {
todo!("LoadLibraryExA") todo!("LoadLibraryExA")
} }
fn LoadLibraryExW() { fn LoadLibraryExW(lpLibFileName: LPCWSTR, hFile: HANDLE, dwFlags: u32) {
todo!("LoadLibraryExW") todo!("LoadLibraryExW: {:?}", lpLibFileName)
} }
fn LoadLibraryW() { fn LoadLibraryW() {
todo!("LoadLibraryW") todo!("LoadLibraryW")
@ -568,8 +647,10 @@ emulate!(
fn OpenSemaphoreA() { fn OpenSemaphoreA() {
todo!("OpenSemaphoreA") todo!("OpenSemaphoreA")
} }
fn QueryPerformanceCounter() { /// <https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter>
todo!("QueryPerformanceCounter") fn QueryPerformanceCounter(lpPerformanceCount: *mut u64) -> bool {
lpPerformanceCount.write(0);
true
} }
fn QueryPerformanceFrequency() { fn QueryPerformanceFrequency() {
todo!("QueryPerformanceFrequency") todo!("QueryPerformanceFrequency")

View file

@ -6,6 +6,7 @@ use std::{
fmt::Debug, fmt::Debug,
ops::Deref, ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Mutex,
}; };
#[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod)] #[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod)]
@ -77,7 +78,7 @@ struct OptionalHeader {
#[repr(C)] #[repr(C)]
struct DataDirectory { struct DataDirectory {
// RVA // RVA
va: u32, rva: u32,
size: u32, size: u32,
} }
@ -220,7 +221,25 @@ struct ExportDirectoryTable {
name_pointer_rva: u32, name_pointer_rva: u32,
ordinal_table_rva: u32, ordinal_table_rva: u32,
} }
const _: () = assert!(size_of::<ExportDirectoryTable>() == 40);
const IMAGE_REL_BASED_ABSOLUTE: u8 = 0;
const IMAGE_REL_BASED_DIR64: u8 = 10;
struct BaseRelocationType(u8);
impl Debug for BaseRelocationType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self.0 {
IMAGE_REL_BASED_ABSOLUTE => "IMAGE_REL_BASED_ABSOLUTE",
1 => "IMAGE_REL_BASED_HIGH",
2 => "IMAGE_REL_BASED_LOW",
3 => "IMAGE_REL_BASED_HIGHLOW",
4 => "IMAGE_REL_BASED_HIGHADJ",
IMAGE_REL_BASED_DIR64 => "IMAGE_REL_BASED_DIR64",
_ => "<unknown>",
};
f.write_str(s)
}
}
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;
@ -250,6 +269,14 @@ impl<'pe> Deref for Image<'pe> {
} }
} }
struct TheGlobalState {
loaded_libraries: Vec<(String, Image<'static>)>,
}
static GLOBAL_STATE: Mutex<TheGlobalState> = Mutex::new(TheGlobalState {
loaded_libraries: Vec::new(),
});
#[tracing::instrument(skip(pe, is_dll))] #[tracing::instrument(skip(pe, is_dll))]
fn load<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image<'pe> { fn load<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image<'pe> {
load_inner(pe, executable_path, is_dll) load_inner(pe, executable_path, is_dll)
@ -354,15 +381,51 @@ fn load_inner<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image
); );
} }
tracing::debug!("Applying relocations");
let mut base_relocations = &image[opt_header.base_relocation_table.rva as usize..]
[..opt_header.base_relocation_table.size as usize];
while !base_relocations.is_empty() {
let page_rva = u32::from_ne_bytes(base_relocations[..4].try_into().unwrap());
let block_size = u32::from_ne_bytes(base_relocations[4..][..4].try_into().unwrap());
base_relocations = &base_relocations[8..];
tracing::trace!(?page_rva, ?block_size, "Base relocation block");
let remaining = (block_size - 8) / 2;
for _ in 0..remaining {
let word = u16::from_ne_bytes(base_relocations[..2].try_into().unwrap());
let relocation_type = word >> 12;
let offset = word & 0xFFF;
let relocation_type = BaseRelocationType(relocation_type as u8);
base_relocations = &base_relocations[2..];
let diff = image.base.wrapping_sub(opt_header.image_base as usize);
let va = image.base + page_rva as usize + offset as usize;
let va_ptr = std::ptr::with_exposed_provenance_mut::<u64>(va);
match relocation_type.0 as u8 {
IMAGE_REL_BASED_ABSOLUTE => {} // need to ignore
IMAGE_REL_BASED_DIR64 => unsafe {
let old = va_ptr.read_unaligned();
let new = old as usize + diff;
va_ptr.write_unaligned(new as u64);
},
_ => panic!("bad relocation type in {executable_path:?}: {relocation_type:?}"),
}
}
}
tracing::debug!("resolving imports");
let import_directory_table = bytemuck::cast_slice::<_, ImportDirectoryTableEntry>( let import_directory_table = bytemuck::cast_slice::<_, ImportDirectoryTableEntry>(
&image.loaded[opt_header.import_table.va as usize..] &image.loaded[opt_header.import_table.rva as usize..]
[..opt_header.import_table.size as usize], [..opt_header.import_table.size as usize],
) )
.to_vec(); .to_vec();
tracing::debug!("checking imports");
for import_directory in import_directory_table { for import_directory in import_directory_table {
tracing::debug!(?import_directory, "Resolving next import"); tracing::debug!(?import_directory, "Resolving next import directory");
let dll_name = let dll_name =
CStr::from_bytes_until_nul(&image.loaded[import_directory.name_rva as usize..]) CStr::from_bytes_until_nul(&image.loaded[import_directory.name_rva as usize..])
@ -409,7 +472,7 @@ fn load_inner<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image
// You may be able to skip the binary search of the "hint" of the import is the correct index already. // You may be able to skip the binary search of the "hint" of the import is the correct index already.
let export_directory_table = bytemuck::cast_slice::<u8, ExportDirectoryTable>( let export_directory_table = bytemuck::cast_slice::<u8, ExportDirectoryTable>(
&img[img.opt_header.export_table.va as usize..] &img[img.opt_header.export_table.rva as usize..]
[..size_of::<ExportDirectoryTable>()], [..size_of::<ExportDirectoryTable>()],
)[0]; )[0];
tracing::debug!( tracing::debug!(
@ -426,7 +489,7 @@ fn load_inner<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image
let name = u32::from_ne_bytes(img[name_ptr..][..4].try_into().unwrap()); let name = u32::from_ne_bytes(img[name_ptr..][..4].try_into().unwrap());
let name = CStr::from_bytes_until_nul(&img[name as usize..]).unwrap(); let name = CStr::from_bytes_until_nul(&img[name as usize..]).unwrap();
names.push(name.to_owned()); names.push(name.to_owned());
tracing::debug!(?name, "DLL {dll_name} has export"); tracing::trace!(?name, "DLL {dll_name} has export");
} }
LoadedDll::Real(img, export_directory_table, names) LoadedDll::Real(img, export_directory_table, names)
@ -455,8 +518,8 @@ fn load_inner<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image
+ (unbiased_ordinal * 4); + (unbiased_ordinal * 4);
let export_rva = u32::from_ne_bytes(img[eat_addr_rva..][..4].try_into().unwrap()); let export_rva = u32::from_ne_bytes(img[eat_addr_rva..][..4].try_into().unwrap());
if (img.opt_header.export_table.va if (img.opt_header.export_table.rva
..(img.opt_header.export_table.va + img.opt_header.export_table.size)) ..(img.opt_header.export_table.rva + img.opt_header.export_table.size))
.contains(&export_rva) .contains(&export_rva)
{ {
todo!("symbol forwarding") todo!("symbol forwarding")
@ -471,7 +534,10 @@ fn load_inner<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image
tracing::debug!("import by ordinal: {ordinal_number}"); tracing::debug!("import by ordinal: {ordinal_number}");
match &dll { match &dll {
LoadedDll::Emulated => panic!("unsupported: emulated import via ordinal"), LoadedDll::Emulated => {
tracing::error!("unsupported: emulated import via ordinal for {dll_name}. resolving them to 0");
0
}
LoadedDll::Real(img, export_directory_table, _) => { LoadedDll::Real(img, export_directory_table, _) => {
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#export-ordinal-table // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#export-ordinal-table
let unbiased_ordinal = let unbiased_ordinal =
@ -530,9 +596,9 @@ fn load_inner<'pe>(pe: &'pe [u8], executable_path: &Path, is_dll: bool) -> Image
}; };
assert_eq!(size_of::<usize>(), size_of::<u64>()); assert_eq!(size_of::<usize>(), size_of::<u64>());
image.loaded[import_directory.import_address_table_rva as usize..] let iat = &mut image.loaded[import_directory.import_address_table_rva as usize..]
[i * size_of::<u64>()..][..size_of::<u64>()] [i * size_of::<u64>()..][..size_of::<u64>()];
.copy_from_slice(&resolved_va.to_ne_bytes()); iat.copy_from_slice(&resolved_va.to_ne_bytes());
} }
} }

View file

@ -93,13 +93,22 @@ mod imp {
} }
pub(crate) unsafe fn call_entrypoint_via_stdcall(fnptr: usize) -> u32 { 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 mut out: u32;
let fnptr = unsafe {
std::mem::transmute::<*const (), unsafe extern "C" fn() -> u32>( cfg_if::cfg_if! {
std::ptr::with_exposed_provenance(fnptr), if #[cfg(target_arch = "x86_64")] {
) std::arch::asm!(
}; "call {}",
unsafe { fnptr() } "mov {1:e}, eax",
in(reg) fnptr,
out(reg) out,
);
} else {
compile_error!("unsupported architecture");
}
}
out
} }
} }

View file

@ -1,11 +1,14 @@
SHELL = bash SHELL = bash
RUSTC = rustc --target x86_64-pc-windows-msvc -Copt-level=3 -Cpanic=abort -Clinker=lld-link -Clink-arg=/NODEFAULTLIB -Clink-arg=/debug:none -Cdebuginfo=0 RUSTC = rustc --target x86_64-pc-windows-msvc -Copt-level=3 -Cpanic=abort -Clinker=lld-link -Clink-arg=/NODEFAULTLIB -Clink-arg=/debug:none -Cdebuginfo=0
build: empty_exe.exe one_dll.exe two_dll.exe build: empty_exe.exe one_dll.exe two_dll.exe constant_data.exe
empty_exe.exe: empty_exe.rs empty_exe.exe: empty_exe.rs
$(RUSTC) empty_exe.rs $(RUSTC) empty_exe.rs
constant_data.exe: constant_data.rs
$(RUSTC) constant_data.rs
one_dll.exe: one_dll.rs small_dll.dll one_dll.exe: one_dll.rs small_dll.dll
$(RUSTC) one_dll.rs $(RUSTC) one_dll.rs