Code:
# -*- coding: utf-8 -*-
import idaapi
import ida_ua
import idautils
import idc
# ---------------------------------------------------------------------------
# CONFIG
# ---------------------------------------------------------------------------
TARGET_OFFSETS = {
0x0, 0x8, 0x10, 0x20, 0x30, 0x40,
0x44, 0x48, 0x4A, 0x4C, 0x4E, 0x50,
0x58, 0x60, 0x4F0, 0x4F8, 0x500
}
MATCH_THRESHOLD = 0.80 # e.g. 80%
LOOKBACK_BYTES = 0x80 # scan 128 bytes before each call
# In Windows x64 calling convention, RCX is typically the 1st function arg
def find_rcx_index():
for idx in range(256):
reg_name = idaapi.get_reg_name(idx, 8)
if reg_name and reg_name.lower() == "rcx":
return idx
return None
RCX_IDX = find_rcx_index()
# ---------------------------------------------------------------------------
# FUNCTION BOUNDARIES
# ---------------------------------------------------------------------------
def get_func_start(ea):
f = idaapi.get_func(ea)
return f.start_ea if f else idc.BADADDR
def get_func_end(ea):
f = idaapi.get_func(ea)
return f.end_ea if f else idc.BADADDR
# ---------------------------------------------------------------------------
# 1) Identify the "init function" by offset pattern
# ---------------------------------------------------------------------------
def collect_base_regs(func_ea):
"""
Check the first ~20 instructions of func, looking for 'mov <reg>, rcx'.
We'll treat <reg> as a potential base reg as well. Always include RCX if found.
"""
possible_bases = set()
if RCX_IDX is not None:
possible_bases.add(RCX_IDX)
count = 0
for insn_ea in idautils.FuncItems(func_ea):
if count > 20:
break
count += 1
mnem = idc.print_insn_mnem(insn_ea).lower()
if mnem != "mov":
continue
insn = ida_ua.insn_t()
if ida_ua.decode_insn(insn, insn_ea) == 0:
continue
dst, src = insn.ops[0], insn.ops[1]
# e.g. mov rdi, rcx
if dst.type == ida_ua.o_reg and src.type == ida_ua.o_reg:
if src.reg == RCX_IDX:
possible_bases.add(dst.reg)
return possible_bases
def gather_write_offsets(func_ea):
"""
For each instruction in func, if it is a memory write of form [reg+disp],
and reg is in the "base regs," collect disp. Return set of offsets found.
"""
offsets = set()
base_regs = collect_base_regs(func_ea)
if not base_regs:
return offsets
for insn_ea in idautils.FuncItems(func_ea):
mnem = idc.print_insn_mnem(insn_ea).lower()
# Typical memory-write instructions
if not (mnem.startswith("mov") or mnem in ("movaps","movups","vmovaps","vmovups","movdqa")):
continue
insn = ida_ua.insn_t()
if ida_ua.decode_insn(insn, insn_ea) == 0:
continue
dst = insn.ops[0]
if dst.type == ida_ua.o_displ and dst.reg in base_regs:
offsets.add(dst.addr)
return offsets
def function_matches_init_pattern(func_ea):
"""
If a function writes to >= MATCH_THRESHOLD * len(TARGET_OFFSETS),
consider it a match (the "init function").
"""
found_offsets = gather_write_offsets(func_ea)
if not found_offsets:
return False
# How many of our known offsets does this function write?
match_count = len(TARGET_OFFSETS & found_offsets)
ratio = float(match_count) / float(len(TARGET_OFFSETS))
return ratio >= MATCH_THRESHOLD
def find_init_functions():
"""
Scan all functions in the database. Return any that match the offset pattern.
"""
matches = []
for fva in idautils.Functions():
if function_matches_init_pattern(fva):
matches.append(fva)
return matches
# ---------------------------------------------------------------------------
# 2) Caller Analysis: find pointers loaded into RCX
# ---------------------------------------------------------------------------
def reg_name(reg_idx):
"""Utility to get a register name from index."""
return idaapi.get_reg_name(reg_idx, 8) or ""
def read_qword(addr):
"""Try to read a 64-bit value at 'addr'. Return None if invalid/out of range."""
import ida_bytes
try:
return ida_bytes.get_qword(addr)
except:
return None
def parse_rcx_load(ea):
"""
If the instruction at 'ea' sets RCX, parse the pointer from:
- mov rcx, <imm>
- mov rcx, ds:AbsoluteAddr
- mov rcx, [rip + disp] => read pointer from that memory location
- lea rcx, [rip + disp] => address = next_ea + disp
Return the pointer or None if unrecognized.
"""
mnem = idc.print_insn_mnem(ea).lower()
if mnem not in ("mov","lea"):
return None
insn = ida_ua.insn_t()
if ida_ua.decode_insn(insn, ea) == 0:
return None
dst, src = insn.ops[0], insn.ops[1]
if dst.type != ida_ua.o_reg or dst.reg != RCX_IDX:
return None
# 1) mov rcx, <imm>
if src.type == ida_ua.o_imm:
val = src.value
return val if val > 0x10000 else None
# 2) mov rcx, ds:AbsoluteAddr
if src.type == ida_ua.o_mem:
return src.addr # e.g. 0x143874C10
# 3) mov/lea rcx, [rip + disp]
if src.type == ida_ua.o_displ and reg_name(src.reg).lower() == "rip":
next_ea = ea + insn.size
target = next_ea + src.addr
if mnem == "lea":
# lea rcx, [rip + disp] => rcx = (next_ea + disp)
return target if target > 0x10000 else None
else:
# mov rcx, [rip + disp] => we read a pointer from 'target'
ptr_val = read_qword(target)
if ptr_val and ptr_val > 0x10000:
return ptr_val
return None
return None
def find_pointers_in_callers(func_ea, lookback=LOOKBACK_BYTES):
"""
For each code xref to 'func_ea', look up to 'lookback' bytes before the call
for an instruction that sets RCX. Return all discovered pointers.
"""
pointers = []
if RCX_IDX is None:
return pointers
for xref in idautils.CodeRefsTo(func_ea, 0):
call_ea = xref
fn_start = get_func_start(call_ea)
scan_start = max(call_ea - lookback, fn_start)
ea = scan_start
while ea < call_ea:
ptr_val = parse_rcx_load(ea)
if ptr_val and ptr_val > 0x10000:
pointers.append(ptr_val)
ea = idc.next_head(ea)
return list(set(pointers))
# ---------------------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------------------
def main():
if RCX_IDX is None:
print("[!] Could not locate RCX register index. Aborting.")
return
print("[*] Searching for init function(s) that write known structure offsets...")
init_funcs = find_init_functions()
if not init_funcs:
print("[-] No matching functions found. Maybe adjust the threshold or offsets.")
return
print("[+] Found %d candidate init function(s):" % len(init_funcs))
for f in init_funcs:
print(" 0x%X" % f)
all_pointers = []
for f in init_funcs:
# For each init function, gather all pointers passed in RCX by its callers
ptrs = find_pointers_in_callers(f, lookback=LOOKBACK_BYTES)
all_pointers.extend(ptrs)
# Deduplicate
all_pointers = list(set(all_pointers))
if not all_pointers:
print("[-] No pointers found among the callers. The code may load RCX differently.")
return
print("[*] Potential addresses discovered (possibly the Player GUID or base):")
for p in all_pointers:
print(" 0x%X" % p)
print("[*] Done.")
if __name__ == "__main__":
main()