Skip to content

Commit

Permalink
Mono Module attach
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexKnauth committed May 13, 2024
1 parent 28b63a7 commit fbfe5c2
Showing 1 changed file with 115 additions and 31 deletions.
146 changes: 115 additions & 31 deletions src/game_engine/unity/mono.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
//! Support for attaching to Unity games that are using the standard Mono
//! backend.
#[cfg(feature = "alloc")]
use crate::file_format::macho;
use crate::{
deep_pointer::DeepPointer, file_format::pe, future::retry, signature::Signature,
string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process,
deep_pointer::DeepPointer,
file_format::{elf, pe},
future::retry,
signature::Signature,
string::ArrayCString,
Address, Address32, Address64, Error, PointerSize, Process,
};
use core::{array, cell::RefCell, iter};

Expand Down Expand Up @@ -36,34 +42,85 @@ 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<Self> {
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),
#[cfg(feature = "alloc")]
("libmono.0.dylib", BinaryFormat::MachO),
("mono-2.0-bdwgc.dll", BinaryFormat::PE),
("libmonobdwgc-2.0.so", BinaryFormat::ELF),
#[cfg(feature = "alloc")]
("libmonobdwgc-2.0.dylib", BinaryFormat::MachO),
]
.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)?,
#[cfg(feature = "alloc")]
BinaryFormat::MachO => macho::pointer_size(process, module_range)?,
};

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
}
#[cfg(feature = "alloc")]
BinaryFormat::MachO => {
macho::symbols(process, module_range)?
.find(|symbol| {
symbol
.get_name::<26>(process)
.is_ok_and(|name| name.matches("_mono_assembly_foreach"))
})?
.address
}
};

let assemblies_pointer: Address = match pointer_size {
PointerSize::Bit64 => {
let assemblies_pointer: 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::<i32>(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::<i32>(scan_address).ok()?
}
#[cfg(feature = "alloc")]
(PointerSize::Bit64, BinaryFormat::MachO) => {
const SIG_MONO_64_MACHO: Signature<3> = Signature::new("48 8B 3D");
let scan_address: Address = SIG_MONO_64_MACHO
.scan_process_range(process, (root_domain_function_address, 0x100))?
+ 3;
scan_address + 0x4 + process.read::<i32>(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");

Expand Down Expand Up @@ -811,9 +868,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,
Expand Down Expand Up @@ -899,7 +960,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,
Expand Down Expand Up @@ -990,6 +1051,14 @@ impl Offsets {
}
}

#[derive(Copy, Clone, PartialEq, Hash, Debug)]
enum BinaryFormat {
PE,
ELF,
#[cfg(feature = "alloc")]
MachO,
}

/// 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)]
Expand All @@ -1005,7 +1074,10 @@ pub enum Version {
}

fn detect_version(process: &Process) -> Option<Version> {
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()
|| process.get_module_address("libmono.0.dylib").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.
Expand All @@ -1029,11 +1101,23 @@ fn detect_version(process: &Process) -> Option<Version> {
});
}

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),
#[cfg(feature = "alloc")]
("UnityPlayer.dylib", BinaryFormat::MachO),
]
.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(),
#[cfg(feature = "alloc")]
BinaryFormat::MachO => process.get_module_range(name).ok(),
})?;

const SIG_202X: Signature<6> = Signature::new("00 32 30 32 ?? 2E");

Expand Down

0 comments on commit fbfe5c2

Please sign in to comment.