Update r2_analyze.py: Fix game_vars() extractor, extract basic blocks referencing World_Ptr, add typedefs and function signatures

This commit is contained in:
Daniel S. 2019-11-28 19:59:14 +01:00
parent a0b08b7544
commit 03f04ae062

View file

@ -5,47 +5,98 @@ from tqdm import tqdm
from pprint import pprint from pprint import pprint
import os import os
import sys 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" r2cmds = []
assert r2.cmdj("itj")['md5'] == "a934c85dca5ab1c32f05c0977f62e186","Hash mismatch" 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): def r2_cmd(cmd):
global r2,r2cmds global r2, r2cmds
r2cmds.append(cmd) r2cmds.append(cmd)
return r2.cmd(cmd) return r2.cmd(cmd)
def r2_cmdj(cmd): def r2_cmdj(cmd):
global r2,r2cmds global r2, r2cmds
r2cmds.append(cmd) r2cmds.append(cmd)
return r2.cmdj(cmd) return r2.cmdj(cmd)
def r2_cmdJ(cmd): def r2_cmdJ(cmd):
global r2,r2cmds global r2, r2cmds
r2cmds.append(cmd) r2cmds.append(cmd)
return r2.cmdJ(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)} 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 = { 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", 0x404A50: "find_entity",
0x404BB0: "ht_hash", 0x47C1E0: "ht_hash_ent_list",
0x404460: "reg_c_callback", 0x404BB0: "ht_hash_ent",
0x404460: "register_c_callback",
0x417470: "load_game", 0x417470: "load_game",
0x5E3800: "fopen_1", 0x5E3800: "fopen_1",
0x419950: "fopen_2", 0x419950: "fopen_2",
0x403370: "debug_init", 0x403370: "debug_init",
0x401770: "init", 0x401770: "init",
0x4026D0: "init_py", 0x4026D0: "init_py",
0x5A8FB0: "init_py_mod", 0x405B40: "init_py_sub",
0x5A8FB0: "Py_InitModule",
0x41AB50: "open_pak", 0x41AB50: "open_pak",
0x5A8390: "py_exec", 0x5A8390: "py_exec",
0x414570: "setup_game_vars", 0x414570: "setup_game_vars",
@ -61,40 +112,48 @@ functions = {
0x479870: "make_world", 0x479870: "make_world",
} }
for t in types:
r2_cmd(f'"td {t}"')
for addr, args in flags.items(): for addr, args in flags.items():
name, size = args 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(): 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(): def vtables():
ret={} ret = {}
print("[*] Analyzing VTables") print("[*] Analyzing VTables")
vtables = r2_cmdJ("avj") vtables = r2_cmdJ("avj")
for c in tqdm(vtables,ascii=True): for c in tqdm(vtables, ascii=True):
methods=[] methods = []
for m in tqdm(c.methods,ascii=True,leave=False): for m in tqdm(c.methods, ascii=True, leave=False):
methods.append(hex(m.offset)) methods.append(hex(m.offset))
r2.cmd(f"afr @{hex(m.offset)} 2>NUL") r2.cmd(f"afr @{hex(m.offset)} 2>{os.devnull}")
ret[hex(c.offset)]=methods ret[hex(c.offset)] = methods
return ret return ret
def c_callbacks(): def c_callbacks():
print("[*] Parsing C Callbacks") print("[*] Parsing C Callbacks")
funcs={} funcs = {}
res = r2_cmd("/r 0x404460 ~CALL[1]").splitlines() res = r2_cmd("/r fcn.register_c_callback ~CALL[1]").splitlines()
for addr in tqdm(res,ascii=True): for addr in tqdm(res, ascii=True):
func,name=r2_cmdJ(f"s {addr};so -3;pdj 2") func, name = r2_cmdJ(f"s {addr};so -3;pdj 2")
func=func.refs[0].addr func = func.refs[0].addr
name=r2_cmd(f"psz @{hex(name.refs[0].addr)}").strip() name = r2_cmd(f"psz @{hex(name.refs[0].addr)}").strip()
r2_cmd(f"afr CB_{name} {hex(func)} 2>NUL") r2_cmd(f"afr fcn.CB_{name} {hex(func)} 2>NUL")
funcs[name]=hex(func) funcs[name] = hex(func)
return funcs return funcs
def assertions(): def assertions():
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}") print(f"[*] Parsing C assertions for {a_addr}")
res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines() res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines()
print() print()
@ -106,94 +165,119 @@ def assertions():
msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip() msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip()
path = os.path.abspath(file.replace("\\\\", "\\")) path = os.path.abspath(file.replace("\\\\", "\\"))
assertions.setdefault(path, []) assertions.setdefault(path, [])
assertions[path].append([int(addr, 16), msg]) assertions[path].append([addr, msg])
except: except:
pass pass
for path in assertions:
assertions[path].sort(key=lambda v:int(v[0],16))
return assertions return assertions
def world(): def world():
ret={}
print("[*] Parsing World offsets") 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() print()
for hit in res: for ent in res:
func, offset = hit.split() func,hit=ent.split()
offset = int(offset, 16) ret[hit]={'asm':[],'func':func}
print("=" * 5, func, "=" * 5) for ins in r2_cmdJ(f"pdbj @{hit}"):
for op in r2_cmdJ(f"pdfj @{func}")["ops"]: ret[hit]['asm'].append(ins.disasm)
if op.offset >= offset: return ret
# print(op.disasm,op.get('refs',[]))
print(op.disasm)
def py_mods(): def py_mods():
print("[*] Parsing Python modules") print("[*] Parsing Python modules")
res = r2_cmd("/r 0x5a8fb0 ~CALL[1]").splitlines() res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines()
print() print()
py_mods={} py_mods = {}
for call_loc in tqdm(res,ascii=True): for call_loc in tqdm(res, ascii=True):
args = r2_cmdJ(f"s {call_loc};so -3;pdj 3") args = r2_cmdJ(f"s {call_loc};so -3;pdj 3")
refs = [] refs = []
if not all([arg.type=="push" for arg in args]): if not all([arg.type == "push" for arg in args]):
continue continue
for arg in args: for arg in args:
refs.append(hex(arg.val)) refs.append(hex(arg.val))
doc,methods,name=refs doc, methods, name = refs
doc=r2_cmd(f"psz @{doc}").strip() doc = r2_cmd(f"psz @{doc}").strip()
name=r2_cmd(f"psz @{name}").strip() name = r2_cmd(f"psz @{name}").strip()
r2_cmd(f"s {methods}") r2_cmd(f"s {methods}")
r2_cmd(f"f PyMethodDef_{name} 4 {methods}") r2_cmd(f"f loc.py.MethodDef_{name} 4 {methods}")
py_mods[name]={'methods_addr':methods,'doc':doc,'methods':{}} py_mods[name] = {"methods_addr": methods, "doc": doc, "methods": {}}
while True: while True:
m_name,m_func,_,m_doc=[v.value for v in r2_cmdJ(f"pfj xxxx")] m_name, m_func, _, m_doc = [v.value for v in r2_cmdJ(f"pfj xxxx")]
if m_name==0: if m_name == 0:
break break
m_name,m_func,m_doc=map(hex,(m_name,m_func,m_doc)) 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 = r2_cmd(f"psz @{m_name}").strip()
r2_cmd(f"f Py_{name}_{m_name}_doc 4 {m_doc}") r2_cmd(f"f Py_{name}_{m_name}_doc 4 {m_doc}")
m_doc=r2_cmd(f"psz @{m_doc}").strip() m_doc = r2_cmd(f"psz @{m_doc}").strip()
py_mods[name]['methods'][m_name]={'addr':m_func,'doc':m_doc} 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(f"afr fcn.py.{name}.{m_name} {m_func} 2>NUL")
r2_cmd("s +16") r2_cmd("s +16")
return py_mods return py_mods
def game_vars(): def game_vars():
ret={}
print("[*] Parsing Game variables") print("[*] Parsing Game variables")
res = r2_cmd("/r 0x414570 ~CALL[1]").splitlines() res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines()
print() print()
for line in tqdm(res,ascii=True): for line in tqdm(res, ascii=True):
addr = line.strip() 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 = [] args_a = []
for arg in args: push_cnt=0
if "refs" in arg: for arg in args[::-1]:
addr = hex(arg.refs[0].addr) if arg['type'] not in ["push","mov"]:
s = r2_cmd(f"ps @{hex(arg.refs[0].addr)}").strip() continue
args_a.append((addr, s)) if arg['type']=="push":
print(args_a) 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(), py_mods=py_mods(),
assertions=assertions(), assertions=assertions(),
c_callbacks=c_callbacks(),
vtables=vtables(), 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: with open(os.path.join(folder, "Scrap_dissect.json"), "w") as of:
json.dump(ret,of,indent=4) json.dump(ret, of, indent=4)
print("[+] Wrote Scrap_dissect.json") 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 cmd in r2cmds:
for start in ['f ','afr ','aaaa']: for start in ["f ", "afr ", "aaaaa","afs"]:
if cmd.startswith(start): if cmd.strip('"').startswith(start):
wcmds.append(cmd) wcmds.append(cmd)
break break
of.write("\n".join(wcmds)) of.write("\n".join(wcmds))
print("[+] Wrote Scrap_dissect.r2") print("[+] Wrote Scrap_dissect.r2")
print("[*] Done!") print(f"[*] Done, now cd to '{folder}'...")
print("[*] Run 'r2 -i Scrap_dissect.r2 Scrap.exe' to load parsed infos into radare2") print(
"[*] ...and run 'r2 -i Scrap_dissect.r2 Scrap.exe' to load the parsed infos into radare2"
)