// Runtime in-memory patch for ProtoSSL's certificate verification function inside // EAWebKit.dll. Rather than patching the DLL on disk (offset-dependent, fragile), // we scan the loaded module for the function's unique byte prologue and overwrite the // first six bytes with `mov eax, 1; ret` — making every cert-chain validation call // immediately return success. // // Why this is safe: the patched function (`ProtoSSL_VerifyCert` at VA 0x180a85570 in // the shipped binary) is only used by ProtoSSL's TLS state machine to validate the // server's certificate chain. Always returning 1 is equivalent to trusting all certs, // which is the behaviour we want for the local self-signed bridge certificate. use windows_sys::Win32::System::{ LibraryLoader::GetModuleHandleA, Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE}, }; // Unique 22-byte prologue of ProtoSSL's cert-verify function. // Confirmed present in the EA-shipped EAWebKit.dll (June 2023 build). const PROLOGUE: &[u8] = &[ 0x44, 0x89, 0x44, 0x24, 0x18, // mov [rsp+0x18], r8d 0x48, 0x89, 0x54, 0x24, 0x10, // mov [rsp+0x10], rdx 0x56, // push rsi 0x57, // push rdi 0x41, 0x55, // push r13 0x41, 0x56, // push r14 0x41, 0x57, // push r15 0x48, 0x83, 0xec, 0x30, // sub rsp, 0x30 ]; // Return 0 (PROTOSSL_ERROR_NONE = success). ProtoSSL convention: 0 = ok, negative = error. // The function sets r15d = 0xFFFFFFFF (-1) for its own error returns, confirming 0 = success. const PATCH: &[u8] = &[ 0x31, 0xc0, // xor eax, eax (eax = 0 = PROTOSSL_ERROR_NONE) 0xc3, // ret 0x90, 0x90, 0x90, // nop padding ]; fn patch_module(module: isize, scan_bytes: usize) -> bool { if module == 0 { return false; } let base = module as usize; let image: &[u8] = unsafe { core::slice::from_raw_parts(base as *const u8, scan_bytes) }; let offset = match image.windows(PROLOGUE.len()).position(|w| w == PROLOGUE) { Some(o) => o, None => return false, }; let target = (base + offset) as *mut u8; let mut old_prot: u32 = 0; unsafe { VirtualProtect( target as *const core::ffi::c_void, PATCH.len(), PAGE_EXECUTE_READWRITE, &mut old_prot, ); core::ptr::copy_nonoverlapping(PATCH.as_ptr(), target, PATCH.len()); VirtualProtect( target as *const core::ffi::c_void, PATCH.len(), old_prot, &mut old_prot, ); } true } /// Patch ProtoSSL cert-verify in EAWebKit.dll (call when EAWebKit is loaded). pub unsafe fn patch_eawebkit_cert_verify() -> bool { let module = GetModuleHandleA(c"EAWebKit.dll".as_ptr().cast()) as isize; // EAWebKit.dll is ~22 MB patch_module(module, 24 * 1024 * 1024) } /// Patch ProtoSSL cert-verify compiled into FIFA23.exe itself (DirtySDK's copy). /// The main exe is ~100 MB; confirmed present at file offset 0xf0c850. pub unsafe fn patch_main_exe_cert_verify() -> bool { let module = GetModuleHandleA(core::ptr::null()) as isize; // Scan first 110 MB — the function is near offset 0xf0c850 (~15 MB in) patch_module(module, 110 * 1024 * 1024) }