Skip to content

Commit

Permalink
Update UDK hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
SonnyX committed Dec 7, 2023
1 parent 9cc1855 commit b51685c
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 192 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/Windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ jobs:
build:
strategy:
matrix:
build: [win32, win64]
build: [win64]
include:
- build: win32
os: windows-latest
host_target: i686-pc-windows-msvc
- build: win64
os: windows-latest
host_target: x86_64-pc-windows-msvc
Expand Down
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.51"
anyhow = "1.0.75"
retour = { version = "0.3", features = ["static-detour"] }
lazy_static = "1.4.0"
libloading = "0.8.1"
sha2 = "0.10.0"
sha2 = "0.10.8"
widestring = "1.0.2"
zerocopy = "0.7.26"
paste = "1.0.11"
zerocopy = "0.7.29"
paste = "1.0.14"
region = "3.0.0"

[dependencies.windows]
Expand Down
119 changes: 119 additions & 0 deletions src/dll.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::os::windows::fs::FileExt;
use std::{ops::Range, fs::File};
use std::sync::OnceLock;

use sha2::{Digest, Sha256};

use windows::{
Win32::{
Foundation::{HANDLE, HINSTANCE},
System::{
LibraryLoader::GetModuleHandleA,
ProcessStatus::{K32GetModuleInformation, MODULEINFO},
SystemServices::{
DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH
},
Threading::GetCurrentProcess,
},
},
core::Error,
};

pub fn dll_main(_hinst_dll: HINSTANCE, fdw_reason: u32, _lpv_reserved: usize) -> i32 {
match fdw_reason {
DLL_PROCESS_ATTACH => {
dll_attach()
}
DLL_PROCESS_DETACH => {}

DLL_THREAD_ATTACH => {}
DLL_THREAD_DETACH => {}

_ => return 0,
}

1
}

/// Called upon DLL attach. This function verifies the UDK and initializes
/// hooks if the UDK matches our known hash.
fn dll_attach() {
let process = unsafe { GetCurrentProcess() };
let module: windows::Win32::Foundation::HMODULE = unsafe { GetModuleHandleA(None) }.expect("Couldn't get Module Handle for the UDK process");

let exe_information = get_module_information(process, module.into()).expect("Failed to get module information for UDK");
let udk_range = Range {
start: exe_information.lpBaseOfDll as usize,
end: exe_information.lpBaseOfDll as usize + exe_information.SizeOfImage as usize,
};

// Now that we're attached, let's hash the UDK executable.
// If the hash does not match what we think it should be, do not attach detours.
let exe_filename = std::env::current_exe().unwrap();

let filemap = pelite::FileMap::open(&exe_filename).unwrap();
let pefile = pelite::PeFile::from_bytes(&filemap).unwrap();
let section = pefile.section_headers().by_name(".text").unwrap();
let range = section.file_range();

let f = File::open(exe_filename).unwrap();
let mut buf = vec![0; (range.end - range.start) as usize];
f.seek_read(&mut buf, range.start as u64).unwrap();

let hash = {
let mut sha = Sha256::new();
sha.update(&buf);
sha.finalize()
};

// Ensure the hash matches a known hash.
if hash[..] != UDK_KNOWN_HASH {
panic!("Unknown UDK hash");
}

// Cache the UDK slice.
UDK_RANGE.set(udk_range).unwrap();
}

#[cfg(target_arch = "x86_64")]
const UDK_KNOWN_HASH: [u8; 32] = [
0xF0, 0x2F, 0x13, 0x1E, 0xF2, 0xE, 0xA3, 0xCE, 0xD1, 0xCE, 0x93, 0x14, 0x53, 0xDE, 0x37, 0xB9,
0x51, 0x1B, 0x92, 0xD0, 0xBA, 0x7C, 0x7, 0x27, 0x5B, 0xA0, 0xAE, 0xFB, 0x7D, 0xFB, 0xE3, 0xE3
];

#[cfg(target_arch = "x86")]
const UDK_KNOWN_HASH: [u8; 32] = [
0x70, 0xC2, 0x91, 0x73, 0xE0, 0x0F, 0x2F, 0xCA, 0x5E, 0xBB, 0x92, 0x76, 0x00, 0x43, 0xDF, 0x70,
0xE0, 0xC0, 0x16, 0xFA, 0xB2, 0x80, 0xF8, 0x20, 0x88, 0x31, 0xD9, 0x99, 0xFE, 0xF0, 0xFF, 0x33
];

/// Cached memory range for UDK.exe
pub static UDK_RANGE: OnceLock<Range<usize>> = OnceLock::new();

/// Return the base pointer for UDK.exe
pub fn get_udk_ptr() -> *const u8 {
let range = UDK_RANGE.get().unwrap();

// TODO: Once Rust gets better raw slice support, we should return a `*const [u8]` instead.
range.start as *const u8
}

/// Wrapped version of the Win32 GetModuleInformation.
fn get_module_information(process: HANDLE, module: HINSTANCE) -> windows::core::Result<MODULEINFO> {
let mut module_info = MODULEINFO {
..Default::default()
};

match unsafe {
K32GetModuleInformation(
process,
module,
&mut module_info,
std::mem::size_of::<MODULEINFO>() as u32,
)
.as_bool()
} {
true => Ok(module_info),
false => Err(Error::from_win32()),
}
}
158 changes: 2 additions & 156 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,160 +3,6 @@ mod dinput8;
#[allow(non_snake_case)]
mod xaudio27;

mod dll;
mod udk_log;
mod udk_offsets;
mod udk_xaudio;

#[cfg(target_arch = "x86_64")]
const UDK_KNOWN_HASH: [u8; 32] = [
0x0D, 0xE6, 0x90, 0x31, 0xEA, 0x41, 0x01, 0xF2, 0x18, 0xB6, 0x61, 0x27, 0xFD, 0x14, 0x3A, 0x8E,
0xC3, 0xF7, 0x48, 0x3E, 0x31, 0x9C, 0x3D, 0x8D, 0xD5, 0x1F, 0xA2, 0x8D, 0x7C, 0xBF, 0x08, 0xF5,
];

#[cfg(target_arch = "x86")]
const UDK_KNOWN_HASH: [u8; 32] = [
0xEF, 0xAF, 0xBA, 0x91, 0xD3, 0x05, 0x2D, 0x07, 0x07, 0xDD, 0xF2, 0xF2, 0x14, 0x15, 0x00, 0xFA,
0x6C, 0x1E, 0x8F, 0x9E, 0xF0, 0x70, 0x40, 0xB8, 0xF9, 0x96, 0x73, 0x8A, 0x00, 0xFB, 0x90, 0x07,
];

use std::ops::Range;
use std::sync::OnceLock;

use anyhow::Context;
use sha2::{Digest, Sha256};

use windows::core::PCWSTR;
use windows::Win32::{
Foundation::{HANDLE, HINSTANCE},
System::{
Diagnostics::Debug::OutputDebugStringW,
LibraryLoader::GetModuleHandleA,
ProcessStatus::{K32GetModuleFileNameExW, K32GetModuleInformation, MODULEINFO},
SystemServices::{
DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH,
},
Threading::GetCurrentProcess,
},
};

/// Cached memory range for UDK.exe
static UDK_RANGE: OnceLock<Range<usize>> = OnceLock::new();

/// Return the base pointer for UDK.exe
pub fn get_udk_ptr() -> *const u8 {
let range = UDK_RANGE.get().unwrap();

// TODO: Once Rust gets better raw slice support, we should return a `*const [u8]` instead.
range.start as *const u8
}

/// Wrapped version of the Win32 OutputDebugString.
fn output_debug_string(s: &str) {
let ws = widestring::WideCString::from_str(s).unwrap();

unsafe {
OutputDebugStringW(PCWSTR(ws.as_ptr()));
}
}

/// Wrapped version of the Win32 GetModuleFileName.
fn get_module_filename(process: HANDLE, module: HINSTANCE) -> windows::core::Result<String> {
// Use a temporary buffer the size of MAX_PATH for now.
// TODO: Dynamic allocation for longer filenames. As of now, this will truncate longer filenames.
let mut buf = [0u16; 256];

let len = unsafe { K32GetModuleFileNameExW(process, module, &mut buf) } as usize;

if len == 0 {
// Function failed.
return Err(windows::core::Error::from_win32());
}

Ok(String::from_utf16_lossy(&buf[..len]))
}

/// Wrapped version of the Win32 GetModuleInformation.
fn get_module_information(process: HANDLE, module: HINSTANCE) -> windows::core::Result<MODULEINFO> {
let mut module_info = MODULEINFO {
..Default::default()
};

match unsafe {
K32GetModuleInformation(
process,
module,
&mut module_info,
std::mem::size_of::<MODULEINFO>() as u32,
)
.as_bool()
} {
true => Ok(module_info),
false => Err(windows::core::Error::from_win32()),
}
}

/// Called upon DLL attach. This function verifies the UDK and initializes
/// hooks if the UDK matches our known hash.
fn dll_attach() -> anyhow::Result<()> {
let process = unsafe { GetCurrentProcess() };
let module = unsafe { GetModuleHandleA(None) }.context("failed to get module handle")?;

let exe_info = get_module_information(process, module.into())
.context("Failed to get module information for UDK")?;

// Now that we're attached, let's hash the UDK executable.
// If the hash does not match what we think it should be, do not attach detours.
let exe_filename = get_module_filename(process, module.into())?;

let mut exe = std::fs::File::open(exe_filename)?;
let hash = {
let mut sha = Sha256::new();
std::io::copy(&mut exe, &mut sha)?;
sha.finalize()
};

// Ensure the hash matches a known hash.
if hash[..] != UDK_KNOWN_HASH {
output_debug_string(&format!("Hash: {:02X?}\n", hash));
output_debug_string(&format!("Expected: {:02X?}\n", UDK_KNOWN_HASH));
anyhow::bail!("Unknown UDK hash");
}

// Cache the UDK slice.
UDK_RANGE
.set(Range {
start: exe_info.lpBaseOfDll as usize,
end: exe_info.lpBaseOfDll as usize + exe_info.SizeOfImage as usize,
})
.unwrap();

// Initialize detours.
udk_xaudio::init()?;

Ok(())
}

#[no_mangle]
pub extern "stdcall" fn DllMain(
_hinst_dll: HINSTANCE,
fdw_reason: u32,
_lpv_reserved: usize,
) -> i32 {
match fdw_reason {
DLL_PROCESS_ATTACH => {
if let Err(e) = dll_attach() {
// Print a debug message for anyone who's listening.
output_debug_string(&format!("{:?}\n", e));
eprintln!("{:?}", e);
}
}
DLL_PROCESS_DETACH => {}

DLL_THREAD_ATTACH => {}
DLL_THREAD_DETACH => {}

_ => return 0,
}

return 1;
}
mod udk_xaudio;
22 changes: 17 additions & 5 deletions src/udk_log.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
//! This module contains functionality relevant to UDK logging.
use crate::get_udk_ptr;
use crate::dll::get_udk_ptr;

use crate::udk_offsets::{DEBUG_FN_OFFSET, DEBUG_LOG_OFFSET};
/// Offset from the beginning of UDK64.exe to the debug log object.
#[cfg(target_arch = "x86_64")]
const DEBUG_LOG_OFFSET: usize = 0x0355_1720;
/// Address of UDK's log function.
#[cfg(target_arch = "x86_64")]
const DEBUG_FN_OFFSET: usize = 0x0024_6A20;

/// Offset from the beginning of UDK64.exe to the debug log object.
#[cfg(target_arch = "x86")]
const DEBUG_LOG_OFFSET: usize = 0x029a_31a8;
/// Address of UDK's log function.
#[cfg(target_arch = "x86")]
const DEBUG_FN_OFFSET: usize = 0x0002_1c500;

/// This is the type signature of UDK's log function.
type UDKLogFn = unsafe extern "C" fn(usize, u32, *const widestring::WideChar);
Expand All @@ -19,9 +31,9 @@ pub enum LogType {

/// Log a message via the UDK logging framework.
pub fn log(typ: LogType, msg: &str) {
let udk_slice = get_udk_ptr();
let log_obj = unsafe { udk_slice.add(DEBUG_LOG_OFFSET) };
let log_fn: UDKLogFn = unsafe { std::mem::transmute(udk_slice.add(DEBUG_FN_OFFSET)) };
let udk_ptr = get_udk_ptr();
let log_obj = unsafe { udk_ptr.add(DEBUG_LOG_OFFSET) };
let log_fn: UDKLogFn = unsafe { std::mem::transmute(udk_ptr.add(DEBUG_FN_OFFSET)) };

// Convert the UTF-8 Rust string into an OS wide string.
let wmsg: widestring::U16CString = widestring::WideCString::from_str(format!("TotemArts Extensions: {}", msg)).unwrap();
Expand Down
22 changes: 0 additions & 22 deletions src/udk_offsets.rs

This file was deleted.

8 changes: 7 additions & 1 deletion src/udk_xaudio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ use retour::static_detour;

use crate::get_udk_ptr;
use crate::udk_log::{log, LogType};
use crate::udk_offsets::{UDK_CREATEFX_PTR_OFFSET, UDK_XAUDIO2CREATE_OFFSET};
use crate::xaudio27::{IXAudio27, XAudio27Wrapper};

use windows::core::{GUID, HRESULT};
use windows::Win32::Foundation::{E_FAIL, S_OK};

// pub const UDK_INITHW_OFFSET: usize = 0x0171_1ED0;
// pub const UDK_XAUDIO2_OFFSET: usize = 0x036C_90F8;
#[cfg(target_arch = "x86_64")]
pub const UDK_XAUDIO2CREATE_OFFSET: usize = 0x0170_F4D0;
#[cfg(target_arch = "x86_64")]
pub const UDK_CREATEFX_PTR_OFFSET: usize = 0x024B_E8B0;

static_detour! {
static XAudio2CreateHook: extern "C" fn(*mut IXAudio27, u32, u32) -> HRESULT;
}
Expand Down

0 comments on commit b51685c

Please sign in to comment.