From 7f34e298b84959c8afa24d59f33fc2bc2d3c7dcb Mon Sep 17 00:00:00 2001 From: Daniel Seiller Date: Fri, 3 Jan 2020 04:20:18 +0100 Subject: [PATCH] Separated configuration for analysis into YAML file --- NOTES.md | 6 +- config.yml | 195 +++++++++++++++++++++++++++++++++++++++ r2_analyze.py | 251 +++++--------------------------------------------- 3 files changed, 219 insertions(+), 233 deletions(-) create mode 100644 config.yml diff --git a/NOTES.md b/NOTES.md index bf470fd..e33c026 100644 --- a/NOTES.md +++ b/NOTES.md @@ -2,6 +2,7 @@ - Engine: ScrapEngine - Ingame Scripting Language: Python 1.5.2 +- Interesting memory locations and functions are noted in `config.yml` # Launch options: @@ -32,11 +33,6 @@ * `idiota` * `capullo` -## Python Stuff - -- `0x79C698`: Modules List (Module Name as `char*` followed by Pointer to Init Function) -- `0x5A8FB0`: InitPyMod -- `0x5A8390`: PyExec ## Other interesting Memory Addresses diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..42d34e2 --- /dev/null +++ b/config.yml @@ -0,0 +1,195 @@ +notes: | + 0x7faa4c: temp storage? + 0x7d2094: some reference count + +comments: + 0x6113f9: Check if Window exists + +flags: + 0x7FE944: P_World + 0x7FBE4C: P_Vars + 0x79C698: Py_Mods + 0x852914: P_D3D8_Dev + 0x7FCC00: N_Paks_opened + 0x7fcbec: Hash_Index_Size + 0x7fcbf0: P_Hash_Index + 0x7fcc08: Lst_File + 0x7fcc04: Pak_Locked + 0x7fc1b0: Pak_Index + 0x84cb64: P_ConHandler + 0x801e10: num_arrows + 0x7fac84: P_Callbacks + 0x80b2cc: P_ActClassList + 0x807a20: P_Scorer + 0x80a398: P_SoundSys + 0x84cb58: H_RichEd + 0x84cb4c: P_HWND_Console + 0x80cb40: Console_Win_Buffer + 0x84d400: Lib_preloaded + 0x7fac1c: Activate_Viewer + 0x8b18f0: P_Models + 0x8b18f4: P_Scenes + 0x8b18f8: P_ActiveModels + 0x803bc0: net_is_server + 0x8045e4: net_is_master + 0x8038a8: net_is_client + 0x7fadd8: is_python + 0x7fc084: pak_lock + 0x7fbe7c: current_language + 0x7d2094: py_refcnt_unk + +VMTs: + 0x78d4d8: Py_entity + 0x78cc6c: World + 0x78b680: FilePak_1 + 0x78b6a4: FilePak_2 + 0x78b638: AbstractFile + 0x78b4d8: App + 0x78b480: Window + 0x78b5c0: File + 0x78b65c: FileMem + 0x78b6d0: IDevice_1 + 0x78b6f4: IDevice_2 + 0x78b6fc: IDevice_Kb + 0x78b720: IDevice_Mouse + 0x78b74c: IDevice_Joy + 0x7933ac: 3d_Gfx + 0x7933a0: NodeFX + +types: + - "struct PyMethodDef { char *ml_name; void *ml_meth; int ml_flags; char *ml_doc;};" + - "struct GameVar { struct GameVar* next; const char* name; const char* desc; uint64_t d_type; void* value; void* def_value; };" + - "struct HT_Entry { void* data; const char* key; struct HT_Entry* next;};" + - "struct PakEntry { unsigned char* filename; bool locked; void* data; uint32_t seek;};" + - "struct HashIndexEntry { uint32_t offset; uint32_t size; uint32_t status; const char* name; struct HashIndexEntry* next; };" + - "struct HashIndex { uint32_t size; struct HashIndexEntry** data; };" + - "struct HashTableEntry { void* data; const char *key; struct HashTableEntry* next; };" + - "struct HashTable { uint32_t size; struct HashTableEntry** data; };" + +function_signatures: + 0x5A8390: "int PyRun_SimpleString(const char* command);" + 0x5BB9D0: "int PyArg_ParseTuple(void* PyObj, char* format, ...);" + 0x413ee0: "int dbg_log(const char* fmt,...);" + 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);" + 0x6597d0: "bool read_ini_entry(void* dest,const char* key, const char* section);" + 0x5A8FB0: "void* Py_InitModule(const char* name,void* methods);" + 0x5E3800: "int fopen_from_pak(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);" + 0x414070: "void throw_assertion_2(const char* check,const char* file,const char* date, unsigned int line);" + 0x5FBC50: "void throw_assertion_1(const char* check,const char* file, unsigned int line);" + 0x5BC140: "static char* convertsimple1(void *arg, char **p_format, void *p_va);" + 0x5E3800: "int32_t fopen_from_pak(const char* filename,const char* mode);" + 0x5a90f0: "void* Py_BuildValue(const char* format, ...);" + 0x5B9E70: "void* PyObject_GetAttrString(void* obj, const char* attr);" + +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 + 0x417470: load_game + 0x5E3800: fopen_from_pak + 0x5e3500: fopen + 0x403370: init_debug + 0x401770: init + 0x4026D0: init_py + 0x405B40: init_py_sub + 0x5A8FB0: Py_InitModule + 0x41AB50: open_pak + 0x5A8390: PyRun_SimpleString + 0x414570: setup_game_vars + 0x5FBC50: throw_assertion_1 + 0x414070: throw_assertion_2 + 0x5F7000: read_ini + 0x650F80: load_sm3 + 0x6665A0: load_m3d_1 + 0x666900: load_m3d_2 + 0x479B20: world_constructor + 0x479B40: init_world + 0x402510: deinit_world + 0x479870: make_world + 0x602A70: render_frame + 0x6B738C: handle_exception + 0x5B9E70: PyObject_GetAttrString + 0x413ee0: dbg_log + 0x5f75e0: init_d3d + 0x63a2f0: gdi_draw_line + 0x5e3250: read_stream + 0x5e3bb0: read_stream_wrapper + 0x50b9b0: init_scorer + 0x582e10: init_action_class_list + 0x528910: init_sound_sys + 0x5268d0: try_init_sound_sys + 0x404280: cPyFunction_set_func + 0x414680: load_config + 0x414810: save_config + 0x4f42a0: close_server_socket + 0x4f4d10: close_server + 0x4f48e0: close_client + 0x4f4fb0: is_server + 0x4f4a10: is_client + 0x4fac50: is_master + 0x526910: close_sound_sys + 0x526520: shutdown_sound_sys + 0x5dd700: close_3d_engine + 0x5a7320: close_window + 0x5dff20: set_exception_handler + 0x5a7f20: get_console_wnd + 0x5a73a0: show_console + 0x666c60: read_m3d + 0x417df0: snprintf + 0x5fc930: printf + 0x6597d0: read_ini_entry + 0x5fc0a0: engine_debug_log + 0x5a7440: create_console_window + 0x6114e0: setup_window + 0x404420: clear_functions + 0x405ca0: close_py_subsys + 0x50bcb0: close_scorer + 0x479b20: close_world + 0x582e70: close_action_class + 0x50b6a0: get_scorer + 0x50ea20: scorer_parse_type + 0x636580: list_models + 0x5a90f0: Py_BuildValue + 0x41c5a0: has_lst_file + 0x5a8e90: py_error + 0x5a9890: get_module_dict + 0x5c7bb0: get_current_thread + 0x5aa140: preload_lib + 0x413c10: sprintf + 0x405850: check_is_python + 0x47bf90: setup_ent_list + 0x474f80: ent_list_get_set + +script: | + e asm.cmt.right = true + e cmd.stack = true + e scr.utf8 = true + e asm.describe = false + e graph.cmtright = true + e cfg.sandbox = false + e cfg.newtab = true + e cfg.fortunes.type = tips,fun,creepy,nsfw + e dbg.status = true + e pdb.autoload = true + e emu.str = true + e asm.flags.offset = true \ No newline at end of file diff --git a/r2_analyze.py b/r2_analyze.py index 92715c5..edab8ef 100644 --- a/r2_analyze.py +++ b/r2_analyze.py @@ -7,14 +7,16 @@ from tqdm import tqdm from pprint import pprint import os import sys +import yaml r2cmds = [] x64_dbg_script=[] +script_path = os.path.dirname(os.path.abspath(__file__)) scrap_exe = os.path.abspath(sys.argv[1]) -folder = os.path.abspath(os.path.dirname(scrap_exe)) -script_path=os.path.join(folder, "scrap_dissect.r2") -x64_dbg_script_path=os.path.join(folder, "scrap_dissect.x32dbg.txt") -json_path=os.path.join(folder, "scrap_dissect.json") +scrapland_folder = os.path.abspath(os.path.dirname(scrap_exe)) +r2_script_path=os.path.join(scrapland_folder, "scrap_dissect.r2") +x64_dbg_script_path=os.path.join(scrapland_folder, "scrap_dissect.x32dbg.txt") +json_path=os.path.join(scrapland_folder, "scrap_dissect.json") assert os.path.isfile(scrap_exe), "File not found!" r2 = r2pipe.open(scrap_exe) @@ -74,238 +76,34 @@ def analysis(full=False): for ac in steps: print(f"[*] Running '{ac}'") r2_cmd(f"{ac} 2>NUL") - -# 0x7fac20 -# 0x7fac19 -# 0x7faa4c -# 0x7fac1c # activate viewer -# 0x84d400 # lib preloaded -# 0x413ee0 +with open(os.path.join(script_path,"config.yml")) as cfg: + print("[*] Loading config") + config = type("Config",(object,),yaml.load(cfg,Loader=yaml.SafeLoader)) -# 0x7d2094 refcnt - -comments= { - 0x6113f9:"Check if Window exists" -} - -flags = { - 0x7FE944: "P_World", - 0x7FBE4C: "P_Vars", - 0x79C698: "Py_Mods", - 0x852914: "P_D3D8_Dev", - 0x7FCC00: "N_Paks_opened", - 0x7fcbec: "Hash_Index_Size", - 0x7fcbf0: "P_Hash_Index", - 0x7fcc08: "Lst_File", - 0x7fcc04: "Pak_Locked", - 0x7fc1b0: "Pak_Index", - 0x84cb64: "P_ConHandler", - 0x801e10: "num_arrows", - 0x7fac84: "P_Callbacks", - 0x80b2cc: "P_ActClassList", - 0x807a20: "P_Scorer", - 0x80a398: "P_SoundSys", - 0x84cb58: "H_RichEd", - 0x84cb4c: "P_HWND_Console", - 0x80cb40: "Console_Win_Buffer", - 0x84d400: "Lib_preloaded", - 0x7fac1c: "Activate_Viewer", - 0x8b18f0: "P_Models", - 0x8b18f4: "P_Scenes", - 0x8b18f8: "P_ActiveModels", - 0x803bc0: "net_is_server", - 0x8045e4: "net_is_master", - 0x8038a8: "net_is_client", - 0x7fadd8: "is_python", - 0x7fc084: "pak_lock", - 0x7fbe7c: "current_language", -} - -VMTs = { - 0x78d4d8: "Py_entity", - 0x78cc6c: "World", - 0x78b680: "FilePak_1", - 0x78b6a4: "FilePak_2", - 0x78b638: "AbstractFile", - 0x78b4d8: "App", - 0x78b480: "Window", - 0x78b5c0: "File", - 0x78b65c: "FileMem", - 0x78b6d0: "IDevice_1", - 0x78b6f4: "IDevice_2", - 0x78b6fc: "IDevice_Kb", - 0x78b720: "IDevice_Mouse", - 0x78b74c: "IDevice_Joy", - 0x7933ac: "3d_Gfx", - 0x7933a0: "NodeFX", -} - -types = [ - "struct PyMethodDef { char *ml_name; void *ml_meth; int ml_flags; char *ml_doc;};", - "struct GameVar { struct GameVar* next; const char* name; const char* desc; uint64_t d_type; void* value; void* def_value; };", - "struct HT_Entry { void* data; const char* key; struct HT_Entry* next;};", - "struct PakEntry { unsigned char* filename; bool locked; void* data; uint32_t seek;};", - "struct HashIndexEntry { uint32_t offset; uint32_t size; uint32_t status; const char* name; struct HashIndexEntry* next; };", - "struct HashIndex { uint32_t size; struct HashIndexEntry** data; };", - "struct HashTableEntry { void* data; const char *key; struct HashTableEntry* next; };", - "struct HashTable { uint32_t size; struct HashTableEntry** data; };", -] - -func_sigs = { - 0x5A8390: "int py_exec(const char* script);", - 0x5BB9D0: "int PyArg_ParseTuple(void* PyObj, char* format, ...);", - 0x413ee0: "int dbg_log(const char* fmt,...);", - 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);", - 0x6597d0: "bool read_ini_entry(void* dest,const char* key, const char* section);", - 0x5A8FB0: "void* Py_InitModule(const char* name,void* methods);", - 0x5E3800: "int fopen_from_pak(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);", - 0x414070: "void throw_assertion_2(const char* check,const char* file,const char* date, unsigned int line);", - 0x5FBC50: "void throw_assertion_1(const char* check,const char* file, unsigned int line);", - 0x5BC140: "static char* convertsimple1(void *arg, char **p_format, void *p_va);", - 0x5E3800: "int32_t fopen_from_pak(const char* filename,const char* mode);", - 0x5a90f0: "void* Py_BuildValue(const char* format, ...);" -} - -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", - 0x417470: "load_game", - 0x5E3800: "fopen_from_pak", - 0x5e3500: "fopen", - 0x403370: "init_debug", - 0x401770: "init", - 0x4026D0: "init_py", - 0x405B40: "init_py_sub", - 0x5A8FB0: "Py_InitModule", - 0x41AB50: "open_pak", - 0x5A8390: "py_exec", - 0x414570: "setup_game_vars", - 0x5FBC50: "throw_assertion_1", - 0x414070: "throw_assertion_2", - 0x5F7000: "read_ini", - 0x650F80: "load_sm3", - 0x6665A0: "load_m3d_1", - 0x666900: "load_m3d_2", - 0x479B20: "world_constructor", - 0x479B40: "init_world", - 0x402510: "deinit_world", - 0x479870: "make_world", - 0x602A70: "render_frame", - 0x6B738C: "handle_exception", - 0x5B9E70: "py_getattr", - 0x413ee0: "dbg_log", - 0x5f75e0: "init_d3d", - 0x63a2f0: "gdi_draw_line", - 0x5e3250: "read_stream", - 0x5e3bb0: "read_stream_wrapper", - 0x50b9b0: "init_scorer", - 0x582e10: "init_action_class_list", - 0x528910: "init_sound_sys", - 0x5268d0: "try_init_sound_sys", - 0x404280: "cPyFunction_set_func", - 0x414680: "load_config", - 0x414810: "save_config", - 0x4f42a0: "close_server_socket", - 0x4f4d10: "close_server", - 0x4f48e0: "close_client", - 0x4f4fb0: "is_server", - 0x4f4a10: "is_client", - 0x4fac50: "is_master", - 0x526910: "close_sound_sys", - 0x526520: "shutdown_sound_sys", - 0x5dd700: "close_3d_engine", - 0x5a7320: "close_window", - 0x5dff20: "set_exception_handler", - 0x5a7f20: "get_console_wnd", - 0x5a73a0: "show_console", - 0x666c60: "read_m3d", - 0x417df0: "snprintf", - 0x5fc930: "printf", - 0x6597d0: "read_ini_entry", - 0x5fc0a0: "engine_debug_log", - 0x5a7440: "create_console_window", - 0x6114e0: "setup_window", - 0x404420: "clear_functions", - 0x405ca0: "close_py_subsys", - 0x50bcb0: "close_scorer", - 0x479b20: "close_world", - 0x582e70: "close_action_class", - 0x50b6a0: "get_scorer", - 0x50ea20: "scorer_parse_type", - 0x636580: "list_models", - 0x5a90f0: "Py_BuildValue", - 0x41c5a0: "has_lst_file", - 0x5a8e90: "py_error", - 0x5a9890: "get_module_dict", - 0x5c7bb0: "get_current_thread", - 0x5aa140: "preload_lib", - 0x413c10: "sprintf", - 0x405850: "check_is_python", - 0x47bf90: "setup_ent_list", - 0x474f80: "ent_list_get_set", -} - -# 0x853954 ??? some obj ptr - -# [0x7fbe98] - -# [0x853954]+0x2a3cc debug flag, checked in 0x006113a0 called from 0x005dd5ea -cfg=""" -e asm.cmt.right = true -e cmd.stack = true -e scr.utf8 = true -e asm.describe = false -e graph.cmtright = true -e cfg.sandbox = false -e cfg.newtab = true -e cfg.fortunes.type = tips,fun,creepy,nsfw -e dbg.status = true -e pdb.autoload = true -e emu.str = true -e asm.flags.offset = true -""".strip().splitlines() -for line in cfg: +for line in config.script.strip().splitlines(): r2_cmd(line) analysis(False) -for addr,comment in comments.items(): +for addr,comment in config.comments.items(): r2_cmd(f"CC {comment} @ {hex(addr)}") -for t in types: +for t in config.types: r2_cmd(f'"td {t}"') -for addr, name in flags.items(): +for addr, name in config.flags.items(): x64_dbg_label(addr,name,"loc") r2_cmd(f"f loc.{name} 4 {hex(addr)}") -for addr, name in functions.items(): + +for addr, name in config.functions.items(): x64_dbg_label(addr,name,"fcn") r2_cmd(f"afr fcn.{name} {hex(addr)}") - if addr in func_sigs: - r2_cmd(f'"afs {func_sigs[addr]}" @{hex(addr)}') + +for addr,sig in config.function_signatures: + r2_cmd(f'"afs {config.function_signatures[addr]}" @{hex(addr)}') + def vtables(): @@ -314,7 +112,7 @@ def vtables(): vtables = r2_cmdJ("avj") for c in tqdm(vtables, ascii=True): methods = [] - name=VMTs.get(c.offset,f"{c.offset:08x}") + name=config.VMTs.get(c.offset,f"{c.offset:08x}") x64_dbg_label(c.offset,name,"vmt") r2_cmd(f"f vmt.{name} 4 {hex(c.offset)}") for idx,m in enumerate(tqdm(c.methods, ascii=True, leave=False)): @@ -389,17 +187,14 @@ def bb_refs(addr): ret[hit]["asm"].append(ins.disasm) return ret - def world(): print("[*] Parsing World offsets") return bb_refs("loc.P_World") - def render(): print("[*] Parsing D3D_Device offsets") return bb_refs("loc.P_D3D8_Dev") - def py_mods(): print("[*] Parsing Python modules") res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines() @@ -498,7 +293,7 @@ with open(x64_dbg_script_path,"w") as of: print("[+] Wrote scrap_dissect.x32dbg.txt") -with open(script_path, "w") as of: +with open(r2_script_path, "w") as of: wcmds = [] for cmd in r2cmds: record=True @@ -524,7 +319,7 @@ print("[+] Analysis took:",datetime.today()-t_start) print("[+] Executing Cutter") try: - start_program(['cutter','-A','0','-i',script_path,scrap_exe],cwd=folder,shell=False) + start_program(['cutter','-A','0','-i',r2_script_path,scrap_exe],cwd=scrapland_folder,shell=False) except FileNotFoundError: print("[-] cutter not installed, falling back to r2") - start_program(['r2','-i',script_path,scrap_exe],cwd=folder,shell=False) \ No newline at end of file + start_program(['r2','-i',r2_script_path,scrap_exe],cwd=scrapland_folder,shell=False) \ No newline at end of file