wip: checkpoint FIFA 17 hook diagnostics
This commit is contained in:
+116
-32
@@ -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 (40–4F)
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user