Files
OpenFUT/openfut-hook/src/connect_hook.rs
T
funman300 87241acc1a feat(hook): expand IAT hook coverage with TLS bypass, connect/recv hooks, and logging
Adds connect_hook, connectex_hook, recv_hook, ssl_patch, tls_bypass, lsx, ea_stub,
and origin_spy modules to intercept EA's TLS and socket layers in addition to
getaddrinfo. Adds DLL-level logging to C:\openfut_hook.log for debugging. Also
patches windows-sys feature flags to include Cryptography and Threading APIs needed
by the new hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 10:36:17 -07:00

176 lines
6.4 KiB
Rust

/// Hooks ws2_32!connect via inline detour (no iptables needed).
/// Uses unhook/rehook pattern: restores original bytes, calls real function, re-installs hook.
/// This avoids trampoline RIP-relocation issues entirely.
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::OnceLock;
const AF_INET: u16 = 2;
const PORT_HTTPS_NBO: u16 = 0xBB01; // 443 big-endian
const PORT_BRIDGE_NBO: u16 = 0xFB20; // 8443 big-endian
const PORT_BLAZE_REDIRECTOR_NBO: u16 = 0x3927; // 10041 big-endian
const PORT_BLAZE_MAIN_NBO: u16 = 0x8FA4; // 42127 big-endian
const ADDR_LOOPBACK_NBO: u32 = 0x0100_007F; // 127.0.0.1 big-endian
#[repr(C)]
struct SockaddrIn {
sin_family: u16,
sin_port: u16,
sin_addr: u32,
sin_zero: [u8; 8],
}
// Address of ws2_32!connect (set at hook installation)
static CONNECT_ADDR: AtomicUsize = AtomicUsize::new(0);
// Original 14 bytes saved before we overwrite them
static mut ORIGINAL_BYTES: [u8; 14] = [0u8; 14];
// For WSAConnect IAT fallback
type WsaConnectFn = unsafe extern "system" fn(
s: usize, name: *const u8, namelen: i32,
caller: *const (), callee: *const (),
sqos: *const (), gqos: *const ()) -> i32;
static REAL_WSA: OnceLock<WsaConnectFn> = OnceLock::new();
pub fn set_real_wsa_connect(f: WsaConnectFn) { let _ = REAL_WSA.set(f); }
unsafe fn write_hook(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);
// FF 25 00 00 00 00 JMP [rip+0]
target.write(0xFF); target.add(1).write(0x25);
(target.add(2) as *mut u32).write(0u32);
(target.add(6) as *mut u64).write(dest);
VirtualProtect(target as _, 14, old, &mut old);
}
unsafe fn restore_original(target: *mut u8) {
use windows_sys::Win32::System::Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE};
let mut old: u32 = 0;
VirtualProtect(target as _, 14, PAGE_EXECUTE_READWRITE, &mut old);
core::ptr::copy_nonoverlapping(ORIGINAL_BYTES.as_ptr(), target, 14);
VirtualProtect(target as _, 14, old, &mut old);
}
unsafe fn redirect_if_ea(name: *const u8, namelen: i32) -> Option<([u8; 16], i32)> {
if namelen < 8 { return None; }
let sa = &*(name as *const SockaddrIn);
if sa.sin_family != AF_INET { return None; }
let orig = sa.sin_addr.to_le_bytes();
let orig_port = u16::from_be(sa.sin_port);
let new_port_nbo = match sa.sin_port {
PORT_HTTPS_NBO => PORT_BRIDGE_NBO,
PORT_BLAZE_REDIRECTOR_NBO => PORT_BLAZE_REDIRECTOR_NBO,
PORT_BLAZE_MAIN_NBO => PORT_BLAZE_MAIN_NBO,
_ => return None,
};
crate::write_log(&format!(
"connect_hook: {}.{}.{}.{}:{} → 127.0.0.1:{}\n",
orig[3], orig[2], orig[1], orig[0], orig_port,
u16::from_be(new_port_nbo)
));
let mut buf = [0u8; 16];
let out = &mut *(buf.as_mut_ptr() as *mut SockaddrIn);
out.sin_family = AF_INET;
out.sin_port = new_port_nbo;
out.sin_addr = ADDR_LOOPBACK_NBO;
Some((buf, 16))
}
pub unsafe extern "system" fn hooked_connect(s: usize, name: *const u8, namelen: i32) -> i32 {
let addr = CONNECT_ADDR.load(Ordering::Relaxed) as *mut u8;
// Log every call so we can confirm the hook fires at all
if namelen >= 8 {
let sa = &*(name as *const SockaddrIn);
if sa.sin_family == AF_INET {
let b = sa.sin_addr.to_le_bytes();
let port = u16::from_be(sa.sin_port);
// Log socket type (SOCK_STREAM=1, SOCK_DGRAM=2) to detect UDP connects
let sock_type = {
use windows_sys::Win32::Networking::WinSock::{getsockopt, SOL_SOCKET, SO_TYPE};
let mut ty: i32 = -1;
let mut len: i32 = 4;
getsockopt(s, SOL_SOCKET as i32, SO_TYPE, &mut ty as *mut i32 as *mut u8, &mut len);
ty
};
crate::write_log(&format!(
"connect_hook: call {}.{}.{}.{}:{} sock_type={}\n",
b[3], b[2], b[1], b[0], port, sock_type
));
}
}
// Port 3216 (EA App LSX) is now handled by the native openfut-bridge LSX
// server — no in-process interception needed, just let connect() go through.
let (call_name, call_len) = if let Some((buf, len)) = redirect_if_ea(name, namelen) {
restore_original(addr);
let r = {
let f: unsafe extern "system" fn(usize, *const u8, i32) -> i32
= core::mem::transmute(addr);
f(s, buf.as_ptr(), len)
};
write_hook(addr, hooked_connect as u64);
return r;
} else {
(name, namelen)
};
restore_original(addr);
let r = {
let f: unsafe extern "system" fn(usize, *const u8, i32) -> i32
= core::mem::transmute(addr);
f(s, call_name, call_len)
};
write_hook(addr, hooked_connect as u64);
if namelen >= 8 {
let sa = &*(call_name as *const SockaddrIn);
if sa.sin_family == AF_INET {
let err = if r != 0 {
use windows_sys::Win32::Networking::WinSock::WSAGetLastError;
WSAGetLastError()
} else { 0 };
crate::write_log(&format!("connect_hook: result={r} wsa_err={err}\n"));
}
}
r
}
pub unsafe extern "system" fn hooked_wsa_connect(
s: usize, name: *const u8, namelen: i32,
caller: *const (), callee: *const (),
sqos: *const (), gqos: *const (),
) -> i32 {
let real = REAL_WSA.get().copied().unwrap();
if let Some((buf, len)) = redirect_if_ea(name, namelen) {
real(s, buf.as_ptr(), len, caller, callee, sqos, gqos)
} else {
real(s, name, namelen, caller, callee, sqos, gqos)
}
}
/// Install inline detour on ws2_32!connect.
pub unsafe fn install_inline_connect_hook() -> bool {
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
let ws2 = GetModuleHandleA(b"ws2_32.dll\0".as_ptr());
if ws2.is_null() { return false; }
let connect_fn = match GetProcAddress(ws2, b"connect\0".as_ptr()) {
Some(f) => f as *mut u8,
None => return false,
};
// Save original 14 bytes
core::ptr::copy_nonoverlapping(connect_fn, ORIGINAL_BYTES.as_mut_ptr(), 14);
CONNECT_ADDR.store(connect_fn as usize, Ordering::Relaxed);
// Overwrite first 14 bytes with absolute indirect JMP to our hook
write_hook(connect_fn, hooked_connect as u64);
true
}