add cli support + ida 9 compatibility (#76)

This commit is contained in:
random
2025-12-02 03:28:24 +09:00
committed by GitHub
parent 734a93aaf6
commit ccc8e1baf2
+131 -92
View File
@@ -1,11 +1,19 @@
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
import atexit
import json
import os
CLI_AVAILABLE = True
try:
import idapro
except ImportError:
CLI_AVAILABLE = False
import idaapi
import ida_bytes
import ida_funcs
import ida_kernwin
import ida_name
import ida_typeinf
import json
import ida_kernwin
def iterable(obj):
if obj is None:
@@ -18,21 +26,22 @@ def iterable(obj):
else:
return True
# https://gist.github.com/NyaMisty/693db2ce2e75c230f36b628fd7610852
# 'Synchonize to idb' right click equivalent
def resync_local_types():
def is_autosync(name, tif):
return idaapi.get_ordinal_from_idb_type(name, tif.get_decltype().to_bytes(1, 'little')) != -1
return idaapi.get_ordinal_from_idb_type(name, tif.get_decltype().to_bytes(1, "little")) != -1
for ord in range(1, idaapi.get_ordinal_qty(None)):
t = idaapi.tinfo_t()
t.get_numbered_type(None, ord)
typename = t.get_type_name()
if typename.startswith('#'):
if typename.startswith("#"):
continue
autosync = is_autosync(typename, t)
#print('Processing struct %d: %s%s' % (ord, typename, ' (autosync) ' if autosync else ''))
# print('Processing struct %d: %s%s' % (ord, typename, ' (autosync) ' if autosync else ''))
idaapi.import_type(None, -1, typename, idaapi.IMPTYPE_OVERRIDE)
if autosync:
continue
@@ -41,113 +50,143 @@ def resync_local_types():
continue
struc.ordinal = -1
idaapi.save_struc(struc, False)
def get_type_by_name(name):
t = idaapi.tinfo_t()
t.get_named_type(None, name)
return t
def set_function_signature(ea, typedef):
idaapi.apply_type(ea, ida_typeinf.parse_decl(typedef, ida_typeinf.PT_SIL), idaapi.TINFO_DEFINITE)
def import_primitives():
type_map = {
"BUILTIN_STRING": "string",
"uint8_t": "uint8",
"uint16_t": "uint16",
"uint32_t": "uint32",
"uint64_t": "uint64",
"int8_t": "int8",
"int16_t": "int16",
"int32_t": "int32",
"double": "float64",
"float": "float32",
"complex64_t": "complex64",
"complex128_t": "complex128",
"void*": "uintptr", # should be uint64 or uint32 depending on ptr size, but this works too
"uint8": "byte",
"int32": "rune",
"int": "void*" # int in GO depends on architecture size
"BUILTIN_STRING": "string",
"uint8_t": "uint8",
"uint16_t": "uint16",
"uint32_t": "uint32",
"uint64_t": "uint64",
"int8_t": "int8",
"int16_t": "int16",
"int32_t": "int32",
"double": "float64",
"float": "float32",
"complex64_t": "complex64",
"complex128_t": "complex128",
"void*": "uintptr", # should be uint64 or uint32 depending on ptr size, but this works too
"uint8": "byte",
"int32": "rune",
"int": "void*", # int in GO depends on architecture size
}
ida_typeinf.idc_parse_types("struct BUILTIN_INTERFACE{void *tab;void *data;};", ida_typeinf.HTI_PAKDEF | ida_typeinf.HTI_DCL)
ida_typeinf.idc_parse_types("struct BUILTIN_STRING{char *ptr;size_t len;};", ida_typeinf.HTI_PAKDEF | ida_typeinf.HTI_DCL)
ida_typeinf.idc_parse_types("struct complex64_t{float real;float imag;};", ida_typeinf.HTI_PAKDEF | ida_typeinf.HTI_DCL)
ida_typeinf.idc_parse_types("struct complex128_t{double real;double imag;};", ida_typeinf.HTI_PAKDEF | ida_typeinf.HTI_DCL)
for ida_type, gotype in type_map.items():
ida_typeinf.idc_parse_types(f"typedef {ida_type} {gotype};", ida_typeinf.HTI_PAKDEF | ida_typeinf.HTI_DCL)
def forward_declare_structs(types):
for typ in types:
if typ['Kind'] == 'Struct':
if typ["Kind"] == "Struct":
ida_typeinf.idc_parse_types(f"struct {typ['CStr']};", ida_typeinf.HTI_PAKDEF | ida_typeinf.HTI_DCL)
hints = ida_kernwin.ask_file(0, "*.*", "GoReSym output file")
with open(hints, "r", encoding="utf-8") as rp:
buf = rp.read()
hints = json.loads(buf)
if iterable(hints['UserFunctions']):
for func in hints['UserFunctions']:
ida_bytes.del_items(func['Start'])
ida_funcs.add_func(func['Start'])
print("Renaming %s to %s" % (hex(func['Start']), func['FullName']))
idaapi.add_func(func['Start'], func['End'])
idaapi.set_name(func['Start'], func['FullName'], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
if iterable(hints['StdFunctions']):
for func in hints['StdFunctions']:
print("Renaming %s to %s" % (hex(func['Start']), func['FullName']))
ida_bytes.del_items(func['Start'])
ida_funcs.add_func(func['Start'])
idaapi.add_func(func['Start'], func['End'])
idaapi.set_name(func['Start'], func['FullName'], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
def main(json_file):
with open(json_file, "r", encoding="utf-8") as rp:
buf = rp.read()
if iterable(hints['Types']):
import_primitives()
# we must do this to prevent IDA from creating an invalid struct of type int when we import things like typedef <class>* <newname>.
# it would have made typedef struct <class> int; without a forward declaration. That would then break importing the class later with redefinition error.
forward_declare_structs(hints['Types'])
for typ in hints['Types'][::-1]:
if typ.get('CReconstructed'):
errors = ida_typeinf.idc_parse_types(typ['CReconstructed'] + ";", ida_typeinf.HTI_PAKDEF | ida_typeinf.HTI_DCL)
if errors > 0:
print(typ['CReconstructed'], "failed to import")
# just for precation
resync_local_types()
for typ in hints['Types']:
print("Renaming %s to %s" % (hex(typ['VA']), typ['Str']))
idaapi.set_name(typ['VA'], typ['Str'], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
# IDA often thinks these are string pointers, lets undefine that, then set the type correctly
ida_bytes.del_items(typ['VA'], 0, 4)
abi_typ = get_type_by_name("abi_Type")
idaapi.apply_tinfo(typ['VA'], abi_typ, idaapi.TINFO_DEFINITE)
hints = json.loads(buf)
if iterable(hints["UserFunctions"]):
for func in hints["UserFunctions"]:
ida_bytes.del_items(func["Start"])
ida_funcs.add_func(func["Start"])
print("Renaming %s to %s" % (hex(func["Start"]), func["FullName"]))
idaapi.add_func(func["Start"], func["End"])
idaapi.set_name(func["Start"], func["FullName"], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
if iterable(hints['Interfaces']):
for typ in hints['Interfaces']:
print("Renaming %s to %s" % (hex(typ['VA']), typ['Str']))
idaapi.set_name(typ['VA'], typ['Str'], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
# IDA often thinks these are string pointers, lets undefine that, then set the type correctly
ida_bytes.del_items(typ['VA'], 0, 4)
abi_typ = get_type_by_name("abi_Type")
idaapi.apply_tinfo(typ['VA'], abi_typ, idaapi.TINFO_DEFINITE)
if iterable(hints["StdFunctions"]):
for func in hints["StdFunctions"]:
print("Renaming %s to %s" % (hex(func["Start"]), func["FullName"]))
ida_bytes.del_items(func["Start"])
ida_funcs.add_func(func["Start"])
idaapi.add_func(func["Start"], func["End"])
idaapi.set_name(func["Start"], func["FullName"], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
if hints['TabMeta'] is not None:
tabmeta = hints['TabMeta']
va = tabmeta['VA']
if va is not None and va != 0:
idaapi.set_name(va, 'runtime_pclntab', idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
if iterable(hints["Types"]):
import_primitives()
if hints['ModuleMeta'] is not None:
modmeta = hints['ModuleMeta']
va = modmeta['VA']
if va is not None and va != 0:
idaapi.set_name(va, 'runtime_firstmoduledata', idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
# we must do this to prevent IDA from creating an invalid struct of type int when we import things like typedef <class>* <newname>.
# it would have made typedef struct <class> int; without a forward declaration. That would then break importing the class later with redefinition error.
forward_declare_structs(hints["Types"])
for typ in hints["Types"][::-1]:
if typ.get("CReconstructed"):
errors = ida_typeinf.idc_parse_types(typ["CReconstructed"] + ";", ida_typeinf.HTI_PAKDEF | ida_typeinf.HTI_DCL)
if errors > 0:
print(typ["CReconstructed"], "failed to import")
# just for precation
# resync_local_types()
for typ in hints["Types"]:
print("Renaming %s to %s" % (hex(typ["VA"]), typ["Str"]))
idaapi.set_name(typ["VA"], typ["Str"], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
# IDA often thinks these are string pointers, lets undefine that, then set the type correctly
ida_bytes.del_items(typ["VA"], 0, 4)
abi_typ = get_type_by_name("abi_Type")
idaapi.apply_tinfo(typ["VA"], abi_typ, idaapi.TINFO_DEFINITE)
if iterable(hints["Interfaces"]):
for typ in hints["Interfaces"]:
print("Renaming %s to %s" % (hex(typ["VA"]), typ["Str"]))
idaapi.set_name(typ["VA"], typ["Str"], idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
# IDA often thinks these are string pointers, lets undefine that, then set the type correctly
ida_bytes.del_items(typ["VA"], 0, 4)
abi_typ = get_type_by_name("abi_Type")
idaapi.apply_tinfo(typ["VA"], abi_typ, idaapi.TINFO_DEFINITE)
if hints["TabMeta"] is not None:
tabmeta = hints["TabMeta"]
va = tabmeta["VA"]
if va is not None and va != 0:
idaapi.set_name(va, "runtime_pclntab", idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
if hints["ModuleMeta"] is not None:
modmeta = hints["ModuleMeta"]
va = modmeta["VA"]
if va is not None and va != 0:
idaapi.set_name(va, "runtime_firstmoduledata", idaapi.SN_NOWARN | idaapi.SN_NOCHECK | ida_name.SN_FORCE)
def getargs() -> str:
import argparse
parser = argparse.ArgumentParser(description="Apply GoReSym renaming and type hints to an IDA database.")
parser.add_argument("binary", help="Path to the Go binary file.")
parser.add_argument("json_file", help="Path to the GoReSym output JSON file.")
args = parser.parse_args()
assert os.path.isfile(args.json_file)
assert os.path.isfile(args.binary)
idapro.open_database(args.binary, run_auto_analysis=True)
atexit.register(idapro.close_database, True)
return args.json_file
if __name__ == "__main__":
json_path = None
if CLI_AVAILABLE and ida_kernwin.is_ida_library(None, 0, None):
json_path = getargs()
else:
json_path = ida_kernwin.ask_file(0, "*.json", "GoReSym output file")
assert json_path is not None and os.path.isfile(json_path)
main(json_path)