diff --git a/NOTES.md b/NOTES.md index 5e54bf1..a561e29 100644 --- a/NOTES.md +++ b/NOTES.md @@ -55,7 +55,6 @@ - World_Init @ 0x479b40 - World_DeInit @ 0x402510 - Make_World @ 0x479870 -- RenderFrame(?) @ 0x602a70 # Data Structures @@ -111,6 +110,15 @@ 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 diff --git a/ScrapHacks/ScrapHack/ScrapHack.cpp b/ScrapHacks/ScrapHack/ScrapHack.cpp index f1ec67a..5c7237d 100644 --- a/ScrapHacks/ScrapHack/ScrapHack.cpp +++ b/ScrapHacks/ScrapHack/ScrapHack.cpp @@ -15,8 +15,10 @@ 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; diff --git a/ScrapHacks/ScrapHack/VMT_Hook.h b/ScrapHacks/ScrapHack/VMT_Hook.h new file mode 100644 index 0000000..4dd898e --- /dev/null +++ b/ScrapHacks/ScrapHack/VMT_Hook.h @@ -0,0 +1,101 @@ + +#pragma once +class VMT_Hook +{ +private: + MEMORY_BASIC_INFORMATION mbi; + void *orig; + void *detour; + DWORD *vtable; + size_t ord; + bool enabled; + static map> 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(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(detour); + hooks[key] = make_shared(obj, ord, detour); + hooks[key]->enable(); + } + + static shared_ptr get(void *func) + { + uintptr_t addr = reinterpret_cast(func); + return VMT_Hook::get(addr); + } + + static shared_ptr get(uintptr_t addr) + { + return hooks.at(addr); + } + + static size_t drop(void *func) + { + uintptr_t addr = reinterpret_cast(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(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(this->detour); + VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); + enabled = true; + } + } + + template + T func() + { + return reinterpret_cast(this->orig); + } +}; + +map> VMT_Hook::hooks; diff --git a/r2_analyze.py b/r2_analyze.py index 00c26d3..8733fc9 100644 --- a/r2_analyze.py +++ b/r2_analyze.py @@ -5,98 +5,47 @@ from tqdm import tqdm from pprint import pprint import os import sys - -r2cmds = [] -scrap_exe = sys.argv[1] -folder = os.path.abspath(os.path.dirname(scrap_exe)) - -assert os.path.isfile(scrap_exe), "File not found!" +r2cmds=[] +scrap_exe=sys.argv[1] +folder=os.path.join(os.path.dirname(scrap_exe)) r2 = r2pipe.open(scrap_exe) -file_hashes = r2.cmdj("itj") -target_hashes = { - "sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa", - "md5": "a934c85dca5ab1c32f05c0977f62e186", -} - -assert file_hashes == target_hashes, "Hash mismatch" +assert r2.cmdj("itj")['sha1'] == "d2dde960e8eca69d60c2e39a439088b75f0c89fa","Hash mismatch" +assert r2.cmdj("itj")['md5'] == "a934c85dca5ab1c32f05c0977f62e186","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 'aaaaa'") +print("[*] Running 'aaaa'") -r2_cmd("aaaaa") +r2_cmd("aaaa") -#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", - 0x47C1E0: "ht_hash_ent_list", - 0x404BB0: "ht_hash_ent", - 0x404460: "register_c_callback", + 0x404BB0: "ht_hash", + 0x404460: "reg_c_callback", 0x417470: "load_game", 0x5E3800: "fopen_1", 0x419950: "fopen_2", 0x403370: "debug_init", 0x401770: "init", 0x4026D0: "init_py", - 0x405B40: "init_py_sub", - 0x5A8FB0: "Py_InitModule", + 0x5A8FB0: "init_py_mod", 0x41AB50: "open_pak", 0x5A8390: "py_exec", 0x414570: "setup_game_vars", @@ -110,51 +59,42 @@ 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 loc.{name} {size} {hex(addr)}") - -for addr, name in functions.items(): - r2_cmd(f"afr fcn.{name} {hex(addr)}") - if addr in func_sigs: - r2_cmd(f'"afs {func_sigs[addr]}" @{hex(addr)}') + r2_cmd(f"f {name} {size} {hex(addr)}") +for addr, name in functions.items(): + r2_cmd(f"afr {name} {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>{os.devnull}") - ret[hex(c.offset)] = methods + r2.cmd(f"afr @{hex(m.offset)} 2>NUL") + ret[hex(c.offset)]=methods return ret - def c_callbacks(): print("[*] Parsing C Callbacks") - 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) + 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) return funcs - def assertions(): assertions = {} - for a_addr in ["fcn.throw_assertion_1", "fcn.throw_assertion_2"]: + for a_addr in ['0x414070','0x5fbc50']: print(f"[*] Parsing C assertions for {a_addr}") res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines() print() @@ -166,119 +106,94 @@ def assertions(): msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip() path = os.path.abspath(file.replace("\\\\", "\\")) assertions.setdefault(path, []) - assertions[path].append([addr, msg]) + assertions[path].append([int(addr, 16), 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 loc.World_Ptr ~fcn[0,1]").splitlines() + res = r2_cmd("/r 0x7fe944 ~&fcn,DATA[0,1]").splitlines() print() - 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 + 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) + def py_mods(): print("[*] Parsing Python modules") - res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines() + res = r2_cmd("/r 0x5a8fb0 ~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 loc.py.MethodDef_{name} 4 {methods}") - py_mods[name] = {"methods_addr": methods, "doc": doc, "methods": {}} + r2_cmd(f"f PyMethodDef_{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 fcn.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 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 fcn.setup_game_vars ~CALL[1]").splitlines() + res = r2_cmd("/r 0x414570 ~CALL[1]").splitlines() print() - for line in tqdm(res, ascii=True): + for line in tqdm(res,ascii=True): addr = line.strip() - r2_cmd(f"s {addr}") - args = r2_cmd("pdj -5") # seek and print disassembly - if not args: - continue - args=json.loads(args) + args = r2_cmdJ(f"s {addr};so -4;pdj 4") # seek and print disassembly 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 + 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) - -ret = dict( - game_vars=game_vars(), - c_callbacks=c_callbacks(), +ret=dict( py_mods=py_mods(), assertions=assertions(), + c_callbacks=c_callbacks(), vtables=vtables(), - world=world(), + #game_vars=game_vars(), + #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 ", "aaaaa","afs"]: - if cmd.strip('"').startswith(start): + for start in ['f ','afr ','aaaa']: + if cmd.startswith(start): wcmds.append(cmd) break of.write("\n".join(wcmds)) - print("[+] Wrote Scrap_dissect.r2") -print(f"[*] Done, now cd to '{folder}'...") -print( - "[*] ...and run 'r2 -i Scrap_dissect.r2 Scrap.exe' to load the parsed infos into radare2" -) +print("[*] Done!") +print("[*] Run 'r2 -i Scrap_dissect.r2 Scrap.exe' to load parsed infos into radare2")