From 56e44412b175bc5078fbd74b058ad0b70aeb8324 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Thu, 1 Feb 2024 18:06:43 -0500 Subject: [PATCH 1/5] Add elf pointer_size --- src/file_format/elf.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/file_format/elf.rs b/src/file_format/elf.rs index e5a28dd1..179bfede 100644 --- a/src/file_format/elf.rs +++ b/src/file_format/elf.rs @@ -1005,6 +1005,13 @@ pub fn is_64_bit(process: &Process, module_address: Address) -> Option { } } +/// Checks if a given ELF module is 64-bit or 32-bit +pub fn pointer_size(process: &Process, module_address: Address) -> Option { + let header = process.read::
(module_address).ok()?; + let info = Info::parse(bytemuck::bytes_of(&header))?; + info.bitness.pointer_size() +} + #[derive(Debug, Copy, Clone, Pod, Zeroable)] #[repr(C)] struct ProgramHeader32 { From 1a18cd806d4079bf1049bb933974e5ab090e0016 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Fri, 7 Mar 2025 10:34:01 -0500 Subject: [PATCH 2/5] Support Linux SceneManager --- src/game_engine/unity/scene.rs | 78 ++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/src/game_engine/unity/scene.rs b/src/game_engine/unity/scene.rs index 72625c27..60e1a69a 100644 --- a/src/game_engine/unity/scene.rs +++ b/src/game_engine/unity/scene.rs @@ -13,8 +13,11 @@ use core::{ }; use crate::{ - file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32, - Address64, Error, PointerSize, Process, + file_format::{elf, pe}, + future::retry, + signature::Signature, + string::ArrayCString, + Address, Address32, Address64, Error, PointerSize, Process, }; const CSTR: usize = 128; @@ -34,38 +37,60 @@ pub struct SceneManager { impl SceneManager { /// Attaches to the scene manager in the given process. pub fn attach(process: &Process) -> Option { - const SIG_64_BIT: Signature<13> = Signature::new("48 83 EC 20 4C 8B ?5 ???????? 33 F6"); + const SIG_64_BIT_PE: Signature<13> = Signature::new("48 83 EC 20 4C 8B ?5 ???????? 33 F6"); + const SIG_64_BIT_ELF: Signature<13> = Signature::new("41 54 53 50 4C 8B ?5 ???????? 41 83"); const SIG_32_1: Signature<12> = Signature::new("55 8B EC 51 A1 ???????? 53 33 DB"); const SIG_32_2: Signature<6> = Signature::new("53 8D 41 ?? 33 DB"); const SIG_32_3: Signature<14> = Signature::new("55 8B EC 83 EC 18 A1 ???????? 33 C9 53"); - let unity_player = process - .get_module_address("UnityPlayer.dll") - .ok() - .and_then(|address| { - Some((address, pe::read_size_of_image(process, address)? as u64)) - })?; + let (unity_player, format) = [ + ("UnityPlayer.dll", BinaryFormat::PE), + ("UnityPlayer.so", BinaryFormat::ELF), + ] + .into_iter() + .find_map(|(name, format)| match format { + BinaryFormat::PE => { + let address = process.get_module_address(name).ok()?; + Some(( + (address, pe::read_size_of_image(process, address)? as u64), + format, + )) + } + _ => Some((process.get_module_range(name).ok()?, format)), + })?; - let pointer_size = match pe::MachineType::read(process, unity_player.0)? { - pe::MachineType::X86_64 => PointerSize::Bit64, - _ => PointerSize::Bit32, + let pointer_size = match format { + BinaryFormat::PE => pe::MachineType::read(process, unity_player.0)?.pointer_size()?, + BinaryFormat::ELF => elf::pointer_size(process, unity_player.0)?, }; let is_il2cpp = process.get_module_address("GameAssembly.dll").is_ok(); // There are multiple signatures that can be used, depending on the version of Unity // used in the target game. - let base_address: Address = if pointer_size == PointerSize::Bit64 { - let addr = SIG_64_BIT.scan_process_range(process, unity_player)? + 7; - addr + 0x4 + process.read::(addr).ok()? - } else if let Some(addr) = SIG_32_1.scan_process_range(process, unity_player) { - process.read::(addr + 5).ok()?.into() - } else if let Some(addr) = SIG_32_2.scan_process_range(process, unity_player) { - process.read::(addr.add_signed(-4)).ok()?.into() - } else if let Some(addr) = SIG_32_3.scan_process_range(process, unity_player) { - process.read::(addr + 7).ok()?.into() - } else { - return None; + let base_address: Address = match (pointer_size, format) { + (PointerSize::Bit64, BinaryFormat::PE) => { + let addr = SIG_64_BIT_PE.scan_process_range(process, unity_player)? + 7; + addr + 0x4 + process.read::(addr).ok()? + } + (PointerSize::Bit64, BinaryFormat::ELF) => { + let addr = SIG_64_BIT_ELF.scan_process_range(process, unity_player)? + 7; + addr + 0x4 + process.read::(addr).ok()? + } + (PointerSize::Bit32, BinaryFormat::PE) => { + if let Some(addr) = SIG_32_1.scan_process_range(process, unity_player) { + process.read::(addr + 5).ok()?.into() + } else if let Some(addr) = SIG_32_2.scan_process_range(process, unity_player) { + process.read::(addr.add_signed(-4)).ok()?.into() + } else if let Some(addr) = SIG_32_3.scan_process_range(process, unity_player) { + process.read::(addr + 7).ok()?.into() + } else { + return None; + } + } + _ => { + return None; + } }; let offsets = Offsets::new(pointer_size); @@ -437,6 +462,13 @@ impl Transform { } } +#[derive(Copy, Clone, PartialEq, Hash, Debug)] +#[non_exhaustive] +enum BinaryFormat { + PE, + ELF, +} + struct Offsets { scene_count: u8, active_scene: u8, From fb80cbc958eacc34b40f3f632fcfeca3f4a7b110 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Fri, 7 Mar 2025 10:41:01 -0500 Subject: [PATCH 3/5] Mono Module attach Linux --- src/game_engine/unity/mono.rs | 113 ++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 31 deletions(-) diff --git a/src/game_engine/unity/mono.rs b/src/game_engine/unity/mono.rs index 0e9857ec..810dee84 100644 --- a/src/game_engine/unity/mono.rs +++ b/src/game_engine/unity/mono.rs @@ -2,8 +2,11 @@ //! backend. use crate::{ - file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32, - Address64, Error, PointerSize, Process, + file_format::{elf, pe}, + future::retry, + signature::Signature, + string::ArrayCString, + Address, Address32, Address64, Error, PointerSize, Process, }; use core::{ array, @@ -40,34 +43,61 @@ impl Module { /// correct for this function to work. If you don't know the version in /// advance, use [`attach_auto_detect`](Self::attach_auto_detect) instead. pub fn attach(process: &Process, version: Version) -> Option { - let module = ["mono.dll", "mono-2.0-bdwgc.dll"] - .iter() - .find_map(|&name| process.get_module_address(name).ok())?; - - let pointer_size = match pe::MachineType::read(process, module)? { - pe::MachineType::X86_64 => PointerSize::Bit64, - _ => PointerSize::Bit32, + #[allow(unused)] + let (module_range, format) = [ + ("mono.dll", BinaryFormat::PE), + ("libmono.so", BinaryFormat::ELF), + ("mono-2.0-bdwgc.dll", BinaryFormat::PE), + ("libmonobdwgc-2.0.so", BinaryFormat::ELF), + ] + .into_iter() + .find_map(|(name, format)| Some((process.get_module_range(name).ok()?, format)))?; + let module = module_range.0; + + let pointer_size = match format { + BinaryFormat::PE => pe::MachineType::read(process, module)?.pointer_size()?, + BinaryFormat::ELF => elf::pointer_size(process, module)?, }; - let offsets = Offsets::new(version, pointer_size)?; - - let root_domain_function_address = pe::symbols(process, module) - .find(|symbol| { - symbol - .get_name::<25>(process) - .is_ok_and(|name| name.matches("mono_assembly_foreach")) - })? - .address; + let offsets = Offsets::new(version, pointer_size, format)?; + + let root_domain_function_address = match format { + BinaryFormat::PE => { + pe::symbols(process, module) + .find(|symbol| { + symbol + .get_name::<25>(process) + .is_ok_and(|name| name.matches("mono_assembly_foreach")) + })? + .address + } + BinaryFormat::ELF => { + elf::symbols(process, module) + .find(|symbol| { + symbol + .get_name::<25>(process) + .is_ok_and(|name| name.matches("mono_assembly_foreach")) + })? + .address + } + }; - let assemblies: Address = match pointer_size { - PointerSize::Bit64 => { + let assemblies: Address = match (pointer_size, format) { + (PointerSize::Bit64, BinaryFormat::PE) => { const SIG_MONO_64: Signature<3> = Signature::new("48 8B 0D"); let scan_address: Address = SIG_MONO_64 .scan_process_range(process, (root_domain_function_address, 0x100))? + 3; scan_address + 0x4 + process.read::(scan_address).ok()? } - PointerSize::Bit32 => { + (PointerSize::Bit64, BinaryFormat::ELF) => { + const SIG_MONO_64_ELF: Signature<3> = Signature::new("48 8B 3D"); + let scan_address: Address = SIG_MONO_64_ELF + .scan_process_range(process, (root_domain_function_address, 0x100))? + + 3; + scan_address + 0x4 + process.read::(scan_address).ok()? + } + (PointerSize::Bit32, BinaryFormat::PE) => { const SIG_32_1: Signature<2> = Signature::new("FF 35"); const SIG_32_2: Signature<2> = Signature::new("8B 0D"); @@ -754,9 +784,13 @@ struct Offsets { } impl Offsets { - const fn new(version: Version, pointer_size: PointerSize) -> Option<&'static Self> { - match pointer_size { - PointerSize::Bit64 => match version { + const fn new( + version: Version, + pointer_size: PointerSize, + format: BinaryFormat, + ) -> Option<&'static Self> { + match (pointer_size, format) { + (PointerSize::Bit64, BinaryFormat::PE) => match version { Version::V1 => Some(&Self { monoassembly_aname: 0x10, monoassembly_image: 0x58, @@ -842,7 +876,7 @@ impl Offsets { monoclassfieldalignment: 0x20, }), }, - PointerSize::Bit32 => match version { + (PointerSize::Bit32, BinaryFormat::PE) => match version { Version::V1 => Some(&Self { monoassembly_aname: 0x8, monoassembly_image: 0x40, @@ -933,6 +967,13 @@ impl Offsets { } } +#[derive(Copy, Clone, PartialEq, Hash, Debug)] +#[non_exhaustive] +enum BinaryFormat { + PE, + ELF, +} + /// The version of Mono that was used for the game. These don't correlate to the /// Mono version numbers. #[derive(Copy, Clone, PartialEq, Hash, Debug)] @@ -948,7 +989,9 @@ pub enum Version { } fn detect_version(process: &Process) -> Option { - if process.get_module_address("mono.dll").is_ok() { + if process.get_module_address("mono.dll").is_ok() + || process.get_module_address("libmono.so").is_ok() + { // If the module mono.dll is present, then it's either V1 or V1Cattrs. // In order to distinguish between them, we check the first class listed in the // default Assembly-CSharp image and check for the pointer to its name, assuming it's using V1. @@ -972,11 +1015,19 @@ fn detect_version(process: &Process) -> Option { }); } - let unity_module = { - let address = process.get_module_address("UnityPlayer.dll").ok()?; - let range = pe::read_size_of_image(process, address)? as u64; - (address, range) - }; + let unity_module = [ + ("UnityPlayer.dll", BinaryFormat::PE), + ("UnityPlayer.so", BinaryFormat::ELF), + ] + .into_iter() + .find_map(|(name, format)| match format { + BinaryFormat::PE => { + let address = process.get_module_address(name).ok()?; + let range = pe::read_size_of_image(process, address)? as u64; + Some((address, range)) + } + BinaryFormat::ELF => process.get_module_range(name).ok(), + })?; const SIG_202X: Signature<6> = Signature::new("00 32 30 32 ?? 2E"); From 844e82a8745d840d99a9926bdb575d31b802cc8d Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Fri, 7 Mar 2025 10:45:25 -0500 Subject: [PATCH 4/5] Mono Offsets for Bit64, ELF, V1/V1Cattrs/V2 --- src/game_engine/unity/mono.rs | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/game_engine/unity/mono.rs b/src/game_engine/unity/mono.rs index 810dee84..2e94915f 100644 --- a/src/game_engine/unity/mono.rs +++ b/src/game_engine/unity/mono.rs @@ -833,6 +833,8 @@ impl Offsets { monovtable_vtable: 0x48, monoclassfieldalignment: 0x20, }), + // 64-bit PE V2 matches Unity2019_4_2020_3_x64_PE_Offsets from + // https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L49 Version::V2 => Some(&Self { monoassembly_aname: 0x10, monoassembly_image: 0x60, @@ -876,6 +878,74 @@ impl Offsets { monoclassfieldalignment: 0x20, }), }, + (PointerSize::Bit64, BinaryFormat::ELF) => match version { + Version::V1 => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x58, + monoimage_class_cache: 0x3D0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0xF8, + monoclassdef_klass: 0x0, + monoclass_name: 0x40, + monoclass_name_space: 0x48, + monoclass_fields: 0xA0, + monoclassdef_field_count: 0x8C, + monoclass_runtime_info: 0xF0, + monoclass_vtable_size: 0x18, // MonoVtable.data + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x48, + monoclassfieldalignment: 0x20, + }), + Version::V1Cattrs => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x58, + monoimage_class_cache: 0x3D0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0x100, + monoclassdef_klass: 0x0, + monoclass_name: 0x48, + monoclass_name_space: 0x50, + monoclass_fields: 0xA8, + monoclassdef_field_count: 0x94, + monoclass_runtime_info: 0xF8, + monoclass_vtable_size: 0x18, // MonoVtable.data + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x48, + monoclassfieldalignment: 0x20, + }), + // 64-bit ELF V2 happens to match Unity2019_4_2020_3_x64_MachO_Offsets from + // https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L86 + Version::V2 => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x60, + monoimage_class_cache: 0x4C0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0x100, + monoclassdef_klass: 0x0, + monoclass_name: 0x40, + monoclass_name_space: 0x48, + monoclass_fields: 0x90, + monoclassdef_field_count: 0xF8, + monoclass_runtime_info: 0xC8, + monoclass_vtable_size: 0x54, + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x40, + monoclassfieldalignment: 0x20, + }), + _ => None, + }, (PointerSize::Bit32, BinaryFormat::PE) => match version { Version::V1 => Some(&Self { monoassembly_aname: 0x8, @@ -919,6 +989,8 @@ impl Offsets { monovtable_vtable: 0x28, monoclassfieldalignment: 0x10, }), + // 32-bit PE V2 matches Unity2018_4_10_x86_PE_Offsets from + // https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L12 Version::V2 => Some(&Self { monoassembly_aname: 0x8, monoassembly_image: 0x44, From 0815f3c9e36acd6a645996a9c9e7d07caeb22622 Mon Sep 17 00:00:00 2001 From: AlexKnauth Date: Sun, 9 Mar 2025 16:59:46 -0400 Subject: [PATCH 5/5] Mono Offsets for Bit64, ELF, V3 --- src/game_engine/unity/mono.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/game_engine/unity/mono.rs b/src/game_engine/unity/mono.rs index 2e94915f..fa442f55 100644 --- a/src/game_engine/unity/mono.rs +++ b/src/game_engine/unity/mono.rs @@ -944,7 +944,27 @@ impl Offsets { monovtable_vtable: 0x40, monoclassfieldalignment: 0x20, }), - _ => None, + Version::V3 => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x60, + monoimage_class_cache: 0x4D0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0x100, + monoclassdef_klass: 0x0, + monoclass_name: 0x40, + monoclass_name_space: 0x48, + monoclass_fields: 0x90, + monoclassdef_field_count: 0xF8, + monoclass_runtime_info: 0xC8, + monoclass_vtable_size: 0x54, + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x48, + monoclassfieldalignment: 0x20, + }), }, (PointerSize::Bit32, BinaryFormat::PE) => match version { Version::V1 => Some(&Self {