diag(fifa17): capture WEBFILE_DL url + guarded CACHE_PACKNAMES bypass
- Passive: log FUN_18017ff90 param_1 = the pack-names/cards-tournament-list WEBFILE_DL url (via relocating installer; rip-relative MOV R8,[DAT_1802e6580]). - Guarded one-shot (staging client only): in the final completion FUN_1800ffe90, when the delivered result string is CACHE_PACKNAMES_FAILED, rewrite result byte0 so it delivers SUCCESS -> LoadSeasons_Complete advances to LoadCurrentOfflineSeason. String-verified, once per process.
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
//! flow. Targets are chosen so their copied prologues are position-independent
|
//! flow. Targets are chosen so their copied prologues are position-independent
|
||||||
//! (no rip-relative / rel32 in the copied bytes).
|
//! (no rip-relative / rel32 in the copied bytes).
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
|
||||||
use windows_sys::Win32::System::Diagnostics::Debug::FlushInstructionCache;
|
use windows_sys::Win32::System::Diagnostics::Debug::FlushInstructionCache;
|
||||||
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleA;
|
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleA;
|
||||||
@@ -28,6 +28,8 @@ use crate::sbc_trace::{
|
|||||||
use crate::write_log;
|
use crate::write_log;
|
||||||
|
|
||||||
static REPORTS: AtomicUsize = AtomicUsize::new(0);
|
static REPORTS: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
/// One-shot guard for the staging-only CACHE_PACKNAMES_FAILED -> SUCCESS bypass.
|
||||||
|
static BYPASS_DONE: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
unsafe fn rd_i32(addr: usize) -> Option<i32> {
|
unsafe fn rd_i32(addr: usize) -> Option<i32> {
|
||||||
readable_range(addr, 4).then(|| core::ptr::read_volatile(addr as *const i32))
|
readable_range(addr, 4).then(|| core::ptr::read_volatile(addr as *const i32))
|
||||||
@@ -315,30 +317,47 @@ unsafe fn install_detour_reloc(
|
|||||||
// string ptr when byte0==0). Logs the EXACT status string delivered. Passive.
|
// string ptr when byte0==0). Logs the EXACT status string delivered. Passive.
|
||||||
static FINAL_COMPLETION_TRAMP: AtomicUsize = AtomicUsize::new(0);
|
static FINAL_COMPLETION_TRAMP: AtomicUsize = AtomicUsize::new(0);
|
||||||
unsafe extern "system" fn final_completion_wrapper(ctx: usize, result: usize, r8: usize, r9: usize) -> usize {
|
unsafe extern "system" fn final_completion_wrapper(ctx: usize, result: usize, r8: usize, r9: usize) -> usize {
|
||||||
let n = REPORTS.fetch_add(1, Ordering::Relaxed);
|
// Read the delivered status: byte0==0 => failure with an error string at +8.
|
||||||
if n < 64 {
|
|
||||||
let flag = rd_u8(result);
|
let flag = rd_u8(result);
|
||||||
let (kind, s) = match flag {
|
let errstr = if flag == Some(0) {
|
||||||
Some(0) => {
|
|
||||||
let p = if readable_range(result + 8, 8) {
|
let p = if readable_range(result + 8, 8) {
|
||||||
core::ptr::read_volatile((result + 8) as *const usize)
|
core::ptr::read_volatile((result + 8) as *const usize)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
("ERROR", rd_cstr(p, 96))
|
rd_cstr(p, 96)
|
||||||
}
|
} else {
|
||||||
Some(_) => ("SUCCESS", String::from("SUCCESS")),
|
String::new()
|
||||||
None => ("??", String::from("<result unreadable>")),
|
|
||||||
};
|
};
|
||||||
|
let n = REPORTS.fetch_add(1, Ordering::Relaxed);
|
||||||
|
if n < 64 {
|
||||||
let cbref = if readable_range(ctx + 0x18, 8) {
|
let cbref = if readable_range(ctx + 0x18, 8) {
|
||||||
core::ptr::read_volatile((ctx + 0x18) as *const usize)
|
core::ptr::read_volatile((ctx + 0x18) as *const usize)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
let kind = match flag {
|
||||||
|
Some(0) => "ERROR",
|
||||||
|
Some(_) => "SUCCESS",
|
||||||
|
None => "??",
|
||||||
|
};
|
||||||
|
let shown = if flag == Some(0) { errstr.as_str() } else { "SUCCESS" };
|
||||||
write_log(&format!(
|
write_log(&format!(
|
||||||
"SEASONS_LOAD_CALLBACK: final kind={kind} result={s:?} flag={flag:?} ctx={ctx:#x} cbref={cbref:#x}\n"
|
"SEASONS_LOAD_CALLBACK: final kind={kind} result={shown:?} flag={flag:?} ctx={ctx:#x} cbref={cbref:#x}\n"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
// Guarded one-shot bypass (staging diagnostic only): rewrite the pack-names
|
||||||
|
// failure to SUCCESS so the offline-season load advances to
|
||||||
|
// LoadCurrentOfflineSeason. Fires only for the exact CACHE_PACKNAMES failure,
|
||||||
|
// once per process; verified by the error string before touching memory.
|
||||||
|
if flag == Some(0)
|
||||||
|
&& errstr.contains("CACHE_PACKNAMES")
|
||||||
|
&& readable_range(result, 1)
|
||||||
|
&& !BYPASS_DONE.swap(true, Ordering::AcqRel)
|
||||||
|
{
|
||||||
|
core::ptr::write_volatile(result as *mut u8, 1u8); // take the SUCCESS branch
|
||||||
|
write_log("SEASONS_BYPASS: forced CACHE_PACKNAMES_FAILED -> SUCCESS (one-shot, staging)\n");
|
||||||
|
}
|
||||||
let t = FINAL_COMPLETION_TRAMP.load(Ordering::Acquire);
|
let t = FINAL_COMPLETION_TRAMP.load(Ordering::Acquire);
|
||||||
if t == 0 {
|
if t == 0 {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -375,6 +394,25 @@ unsafe extern "system" fn stage1_completion_wrapper(param1: usize, result: usize
|
|||||||
original(param1, result, r8, r9)
|
original(param1, result, r8, r9)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WEBFILE_DL download start FUN_18017ff90(url, ctx): param_1 (rcx) is the C-string
|
||||||
|
// URL of the pack-names/cards-tournament-list web file. Passive capture. Its
|
||||||
|
// prologue has a rip-relative `MOV R8,[DAT_1802e6580]`, so it uses the relocating
|
||||||
|
// installer (disp32 at copied offset 7, instruction end 11).
|
||||||
|
static URL_CAPTURE_TRAMP: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
unsafe extern "system" fn url_capture_wrapper(rcx: usize, rdx: usize, r8: usize, r9: usize) -> usize {
|
||||||
|
let n = REPORTS.fetch_add(1, Ordering::Relaxed);
|
||||||
|
if n < 64 {
|
||||||
|
write_log(&format!("SEASONS_WEBFILE_URL: url={:?}\n", rd_cstr(rcx, 256)));
|
||||||
|
}
|
||||||
|
let t = URL_CAPTURE_TRAMP.load(Ordering::Acquire);
|
||||||
|
if t == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let original: unsafe extern "system" fn(usize, usize, usize, usize) -> usize =
|
||||||
|
core::mem::transmute(t);
|
||||||
|
original(rcx, rdx, r8, r9)
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn worker() {
|
unsafe fn worker() {
|
||||||
let mut base = 0usize;
|
let mut base = 0usize;
|
||||||
for _ in 0..600u32 {
|
for _ in 0..600u32 {
|
||||||
@@ -440,6 +478,12 @@ unsafe fn worker() {
|
|||||||
&[0x48, 0x8b, 0xc4, 0x55, 0x48, 0x8d, 0x68, 0xa1, 0x48, 0x81, 0xec, 0xc0, 0x00, 0x00, 0x00],
|
&[0x48, 0x8b, 0xc4, 0x55, 0x48, 0x8d, 0x68, 0xa1, 0x48, 0x81, 0xec, 0xc0, 0x00, 0x00, 0x00],
|
||||||
stage1_completion_wrapper as *const () as usize, &STAGE1_COMPLETION_TRAMP,
|
stage1_completion_wrapper as *const () as usize, &STAGE1_COMPLETION_TRAMP,
|
||||||
);
|
);
|
||||||
|
install_detour_reloc(
|
||||||
|
base, 0x17ff90, "start_webfile_dl_url", 14,
|
||||||
|
&[0x48, 0x83, 0xec, 0x38, 0x4c, 0x8b, 0x05, 0xe5, 0x65, 0x16, 0x00, 0x4c, 0x8b, 0xd1],
|
||||||
|
7, 11,
|
||||||
|
url_capture_wrapper as *const () as usize, &URL_CAPTURE_TRAMP,
|
||||||
|
);
|
||||||
write_log("SEASON_TRACE: all season-native traces armed\n");
|
write_log("SEASON_TRACE: all season-native traces armed\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user