There is a single function in RE engine that is called with the file extension (UTF16 string), and version (int):
-
Dump game with x64dbg+scylla, open with IDA Pro/Ghidra/Binja/Radare/whatever and let the auto analysis finish.
-
Search for a common file type string (e.g.
motlist
) as UTF16: -
Xref the string (it should only have one reference)
We use emulation here to avoid manually parsing all of the different ways the data is loaded into the registers before the call.
It works by finding all the code xrefs to the registration function, walking back a few instructions from the call
(to handle different ordering by the compiler), emulating, then reading the resulting register values.
The following script is IDA-specific, but you can easily change the Disassembler
class to work with Ghidrathon, r2pipe, binja api, etc, for your specific disassembler.
from typing import List
from unicorn import *
from unicorn.x86_const import *
class Disassembler:
import idc
import ida_bytes
import ida_xref
@staticmethod
def get_code_xrefs(address: int) -> List[int]:
xrefs = []
xref = Disassembler.ida_xref.get_first_cref_to(address)
while xref != Disassembler.idc.BADADDR:
xrefs.append(xref)
xref = Disassembler.ida_xref.get_next_cref_to(address, xref)
return xrefs
@staticmethod
def prev_head(address: int) -> int:
return Disassembler.idc.prev_head(address)
@staticmethod
def get_bytes(address: int, size: int) -> bytes:
return Disassembler.ida_bytes.get_bytes(address, size)
def hook_mem_invalid(uc, access, address, size, value, user_data):
PAGE_SIZE=4096
try:
base = address & ~(PAGE_SIZE - 1)
uc.mem_map(base, PAGE_SIZE)
# Try to read unmapped code from disassembler, otherwise zero
data = Disassembler.get_bytes(base, PAGE_SIZE) or b'\x00' * PAGE_SIZE
uc.mem_write(base, data)
return True
except Exception as e:
print(f'Failed to map {base:X}: {e}')
return False
if __name__ == '__main__':
register_file_extension_version_ea = 0x140208C90
emu = Uc(UC_ARCH_X86, UC_MODE_64)
emu.hook_add(UC_HOOK_MEM_UNMAPPED, hook_mem_invalid)
for xref in Disassembler.get_code_xrefs(register_file_extension_version_ea):
# Start a few instructions prior to the call
end_ea = xref
start_ea = end_ea
for i in range(5):
start_ea = Disassembler.prev_head(start_ea)
try:
emu.emu_start(start_ea, end_ea)
# read the call arguments from the registers
file_version = emu.reg_read(UC_X86_REG_RDX)
ext_string_ptr = emu.reg_read(UC_X86_REG_RCX)
# read the string data (statically, from the disassembler)
ext_string = Disassembler.get_bytes(ext_string_ptr, 32).decode('utf16').split('\0')[0]
print(f'{ext_string}.{file_version}')
except UcError as e:
print('Error during emulation:', e)