Compare commits
4 commits
a0b08b7544
...
1804541acd
Author | SHA1 | Date | |
---|---|---|---|
1804541acd | |||
005668103a | |||
5f55c75a4b | |||
03f04ae062 |
4 changed files with 166 additions and 192 deletions
10
NOTES.md
10
NOTES.md
|
@ -55,6 +55,7 @@
|
|||
- World_Init @ 0x479b40
|
||||
- World_DeInit @ 0x402510
|
||||
- Make_World @ 0x479870
|
||||
- RenderFrame(?) @ 0x602a70
|
||||
|
||||
# Data Structures
|
||||
|
||||
|
@ -110,15 +111,6 @@ Data format:
|
|||
| 0x14 | `void*` | pointer to self (why?) |
|
||||
| 0x28 | `float[3]` | Position in Game World |
|
||||
|
||||
## Game Window Object (?) Pointer @ `0x7fa380`
|
||||
|
||||
| Offset | Type | Description |
|
||||
| ------ | ------------- | -------------------- |
|
||||
| 0x0000 | `void**` | Virtual Method Table |
|
||||
| 0x0004 | `const char*` | Some Model Name (?) |
|
||||
| 0x0008 | `void*` | Pointer to something |
|
||||
| 0x000C | `void*` | Ditto |
|
||||
|
||||
|
||||
# File Formats
|
||||
|
||||
|
|
|
@ -15,10 +15,8 @@ using namespace std;
|
|||
#include "Structures.h"
|
||||
#include "Py_Utils.h"
|
||||
#include "Hook.h"
|
||||
#include "VMT_Hook.h"
|
||||
#include "D3D8_Hook.h"
|
||||
#include "REPL.h"
|
||||
bool do_sleep=true;
|
||||
HMODULE hD3D8Dll = 0;
|
||||
|
||||
bool initialized = false;
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
|
||||
#pragma once
|
||||
class VMT_Hook
|
||||
{
|
||||
private:
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
void *orig;
|
||||
void *detour;
|
||||
DWORD *vtable;
|
||||
size_t ord;
|
||||
bool enabled;
|
||||
static map<uintptr_t, shared_ptr<VMT_Hook>> hooks;
|
||||
static DWORD *GetVTable(void *addr)
|
||||
{
|
||||
return (DWORD *)*(DWORD *)addr;
|
||||
};
|
||||
|
||||
public:
|
||||
VMT_Hook(void *obj, size_t ord, void *detour)
|
||||
{
|
||||
this->vtable = GetVTable(obj);
|
||||
this->detour = detour;
|
||||
this->orig = reinterpret_cast<void *>(vtable[ord]);
|
||||
this->ord = ord;
|
||||
this->enabled = false;
|
||||
|
||||
VirtualQuery(&this->vtable[this->ord], &mbi, sizeof(mbi));
|
||||
cout << "Hooking: " << this->vtable << "[" << this->ord << "]: (" << this->orig << " -> " << this->detour << ")" << endl;
|
||||
}
|
||||
|
||||
~VMT_Hook()
|
||||
{
|
||||
cout << "Unhooking: " << this->vtable << "[" << this->ord << "]: (" << this->orig << " -> " << this->detour << ")" << endl;
|
||||
this->disable();
|
||||
}
|
||||
|
||||
static void create(void *obj, size_t ord, void *detour)
|
||||
{
|
||||
uintptr_t key = reinterpret_cast<uintptr_t>(detour);
|
||||
hooks[key] = make_shared<VMT_Hook>(obj, ord, detour);
|
||||
hooks[key]->enable();
|
||||
}
|
||||
|
||||
static shared_ptr<VMT_Hook> get(void *func)
|
||||
{
|
||||
uintptr_t addr = reinterpret_cast<uintptr_t>(func);
|
||||
return VMT_Hook::get(addr);
|
||||
}
|
||||
|
||||
static shared_ptr<VMT_Hook> get(uintptr_t addr)
|
||||
{
|
||||
return hooks.at(addr);
|
||||
}
|
||||
|
||||
static size_t drop(void *func)
|
||||
{
|
||||
uintptr_t addr = reinterpret_cast<uintptr_t>(func);
|
||||
return VMT_Hook::drop(addr);
|
||||
}
|
||||
|
||||
static size_t drop(uintptr_t addr)
|
||||
{
|
||||
return hooks.erase(addr);
|
||||
}
|
||||
|
||||
static void clear()
|
||||
{
|
||||
return hooks.clear();
|
||||
}
|
||||
|
||||
void disable()
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
cout << "Disabling: " << this->vtable << "[" << this->ord << "]: (" << this->orig << " -> " << this->detour << ")" << endl;
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, NULL);
|
||||
this->vtable[ord] = reinterpret_cast<DWORD>(this->orig);
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
void enable()
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
cout << "Enabling: " << this->vtable << "[" << this->ord << "]: (" << this->orig << " -> " << this->detour << ")" << endl;
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, NULL);
|
||||
this->vtable[ord] = reinterpret_cast<DWORD>(this->detour);
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
|
||||
enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T func()
|
||||
{
|
||||
return reinterpret_cast<T>(this->orig);
|
||||
}
|
||||
};
|
||||
|
||||
map<uintptr_t, shared_ptr<VMT_Hook>> VMT_Hook::hooks;
|
245
r2_analyze.py
245
r2_analyze.py
|
@ -5,47 +5,98 @@ from tqdm import tqdm
|
|||
from pprint import pprint
|
||||
import os
|
||||
import sys
|
||||
r2cmds=[]
|
||||
scrap_exe=sys.argv[1]
|
||||
folder=os.path.join(os.path.dirname(scrap_exe))
|
||||
r2 = r2pipe.open(scrap_exe)
|
||||
|
||||
assert r2.cmdj("itj")['sha1'] == "d2dde960e8eca69d60c2e39a439088b75f0c89fa","Hash mismatch"
|
||||
assert r2.cmdj("itj")['md5'] == "a934c85dca5ab1c32f05c0977f62e186","Hash mismatch"
|
||||
r2cmds = []
|
||||
scrap_exe = sys.argv[1]
|
||||
folder = os.path.abspath(os.path.dirname(scrap_exe))
|
||||
|
||||
assert os.path.isfile(scrap_exe), "File not found!"
|
||||
r2 = r2pipe.open(scrap_exe)
|
||||
file_hashes = r2.cmdj("itj")
|
||||
target_hashes = {
|
||||
"sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa",
|
||||
"md5": "a934c85dca5ab1c32f05c0977f62e186",
|
||||
}
|
||||
|
||||
assert file_hashes == target_hashes, "Hash mismatch"
|
||||
|
||||
|
||||
def r2_cmd(cmd):
|
||||
global r2,r2cmds
|
||||
global r2, r2cmds
|
||||
r2cmds.append(cmd)
|
||||
return r2.cmd(cmd)
|
||||
|
||||
|
||||
def r2_cmdj(cmd):
|
||||
global r2,r2cmds
|
||||
global r2, r2cmds
|
||||
r2cmds.append(cmd)
|
||||
return r2.cmdj(cmd)
|
||||
|
||||
|
||||
def r2_cmdJ(cmd):
|
||||
global r2,r2cmds
|
||||
global r2, r2cmds
|
||||
r2cmds.append(cmd)
|
||||
return r2.cmdJ(cmd)
|
||||
|
||||
|
||||
print("[*] Running 'aaaa'")
|
||||
print("[*] Running 'aaaaa'")
|
||||
|
||||
r2_cmd("aaaa")
|
||||
r2_cmd("aaaaa")
|
||||
|
||||
#0x7fac20
|
||||
#0x7fac19
|
||||
#0x7faa4c
|
||||
#0x7fac1c # activate viewer
|
||||
#0x84d400
|
||||
|
||||
#0x413ee0
|
||||
|
||||
#0x7d2094 refcnt
|
||||
flags = {0x7FE944: ("World_Ptr", 4), 0x79C698: ("Py_Mods", 4)}
|
||||
|
||||
types = ["struct PyMethodDef {char *ml_name; void *ml_meth; int ml_flags; char *ml_doc;};"]
|
||||
|
||||
func_sigs = {
|
||||
0x5a8390: "int py_exec(const char* script);",
|
||||
0x5bb9d0: "int PyArg_ParseTuple(void* PyObj, char* format, ...);",
|
||||
0x4134c0: "int write_log(unsigned int color, const char* msg);",
|
||||
0x47C1E0: "int ht_hash_ent_list(const char* str);",
|
||||
0x404BB0: "int ht_hash_ent(const char* str);",
|
||||
0x4016f0: "int reg_get_val(const char* value);",
|
||||
0x414280: "int prepare_html_log(const char* filename);",
|
||||
0x6b1c70: "bool strcmp(const char* s1,const char* s2);",
|
||||
0x5A8FB0: "void* Py_InitModule(const char* name,void* methods);",
|
||||
0x5E3800: "int fopen_1(const char* filename);",
|
||||
0x419950: "int fopen_2(const char* filename);",
|
||||
0x41AB50: "int open_pak(const char* filename, int unk_1,void* unk_ptr);",
|
||||
0x404460: "int register_c_callback(const char* name,void* func);"
|
||||
}
|
||||
|
||||
functions = {
|
||||
0x6b1c70: "strcmp",
|
||||
0x5bb9d0: "PyArg_ParseTuple",
|
||||
0x5dd510: "init_engine_3d",
|
||||
0x401180: "create_window",
|
||||
0x401240: "create_main_window",
|
||||
0x4016f0: "reg_get_val",
|
||||
0x4134c0: "write_log",
|
||||
0x414280: "prepare_html_log",
|
||||
0x418220: "get_version_info",
|
||||
0x4137e0: "write_html_log",
|
||||
0x402190: "handle_console_input",
|
||||
0x5F9520: "handle_render_console_input",
|
||||
0x404A50: "find_entity",
|
||||
0x404BB0: "ht_hash",
|
||||
0x404460: "reg_c_callback",
|
||||
0x47C1E0: "ht_hash_ent_list",
|
||||
0x404BB0: "ht_hash_ent",
|
||||
0x404460: "register_c_callback",
|
||||
0x417470: "load_game",
|
||||
0x5E3800: "fopen_1",
|
||||
0x419950: "fopen_2",
|
||||
0x403370: "debug_init",
|
||||
0x401770: "init",
|
||||
0x4026D0: "init_py",
|
||||
0x5A8FB0: "init_py_mod",
|
||||
0x405B40: "init_py_sub",
|
||||
0x5A8FB0: "Py_InitModule",
|
||||
0x41AB50: "open_pak",
|
||||
0x5A8390: "py_exec",
|
||||
0x414570: "setup_game_vars",
|
||||
|
@ -59,42 +110,51 @@ functions = {
|
|||
0x479B40: "world_init",
|
||||
0x402510: "world_deinit",
|
||||
0x479870: "make_world",
|
||||
0x602a70: "render_frame"
|
||||
}
|
||||
|
||||
for t in types:
|
||||
r2_cmd(f'"td {t}"')
|
||||
|
||||
for addr, args in flags.items():
|
||||
name, size = args
|
||||
r2_cmd(f"f {name} {size} {hex(addr)}")
|
||||
|
||||
r2_cmd(f"f loc.{name} {size} {hex(addr)}")
|
||||
|
||||
for addr, name in functions.items():
|
||||
r2_cmd(f"afr {name} {hex(addr)}")
|
||||
r2_cmd(f"afr fcn.{name} {hex(addr)}")
|
||||
if addr in func_sigs:
|
||||
r2_cmd(f'"afs {func_sigs[addr]}" @{hex(addr)}')
|
||||
|
||||
|
||||
def vtables():
|
||||
ret={}
|
||||
ret = {}
|
||||
print("[*] Analyzing VTables")
|
||||
vtables = r2_cmdJ("avj")
|
||||
for c in tqdm(vtables,ascii=True):
|
||||
methods=[]
|
||||
for m in tqdm(c.methods,ascii=True,leave=False):
|
||||
for c in tqdm(vtables, ascii=True):
|
||||
methods = []
|
||||
for m in tqdm(c.methods, ascii=True, leave=False):
|
||||
methods.append(hex(m.offset))
|
||||
r2.cmd(f"afr @{hex(m.offset)} 2>NUL")
|
||||
ret[hex(c.offset)]=methods
|
||||
r2.cmd(f"afr @{hex(m.offset)} 2>{os.devnull}")
|
||||
ret[hex(c.offset)] = methods
|
||||
return ret
|
||||
|
||||
|
||||
def c_callbacks():
|
||||
print("[*] Parsing C Callbacks")
|
||||
funcs={}
|
||||
res = r2_cmd("/r 0x404460 ~CALL[1]").splitlines()
|
||||
for addr in tqdm(res,ascii=True):
|
||||
func,name=r2_cmdJ(f"s {addr};so -3;pdj 2")
|
||||
func=func.refs[0].addr
|
||||
name=r2_cmd(f"psz @{hex(name.refs[0].addr)}").strip()
|
||||
r2_cmd(f"afr CB_{name} {hex(func)} 2>NUL")
|
||||
funcs[name]=hex(func)
|
||||
funcs = {}
|
||||
res = r2_cmd("/r fcn.register_c_callback ~CALL[1]").splitlines()
|
||||
for addr in tqdm(res, ascii=True):
|
||||
func, name = r2_cmdJ(f"s {addr};so -3;pdj 2")
|
||||
func = func.refs[0].addr
|
||||
name = r2_cmd(f"psz @{hex(name.refs[0].addr)}").strip()
|
||||
r2_cmd(f"afr fcn.CB_{name} {hex(func)} 2>NUL")
|
||||
funcs[name] = hex(func)
|
||||
return funcs
|
||||
|
||||
|
||||
def assertions():
|
||||
assertions = {}
|
||||
for a_addr in ['0x414070','0x5fbc50']:
|
||||
for a_addr in ["fcn.throw_assertion_1", "fcn.throw_assertion_2"]:
|
||||
print(f"[*] Parsing C assertions for {a_addr}")
|
||||
res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines()
|
||||
print()
|
||||
|
@ -106,94 +166,119 @@ def assertions():
|
|||
msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip()
|
||||
path = os.path.abspath(file.replace("\\\\", "\\"))
|
||||
assertions.setdefault(path, [])
|
||||
assertions[path].append([int(addr, 16), msg])
|
||||
assertions[path].append([addr, msg])
|
||||
except:
|
||||
pass
|
||||
for path in assertions:
|
||||
assertions[path].sort(key=lambda v:int(v[0],16))
|
||||
return assertions
|
||||
|
||||
|
||||
def world():
|
||||
ret={}
|
||||
print("[*] Parsing World offsets")
|
||||
res = r2_cmd("/r 0x7fe944 ~&fcn,DATA[0,1]").splitlines()
|
||||
res = r2_cmd("/r loc.World_Ptr ~fcn[0,1]").splitlines()
|
||||
print()
|
||||
for hit in res:
|
||||
func, offset = hit.split()
|
||||
offset = int(offset, 16)
|
||||
print("=" * 5, func, "=" * 5)
|
||||
for op in r2_cmdJ(f"pdfj @{func}")["ops"]:
|
||||
if op.offset >= offset:
|
||||
# print(op.disasm,op.get('refs',[]))
|
||||
print(op.disasm)
|
||||
|
||||
for ent in res:
|
||||
func,hit=ent.split()
|
||||
ret[hit]={'asm':[],'func':func}
|
||||
for ins in r2_cmdJ(f"pdbj @{hit}"):
|
||||
ret[hit]['asm'].append(ins.disasm)
|
||||
return ret
|
||||
|
||||
def py_mods():
|
||||
print("[*] Parsing Python modules")
|
||||
res = r2_cmd("/r 0x5a8fb0 ~CALL[1]").splitlines()
|
||||
res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines()
|
||||
print()
|
||||
py_mods={}
|
||||
for call_loc in tqdm(res,ascii=True):
|
||||
py_mods = {}
|
||||
for call_loc in tqdm(res, ascii=True):
|
||||
args = r2_cmdJ(f"s {call_loc};so -3;pdj 3")
|
||||
refs = []
|
||||
if not all([arg.type=="push" for arg in args]):
|
||||
if not all([arg.type == "push" for arg in args]):
|
||||
continue
|
||||
for arg in args:
|
||||
refs.append(hex(arg.val))
|
||||
doc,methods,name=refs
|
||||
doc=r2_cmd(f"psz @{doc}").strip()
|
||||
name=r2_cmd(f"psz @{name}").strip()
|
||||
doc, methods, name = refs
|
||||
doc = r2_cmd(f"psz @{doc}").strip()
|
||||
name = r2_cmd(f"psz @{name}").strip()
|
||||
r2_cmd(f"s {methods}")
|
||||
r2_cmd(f"f PyMethodDef_{name} 4 {methods}")
|
||||
py_mods[name]={'methods_addr':methods,'doc':doc,'methods':{}}
|
||||
r2_cmd(f"f loc.py.MethodDef_{name} 4 {methods}")
|
||||
py_mods[name] = {"methods_addr": methods, "doc": doc, "methods": {}}
|
||||
while True:
|
||||
m_name,m_func,_,m_doc=[v.value for v in r2_cmdJ(f"pfj xxxx")]
|
||||
if m_name==0:
|
||||
m_name, m_func, _, m_doc = [v.value for v in r2_cmdJ(f"pfj xxxx")]
|
||||
if m_name == 0:
|
||||
break
|
||||
m_name,m_func,m_doc=map(hex,(m_name,m_func,m_doc))
|
||||
m_name=r2_cmd(f"psz @{m_name}").strip()
|
||||
m_name, m_func, m_doc = map(hex, (m_name, m_func, m_doc))
|
||||
m_name = r2_cmd(f"psz @{m_name}").strip()
|
||||
r2_cmd(f"f Py_{name}_{m_name}_doc 4 {m_doc}")
|
||||
m_doc=r2_cmd(f"psz @{m_doc}").strip()
|
||||
py_mods[name]['methods'][m_name]={'addr':m_func,'doc':m_doc}
|
||||
r2_cmd(f"afr Py_{name}_{m_name} {m_func} 2>NUL")
|
||||
m_doc = r2_cmd(f"psz @{m_doc}").strip()
|
||||
py_mods[name]["methods"][m_name] = {"addr": m_func, "doc": m_doc}
|
||||
r2_cmd(f"afr fcn.py.{name}.{m_name} {m_func} 2>NUL")
|
||||
r2_cmd("s +16")
|
||||
return py_mods
|
||||
|
||||
|
||||
def game_vars():
|
||||
ret={}
|
||||
print("[*] Parsing Game variables")
|
||||
res = r2_cmd("/r 0x414570 ~CALL[1]").splitlines()
|
||||
res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines()
|
||||
print()
|
||||
for line in tqdm(res,ascii=True):
|
||||
for line in tqdm(res, ascii=True):
|
||||
addr = line.strip()
|
||||
args = r2_cmdJ(f"s {addr};so -4;pdj 4") # seek and print disassembly
|
||||
r2_cmd(f"s {addr}")
|
||||
args = r2_cmd("pdj -5") # seek and print disassembly
|
||||
if not args:
|
||||
continue
|
||||
args=json.loads(args)
|
||||
args_a = []
|
||||
for arg in args:
|
||||
if "refs" in arg:
|
||||
addr = hex(arg.refs[0].addr)
|
||||
s = r2_cmd(f"ps @{hex(arg.refs[0].addr)}").strip()
|
||||
args_a.append((addr, s))
|
||||
print(args_a)
|
||||
push_cnt=0
|
||||
for arg in args[::-1]:
|
||||
if arg['type'] not in ["push","mov"]:
|
||||
continue
|
||||
if arg['type']=="push":
|
||||
push_cnt+=1
|
||||
args_a.append(arg)
|
||||
if push_cnt==3:
|
||||
break
|
||||
if len(args_a)!=4:
|
||||
continue
|
||||
if not all(['val' in v for v in args_a]):
|
||||
continue
|
||||
addr,name,_,desc=[v['val'] for v in args_a]
|
||||
name=r2_cmd(f"psz @{hex(name)}").strip()
|
||||
desc=r2_cmd(f"psz @{hex(desc)}").strip()
|
||||
addr=hex(addr)
|
||||
r2_cmd(f"f var_{name} 4 {addr}")
|
||||
ret[addr]={'name':name,'desc':desc}
|
||||
return ret
|
||||
|
||||
ret=dict(
|
||||
|
||||
ret = dict(
|
||||
game_vars=game_vars(),
|
||||
c_callbacks=c_callbacks(),
|
||||
py_mods=py_mods(),
|
||||
assertions=assertions(),
|
||||
c_callbacks=c_callbacks(),
|
||||
vtables=vtables(),
|
||||
#game_vars=game_vars(),
|
||||
#world=world(),
|
||||
world=world(),
|
||||
)
|
||||
|
||||
r2_cmd("aaaaa") # Propagate type infos
|
||||
|
||||
with open(os.path.join(folder,"Scrap_dissect.json"),"w") as of:
|
||||
json.dump(ret,of,indent=4)
|
||||
with open(os.path.join(folder, "Scrap_dissect.json"), "w") as of:
|
||||
json.dump(ret, of, indent=4)
|
||||
print("[+] Wrote Scrap_dissect.json")
|
||||
with open(os.path.join(folder,"Scrap_dissect.r2"),"w") as of:
|
||||
wcmds=[]
|
||||
|
||||
with open(os.path.join(folder, "Scrap_dissect.r2"), "w") as of:
|
||||
wcmds = []
|
||||
for cmd in r2cmds:
|
||||
for start in ['f ','afr ','aaaa']:
|
||||
if cmd.startswith(start):
|
||||
for start in ["f ", "afr ", "aaaaa","afs"]:
|
||||
if cmd.strip('"').startswith(start):
|
||||
wcmds.append(cmd)
|
||||
break
|
||||
of.write("\n".join(wcmds))
|
||||
|
||||
print("[+] Wrote Scrap_dissect.r2")
|
||||
print("[*] Done!")
|
||||
print("[*] Run 'r2 -i Scrap_dissect.r2 Scrap.exe' to load parsed infos into radare2")
|
||||
print(f"[*] Done, now cd to '{folder}'...")
|
||||
print(
|
||||
"[*] ...and run 'r2 -i Scrap_dissect.r2 Scrap.exe' to load the parsed infos into radare2"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue