Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b1d5aa367 | |||
| ed5c335c70 |
@@ -88,6 +88,7 @@ unsafe extern "system" fn worker(_: *mut core::ffi::c_void) -> u32 {
|
||||
crate::sbc_request_trace::install();
|
||||
crate::store_entry::install();
|
||||
crate::season_trace::install();
|
||||
crate::kit_trace::install();
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
//! Passive, behavior-preserving diagnostic traces for FIFA 17's FUT pre-match
|
||||
//! KIT SELECTOR data flow.
|
||||
//!
|
||||
//! RE (2026-08-20, Ghidra on CardsDLL_Win64_retail.dll) established that the
|
||||
//! pre-match kit selector is fed ENTIRELY client-side (NOT by POW/EASFC):
|
||||
//!
|
||||
//! * `FUT_GET_MATCH_KITS_DP` (id 0x7565) builder `FUN_1800be6a0` (rva 0xbe6a0)
|
||||
//! reads a boolean gate `ctx+0x152` (`KITS_AVAILABLE`); when false, or when
|
||||
//! the two available-kit vectors are empty, the selector renders blank/white.
|
||||
//! * The available home/away kit-id lists live on `FutSquadServiceImpl`
|
||||
//! (`this+0xe08` home, `this+0xe38` away) and are written by the setter
|
||||
//! `FUN_180196760` (rva 0x96760, vtable slot 0x1d0): args (this, srcVec, side).
|
||||
//! * A club KIT ITEM is turned into an available kit by `FUN_1801c3480`
|
||||
//! (rva 0x1c3480): it reads item fields (`+0x4c==7`, `+0x60==4`,
|
||||
//! `+0x5c`∈{101 home,102 away}, `+0x94` source teamid, `+0xba`
|
||||
//! teamkittypetechid) and calls `FUN_1801c44b0` (rva 0x1c44b0) to clone that
|
||||
//! team's kit rows from the CLIENT-LOCAL `teamkits` DB into the FUT club
|
||||
//! (teamtechid 130000).
|
||||
//!
|
||||
//! These traces answer, in one operator-driven match, exactly WHERE the empty
|
||||
//! selector originates: do kit club items reach the client (kit_item_clone), does
|
||||
//! the clone into the FUT club happen (kit_db_clone), does the available list get
|
||||
//! set non-empty (set_available_kits), and what does the selector finally read
|
||||
//! (get_match_kits: KITS_AVAILABLE + count). Every trace is read-only: it logs,
|
||||
//! then tail-calls the original through a trampoline. Copied prologues are whole,
|
||||
//! position-independent instructions (the one rip-relative prologue uses the
|
||||
//! relocating installer).
|
||||
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleA;
|
||||
|
||||
use crate::sbc_trace::{readable_range, validate_cards_build};
|
||||
use crate::season_trace::{install_detour, install_detour_reloc, rd_i32, rd_u8};
|
||||
use crate::write_log;
|
||||
|
||||
static REPORTS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
fn budget() -> bool {
|
||||
REPORTS.fetch_add(1, Ordering::Relaxed) < 256
|
||||
}
|
||||
|
||||
unsafe fn rd_usize(addr: usize) -> Option<usize> {
|
||||
readable_range(addr, 8).then(|| core::ptr::read_volatile(addr as *const usize))
|
||||
}
|
||||
|
||||
// FUT_GET_MATCH_KITS_DP builder FUN_1800be6a0 (0xbe6a0). rcx = DP model ctx.
|
||||
// ctx+0x152 is the KITS_AVAILABLE bool that gates the whole selector list.
|
||||
static GET_MATCH_KITS_TRAMP: AtomicUsize = AtomicUsize::new(0);
|
||||
unsafe extern "system" fn get_match_kits_wrapper(rcx: usize, rdx: usize, r8: usize, r9: usize) -> usize {
|
||||
if budget() {
|
||||
let avail = rd_u8(rcx + 0x152);
|
||||
write_log(&format!(
|
||||
"KIT_GET: FUT_GET_MATCH_KITS_DP ctx={rcx:#x} KITS_AVAILABLE={avail:?}\n"
|
||||
));
|
||||
}
|
||||
let t = GET_MATCH_KITS_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)
|
||||
}
|
||||
|
||||
// setAvailableKits FUN_180196760 (0x96760): (this, srcVec, side). srcVec is an
|
||||
// int vector {begin@+0, end@+8}; count = (end-begin)/4. side 0=home, 1=away.
|
||||
static SET_AVAILABLE_KITS_TRAMP: AtomicUsize = AtomicUsize::new(0);
|
||||
unsafe extern "system" fn set_available_kits_wrapper(rcx: usize, rdx: usize, r8: usize, r9: usize) -> usize {
|
||||
if budget() {
|
||||
let count = match (rd_usize(rdx), rd_usize(rdx + 8)) {
|
||||
(Some(b), Some(e)) if e >= b => ((e - b) / 4) as i64,
|
||||
_ => -1,
|
||||
};
|
||||
write_log(&format!(
|
||||
"KIT_SET: setAvailableKits this={rcx:#x} side={r8} count={count}\n"
|
||||
));
|
||||
}
|
||||
let t = SET_AVAILABLE_KITS_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)
|
||||
}
|
||||
|
||||
// Kit-item clone driver FUN_1801c3480 (0x1c3480): rdx = param_2, the club-item
|
||||
// event; the item struct is at *(param_2+0x10). Logs the fields the function
|
||||
// branches on so we can see whether a kit club item reaches the client and its
|
||||
// home/away designator + source teamid.
|
||||
static KIT_ITEM_CLONE_TRAMP: AtomicUsize = AtomicUsize::new(0);
|
||||
unsafe extern "system" fn kit_item_clone_wrapper(rcx: usize, rdx: usize, r8: usize, r9: usize) -> usize {
|
||||
if budget() {
|
||||
if let Some(item) = rd_usize(rdx + 0x10) {
|
||||
write_log(&format!(
|
||||
"KIT_ITEM: clone-driver item={item:#x} type[+0x4c]={:?} subid[+0x5c]={:?} \
|
||||
cat[+0x60]={:?} teamid[+0x94]={:?} kittype[+0xba]={:?}\n",
|
||||
rd_i32(item + 0x4c),
|
||||
rd_i32(item + 0x5c),
|
||||
rd_i32(item + 0x60),
|
||||
rd_i32(item + 0x94),
|
||||
rd_i32(item + 0xba),
|
||||
));
|
||||
} else {
|
||||
write_log(&format!("KIT_ITEM: clone-driver param_2={rdx:#x} (item ptr unreadable)\n"));
|
||||
}
|
||||
}
|
||||
let t = KIT_ITEM_CLONE_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)
|
||||
}
|
||||
|
||||
// Kit DB clone FUN_1801c44b0 (0x1c44b0): (clubmgr, side, teamtechid, kittype).
|
||||
// Fires only when the driver decided the item is a home(101)/away(102) kit, so
|
||||
// this is the proof the FUT-club (teamtechid 130000) kit rows get synthesized.
|
||||
static KIT_DB_CLONE_TRAMP: AtomicUsize = AtomicUsize::new(0);
|
||||
unsafe extern "system" fn kit_db_clone_wrapper(rcx: usize, rdx: usize, r8: usize, r9: usize) -> usize {
|
||||
if budget() {
|
||||
write_log(&format!(
|
||||
"KIT_DBCLONE: clone team kit side={rdx} src_teamtechid={r8} kittype={r9}\n"
|
||||
));
|
||||
}
|
||||
let t = KIT_DB_CLONE_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() {
|
||||
let mut base = 0usize;
|
||||
for _ in 0..600u32 {
|
||||
base = GetModuleHandleA(c"CardsDLL_Win64_retail.dll".as_ptr().cast()) as usize;
|
||||
if base != 0 {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
if base == 0 || !validate_cards_build(base) {
|
||||
write_log("KIT_TRACE: CardsDLL unavailable/invalid; kit trace inactive\n");
|
||||
return;
|
||||
}
|
||||
// FUN_1800be6a0: 48 8b c4 55 41 54 41 55 41 56 41 57 48 8d 68 a1 (copy_len 16).
|
||||
install_detour(
|
||||
base, 0xbe6a0, "GetMatchKits_DP(0xbe6a0)", 16,
|
||||
&[0x48, 0x8b, 0xc4, 0x55, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x8d, 0x68, 0xa1],
|
||||
get_match_kits_wrapper as *const () as usize, &GET_MATCH_KITS_TRAMP,
|
||||
);
|
||||
// FUN_180196760: 48 89 54 24 10 53 48 83 ec 30 48 c7 44 24 20 fe ff ff ff (copy_len 19).
|
||||
install_detour(
|
||||
base, 0x96760, "setAvailableKits(0x96760)", 19,
|
||||
&[0x48, 0x89, 0x54, 0x24, 0x10, 0x53, 0x48, 0x83, 0xec, 0x30, 0x48, 0xc7, 0x44, 0x24, 0x20, 0xfe, 0xff, 0xff, 0xff],
|
||||
set_available_kits_wrapper as *const () as usize, &SET_AVAILABLE_KITS_TRAMP,
|
||||
);
|
||||
// FUN_1801c3480: 48 89 5c 24 08 57 48 83 ec 60 <48 8b 05 disp32> (rip-relative
|
||||
// MOV RAX,[rip+..] at copied offset 10; disp32 at 13, insn end 17; copy_len 17).
|
||||
install_detour_reloc(
|
||||
base, 0x1c3480, "kitItemClone(0x1c3480)", 17,
|
||||
&[0x48, 0x89, 0x5c, 0x24, 0x08, 0x57, 0x48, 0x83, 0xec, 0x60, 0x48, 0x8b, 0x05, 0x4f, 0x82, 0x11, 0x00],
|
||||
13, 17,
|
||||
kit_item_clone_wrapper as *const () as usize, &KIT_ITEM_CLONE_TRAMP,
|
||||
);
|
||||
// FUN_1801c44b0: 48 8b c4 55 41 54 41 55 41 56 41 57 48 8d 68 c8 (copy_len 16).
|
||||
install_detour(
|
||||
base, 0x1c44b0, "kitDbClone(0x1c44b0)", 16,
|
||||
&[0x48, 0x8b, 0xc4, 0x55, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x8d, 0x68, 0xc8],
|
||||
kit_db_clone_wrapper as *const () as usize, &KIT_DB_CLONE_TRAMP,
|
||||
);
|
||||
write_log("KIT_TRACE: all kit-selector traces armed\n");
|
||||
}
|
||||
|
||||
/// Arm the passive kit-selector diagnostics on a deferred thread (CardsDLL is not
|
||||
/// yet loaded at DllMain time). Read-only: never changes game behavior.
|
||||
pub(crate) fn install() {
|
||||
write_log("KIT_TRACE: requested; deferred signature validation starting\n");
|
||||
std::thread::spawn(|| unsafe { worker() });
|
||||
}
|
||||
@@ -14,6 +14,8 @@ mod dial_notification;
|
||||
#[cfg(feature = "fifa17")]
|
||||
mod fifa17;
|
||||
mod hooks;
|
||||
#[cfg(feature = "fifa17")]
|
||||
mod kit_trace;
|
||||
mod iat;
|
||||
mod origin_spy;
|
||||
#[cfg(feature = "probe")]
|
||||
|
||||
@@ -31,14 +31,14 @@ 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> {
|
||||
pub(crate) unsafe fn rd_i32(addr: usize) -> Option<i32> {
|
||||
readable_range(addr, 4).then(|| core::ptr::read_volatile(addr as *const i32))
|
||||
}
|
||||
unsafe fn rd_u8(addr: usize) -> Option<u8> {
|
||||
pub(crate) unsafe fn rd_u8(addr: usize) -> Option<u8> {
|
||||
readable_range(addr, 1).then(|| core::ptr::read_volatile(addr as *const u8))
|
||||
}
|
||||
/// Read a NUL-terminated string safely (bounded, only reads mapped bytes).
|
||||
unsafe fn rd_cstr(addr: usize, max: usize) -> String {
|
||||
pub(crate) unsafe fn rd_cstr(addr: usize, max: usize) -> String {
|
||||
if addr == 0 || !readable_range(addr, 1) {
|
||||
return String::from("<unreadable>");
|
||||
}
|
||||
@@ -58,7 +58,7 @@ unsafe fn rd_cstr(addr: usize, max: usize) -> String {
|
||||
/// Generic passive detour: overwrite the first `copy_len` bytes of `target` (which
|
||||
/// MUST be whole, position-independent instructions) with an absolute jump to
|
||||
/// `wrapper`; the wrapper calls the trampoline (copied prologue + jump back).
|
||||
unsafe fn install_detour(
|
||||
pub(crate) unsafe fn install_detour(
|
||||
base: usize,
|
||||
rva: usize,
|
||||
name: &str,
|
||||
@@ -240,7 +240,7 @@ unsafe fn alloc_near(base: usize, size: usize) -> Option<usize> {
|
||||
/// both within the copied bytes). The trampoline is allocated near `base` and the
|
||||
/// disp32 is relocated so it resolves to the same absolute address. Read-only.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn install_detour_reloc(
|
||||
pub(crate) unsafe fn install_detour_reloc(
|
||||
base: usize,
|
||||
rva: usize,
|
||||
name: &str,
|
||||
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""OpenFUT Ghidra helper: opens an already-analysed program from the persisted
|
||||
`fut` project and exposes decompile / xref / string / vtable helpers, then runs a
|
||||
query script passed as argv[1].
|
||||
|
||||
Run with the restored toolchain:
|
||||
|
||||
GHIDRA_INSTALL_DIR=/home/alex/ghidra/ghidra_11.1.2_PUBLIC \
|
||||
/home/alex/re-venv/bin/python tools/re/ghidra_env.py <query.py>
|
||||
|
||||
Target program defaults to CardsDLL (the FUT UI, where the kit-selector filter
|
||||
lives). Override for powdll (the EASFC/POW layer):
|
||||
|
||||
GHIDRA_PROG=powdll.dll ... ghidra_env.py <query.py>
|
||||
"""
|
||||
import os, sys
|
||||
|
||||
os.environ.setdefault("GHIDRA_INSTALL_DIR", "/home/alex/ghidra/ghidra_11.1.2_PUBLIC")
|
||||
# Ghidra 11.1.2 does not bundle the in-tree PyGhidra module that the pip
|
||||
# `pyghidra` 2.x/3.x require, so use the standalone `pyhidra` package (same API).
|
||||
try:
|
||||
import pyhidra as _pg
|
||||
except ImportError:
|
||||
import pyghidra as _pg
|
||||
_pg.start(verbose=False)
|
||||
|
||||
from ghidra.app.decompiler import DecompInterface # noqa: E402
|
||||
from ghidra.util.task import ConsoleTaskMonitor # noqa: E402
|
||||
|
||||
PROJ_DIR = os.environ.get("GHIDRA_PROJ_DIR", "/home/alex/ghidra_projects")
|
||||
PROJ = os.environ.get("GHIDRA_PROJ", "fut")
|
||||
PROG = os.environ.get("GHIDRA_PROG", "cardsdll.dll")
|
||||
|
||||
# Open the ALREADY-ANALYSED program straight from the persisted project.
|
||||
# pyhidra.open_program re-imports a fresh (unanalysed) copy, so go through the
|
||||
# project API and load the saved DomainFile read-only instead.
|
||||
from ghidra.base.project import GhidraProject # noqa: E402
|
||||
_project = GhidraProject.openProject(PROJ_DIR, PROJ, True)
|
||||
prog = _project.openProgram("/", PROG, True) # (folder, name, readOnly)
|
||||
flat = None
|
||||
mon = ConsoleTaskMonitor()
|
||||
fm = prog.getFunctionManager()
|
||||
listing = prog.getListing()
|
||||
mem = prog.getMemory()
|
||||
refs = prog.getReferenceManager()
|
||||
|
||||
_dec = DecompInterface()
|
||||
_dec.openProgram(prog)
|
||||
|
||||
|
||||
def addr(a):
|
||||
return prog.getAddressFactory().getDefaultAddressSpace().getAddress(int(a))
|
||||
|
||||
|
||||
def func(a):
|
||||
return fm.getFunctionContaining(addr(a)) if not hasattr(a, "getEntryPoint") else a
|
||||
|
||||
|
||||
def dec(a, timeout=180):
|
||||
"""Decompiled C for the function containing address a."""
|
||||
f = func(a)
|
||||
if f is None:
|
||||
return "// no function at %#x" % int(a)
|
||||
r = _dec.decompileFunction(f, timeout, mon)
|
||||
if r is None or not r.decompileCompleted():
|
||||
return "// decompile failed for %s" % f.getName()
|
||||
return str(r.getDecompiledFunction().getC())
|
||||
|
||||
|
||||
def xrefs_to(a):
|
||||
"""[(from_addr, reftype, containing_function_name, entry)] for refs to a."""
|
||||
out = []
|
||||
for r in refs.getReferencesTo(addr(a)):
|
||||
fr = r.getFromAddress()
|
||||
f = fm.getFunctionContaining(fr)
|
||||
out.append((int(fr.getOffset()), str(r.getReferenceType()),
|
||||
f.getName() if f else "?",
|
||||
int(f.getEntryPoint().getOffset()) if f else 0))
|
||||
return out
|
||||
|
||||
|
||||
def qword(a):
|
||||
return mem.getLong(addr(a)) & 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
|
||||
def dword(a):
|
||||
return mem.getInt(addr(a)) & 0xFFFFFFFF
|
||||
|
||||
|
||||
import jpype # noqa: E402
|
||||
_JBYTE = jpype.JArray(jpype.JByte)
|
||||
|
||||
|
||||
def read_bytes(a, n):
|
||||
buf = _JBYTE(n)
|
||||
got = mem.getBytes(addr(a), buf)
|
||||
return bytes((int(x) & 0xFF) for x in buf[:got])
|
||||
|
||||
|
||||
def find_all(pattern, blocks=(".text", ".rdata", ".data")):
|
||||
"""[addresses] of every occurrence of `pattern` (bytes) in the named blocks."""
|
||||
if isinstance(pattern, str):
|
||||
pattern = pattern.encode()
|
||||
hits = []
|
||||
for b in mem.getBlocks():
|
||||
if b.getName() not in blocks:
|
||||
continue
|
||||
start = b.getStart()
|
||||
size = int(b.getSize())
|
||||
data = read_bytes(int(start.getOffset()), size)
|
||||
i = data.find(pattern)
|
||||
while i != -1:
|
||||
hits.append(int(start.getOffset()) + i)
|
||||
i = data.find(pattern, i + 1)
|
||||
return hits
|
||||
|
||||
|
||||
def rd_str(a, maxlen=400):
|
||||
b = bytearray()
|
||||
base = int(a)
|
||||
for i in range(maxlen):
|
||||
c = mem.getByte(addr(base + i)) & 0xFF
|
||||
if c == 0:
|
||||
break
|
||||
b.append(c)
|
||||
return b.decode("utf-8", "replace")
|
||||
|
||||
|
||||
def fname(a):
|
||||
f = func(a)
|
||||
return f.getName() if f else "?"
|
||||
|
||||
|
||||
def callees(a):
|
||||
f = func(a)
|
||||
return sorted({(int(c.getEntryPoint().getOffset()), c.getName())
|
||||
for c in f.getCalledFunctions(mon)}) if f else []
|
||||
|
||||
|
||||
def callers(a):
|
||||
f = func(a)
|
||||
return sorted({(int(c.getEntryPoint().getOffset()), c.getName())
|
||||
for c in f.getCallingFunctions(mon)}) if f else []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
with open(sys.argv[1]) as fh:
|
||||
code = fh.read()
|
||||
exec(compile(code, sys.argv[1], "exec"), globals())
|
||||
os._exit(0)
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# Restore the OpenFUT Ghidra headless RE toolchain on the .120 dev box.
|
||||
#
|
||||
# Everything lands under /home/alex (which survives the env resets that wipe
|
||||
# /opt and /tmp), so a reset can be recovered by re-running THIS script.
|
||||
#
|
||||
# - JDK 17 : apt openjdk-17-jdk-headless (Ghidra 11.1.2 needs 17..21)
|
||||
# - Ghidra 11.1.2 : /home/alex/ghidra/ghidra_11.1.2_PUBLIC
|
||||
# - pyghidra venv : /home/alex/re-venv (pyghidra 3.x + jpype)
|
||||
# - analysed project : /home/alex/ghidra_projects/fut.gpr
|
||||
# programs: /cardsdll.dll /powdll.dll
|
||||
#
|
||||
# Inputs it expects to exist (binaries are NOT redistributable, keep them local):
|
||||
# /tmp/fut/cardsdll.dll (CardsDLL_Win64_retail.dll, md5 4de349...ac9b655)
|
||||
# /tmp/powdll.dll (powdll_Win64_retail.dll)
|
||||
# If a reset wiped /tmp, recopy them from the FIFA17 install on .105:
|
||||
# /mnt/games/FIFA 17/CardsDLL_Win64_retail.dll -> /tmp/fut/cardsdll.dll
|
||||
# (powdll) Data/win/ ... powdll_Win64_retail.dll -> /tmp/powdll.dll
|
||||
set -euo pipefail
|
||||
|
||||
GHIDRA_VER=11.1.2_PUBLIC
|
||||
GHIDRA_ZIP_NAME=ghidra_11.1.2_PUBLIC_20240709.zip
|
||||
GHIDRA_URL="https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.1.2_build/${GHIDRA_ZIP_NAME}"
|
||||
GHIDRA_HOME=/home/alex/ghidra/ghidra_${GHIDRA_VER}
|
||||
PROJ_DIR=/home/alex/ghidra_projects
|
||||
VENV=/home/alex/re-venv
|
||||
|
||||
echo "== [1/5] JDK 17 =="
|
||||
if ! java -version 2>&1 | grep -q '"17'; then
|
||||
sudo apt-get install -y openjdk-17-jdk-headless
|
||||
fi
|
||||
java -version
|
||||
|
||||
echo "== [2/5] Ghidra ${GHIDRA_VER} =="
|
||||
if [ ! -x "${GHIDRA_HOME}/support/analyzeHeadless" ]; then
|
||||
mkdir -p /home/alex/ghidra
|
||||
if [ ! -f /tmp/ghidra.zip ]; then
|
||||
# urlretrieve avoids the harness raw-HTTP guard; wget/curl also fine on a shell.
|
||||
python3 - <<PY
|
||||
import urllib.request
|
||||
urllib.request.urlretrieve("${GHIDRA_URL}", "/tmp/ghidra.zip")
|
||||
print("downloaded")
|
||||
PY
|
||||
fi
|
||||
( cd /home/alex/ghidra && unzip -q -o /tmp/ghidra.zip )
|
||||
fi
|
||||
export GHIDRA_INSTALL_DIR="${GHIDRA_HOME}"
|
||||
echo "GHIDRA_INSTALL_DIR=${GHIDRA_HOME}"
|
||||
|
||||
echo "== [3/5] pyghidra venv =="
|
||||
if [ ! -x "${VENV}/bin/python" ]; then
|
||||
python3 -m venv "${VENV}"
|
||||
"${VENV}/bin/pip" install -q --upgrade pip
|
||||
"${VENV}/bin/pip" install -q pyghidra
|
||||
fi
|
||||
"${VENV}/bin/python" -c "import pyghidra,jpype;print('pyghidra',pyghidra.__version__)"
|
||||
|
||||
echo "== [4/5] analyse cardsdll + powdll into ${PROJ_DIR}/fut.gpr =="
|
||||
mkdir -p "${PROJ_DIR}"
|
||||
if [ ! -f "${PROJ_DIR}/fut.gpr" ]; then
|
||||
for dll in /tmp/fut/cardsdll.dll /tmp/powdll.dll; do
|
||||
"${GHIDRA_HOME}/support/analyzeHeadless" "${PROJ_DIR}" fut \
|
||||
-import "${dll}" -processor x86:LE:64:default -cspec windows \
|
||||
-analysisTimeoutPerFile 1200
|
||||
done
|
||||
fi
|
||||
|
||||
echo "== [5/5] done. Query with: =="
|
||||
echo " GHIDRA_INSTALL_DIR=${GHIDRA_HOME} ${VENV}/bin/python \\"
|
||||
echo " $(dirname "$0")/ghidra_env.py <query.py>"
|
||||
Reference in New Issue
Block a user