use super::super::binary::find_code_bounds;
use super::super::constants::*;
use super::super::fname::FNameReader;
use super::super::source::MemorySource;
use super::types::{UClassMetaclassInfo, UObjectInfo};
use anyhow::{bail, Result};
use byteorder::{ByteOrder, LE};
pub fn find_all_uclasses(
source: &dyn MemorySource,
fname_reader: &mut FNameReader,
) -> Result<Vec<UObjectInfo>> {
let code_bounds = find_code_bounds(source)?;
let mut results = Vec::new();
let mut scanned_bytes = 0usize;
eprintln!(
"Scanning for UClass instances (ClassPrivate == {:#x})...",
UCLASS_METACLASS_ADDR
);
for region in source.regions() {
if !region.is_readable() {
continue;
}
let in_pe = region.start >= 0x140000000 && region.start <= 0x175000000;
let in_heap = region.start >= 0x1000000 && region.start < 0x140000000;
if !in_pe && !in_heap {
continue;
}
if region.size() > 100 * 1024 * 1024 {
continue;
}
let data = match source.read_bytes(region.start, region.size()) {
Ok(d) => d,
Err(_) => continue,
};
scanned_bytes += data.len();
for i in (0..data.len().saturating_sub(UOBJECT_HEADER_SIZE)).step_by(8) {
if i + UOBJECT_CLASS_OFFSET + 8 > data.len() {
continue;
}
let class_ptr =
LE::read_u64(&data[i + UOBJECT_CLASS_OFFSET..i + UOBJECT_CLASS_OFFSET + 8])
as usize;
if class_ptr != UCLASS_METACLASS_ADDR {
continue;
}
let obj_addr = region.start + i;
let vtable_ptr = LE::read_u64(&data[i..i + 8]) as usize;
if vtable_ptr < MIN_VTABLE_ADDR || vtable_ptr > MAX_VTABLE_ADDR {
continue;
}
if let Ok(vtable_data) = source.read_bytes(vtable_ptr, 8) {
let first_func = LE::read_u64(&vtable_data) as usize;
if !code_bounds.contains(first_func) {
continue;
}
} else {
continue;
}
let name_index =
LE::read_u32(&data[i + UOBJECT_NAME_OFFSET..i + UOBJECT_NAME_OFFSET + 4]);
let name = match fname_reader.read_name(source, name_index) {
Ok(n) => n,
Err(_) => format!("FName_{}", name_index),
};
results.push(UObjectInfo {
address: obj_addr,
class_ptr,
name_index,
name,
class_name: "Class".to_string(),
});
}
}
eprintln!(
"Scanned {} MB, found {} UClass instances",
scanned_bytes / 1_000_000,
results.len()
);
results.sort_by(|a, b| a.name.cmp(&b.name));
Ok(results)
}
pub fn discover_uclass_metaclass_exhaustive(
source: &dyn MemorySource,
fname_reader: &mut FNameReader,
) -> Result<UClassMetaclassInfo> {
let code_bounds = find_code_bounds(source)?;
eprintln!("=== Exhaustive UClass Metaclass Discovery ===");
let class_fname_idx = fname_reader
.find_class_index(source)
.unwrap_or(FNAME_CLASS_INDEX);
eprintln!(
"Looking for self-referential object with FName 'Class' ({})...",
class_fname_idx
);
let class_offsets = [0x08, 0x10, 0x18, 0x20, 0x28];
let name_offsets = [0x18, 0x20, 0x28, 0x30, 0x38, 0x40];
let class_fname_bytes = class_fname_idx.to_le_bytes();
eprintln!(
"Scanning all memory for objects with FName {} ('Class')...",
class_fname_idx
);
let mut scanned_mb = 0usize;
for region in source.regions() {
if !region.is_readable() {
continue;
}
let chunk_size = 256 * 1024 * 1024; let mut offset = 0usize;
while offset < region.size() {
let read_size = (region.size() - offset).min(chunk_size);
let chunk_start = region.start + offset;
let data = match source.read_bytes(chunk_start, read_size) {
Ok(d) => d,
Err(_) => {
offset += chunk_size;
continue;
}
};
scanned_mb += data.len() / (1024 * 1024);
if scanned_mb % 1000 == 0 && scanned_mb > 0 {
eprint!("\r Scanned {} MB...", scanned_mb);
}
let mut pos = 0;
while pos + 64 < data.len() {
for &name_offset in &name_offsets {
if pos + name_offset + 4 > data.len() {
continue;
}
if data[pos + name_offset..pos + name_offset + 4] != class_fname_bytes[..] {
continue;
}
for &class_offset in &class_offsets {
if class_offset == name_offset {
continue;
}
let max_offset = class_offset.max(name_offset) + 8;
if pos + max_offset > data.len() {
continue;
}
let obj_addr = chunk_start + pos;
let vtable_ptr = LE::read_u64(&data[pos..pos + 8]) as usize;
if vtable_ptr < MIN_VTABLE_ADDR || vtable_ptr > MAX_VTABLE_ADDR {
continue;
}
if let Ok(vtable_data) = source.read_bytes(vtable_ptr, 8) {
let first_func = LE::read_u64(&vtable_data) as usize;
if !code_bounds.contains(first_func) {
continue;
}
} else {
continue;
}
let class_ptr =
LE::read_u64(&data[pos + class_offset..pos + class_offset + 8])
as usize;
if class_ptr == obj_addr {
eprintln!("\rFound UClass metaclass at {:#x}!", obj_addr);
eprintln!(" vtable: {:#x}", vtable_ptr);
eprintln!(" ClassPrivate offset: {:#x}", class_offset);
eprintln!(" NamePrivate offset: {:#x}", name_offset);
let fname_idx =
LE::read_u32(&data[pos + name_offset..pos + name_offset + 4]);
let name = fname_reader
.read_name(source, fname_idx)
.unwrap_or_else(|_| format!("FName_{}", fname_idx));
return Ok(UClassMetaclassInfo {
address: obj_addr,
vtable: vtable_ptr,
class_offset,
name_offset,
fname_index: fname_idx,
name,
});
}
}
}
pos += 8; }
offset += chunk_size;
}
}
eprintln!("\r Scanned {} MB total", scanned_mb);
eprintln!(
"\nNo self-referential object with FName {} found.",
class_fname_idx
);
eprintln!("Searching all memory for any self-referential objects...");
let mut self_refs: Vec<(usize, usize, usize, usize, u32, String)> = Vec::new();
let mut scanned_mb2 = 0usize;
'outer: for region in source.regions() {
if !region.is_readable() {
continue;
}
let chunk_size = 256 * 1024 * 1024;
let mut offset = 0usize;
while offset < region.size() && self_refs.len() < 50 {
let read_size = (region.size() - offset).min(chunk_size);
let chunk_start = region.start + offset;
let data = match source.read_bytes(chunk_start, read_size) {
Ok(d) => d,
Err(_) => {
offset += chunk_size;
continue;
}
};
scanned_mb2 += data.len() / (1024 * 1024);
if scanned_mb2 % 2000 == 0 && scanned_mb2 > 0 {
eprint!("\r Scanned {} MB for self-refs...", scanned_mb2);
}
for &class_offset in &class_offsets {
for &name_offset in &name_offsets {
if class_offset == name_offset {
continue;
}
let max_offset = class_offset.max(name_offset) + 8;
for i in (0..data.len().saturating_sub(max_offset)).step_by(8) {
let obj_addr = chunk_start + i;
let class_ptr =
LE::read_u64(&data[i + class_offset..i + class_offset + 8]) as usize;
if class_ptr != obj_addr {
continue;
}
let vtable_ptr = LE::read_u64(&data[i..i + 8]) as usize;
if vtable_ptr < MIN_VTABLE_ADDR || vtable_ptr > MAX_VTABLE_ADDR {
continue;
}
if let Ok(vtable_data) = source.read_bytes(vtable_ptr, 8) {
let first_func = LE::read_u64(&vtable_data) as usize;
if !code_bounds.contains(first_func) {
continue;
}
} else {
continue;
}
let fname_idx = LE::read_u32(&data[i + name_offset..i + name_offset + 4]);
let name = fname_reader
.read_name(source, fname_idx)
.unwrap_or_else(|_| format!("FName_{}", fname_idx));
self_refs.push((
obj_addr,
vtable_ptr,
class_offset,
name_offset,
fname_idx,
name,
));
if self_refs.len() >= 50 {
break 'outer;
}
}
}
}
offset += chunk_size;
}
}
eprintln!("\r Scanned {} MB for self-refs", scanned_mb2);
eprintln!(
"Found {} self-referential objects with valid vtables:",
self_refs.len()
);
for (addr, vt, cls_off, name_off, idx, name) in &self_refs {
let marker = if *idx == class_fname_idx || name == "Class" {
" <-- METACLASS!"
} else {
""
};
eprintln!(
" {:#x}: vt={:#x}, cls@+{:#x}, name@+{:#x}, FName={}(\"{}\"){}",
addr, vt, cls_off, name_off, idx, name, marker
);
}
if let Some((addr, vt, cls_off, name_off, idx, name)) = self_refs
.iter()
.find(|(_, _, _, _, idx, name)| *idx == class_fname_idx || name == "Class")
{
return Ok(UClassMetaclassInfo {
address: *addr,
vtable: *vt,
class_offset: *cls_off,
name_offset: *name_off,
fname_index: *idx,
name: name.clone(),
});
}
bail!("UClass metaclass not found in dump. The dump may be incomplete or the FName format is different.")
}