wip: checkpoint FIFA 17 hook diagnostics

This commit is contained in:
funman300
2026-08-07 12:03:21 -07:00
parent 3d895fb7ac
commit 09ed26ba16
11 changed files with 596 additions and 248 deletions
+116 -32
View File
@@ -12,7 +12,8 @@ unsafe fn write_jmp(target: *mut u8, dest: u64) {
use windows_sys::Win32::System::Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE};
let mut old: u32 = 0;
VirtualProtect(target as _, 14, PAGE_EXECUTE_READWRITE, &mut old);
target.write(0xFF); target.add(1).write(0x25);
target.write(0xFF);
target.add(1).write(0x25);
(target.add(2) as *mut u32).write(0);
(target.add(6) as *mut u64).write(dest);
VirtualProtect(target as _, 14, old, &mut old);
@@ -43,10 +44,15 @@ unsafe fn make_trampoline(orig: *mut u8, name: &str) -> Option<usize> {
}
let mem = VirtualAlloc(
core::ptr::null_mut(), 64,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE,
core::ptr::null_mut(),
64,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE,
);
if mem.is_null() { crate::write_log("recv_hook: VirtualAlloc failed\n"); return None; }
if mem.is_null() {
crate::write_log("recv_hook: VirtualAlloc failed\n");
return None;
}
let t = mem as *mut u8;
core::ptr::copy_nonoverlapping(orig, t, copy_len);
@@ -56,7 +62,9 @@ unsafe fn make_trampoline(orig: *mut u8, name: &str) -> Option<usize> {
t.add(copy_len + 1).write(0x25);
(t.add(copy_len + 2) as *mut u32).write(0);
(t.add(copy_len + 6) as *mut u64).write(cont);
crate::write_log(&format!("recv_hook: {name} trampoline copy_len={copy_len}\n"));
crate::write_log(&format!(
"recv_hook: {name} trampoline copy_len={copy_len}\n"
));
Some(t as usize)
}
@@ -67,8 +75,12 @@ fn has_rip_relative_branch(bytes: &[u8]) -> bool {
let mut pos = 0;
while pos < bytes.len() {
let (len, branch) = decode_instr_len(&bytes[pos..]);
if branch { return true; }
if len == 0 { break; } // unknown/truncated — stop safely
if branch {
return true;
}
if len == 0 {
break;
} // unknown/truncated — stop safely
pos += len;
}
false
@@ -78,9 +90,29 @@ fn modrm_extra(modrm: u8) -> usize {
let md = (modrm >> 6) & 3;
let rm = modrm & 7;
match md {
0 => if rm == 5 { 4 } else if rm == 4 { 1 } else { 0 },
1 => if rm == 4 { 2 } else { 1 },
2 => if rm == 4 { 5 } else { 4 },
0 => {
if rm == 5 {
4
} else if rm == 4 {
1
} else {
0
}
}
1 => {
if rm == 4 {
2
} else {
1
}
}
2 => {
if rm == 4 {
5
} else {
4
}
}
_ => 0,
}
}
@@ -88,16 +120,31 @@ fn modrm_extra(modrm: u8) -> usize {
/// Returns (instruction_length_in_bytes, is_rip_relative_branch).
/// Returns (0, false) for unknown/truncated.
fn decode_instr_len(b: &[u8]) -> (usize, bool) {
if b.is_empty() { return (0, false); }
if b.is_empty() {
return (0, false);
}
let mut i = 0;
// Legacy prefixes
while let Some(&p) = b.get(i) {
if matches!(p, 0x66 | 0x67 | 0xF0 | 0xF2 | 0xF3) { i += 1; } else { break; }
if matches!(p, 0x66 | 0x67 | 0xF0 | 0xF2 | 0xF3) {
i += 1;
} else {
break;
}
}
// REX prefix (404F)
if b.get(i).copied().map(|x| (0x40..=0x4F).contains(&x)).unwrap_or(false) { i += 1; }
if b.get(i)
.copied()
.map(|x| (0x40..=0x4F).contains(&x))
.unwrap_or(false)
{
i += 1;
}
let op = match b.get(i) { Some(&x) => x, None => return (0, false) };
let op = match b.get(i) {
Some(&x) => x,
None => return (0, false),
};
i += 1;
match op {
@@ -112,28 +159,45 @@ fn decode_instr_len(b: &[u8]) -> (usize, bool) {
0xE9 | 0xE8 => (i + 4, true),
// 0F prefix
0x0F => {
let op2 = match b.get(i) { Some(&x) => x, None => return (0, false) };
let op2 = match b.get(i) {
Some(&x) => x,
None => return (0, false),
};
i += 1;
if (0x80..=0x8F).contains(&op2) { return (i + 4, true); } // Jcc rel32
// Most 0F XX: ModRM
let modrm = match b.get(i) { Some(&x) => x, None => return (0, false) };
if (0x80..=0x8F).contains(&op2) {
return (i + 4, true);
} // Jcc rel32
// Most 0F XX: ModRM
let modrm = match b.get(i) {
Some(&x) => x,
None => return (0, false),
};
(i + 1 + modrm_extra(modrm), false)
}
// Instructions with ModRM only (no immediate)
0x85 | 0x87 | 0x88 | 0x89 | 0x8A | 0x8B | 0x8C | 0x8D | 0x8E | 0x8F |
0x01 | 0x03 | 0x09 | 0x0B | 0x11 | 0x13 | 0x21 | 0x23 | 0x29 | 0x2B |
0x31 | 0x33 | 0x39 | 0x3B | 0xD3 | 0xFF | 0xF7 => {
let modrm = match b.get(i) { Some(&x) => x, None => return (0, false) };
0x85 | 0x87 | 0x88 | 0x89 | 0x8A | 0x8B | 0x8C | 0x8D | 0x8E | 0x8F | 0x01 | 0x03
| 0x09 | 0x0B | 0x11 | 0x13 | 0x21 | 0x23 | 0x29 | 0x2B | 0x31 | 0x33 | 0x39 | 0x3B
| 0xD3 | 0xFF | 0xF7 => {
let modrm = match b.get(i) {
Some(&x) => x,
None => return (0, false),
};
(i + 1 + modrm_extra(modrm), false)
}
// ModRM + imm8
0x6B | 0x80 | 0x83 | 0xC0 | 0xC1 | 0xC6 => {
let modrm = match b.get(i) { Some(&x) => x, None => return (0, false) };
let modrm = match b.get(i) {
Some(&x) => x,
None => return (0, false),
};
(i + 1 + modrm_extra(modrm) + 1, false)
}
// ModRM + imm32
0x69 | 0x81 | 0xC7 => {
let modrm = match b.get(i) { Some(&x) => x, None => return (0, false) };
let modrm = match b.get(i) {
Some(&x) => x,
None => return (0, false),
};
(i + 1 + modrm_extra(modrm) + 4, false)
}
// MOV reg, imm8/imm32
@@ -152,7 +216,9 @@ fn decode_instr_len(b: &[u8]) -> (usize, bool) {
unsafe fn get_fn(dll: &[u8], sym: &[u8]) -> Option<*mut u8> {
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
let h = GetModuleHandleA(dll.as_ptr());
if h.is_null() { return None; }
if h.is_null() {
return None;
}
GetProcAddress(h, sym.as_ptr()).map(|f| f as *mut u8)
}
@@ -166,7 +232,9 @@ unsafe fn peer_is_lsx(s: usize) -> bool {
use windows_sys::Win32::Networking::WinSock::getpeername;
let mut sa = [0u8; 16];
let mut sl: i32 = 16;
if getpeername(s, sa.as_mut_ptr() as *mut _, &mut sl) != 0 { return false; }
if getpeername(s, sa.as_mut_ptr() as *mut _, &mut sl) != 0 {
return false;
}
// sockaddr_in: sa_family (2 bytes) then sin_port (2 bytes, network order).
u16::from_be_bytes([sa[2], sa[3]]) == 3216
}
@@ -190,7 +258,10 @@ pub fn set_real_send(f: unsafe extern "system" fn(usize, *const u8, i32, i32) ->
/// hook calls) and overwrite the entry with a JMP to `hooked_recv`. Inline hooks
/// catch calls from every module and dynamically-resolved calls, unlike IAT.
pub unsafe fn install_recv_hook() -> bool {
let ptr = match get_fn(b"ws2_32.dll\0", b"recv\0") { Some(p) => p, None => return false };
let ptr = match get_fn(b"ws2_32.dll\0", b"recv\0") {
Some(p) => p,
None => return false,
};
match make_trampoline(ptr, "recv") {
Some(t) => REAL_RECV.store(t, Ordering::Relaxed),
None => return false,
@@ -200,7 +271,10 @@ pub unsafe fn install_recv_hook() -> bool {
}
pub unsafe fn install_send_hook() -> bool {
let ptr = match get_fn(b"ws2_32.dll\0", b"send\0") { Some(p) => p, None => return false };
let ptr = match get_fn(b"ws2_32.dll\0", b"send\0") {
Some(p) => p,
None => return false,
};
match make_trampoline(ptr, "send") {
Some(t) => REAL_SEND.store(t, Ordering::Relaxed),
None => return false,
@@ -211,7 +285,9 @@ pub unsafe fn install_send_hook() -> bool {
pub unsafe extern "system" fn hooked_recv(s: usize, buf: *mut u8, len: i32, flags: i32) -> i32 {
let t = REAL_RECV.load(Ordering::Relaxed);
if t == 0 { return -1; }
if t == 0 {
return -1;
}
let f: unsafe extern "system" fn(usize, *mut u8, i32, i32) -> i32 = core::mem::transmute(t);
// Pass through to anadius's real socket, then log what it sent back
// (anadius's LSX response — the ground truth we want to diff against).
@@ -219,7 +295,10 @@ pub unsafe extern "system" fn hooked_recv(s: usize, buf: *mut u8, len: i32, flag
if n > 0 && peer_is_lsx(s) {
let data = core::slice::from_raw_parts(buf, n as usize);
let text = core::str::from_utf8(data).unwrap_or("(binary)");
crate::write_log(&format!("CAP recv<-anadius s={s} n={n}: {}\n", &text[..text.len().min(2400)]));
crate::write_log(&format!(
"CAP recv<-anadius s={s} n={n}: {}\n",
&text[..text.len().min(2400)]
));
}
n
}
@@ -228,10 +307,15 @@ pub unsafe extern "system" fn hooked_send(s: usize, buf: *const u8, len: i32, fl
if len > 0 && peer_is_lsx(s) {
let data = core::slice::from_raw_parts(buf, len as usize);
let text = core::str::from_utf8(data).unwrap_or("(binary)");
crate::write_log(&format!("CAP send->anadius s={s} len={len}: {}\n", &text[..text.len().min(2400)]));
crate::write_log(&format!(
"CAP send->anadius s={s} len={len}: {}\n",
&text[..text.len().min(2400)]
));
}
let t = REAL_SEND.load(Ordering::Relaxed);
if t == 0 { return -1; }
if t == 0 {
return -1;
}
let f: unsafe extern "system" fn(usize, *const u8, i32, i32) -> i32 = core::mem::transmute(t);
f(s, buf, len, flags)
}