feat: add mem module with pointer classification to openwa-core
Browse filesAdd DATA_END constant and mem module with can_read, classify_pointer,
and classify_region functions extracted from the validation dump logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
crates/openwa-core/Cargo.toml
CHANGED
|
@@ -7,6 +7,7 @@ description = "Core library for Worms Armageddon reverse engineering — types,
|
|
| 7 |
[dependencies]
|
| 8 |
heapless = "0.9"
|
| 9 |
hound = "3"
|
|
|
|
| 10 |
|
| 11 |
[target.'cfg(target_os = "windows")'.dependencies]
|
| 12 |
windows = { version = "0.62", features = [
|
|
|
|
| 7 |
[dependencies]
|
| 8 |
heapless = "0.9"
|
| 9 |
hound = "3"
|
| 10 |
+
openwa-debug-proto = { path = "../openwa-debug-proto" }
|
| 11 |
|
| 12 |
[target.'cfg(target_os = "windows")'.dependencies]
|
| 13 |
windows = { version = "0.62", features = [
|
crates/openwa-core/src/address.rs
CHANGED
|
@@ -17,6 +17,7 @@ pub mod va {
|
|
| 17 |
pub const TEXT_END: u32 = 0x0061_9FFF;
|
| 18 |
pub const RDATA_START: u32 = 0x0061_A000;
|
| 19 |
pub const DATA_START: u32 = 0x0069_4000;
|
|
|
|
| 20 |
|
| 21 |
// === Vtables (in .rdata) ===
|
| 22 |
|
|
|
|
| 17 |
pub const TEXT_END: u32 = 0x0061_9FFF;
|
| 18 |
pub const RDATA_START: u32 = 0x0061_A000;
|
| 19 |
pub const DATA_START: u32 = 0x0069_4000;
|
| 20 |
+
pub const DATA_END: u32 = 0x008C_5000; // .rsrc starts here; .data/.bss ends just before
|
| 21 |
|
| 22 |
// === Vtables (in .rdata) ===
|
| 23 |
|
crates/openwa-core/src/lib.rs
CHANGED
|
@@ -14,6 +14,7 @@ pub mod render;
|
|
| 14 |
pub mod address;
|
| 15 |
pub mod fixed;
|
| 16 |
pub mod log;
|
|
|
|
| 17 |
pub mod rebase;
|
| 18 |
pub mod task;
|
| 19 |
pub mod vtable;
|
|
|
|
| 14 |
pub mod address;
|
| 15 |
pub mod fixed;
|
| 16 |
pub mod log;
|
| 17 |
+
pub mod mem;
|
| 18 |
pub mod rebase;
|
| 19 |
pub mod task;
|
| 20 |
pub mod vtable;
|
crates/openwa-core/src/mem.rs
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Memory inspection utilities — pointer classification and safe read checks.
|
| 2 |
+
|
| 3 |
+
use crate::address::va;
|
| 4 |
+
use openwa_debug_proto::{PointerInfo, PointerKind};
|
| 5 |
+
|
| 6 |
+
#[cfg(target_os = "windows")]
|
| 7 |
+
extern "system" {
|
| 8 |
+
fn IsBadReadPtr(lp: *const u8, ucb: usize) -> i32;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
/// Check if a pointer is safe to read.
|
| 12 |
+
#[cfg(target_os = "windows")]
|
| 13 |
+
#[inline]
|
| 14 |
+
pub unsafe fn can_read(ptr: u32, size: u32) -> bool {
|
| 15 |
+
ptr >= 0x10000 && IsBadReadPtr(ptr as *const u8, size as usize) == 0
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
/// Stub for non-Windows (always returns false).
|
| 19 |
+
#[cfg(not(target_os = "windows"))]
|
| 20 |
+
pub unsafe fn can_read(_ptr: u32, _size: u32) -> bool {
|
| 21 |
+
false
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
/// Classify a DWORD value as a pointer kind, if applicable.
|
| 25 |
+
///
|
| 26 |
+
/// Returns `None` for zero values, small values (< 0x10000), and
|
| 27 |
+
/// values that don't point into any known section or readable memory.
|
| 28 |
+
///
|
| 29 |
+
/// `delta` is `runtime_base - 0x400000` (the ASLR offset).
|
| 30 |
+
#[cfg(target_os = "windows")]
|
| 31 |
+
pub unsafe fn classify_pointer(value: u32, delta: u32) -> Option<PointerInfo> {
|
| 32 |
+
if value == 0 || value < 0x10000 {
|
| 33 |
+
return None;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
let ghidra_val = value.wrapping_sub(delta);
|
| 37 |
+
|
| 38 |
+
// .rdata → Vtable (likely vtable or function pointer table)
|
| 39 |
+
if ghidra_val >= va::RDATA_START && ghidra_val < va::DATA_START {
|
| 40 |
+
let detail = if can_read(value, 4) {
|
| 41 |
+
let vt0 = *(value as *const u32);
|
| 42 |
+
Some(format!("vt[0]=ghidra:0x{:08X}", vt0.wrapping_sub(delta)))
|
| 43 |
+
} else {
|
| 44 |
+
Some("(unreadable)".to_string())
|
| 45 |
+
};
|
| 46 |
+
return Some(PointerInfo {
|
| 47 |
+
offset: 0, // caller fills this in
|
| 48 |
+
raw_value: value,
|
| 49 |
+
ghidra_value: ghidra_val,
|
| 50 |
+
kind: PointerKind::Vtable,
|
| 51 |
+
detail,
|
| 52 |
+
});
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// .text → Code
|
| 56 |
+
if ghidra_val >= va::TEXT_START && ghidra_val <= va::TEXT_END {
|
| 57 |
+
return Some(PointerInfo {
|
| 58 |
+
offset: 0,
|
| 59 |
+
raw_value: value,
|
| 60 |
+
ghidra_value: ghidra_val,
|
| 61 |
+
kind: PointerKind::Code,
|
| 62 |
+
detail: None,
|
| 63 |
+
});
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// .data/.bss → Data
|
| 67 |
+
if ghidra_val >= va::DATA_START && ghidra_val < va::DATA_END {
|
| 68 |
+
return Some(PointerInfo {
|
| 69 |
+
offset: 0,
|
| 70 |
+
raw_value: value,
|
| 71 |
+
ghidra_value: ghidra_val,
|
| 72 |
+
kind: PointerKind::Data,
|
| 73 |
+
detail: None,
|
| 74 |
+
});
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// Heap pointer checks
|
| 78 |
+
if can_read(value, 4) {
|
| 79 |
+
let first = *(value as *const u32);
|
| 80 |
+
let ghidra_first = first.wrapping_sub(delta);
|
| 81 |
+
|
| 82 |
+
// Object — heap pointer whose first DWORD is a vtable
|
| 83 |
+
if ghidra_first >= va::RDATA_START && ghidra_first < va::DATA_START {
|
| 84 |
+
let detail = if can_read(first, 4) {
|
| 85 |
+
let vt0 = *(first as *const u32);
|
| 86 |
+
Some(format!(
|
| 87 |
+
"vtable=ghidra:0x{:08X} vt[0]=ghidra:0x{:08X}",
|
| 88 |
+
ghidra_first,
|
| 89 |
+
vt0.wrapping_sub(delta)
|
| 90 |
+
))
|
| 91 |
+
} else {
|
| 92 |
+
Some(format!("vtable=ghidra:0x{:08X} vt[0]=?", ghidra_first))
|
| 93 |
+
};
|
| 94 |
+
return Some(PointerInfo {
|
| 95 |
+
offset: 0,
|
| 96 |
+
raw_value: value,
|
| 97 |
+
ghidra_value: ghidra_val,
|
| 98 |
+
kind: PointerKind::Object,
|
| 99 |
+
detail,
|
| 100 |
+
});
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// Generic heap pointer
|
| 104 |
+
return Some(PointerInfo {
|
| 105 |
+
offset: 0,
|
| 106 |
+
raw_value: value,
|
| 107 |
+
ghidra_value: ghidra_val,
|
| 108 |
+
kind: PointerKind::Heap,
|
| 109 |
+
detail: Some(format!("*=0x{:08X}", first)),
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
None
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
/// Classify all DWORD-aligned values in a byte slice.
|
| 117 |
+
///
|
| 118 |
+
/// Returns a vec of `PointerInfo` with offsets filled in relative to `base_offset`.
|
| 119 |
+
#[cfg(target_os = "windows")]
|
| 120 |
+
pub unsafe fn classify_region(
|
| 121 |
+
data: &[u8],
|
| 122 |
+
base_offset: u32,
|
| 123 |
+
delta: u32,
|
| 124 |
+
) -> Vec<PointerInfo> {
|
| 125 |
+
let mut pointers = Vec::new();
|
| 126 |
+
let dword_count = data.len() / 4;
|
| 127 |
+
for i in 0..dword_count {
|
| 128 |
+
let byte_offset = i * 4;
|
| 129 |
+
let value = u32::from_le_bytes([
|
| 130 |
+
data[byte_offset],
|
| 131 |
+
data[byte_offset + 1],
|
| 132 |
+
data[byte_offset + 2],
|
| 133 |
+
data[byte_offset + 3],
|
| 134 |
+
]);
|
| 135 |
+
if let Some(mut info) = classify_pointer(value, delta) {
|
| 136 |
+
info.offset = base_offset + byte_offset as u32;
|
| 137 |
+
pointers.push(info);
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
pointers
|
| 141 |
+
}
|