From 15a6f877eee6b6753fb27a383a676eb5f39dc8f2 Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 29 Jun 2026 17:56:23 -0700 Subject: [PATCH] feat(protossl-scan): accept module+offset targets for disasm/xref Add `module+0xoffset` addressing (e.g. `anadius64.dll+0x2BB90`) to the disasm and xref commands. Offsets are stable across launches while the base is ASLR'd, so this resolves the module's current base in the running process instead of requiring a stale absolute address each session. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/protossl-scan/src/main.rs | 50 +++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/tools/protossl-scan/src/main.rs b/tools/protossl-scan/src/main.rs index dd405dd..b7c35f0 100644 --- a/tools/protossl-scan/src/main.rs +++ b/tools/protossl-scan/src/main.rs @@ -124,17 +124,25 @@ fn main() { // loads and call targets. Use this on a code anchor (e.g. the lea that // loads the "_ProtoSSLSendPacket" string) to read the real code. Some("disasm") => { - let target = match args.get(1).and_then(|s| parse_hex(s)) { - Some(t) => t, + let arg = match args.get(1) { + Some(a) => a.clone(), None => { - eprintln!("Usage: protossl-scan disasm [pid|name]"); - eprintln!("Example: protossl-scan disasm 0x140EFA631"); + eprintln!("Usage: protossl-scan disasm [pid|name]"); + eprintln!("Examples: protossl-scan disasm 0x140EFA631"); + eprintln!(" protossl-scan disasm anadius64.dll+0x2BB90"); std::process::exit(1); } }; let pid = resolve_pid(args.get(2).map(|s| s.as_str())); let process = open_for_read(pid); let modules = enumerate_modules(pid); + let target = match resolve_target(&arg, &modules) { + Some(t) => t, + None => { + eprintln!("Could not resolve '{arg}' (unknown module or bad address)"); + std::process::exit(1); + } + }; run_disasm(process, &modules, target); unsafe { let _ = CloseHandle(process); @@ -142,12 +150,11 @@ fn main() { } // xref [pid|name] (power-user form, exact absolute address) Some("xref") => { - let target = match args.get(1).and_then(|s| parse_hex(s)) { - Some(t) => t, + let arg = match args.get(1) { + Some(a) => a.clone(), None => { - eprintln!("Usage: protossl-scan xref [pid|name]"); + eprintln!("Usage: protossl-scan xref [pid|name]"); eprintln!("Example: protossl-scan xref 0x147D198B9"); - eprintln!("Tip: pass the FULL absolute address, not the +offset."); eprintln!("Or just use: protossl-scan xref-str ProtoSSLSend"); std::process::exit(1); } @@ -155,6 +162,13 @@ fn main() { let pid = resolve_pid(args.get(2).map(|s| s.as_str())); let process = open_for_read(pid); let modules = enumerate_modules(pid); + let target = match resolve_target(&arg, &modules) { + Some(t) => t, + None => { + eprintln!("Could not resolve '{arg}' (unknown module or bad address)"); + std::process::exit(1); + } + }; run_xref(process, &modules, target); unsafe { let _ = CloseHandle(process); @@ -525,6 +539,26 @@ fn parse_hex(s: &str) -> Option { usize::from_str_radix(trimmed, 16).ok() } +/// Resolve a target that is either a raw hex address or a `module+0xoffset` +/// form (e.g. `anadius64.dll+0x2BB90`). The module form is ASLR-robust: it adds +/// the offset to the module's CURRENT base in the running process. +fn resolve_target(s: &str, modules: &[ModuleInfo]) -> Option { + if let Some(idx) = s.find('+') { + let name = s[..idx].trim().to_ascii_lowercase(); + let want = name.strip_suffix(".dll").unwrap_or(&name); + let off = parse_hex(s[idx + 1..].trim())?; + for m in modules { + let mn = m.name.to_ascii_lowercase(); + let mn = mn.strip_suffix(".dll").unwrap_or(&mn); + if mn == want { + return Some(m.base + off); + } + } + return None; + } + parse_hex(s) +} + /// Read a single u64 from the target process at `addr`. Returns None if the /// memory can't be read (e.g. unmapped). fn read_u64(process: HANDLE, addr: usize) -> Option {