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>
This commit is contained in:
+43
-17
@@ -1,13 +1,13 @@
|
||||
use std::{ffi::CStr, sync::OnceLock};
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
sync::{
|
||||
OnceLock,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
use windows_sys::Win32::Networking::WinSock::{ADDRINFOA, getaddrinfo as sys_getaddrinfo};
|
||||
|
||||
const INTERCEPT: &[&str] = &[
|
||||
"fut.ea.com",
|
||||
"utas.mob.v4.fut.ea.com",
|
||||
"utas.s2.fut.ea.com",
|
||||
];
|
||||
|
||||
type GetaddrinfoFn = unsafe extern "system" fn(
|
||||
*const u8,
|
||||
*const u8,
|
||||
@@ -16,19 +16,35 @@ type GetaddrinfoFn = unsafe extern "system" fn(
|
||||
) -> i32;
|
||||
|
||||
static REAL: OnceLock<GetaddrinfoFn> = OnceLock::new();
|
||||
// Stored as a NUL-terminated byte string so the hook can pass it to getaddrinfo.
|
||||
static REDIRECT_IP: OnceLock<Vec<u8>> = OnceLock::new();
|
||||
|
||||
// Flipped to true the first time we successfully apply the runtime cert patch.
|
||||
// The patch is deferred to here (rather than DllMain) because EAWebKit.dll may
|
||||
// not be loaded yet when the hook DLL is injected.
|
||||
static CERT_PATCHED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn set_real(f: GetaddrinfoFn) {
|
||||
let _ = REAL.set(f);
|
||||
}
|
||||
|
||||
pub fn set_redirect_ip(ip: String) {
|
||||
let mut bytes = ip.into_bytes();
|
||||
bytes.push(0); // NUL-terminate for passing to getaddrinfo
|
||||
bytes.push(0);
|
||||
let _ = REDIRECT_IP.set(bytes);
|
||||
}
|
||||
|
||||
/// Returns true if `host` is an EA / EA-Sports domain that should be redirected
|
||||
/// to the local OpenFUT bridge.
|
||||
fn is_ea_host(host: &str) -> bool {
|
||||
let h = host.to_ascii_lowercase();
|
||||
h.ends_with(".ea.com")
|
||||
|| h == "ea.com"
|
||||
|| h.ends_with(".easports.com")
|
||||
|| h == "easports.com"
|
||||
|| h.ends_with(".ugc.footapi.com")
|
||||
|| h.ends_with(".footapi.com")
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn hooked_getaddrinfo(
|
||||
node_name: *const u8,
|
||||
service_name: *const u8,
|
||||
@@ -37,15 +53,25 @@ pub unsafe extern "system" fn hooked_getaddrinfo(
|
||||
) -> i32 {
|
||||
if !node_name.is_null() {
|
||||
if let Ok(host) = CStr::from_ptr(node_name as *const i8).to_str() {
|
||||
for target in INTERCEPT {
|
||||
if host.eq_ignore_ascii_case(target) {
|
||||
let redirect = REDIRECT_IP
|
||||
.get()
|
||||
.map(|v| v.as_ptr())
|
||||
.unwrap_or(b"127.0.0.1\0".as_ptr());
|
||||
let real = REAL.get().copied().unwrap_or(sys_getaddrinfo);
|
||||
return real(redirect, service_name, hints, result);
|
||||
crate::write_log(&format!("openfut_hook: getaddrinfo({host})\n"));
|
||||
if is_ea_host(host) {
|
||||
// Apply the ProtoSSL cert-verify bypass the first time we see an EA
|
||||
// hostname — EAWebKit.dll must be loaded by now because it's calling us.
|
||||
if !CERT_PATCHED.load(Ordering::Relaxed) {
|
||||
if crate::ssl_patch::patch_eawebkit_cert_verify() {
|
||||
CERT_PATCHED.store(true, Ordering::Relaxed);
|
||||
crate::write_log("openfut_hook: ProtoSSL cert-verify patched (lazy, from getaddrinfo)\n");
|
||||
} else {
|
||||
crate::write_log("openfut_hook: ProtoSSL cert-verify patch FAILED in getaddrinfo\n");
|
||||
}
|
||||
}
|
||||
|
||||
let redirect = REDIRECT_IP
|
||||
.get()
|
||||
.map(|v| v.as_ptr())
|
||||
.unwrap_or(b"127.0.0.1\0".as_ptr());
|
||||
let real = REAL.get().copied().unwrap_or(sys_getaddrinfo);
|
||||
return real(redirect, service_name, hints, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user