118 lines
3.9 KiB
Rust
118 lines
3.9 KiB
Rust
//! Transparent forwarding for the system `version.dll` API.
|
|
//!
|
|
//! The hook is deployed under the `version.dll` filename, so every VERSION API
|
|
//! import must continue to behave exactly as it would without OpenFUT. Resolve
|
|
//! the genuine system DLL once during process attach, then tail-jump from each
|
|
//! exported stub. A tail jump preserves the caller's complete Windows x64 ABI
|
|
//! state, including stack arguments whose signatures differ between exports.
|
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
|
|
|
|
const EXPORT_COUNT: usize = 16;
|
|
|
|
/// Keep this list in the same order as the generated stubs below.
|
|
const EXPORTS: [&[u8]; EXPORT_COUNT] = [
|
|
b"GetFileVersionInfoA\0",
|
|
b"GetFileVersionInfoExA\0",
|
|
b"GetFileVersionInfoExW\0",
|
|
b"GetFileVersionInfoSizeA\0",
|
|
b"GetFileVersionInfoSizeExA\0",
|
|
b"GetFileVersionInfoSizeExW\0",
|
|
b"GetFileVersionInfoSizeW\0",
|
|
b"GetFileVersionInfoW\0",
|
|
b"VerFindFileA\0",
|
|
b"VerFindFileW\0",
|
|
b"VerInstallFileA\0",
|
|
b"VerInstallFileW\0",
|
|
b"VerLanguageNameA\0",
|
|
b"VerLanguageNameW\0",
|
|
b"VerQueryValueA\0",
|
|
b"VerQueryValueW\0",
|
|
];
|
|
|
|
/// Addresses in the genuine system DLL. Atomic storage gives the assembly
|
|
/// stubs stable, directly addressable pointer-sized slots without `static mut`.
|
|
static REAL: [AtomicUsize; EXPORT_COUNT] = [const { AtomicUsize::new(0) }; EXPORT_COUNT];
|
|
|
|
macro_rules! proxy_stub {
|
|
($index:literal, $name:ident) => {
|
|
#[unsafe(no_mangle)]
|
|
#[unsafe(naked)]
|
|
pub unsafe extern "system" fn $name() {
|
|
core::arch::naked_asm!(
|
|
"jmp qword ptr [rip + {base} + {offset}]",
|
|
base = sym REAL,
|
|
offset = const $index * size_of::<usize>(),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
proxy_stub!(0, GetFileVersionInfoA);
|
|
proxy_stub!(1, GetFileVersionInfoExA);
|
|
proxy_stub!(2, GetFileVersionInfoExW);
|
|
proxy_stub!(3, GetFileVersionInfoSizeA);
|
|
proxy_stub!(4, GetFileVersionInfoSizeExA);
|
|
proxy_stub!(5, GetFileVersionInfoSizeExW);
|
|
proxy_stub!(6, GetFileVersionInfoSizeW);
|
|
proxy_stub!(7, GetFileVersionInfoW);
|
|
proxy_stub!(8, VerFindFileA);
|
|
proxy_stub!(9, VerFindFileW);
|
|
proxy_stub!(10, VerInstallFileA);
|
|
proxy_stub!(11, VerInstallFileW);
|
|
proxy_stub!(12, VerLanguageNameA);
|
|
proxy_stub!(13, VerLanguageNameW);
|
|
proxy_stub!(14, VerQueryValueA);
|
|
proxy_stub!(15, VerQueryValueW);
|
|
|
|
/// Resolve forwarding targets before returning from `DLL_PROCESS_ATTACH`.
|
|
/// Calls into our exports may happen as soon as the loader releases its lock,
|
|
/// so deferring this operation to the hook worker would create a race.
|
|
pub(crate) unsafe fn resolve() -> bool {
|
|
// Loading by absolute path prevents this proxy from recursively loading
|
|
// itself. Proton/Wine exposes the Windows system directory at this path.
|
|
let path: Vec<u16> = "C:\\Windows\\System32\\version.dll\0"
|
|
.encode_utf16()
|
|
.collect();
|
|
let module = LoadLibraryW(path.as_ptr());
|
|
if module.is_null() {
|
|
crate::write_log("version_proxy: FATAL: system version.dll load failed\n");
|
|
return false;
|
|
}
|
|
|
|
let mut missing = 0;
|
|
for (slot, name) in REAL.iter().zip(EXPORTS) {
|
|
let address = GetProcAddress(module, name.as_ptr()).map_or(0, |proc| proc as usize);
|
|
slot.store(address, Ordering::Release);
|
|
if address == 0 {
|
|
missing += 1;
|
|
}
|
|
}
|
|
|
|
if missing == 0 {
|
|
crate::write_log("version_proxy: forwarded all 16 exports\n");
|
|
true
|
|
} else {
|
|
crate::write_log(&format!(
|
|
"version_proxy: FATAL: {missing}/16 system exports missing\n"
|
|
));
|
|
false
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn export_table_is_complete_and_nul_terminated() {
|
|
assert_eq!(EXPORTS.len(), EXPORT_COUNT);
|
|
assert!(EXPORTS.iter().all(|name| name.last() == Some(&0)));
|
|
assert!(EXPORTS
|
|
.iter()
|
|
.all(|name| !name[..name.len() - 1].contains(&0)));
|
|
}
|
|
}
|