b0bbc2a07f
The oracle is three-valued, and all three fingerprints are now confirmed live
against a running client rather than read out of Ghidra:
NAMED our sentinel rating 7 survives and a real name appears. The id is
real, and teamid/nation/leagueId come back FILLED by the game
because we send them as zero.
placeholder rating 7 survives but the name is 'Jamal Blackman', team 0. The
players-table row exists and is an empty slot. This is the trap:
169193 does this and it was in VERIFIED_ASSET_IDS.
MISS rating 0x32, teamid 0x78d, nation 0xe, position 2, name ' '. That
is the binary's miss-fill, byte for byte, and it is exactly the
blank card photographed in a pack today.
Scale: 5000 candidates per response ingests cleanly; 20000 was served and then
silently not ingested (the map did not change at all), so the ceiling is between
them and auto chunks default to 5000.
Auto-advance: the client PAGES the club, so one visit yields several fetches.
'auto:lo-hi:step' hands out the next chunk per fetch. Item ids derive from the
candidate's offset in the WHOLE range, not its index in the chunk, so chunks never
collide and results accumulate across fetches for a single probe at the end.
sweep_collect.py accumulates into data/players.json and rejects the placeholder
name as a matter of course. Yield in 20000-24999 was 19 real ids per 5000, which
is why auto-advance matters: the real roster clusters in 150000-240000.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
74 lines
3.2 KiB
C
74 lines
3.2 KiB
C
/* dbdata_probe.c -- call FIFA 17's dbdata.dll!getTableData and dump what it returns.
|
|
*
|
|
* FINDING (2026-08-04): dbdata.dll is NOT a player database. Its single export
|
|
* `getTableData` is an ANTI-TAMPER ATTESTATION function whose name is a decoy.
|
|
* Signature resolved empirically (crash-matrix over the 4 Win64 register args):
|
|
*
|
|
* const char * __cdecl getTableData(int *pOutBase64Len);
|
|
*
|
|
* It returns a heap-allocated, NUL-terminated base64url string:
|
|
* dbdata.dll -> 1012 chars (759 bytes decoded) md5 59b46dce231e419f4c1effbd8024e5ae
|
|
* dbdataEA.dll -> 1004 chars (753 bytes decoded) md5 8e10d1c4ce5ca54974aad42a10f05de0
|
|
* Deterministic across calls and across processes. There is no table selector
|
|
* argument; args 2..4 are ignored.
|
|
*
|
|
* What it actually does (Wine +relay trace):
|
|
* CommandLineToArgvW(GetCommandLineW())
|
|
* for each argv: StrStrW(argv[i], L"/antitamperdiagnosis")
|
|
* GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, &getTableData)
|
|
* GetModuleFileNameA -> CreateFileW(own .dll, GENERIC_READ)
|
|
* ReadFile(0x28f000) <- whole file into a heap buffer
|
|
* VirtualAlloc(1MB, PAGE_EXECUTE_READWRITE) <- unpack scratch, freed before return
|
|
* SetFilePointerEx(0x28e200); ReadFile(0x200) <- Authenticode Security Directory
|
|
* -> 759-byte attestation blob, base64url encoded
|
|
*
|
|
* Flip one byte inside the .xdata payload and it raises 0xC0000096
|
|
* (PRIVILEGED_INSTRUCTION) at image+0x3C0DF instead of returning.
|
|
*
|
|
* Build + run:
|
|
* x86_64-w64-mingw32-gcc -O0 -o dbdata_probe.exe dbdata_probe.c
|
|
* cp "/mnt/games/FIFA 17/dbdata.dll" . # must sit next to the exe
|
|
* WINEDEBUG=-all wine dbdata_probe.exe dbdata.dll 1 out
|
|
* Add WINEDEBUG=+relay to re-derive the call sequence above.
|
|
*/
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
|
|
typedef void *(*F4)(uint64_t, uint64_t, uint64_t, uint64_t);
|
|
|
|
static LONG CALLBACK veh(EXCEPTION_POINTERS *ep) {
|
|
fprintf(stderr, "!! EXCEPTION 0x%08lx rip=%p addr=%p\n",
|
|
(unsigned long)ep->ExceptionRecord->ExceptionCode,
|
|
(void *)ep->ContextRecord->Rip, ep->ExceptionRecord->ExceptionAddress);
|
|
fflush(stderr);
|
|
ExitProcess(9);
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
AddVectoredExceptionHandler(1, veh);
|
|
const char *dllname = (argc > 1) ? argv[1] : "dbdata.dll";
|
|
int iters = (argc > 2) ? atoi(argv[2]) : 1;
|
|
const char *pfx = (argc > 3) ? argv[3] : "gtd";
|
|
HMODULE h = LoadLibraryA(dllname);
|
|
if (!h) { printf("LoadLibraryA(%s) failed err=%lu\n", dllname, GetLastError()); return 1; }
|
|
F4 f = (F4)GetProcAddress(h, "getTableData");
|
|
printf("base=%p getTableData=%p (rva 0x%llx)\n", (void *)h, (void *)f,
|
|
(unsigned long long)((uintptr_t)f - (uintptr_t)h));
|
|
if (!f) return 1;
|
|
for (int i = 0; i < iters; i++) {
|
|
volatile int len = -1;
|
|
void *r = f((uint64_t)(uintptr_t)&len, 0, 0, 0);
|
|
printf("call %d: ret=%p len=%d\n", i, r, len);
|
|
if (!r || len <= 0) continue;
|
|
char fn[256]; sprintf(fn, "%s_%d.bin", pfx, i);
|
|
FILE *fp = fopen(fn, "wb");
|
|
fwrite(r, 1, (size_t)len, fp);
|
|
fclose(fp);
|
|
printf(" wrote %s (%d bytes of base64url)\n", fn, len);
|
|
}
|
|
return 0;
|
|
}
|