Overhaul disassembler script + add Binary Ninja target (#12)
* Overhaul diassembler scripts: - No longer defines top level functions - Split into three classes: StatusHandler (like before), DisassemblerInterface (for interfacing with the used program API), ScriptContext (for definiting general functions that use the disassembler interface) - Add type annotations to all class methods and remove 2.7 compatibility stuff (Ghidra now supports Python 3 so this is unnecessary anymore) - Disassembler backends are now responsible for launching metadata/script processing, to better support disassembler differences - String handling is back in the base ScriptContext class, disassembler interfaces opt into the fake string segment creation and fall back to the old method if it isn't supported * Add Binary Ninja disassembler script backend This uses the new backend-controlled execution to launch metadata processing on a background thread to keep the ui responsive * make binary ninja script use own _BINARYNINJA_ define and add define helpers to header * Update README to account for new script and binary ninja backend * implement fake string segment functions for binary ninja but don't advertise support * also cache API function types in binary ninja backend * fix ida script and disable folders again
This commit is contained in:
@@ -38,14 +38,15 @@ namespace Il2CppInspector.Outputs
|
||||
using var fs = new FileStream(typeHeaderFile, FileMode.Create);
|
||||
_writer = new StreamWriter(fs, Encoding.ASCII);
|
||||
|
||||
const string decompilerIfDef = "#if !defined(_GHIDRA_) && !defined(_IDA_) && !defined(_IDACLANG_)";
|
||||
|
||||
using (_writer)
|
||||
{
|
||||
writeHeader();
|
||||
|
||||
// Write primitive type definitions for when we're not including other headers
|
||||
writeCode($"""
|
||||
#define IS_LIBCLANG_DECOMPILER (defined(_IDACLANG_) || defined(_BINARYNINJA_))
|
||||
#define IS_DECOMPILER (defined(_GHIDRA_) || defined(_IDA_) || IS_LIBCLANG_DECOMPILER)
|
||||
|
||||
#if defined(_GHIDRA_) || defined(_IDA_)
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
@@ -57,7 +58,7 @@ namespace Il2CppInspector.Outputs
|
||||
typedef __int64 int64_t;
|
||||
#endif
|
||||
|
||||
#ifdef _IDACLANG_
|
||||
#if IS_LIBCLANG_DECOMPILER
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
@@ -68,13 +69,13 @@ namespace Il2CppInspector.Outputs
|
||||
typedef long int64_t;
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || defined(_IDACLANG_)
|
||||
#if defined(_GHIDRA_) || IS_LIBCLANG_DECOMPILER
|
||||
typedef int{_model.Package.BinaryImage.Bits}_t intptr_t;
|
||||
typedef uint{_model.Package.BinaryImage.Bits}_t uintptr_t;
|
||||
typedef uint{_model.Package.BinaryImage.Bits}_t size_t;
|
||||
#endif
|
||||
|
||||
{decompilerIfDef}
|
||||
#if !IS_DECOMPILER
|
||||
#define _CPLUSPLUS_
|
||||
#endif
|
||||
""");
|
||||
@@ -114,7 +115,7 @@ namespace Il2CppInspector.Outputs
|
||||
}
|
||||
|
||||
// C does not support namespaces
|
||||
writeCode($"{decompilerIfDef}");
|
||||
writeCode("#if !IS_DECOMPILER");
|
||||
writeCode("namespace app {");
|
||||
writeCode("#endif");
|
||||
writeLine("");
|
||||
@@ -124,7 +125,7 @@ namespace Il2CppInspector.Outputs
|
||||
writeTypesForGroup("Application types from usages", "types_from_usages");
|
||||
writeTypesForGroup("Application unused value types", "unused_concrete_types");
|
||||
|
||||
writeCode($"{decompilerIfDef}");
|
||||
writeCode("#if !IS_DECOMPILER");
|
||||
writeCode("}");
|
||||
writeCode("#endif");
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ namespace Il2CppInspector.Outputs
|
||||
public static IEnumerable<string> GetAvailableTargets() {
|
||||
var ns = typeof(PythonScript).Namespace + ".ScriptResources.Targets";
|
||||
var res = ResourceHelper.GetNamesForNamespace(ns);
|
||||
return res.Select(s => Path.GetFileNameWithoutExtension(s.Substring(ns.Length + 1))).OrderBy(s => s);
|
||||
return res
|
||||
.Select(s => Path.GetFileNameWithoutExtension(s[(ns.Length + 1)..]))
|
||||
.OrderBy(s => s);
|
||||
}
|
||||
|
||||
// Output script file
|
||||
@@ -52,12 +54,11 @@ namespace Il2CppInspector.Outputs
|
||||
|
||||
var jsonMetadataRelativePath = getRelativePath(outputFile, jsonMetadataFile);
|
||||
|
||||
var ns = typeof(PythonScript).Namespace + ".ScriptResources";
|
||||
var preamble = ResourceHelper.GetText(ns + ".shared-preamble.py");
|
||||
var main = ResourceHelper.GetText(ns + ".shared-main.py");
|
||||
var api = ResourceHelper.GetText($"{ns}.Targets.{target}.py");
|
||||
var ns = $"{typeof(PythonScript).Namespace}.ScriptResources";
|
||||
var baseScipt = ResourceHelper.GetText($"{ns}.shared_base.py");
|
||||
var impl = ResourceHelper.GetText($"{ns}.Targets.{target}.py");
|
||||
|
||||
var script = string.Join("\n", new [] { preamble, api, main })
|
||||
var script = string.Join("\n", baseScipt, impl)
|
||||
.Replace("%SCRIPTFILENAME%", Path.GetFileName(outputFile))
|
||||
.Replace("%TYPE_HEADER_RELATIVE_PATH%", typeHeaderRelativePath.ToEscapedString())
|
||||
.Replace("%JSON_METADATA_RELATIVE_PATH%", jsonMetadataRelativePath.ToEscapedString())
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
from binaryninja import *
|
||||
|
||||
#try:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
# import json
|
||||
# import os
|
||||
# import sys
|
||||
# from datetime import datetime
|
||||
#except:
|
||||
# pass
|
||||
|
||||
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
# this is implemented,
|
||||
# however the write API does not seem to work properly here (possibly a bug),
|
||||
# so this is disabled for now
|
||||
supports_fake_string_segment: bool = False
|
||||
|
||||
_status: BaseStatusHandler
|
||||
|
||||
_view: BinaryView
|
||||
_undo_id: str
|
||||
_components: dict[str, Component]
|
||||
_type_cache: dict[str, Type]
|
||||
_function_type_cache: dict[str, Type]
|
||||
|
||||
_address_size: int
|
||||
_endianness: Literal["little", "big"]
|
||||
|
||||
def __init__(self, status: BaseStatusHandler):
|
||||
self._status = status
|
||||
|
||||
def _get_or_create_type(self, type: str) -> Type:
|
||||
if type.startswith("struct "):
|
||||
type = type[len("struct "):]
|
||||
elif type.startswith("class "):
|
||||
type = type[len("class "):]
|
||||
|
||||
if type in self._type_cache:
|
||||
return self._type_cache[type]
|
||||
|
||||
if type.endswith("*"):
|
||||
base_type = self._get_or_create_type(type[:-1].strip())
|
||||
|
||||
parsed = PointerType.create(self._view.arch, base_type) # type: ignore
|
||||
else:
|
||||
parsed = self._view.get_type_by_name(type)
|
||||
if parsed is None:
|
||||
parsed, errors = self._view.parse_type_string(type)
|
||||
|
||||
self._type_cache[type] = parsed
|
||||
return parsed
|
||||
|
||||
def get_script_directory(self) -> str:
|
||||
return CURRENT_PATH
|
||||
|
||||
def on_start(self):
|
||||
self._view = bv # type: ignore
|
||||
self._undo_id = self._view.begin_undo_actions()
|
||||
self._view.set_analysis_hold(True)
|
||||
self._components = {}
|
||||
self._type_cache = {}
|
||||
self._function_type_cache = {}
|
||||
|
||||
self._address_size = self._view.address_size
|
||||
self._endianness = "little" if self._view.endianness == Endianness.LittleEndian else "big"
|
||||
|
||||
self._status.update_step("Parsing header")
|
||||
|
||||
with open(os.path.join(self.get_script_directory(), "il2cpp.h"), "r") as f:
|
||||
parsed_types, errors = TypeParser.default.parse_types_from_source(
|
||||
f.read(),
|
||||
"il2cpp.h",
|
||||
self._view.platform if self._view.platform is not None else Platform["windows-x86_64"],
|
||||
self._view,
|
||||
[
|
||||
"--target=x86_64-pc-linux",
|
||||
"-x", "c++",
|
||||
"-D_BINARYNINJA_=1"
|
||||
]
|
||||
)
|
||||
|
||||
if parsed_types is None:
|
||||
log_error("Failed to import header")
|
||||
log_error(errors)
|
||||
return
|
||||
|
||||
self._status.update_step("Importing header types", len(parsed_types.types))
|
||||
|
||||
def import_progress_func(progress: int, total: int):
|
||||
self._status.update_progress(1)
|
||||
return True
|
||||
|
||||
self._view.define_user_types([(x.name, x.type) for x in parsed_types.types], import_progress_func)
|
||||
|
||||
def on_finish(self):
|
||||
self._view.commit_undo_actions(self._undo_id)
|
||||
self._view.set_analysis_hold(False)
|
||||
self._view.update_analysis()
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
if self._view.get_function_at(address) is not None:
|
||||
return
|
||||
|
||||
self._view.create_user_function(address)
|
||||
|
||||
def define_data_array(self, address: int, type: str, count: int):
|
||||
parsed_type = self._get_or_create_type(type)
|
||||
array_type = ArrayType.create(parsed_type, count)
|
||||
var = self._view.get_data_var_at(address)
|
||||
if var is None:
|
||||
self._view.define_user_data_var(address, array_type)
|
||||
else:
|
||||
var.type = array_type
|
||||
|
||||
def set_data_type(self, address: int, type: str):
|
||||
var = self._view.get_data_var_at(address)
|
||||
dtype = self._get_or_create_type(type)
|
||||
if var is None:
|
||||
self._view.define_user_data_var(address, dtype)
|
||||
else:
|
||||
var.type = dtype
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
if type in self._function_type_cache:
|
||||
function.type = self._function_type_cache[type] # type: ignore
|
||||
else:
|
||||
#log_info(f"skipping function type setting for {address}, {type}")
|
||||
#pass
|
||||
function.type = type.replace("this", "`this`")
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
self._view.set_comment_at(address, cmt)
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
function.comment = cmt
|
||||
|
||||
def set_data_name(self, address: int, name: str):
|
||||
var = self._view.get_data_var_at(address)
|
||||
if var is None:
|
||||
return
|
||||
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
var.name = get_qualified_name(demangled)
|
||||
else:
|
||||
var.name = name
|
||||
|
||||
def set_function_name(self, address: int, name: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
function.name = get_qualified_name(demangled)
|
||||
#function.type = type - this does not work due to the generated types not being namespaced. :(
|
||||
else:
|
||||
function.name = name
|
||||
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
self._view.add_user_data_ref(from_address, to_address)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
self._view.define_user_type(None, type_def)
|
||||
|
||||
# optional
|
||||
def _get_or_create_component(self, name: str):
|
||||
if name in self._components:
|
||||
return self._components[name]
|
||||
|
||||
current = name
|
||||
if current.count("/") != 0:
|
||||
split_idx = current.rindex("/")
|
||||
parent, child = current[:split_idx], current[split_idx:]
|
||||
parent = self._get_or_create_component(name)
|
||||
component = self._view.create_component(child, parent)
|
||||
else:
|
||||
component = self._view.create_component(name)
|
||||
|
||||
self._components[name] = component
|
||||
return component
|
||||
|
||||
def add_function_to_group(self, address: int, group: str):
|
||||
return
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
self._get_or_create_component(group).add_function(function)
|
||||
|
||||
def cache_function_types(self, signatures: list[str]):
|
||||
function_sigs = set(signatures)
|
||||
if len(function_sigs) == 0:
|
||||
return
|
||||
|
||||
typestr = ";\n".join(function_sigs).replace("this", "_this") + ";"
|
||||
res = self._view.parse_types_from_string(typestr)
|
||||
for function_sig, function in zip(function_sigs, res.functions.values()): # type: ignore
|
||||
self._function_type_cache[function_sig] = function
|
||||
|
||||
# only required if supports_fake_string_segment == True
|
||||
def create_fake_segment(self, name: str, size: int) -> int:
|
||||
last_end_addr = self._view.mapped_address_ranges[-1].end
|
||||
if last_end_addr % 0x1000 != 0:
|
||||
last_end_addr += (0x1000 - (last_end_addr % 0x1000))
|
||||
|
||||
self._view.add_user_segment(last_end_addr, size, 0, 0, SegmentFlag.SegmentContainsData)
|
||||
self._view.add_user_section(name, last_end_addr, size, SectionSemantics.ReadOnlyDataSectionSemantics)
|
||||
return last_end_addr
|
||||
|
||||
def write_string(self, address: int, value: str):
|
||||
self._view.write(address, value.encode() + b"\x00")
|
||||
|
||||
def write_address(self, address: int, value: int):
|
||||
self._view.write(address, value.to_bytes(self._address_size, self._endianness))
|
||||
|
||||
|
||||
class BinaryNinjaStatusHandler(BaseStatusHandler):
|
||||
def __init__(self, thread: BackgroundTaskThread):
|
||||
self.step = "Initializing"
|
||||
self.max_items = 0
|
||||
self.current_items = 0
|
||||
self.start_time = datetime.now()
|
||||
self.step_start_time = self.start_time
|
||||
self.last_updated_time = datetime.min
|
||||
self._thread = thread
|
||||
|
||||
def initialize(self): pass
|
||||
|
||||
def update(self):
|
||||
if self.was_cancelled():
|
||||
raise RuntimeError("Cancelled script.")
|
||||
|
||||
current_time = datetime.now()
|
||||
if 0.5 > (current_time - self.last_updated_time).total_seconds():
|
||||
return
|
||||
|
||||
self.last_updated_time = current_time
|
||||
|
||||
step_time = current_time - self.step_start_time
|
||||
total_time = current_time - self.start_time
|
||||
self._thread.progress = f"Processing IL2CPP metadata: {self.step} ({self.current_items}/{self.max_items}), elapsed: {step_time} ({total_time})"
|
||||
|
||||
def update_step(self, step, max_items = 0):
|
||||
self.step = step
|
||||
self.max_items = max_items
|
||||
self.current_items = 0
|
||||
self.step_start_time = datetime.now()
|
||||
self.last_updated_time = datetime.min
|
||||
self.update()
|
||||
|
||||
def update_progress(self, new_progress = 1):
|
||||
self.current_items += new_progress
|
||||
self.update()
|
||||
|
||||
def was_cancelled(self): return False
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
# Entry point
|
||||
class Il2CppTask(BackgroundTaskThread):
|
||||
def __init__(self):
|
||||
BackgroundTaskThread.__init__(self, "Processing IL2CPP metadata...", False)
|
||||
|
||||
def run(self):
|
||||
status = BinaryNinjaStatusHandler(self)
|
||||
backend = BinaryNinjaDisassemblerInterface(status)
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
|
||||
Il2CppTask().start()
|
||||
@@ -1,106 +1,124 @@
|
||||
# Ghidra-specific implementation
|
||||
from ghidra.app.cmd.function import ApplyFunctionSignatureCmd
|
||||
from ghidra.app.script import GhidraScriptUtil
|
||||
from ghidra.app.util.cparser.C import CParserUtils
|
||||
from ghidra.program.model.data import ArrayDataType
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
from ghidra.program.model.symbol import RefType
|
||||
from ghidra.app.cmd.label import DemanglerCmd
|
||||
|
||||
xrefs = currentProgram.getReferenceManager()
|
||||
#try:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
# import json
|
||||
# import os
|
||||
# import sys
|
||||
# from datetime import datetime
|
||||
#except:
|
||||
# pass
|
||||
|
||||
def set_name(addr, name):
|
||||
if not name.startswith("_ZN"):
|
||||
createLabel(toAddr(addr), name, True)
|
||||
return
|
||||
cmd = DemanglerCmd(currentAddress.getAddress(hex(addr)), name)
|
||||
if not cmd.applyTo(currentProgram, monitor):
|
||||
print("Failed to apply demangled name to %s at %s due %s, falling back to mangled" % (name, hex(addr), cmd.getStatusMsg()))
|
||||
createLabel(toAddr(addr), name, True)
|
||||
class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
supports_fake_string_segment = False
|
||||
|
||||
def make_function(start, end = None):
|
||||
addr = toAddr(start)
|
||||
# Don't override existing functions
|
||||
fn = getFunctionAt(addr)
|
||||
if fn is None:
|
||||
# Create new function if none exists
|
||||
createFunction(addr, None)
|
||||
def get_script_directory(self) -> str:
|
||||
return getSourceFile().getParentFile().toString()
|
||||
|
||||
def make_array(addr, numItems, cppType):
|
||||
if cppType.startswith('struct '):
|
||||
cppType = cppType[7:]
|
||||
|
||||
t = getDataTypes(cppType)[0]
|
||||
a = ArrayDataType(t, numItems, t.getLength())
|
||||
addr = toAddr(addr)
|
||||
removeDataAt(addr)
|
||||
createData(addr, a)
|
||||
def on_start(self):
|
||||
self.xrefs = currentProgram.getReferenceManager()
|
||||
|
||||
def define_code(code):
|
||||
# Code declarations are not supported in Ghidra
|
||||
# This only affects string literals for metadata version < 19
|
||||
# TODO: Replace with creating a DataType for enums
|
||||
# Check that the user has parsed the C headers first
|
||||
if len(getDataTypes('Il2CppObject')) == 0:
|
||||
print('STOP! You must import the generated C header file (%TYPE_HEADER_RELATIVE_PATH%) before running this script.')
|
||||
print('See https://github.com/djkaty/Il2CppInspector/blob/master/README.md#adding-metadata-to-your-ghidra-workflow for instructions.')
|
||||
sys.exit()
|
||||
|
||||
# Ghidra sets the image base for ELF to 0x100000 for some reason
|
||||
# https://github.com/NationalSecurityAgency/ghidra/issues/1020
|
||||
# Make sure that the base address is 0
|
||||
# Without this, Ghidra may not analyze the binary correctly and you will just waste your time
|
||||
# If 0 doesn't work for you, replace it with the base address from the output of the CLI or GUI
|
||||
if currentProgram.getExecutableFormat().endswith('(ELF)'):
|
||||
currentProgram.setImageBase(toAddr(0), True)
|
||||
|
||||
# Don't trigger decompiler
|
||||
setAnalysisOption(currentProgram, "Call Convention ID", "false")
|
||||
|
||||
def on_finish(self):
|
||||
pass
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
address = toAddr(address)
|
||||
# Don't override existing functions
|
||||
fn = getFunctionAt(address)
|
||||
if fn is None:
|
||||
# Create new function if none exists
|
||||
createFunction(address, None)
|
||||
|
||||
def define_data_array(self, address: int, type: str, count: int):
|
||||
if type.startswith('struct '):
|
||||
type = type[7:]
|
||||
|
||||
t = getDataTypes(type)[0]
|
||||
a = ArrayDataType(t, count, t.getLength())
|
||||
address = toAddr(address)
|
||||
removeDataAt(address)
|
||||
createData(address, a)
|
||||
|
||||
def set_data_type(self, address: int, type: str):
|
||||
if type.startswith('struct '):
|
||||
type = type[7:]
|
||||
|
||||
try:
|
||||
t = getDataTypes(type)[0]
|
||||
address = toAddr(address)
|
||||
removeDataAt(address)
|
||||
createData(address, t)
|
||||
except:
|
||||
print("Failed to set type: %s" % type)
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
make_function(address)
|
||||
typeSig = CParserUtils.parseSignature(None, currentProgram, type)
|
||||
ApplyFunctionSignatureCmd(toAddr(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
setEOLComment(toAddr(address), cmt)
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
setPlateComment(toAddr(address), cmt)
|
||||
|
||||
def set_data_name(self, address: int, name: str):
|
||||
address = toAddr(address)
|
||||
|
||||
if len(name) > 2000:
|
||||
print("Name length exceeds 2000 characters, skipping (%s)" % name)
|
||||
return
|
||||
|
||||
if not name.startswith("_ZN"):
|
||||
createLabel(address, name, True)
|
||||
return
|
||||
|
||||
cmd = DemanglerCmd(address, name)
|
||||
if not cmd.applyTo(currentProgram, monitor):
|
||||
print(f"Failed to apply demangled name to {name} at {address} due {cmd.getStatusMsg()}, falling back to mangled")
|
||||
createLabel(address, name, True)
|
||||
|
||||
def set_function_name(self, address: int, name: str):
|
||||
return self.set_data_name(address, name)
|
||||
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
self.xrefs.addMemoryReference(toAddr(from_address), toAddr(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
# Code declarations are not supported in Ghidra
|
||||
# This only affects string literals for metadata version < 19
|
||||
# TODO: Replace with creating a DataType for enums
|
||||
pass
|
||||
|
||||
class GhidraStatusHandler(BaseStatusHandler):
|
||||
pass
|
||||
|
||||
def set_function_type(addr, sig):
|
||||
make_function(addr)
|
||||
typeSig = CParserUtils.parseSignature(None, currentProgram, sig)
|
||||
ApplyFunctionSignatureCmd(toAddr(addr), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
|
||||
|
||||
def set_type(addr, cppType):
|
||||
if cppType.startswith('struct '):
|
||||
cppType = cppType[7:]
|
||||
|
||||
try:
|
||||
t = getDataTypes(cppType)[0]
|
||||
addr = toAddr(addr)
|
||||
removeDataAt(addr)
|
||||
createData(addr, t)
|
||||
except:
|
||||
print("Failed to set type: %s" % cppType)
|
||||
|
||||
def set_comment(addr, text):
|
||||
setEOLComment(toAddr(addr), text)
|
||||
|
||||
def set_header_comment(addr, text):
|
||||
setPlateComment(toAddr(addr), text)
|
||||
|
||||
def script_prologue(status):
|
||||
# Check that the user has parsed the C headers first
|
||||
if len(getDataTypes('Il2CppObject')) == 0:
|
||||
print('STOP! You must import the generated C header file (%TYPE_HEADER_RELATIVE_PATH%) before running this script.')
|
||||
print('See https://github.com/djkaty/Il2CppInspector/blob/master/README.md#adding-metadata-to-your-ghidra-workflow for instructions.')
|
||||
sys.exit()
|
||||
|
||||
# Ghidra sets the image base for ELF to 0x100000 for some reason
|
||||
# https://github.com/NationalSecurityAgency/ghidra/issues/1020
|
||||
# Make sure that the base address is 0
|
||||
# Without this, Ghidra may not analyze the binary correctly and you will just waste your time
|
||||
# If 0 doesn't work for you, replace it with the base address from the output of the CLI or GUI
|
||||
if currentProgram.getExecutableFormat().endswith('(ELF)'):
|
||||
currentProgram.setImageBase(toAddr(0), True)
|
||||
|
||||
# Don't trigger decompiler
|
||||
setAnalysisOption(currentProgram, "Call Convention ID", "false")
|
||||
|
||||
def get_script_directory(): return getSourceFile().getParentFile().toString()
|
||||
|
||||
def script_epilogue(status): pass
|
||||
def add_function_to_group(addr, group): pass
|
||||
def add_xref(addr, to):
|
||||
xrefs.addMemoryReference(currentAddress.getAddress(hex(addr)), currentAddress.getAddress(hex(to)), RefType.DATA, SourceType.USER_DEFINED, 0)
|
||||
|
||||
def process_string_literals(status, data):
|
||||
for d in jsonData['stringLiterals']:
|
||||
define_string(d)
|
||||
|
||||
# I don't know how to make inline strings in Ghidra
|
||||
# Just revert back original impl
|
||||
addr = parse_address(d)
|
||||
set_name(addr, d['name'])
|
||||
set_type(addr, r'struct String *')
|
||||
set_comment(addr, d['string'])
|
||||
|
||||
status.update_progress()
|
||||
|
||||
class StatusHandler(BaseStatusHandler): pass
|
||||
status = GhidraStatusHandler()
|
||||
backend = GhidraDisassemblerInterface()
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
@@ -8,6 +8,8 @@ import ida_nalt
|
||||
import ida_ida
|
||||
import ida_ua
|
||||
import ida_segment
|
||||
import ida_funcs
|
||||
import ida_xref
|
||||
|
||||
try: # 7.7+
|
||||
import ida_srclang
|
||||
@@ -23,206 +25,205 @@ try:
|
||||
except ImportError:
|
||||
FOLDERS_AVAILABLE = False
|
||||
|
||||
cached_genflags = 0
|
||||
skip_make_function = False
|
||||
func_dirtree = None
|
||||
is_32_bit = False
|
||||
fake_segments_base = None
|
||||
|
||||
def script_prologue(status):
|
||||
global cached_genflags, skip_make_function, func_dirtree, is_32_bit, fake_segments_base
|
||||
# Disable autoanalysis
|
||||
cached_genflags = ida_ida.inf_get_genflags()
|
||||
ida_ida.inf_set_genflags(cached_genflags & ~ida_ida.INFFL_AUTO)
|
||||
|
||||
# Unload type libraries we know to cause issues - like the c++ linux one
|
||||
PLATFORMS = ["x86", "x64", "arm", "arm64"]
|
||||
PROBLEMATIC_TYPELIBS = ["gnulnx"]
|
||||
|
||||
for lib in PROBLEMATIC_TYPELIBS:
|
||||
for platform in PLATFORMS:
|
||||
ida_typeinf.del_til(f"{lib}_{platform}")
|
||||
|
||||
# Set name mangling to GCC 3.x and display demangled as default
|
||||
ida_ida.inf_set_demnames(ida_ida.DEMNAM_GCC3 | ida_ida.DEMNAM_NAME)
|
||||
|
||||
status.update_step('Processing Types')
|
||||
|
||||
if IDACLANG_AVAILABLE:
|
||||
header_path = os.path.join(get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%")
|
||||
ida_srclang.set_parser_argv("clang", "-target x86_64-pc-linux -x c++ -D_IDACLANG_=1") # -target required for 8.3+
|
||||
ida_srclang.parse_decls_with_parser("clang", None, header_path, True)
|
||||
else:
|
||||
original_macros = ida_typeinf.get_c_macros()
|
||||
ida_typeinf.set_c_macros(original_macros + ";_IDA_=1")
|
||||
ida_typeinf.idc_parse_types(os.path.join(get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%"), ida_typeinf.PT_FILE)
|
||||
ida_typeinf.set_c_macros(original_macros)
|
||||
|
||||
# Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow
|
||||
skip_make_function = ida_segment.get_segm_by_name(".pdata") is not None
|
||||
if skip_make_function:
|
||||
print(".pdata section found, skipping function boundaries")
|
||||
|
||||
if FOLDERS_AVAILABLE:
|
||||
func_dirtree = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS)
|
||||
|
||||
is_32_bit = ida_ida.inf_is_32bit_exactly()
|
||||
|
||||
def script_epilogue(status):
|
||||
# Reenable auto-analysis
|
||||
global cached_genflags
|
||||
ida_ida.inf_set_genflags(cached_genflags)
|
||||
|
||||
# Utility methods
|
||||
|
||||
def set_name(addr, name):
|
||||
ida_name.set_name(addr, name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
def make_function(start, end = None):
|
||||
global skip_make_function
|
||||
if skip_make_function:
|
||||
return
|
||||
|
||||
ida_bytes.del_items(start, ida_bytes.DELIT_SIMPLE, 12) # Undefine x bytes which should hopefully be enough for the first instruction
|
||||
ida_ua.create_insn(start) # Create instruction at start
|
||||
if not ida_funcs.add_func(start, end if end is not None else ida_idaapi.BADADDR): # This fails if the function doesn't start with an instruction
|
||||
print(f"failed to mark function {hex(start)}-{hex(end) if end is not None else '???'} as function")
|
||||
|
||||
TYPE_CACHE = {}
|
||||
|
||||
def get_type(typeName):
|
||||
if typeName not in TYPE_CACHE:
|
||||
info = ida_typeinf.idc_parse_decl(None, typeName, ida_typeinf.PT_RAWARGS)
|
||||
if info is None:
|
||||
print(f"Failed to create type {typeName}.")
|
||||
return None
|
||||
|
||||
TYPE_CACHE[typeName] = info[1:]
|
||||
|
||||
return TYPE_CACHE[typeName]
|
||||
#try:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
# import json
|
||||
# import os
|
||||
# from datetime import datetime
|
||||
#except:
|
||||
# pass
|
||||
|
||||
TINFO_DEFINITE = 0x0001 # These only exist in idc for some reason, so we redefine it here
|
||||
DEFAULT_TIL: "til_t" = None # type: ignore
|
||||
|
||||
def set_type(addr, cppType):
|
||||
cppType += ';'
|
||||
class IDADisassemblerInterface(BaseDisassemblerInterface):
|
||||
supports_fake_string_segment = True
|
||||
|
||||
info = get_type(cppType)
|
||||
if info is None:
|
||||
return
|
||||
_status: BaseStatusHandler
|
||||
|
||||
if ida_typeinf.apply_type(None, info[0], info[1], addr, TINFO_DEFINITE) is None:
|
||||
print(f"set_type({hex(addr)}, {cppType}); failed!")
|
||||
|
||||
def set_function_type(addr, sig):
|
||||
set_type(addr, sig)
|
||||
|
||||
def make_array(addr, numItems, cppType):
|
||||
set_type(addr, cppType)
|
||||
|
||||
flags = ida_bytes.get_flags(addr)
|
||||
if ida_bytes.is_struct(flags):
|
||||
opinfo = ida_nalt.opinfo_t()
|
||||
ida_bytes.get_opcode(opinfo, addr, 0, flags)
|
||||
entrySize = ida_bytes.get_data_elsize(addr, flags, opinfo)
|
||||
tid = opinfo.tid
|
||||
else:
|
||||
entrySize = ida_bytes.get_item_size(addr)
|
||||
tid = ida_idaapi.BADADDR
|
||||
|
||||
ida_bytes.create_data(addr, flags, numItems * entrySize, tid)
|
||||
|
||||
def define_code(code):
|
||||
ida_typeinf.idc_parse_types(code)
|
||||
|
||||
def set_comment(addr, comment, repeatable = True):
|
||||
ida_bytes.set_cmt(addr, comment, repeatable)
|
||||
|
||||
def set_header_comment(addr, comment):
|
||||
func = ida_funcs.get_func(addr)
|
||||
if func is None:
|
||||
return
|
||||
|
||||
ida_funcs.set_func_cmt(func, comment, True)
|
||||
|
||||
def get_script_directory():
|
||||
return os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
folders = []
|
||||
def add_function_to_group(addr, group):
|
||||
global func_dirtree, folders
|
||||
return
|
||||
|
||||
if not FOLDERS_AVAILABLE:
|
||||
return
|
||||
|
||||
if group not in folders:
|
||||
folders.append(group)
|
||||
func_dirtree.mkdir(group)
|
||||
|
||||
name = ida_funcs.get_func_name(addr)
|
||||
func_dirtree.rename(name, f"{group}/{name}")
|
||||
|
||||
def add_xref(addr, to):
|
||||
ida_xref.add_dref(addr, to, ida_xref.XREF_USER | ida_xref.dr_I)
|
||||
|
||||
def write_string(addr, string):
|
||||
encoded_string = string.encode() + b'\x00'
|
||||
string_length = len(encoded_string)
|
||||
ida_bytes.put_bytes(addr, encoded_string)
|
||||
ida_bytes.create_strlit(addr, string_length, ida_nalt.STRTYPE_C)
|
||||
|
||||
def write_address(addr, value):
|
||||
global is_32_bit
|
||||
|
||||
if is_32_bit:
|
||||
ida_bytes.put_dword(addr, value)
|
||||
else:
|
||||
ida_bytes.put_qword(addr, value)
|
||||
|
||||
def create_fake_segment(name, size):
|
||||
global is_32_bit
|
||||
|
||||
start = ida_ida.inf_get_max_ea()
|
||||
end = start + size
|
||||
|
||||
ida_segment.add_segm(0, start, end, name, "DATA")
|
||||
segment = ida_segment.get_segm_by_name(name)
|
||||
segment.bitness = 1 if is_32_bit else 2
|
||||
segment.perm = ida_segment.SEGPERM_READ
|
||||
segment.update()
|
||||
|
||||
return start
|
||||
|
||||
def process_string_literals(status, data):
|
||||
total_string_length = 0
|
||||
for d in data['stringLiterals']:
|
||||
total_string_length += len(d["string"]) + 1
|
||||
_type_cache: dict
|
||||
_folders: list
|
||||
|
||||
aligned_length = total_string_length + (4096 - (total_string_length % 4096))
|
||||
segment_base = create_fake_segment(".fake_strings", aligned_length)
|
||||
_function_dirtree: "ida_dirtree.dirtree_t"
|
||||
_cached_genflags: int
|
||||
_skip_function_creation: bool
|
||||
_is_32_bit: bool
|
||||
_fake_segments_base: int
|
||||
|
||||
current_string_address = segment_base
|
||||
for d in data['stringLiterals']:
|
||||
define_string(d)
|
||||
def __init__(self, status: BaseStatusHandler):
|
||||
self._status = status
|
||||
|
||||
self._type_cache = {}
|
||||
self._folders = []
|
||||
|
||||
ref_addr = parse_address(d)
|
||||
write_string(current_string_address, d["string"])
|
||||
write_address(ref_addr, current_string_address)
|
||||
set_type(ref_addr, r'const char* const')
|
||||
self._cached_genflags = 0
|
||||
self._skip_function_creation = False
|
||||
self._is_32_bit = False
|
||||
self._fake_segments_base = 0
|
||||
|
||||
current_string_address += len(d["string"]) + 1
|
||||
status.update_progress()
|
||||
def _get_type(self, type: str):
|
||||
if type not in self._type_cache:
|
||||
info = ida_typeinf.idc_parse_decl(DEFAULT_TIL, type, ida_typeinf.PT_RAWARGS)
|
||||
if info is None:
|
||||
print(f"Failed to create type {type}.")
|
||||
return None
|
||||
|
||||
self._type_cache[type] = info[1:]
|
||||
|
||||
return self._type_cache[type]
|
||||
|
||||
def get_script_directory(self) -> str:
|
||||
return os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
def on_start(self):
|
||||
# Disable autoanalysis
|
||||
self._cached_genflags = ida_ida.inf_get_genflags()
|
||||
ida_ida.inf_set_genflags(self._cached_genflags & ~ida_ida.INFFL_AUTO)
|
||||
|
||||
# Unload type libraries we know to cause issues - like the c++ linux one
|
||||
PLATFORMS = ["x86", "x64", "arm", "arm64"]
|
||||
PROBLEMATIC_TYPELIBS = ["gnulnx"]
|
||||
|
||||
for lib in PROBLEMATIC_TYPELIBS:
|
||||
for platform in PLATFORMS:
|
||||
ida_typeinf.del_til(f"{lib}_{platform}")
|
||||
|
||||
# Set name mangling to GCC 3.x and display demangled as default
|
||||
ida_ida.inf_set_demnames(ida_ida.DEMNAM_GCC3 | ida_ida.DEMNAM_NAME)
|
||||
|
||||
self._status.update_step('Processing Types')
|
||||
|
||||
if IDACLANG_AVAILABLE:
|
||||
header_path = os.path.join(self.get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%")
|
||||
ida_srclang.set_parser_argv("clang", "-target x86_64-pc-linux -x c++ -D_IDACLANG_=1") # -target required for 8.3+
|
||||
ida_srclang.parse_decls_with_parser("clang", None, header_path, True)
|
||||
else:
|
||||
original_macros = ida_typeinf.get_c_macros()
|
||||
ida_typeinf.set_c_macros(original_macros + ";_IDA_=1")
|
||||
ida_typeinf.idc_parse_types(os.path.join(self.get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%"), ida_typeinf.PT_FILE)
|
||||
ida_typeinf.set_c_macros(original_macros)
|
||||
|
||||
# Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow
|
||||
skip_make_function = ida_segment.get_segm_by_name(".pdata") is not None
|
||||
if skip_make_function:
|
||||
print(".pdata section found, skipping function boundaries")
|
||||
|
||||
if FOLDERS_AVAILABLE:
|
||||
self._function_dirtree = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS)
|
||||
|
||||
self._is_32_bit = ida_ida.inf_is_32bit_exactly()
|
||||
|
||||
def on_finish(self):
|
||||
ida_ida.inf_set_genflags(self._cached_genflags)
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
if self._skip_function_creation:
|
||||
return
|
||||
|
||||
ida_bytes.del_items(address, ida_bytes.DELIT_SIMPLE, 12) # Undefine x bytes which should hopefully be enough for the first instruction
|
||||
ida_ua.create_insn(address) # Create instruction at start
|
||||
if not ida_funcs.add_func(address, end if end is not None else ida_idaapi.BADADDR): # This fails if the function doesn't start with an instruction
|
||||
print(f"failed to mark function {hex(address)}-{hex(end) if end is not None else '???'} as function")
|
||||
|
||||
def define_data_array(self, address: int, type: str, count: int):
|
||||
self.set_data_type(address, type)
|
||||
|
||||
flags = ida_bytes.get_flags(address)
|
||||
if ida_bytes.is_struct(flags):
|
||||
opinfo = ida_nalt.opinfo_t()
|
||||
ida_bytes.get_opinfo(opinfo, address, 0, flags)
|
||||
entrySize = ida_bytes.get_data_elsize(address, flags, opinfo)
|
||||
tid = opinfo.tid
|
||||
else:
|
||||
entrySize = ida_bytes.get_item_size(address)
|
||||
tid = ida_idaapi.BADADDR
|
||||
|
||||
ida_bytes.create_data(address, flags, count * entrySize, tid)
|
||||
|
||||
def set_data_type(self, address: int, type: str):
|
||||
type += ';'
|
||||
|
||||
info = self._get_type(type)
|
||||
if info is None:
|
||||
return
|
||||
|
||||
if ida_typeinf.apply_type(DEFAULT_TIL, info[0], info[1], address, TINFO_DEFINITE) is None:
|
||||
print(f"set_type({hex(address)}, {type}); failed!")
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
self.set_data_type(address, type)
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
ida_bytes.set_cmt(address, cmt, False)
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
func = ida_funcs.get_func(address)
|
||||
if func is None:
|
||||
return
|
||||
|
||||
ida_funcs.set_func_cmt(func, cmt, True)
|
||||
|
||||
def set_data_name(self, address: int, name: str):
|
||||
ida_name.set_name(address, name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
def set_function_name(self, address: int, name: str):
|
||||
self.set_data_name(address, name)
|
||||
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
ida_xref.add_dref(from_address, to_address, ida_xref.XREF_USER | ida_xref.dr_I)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
ida_typeinf.idc_parse_types(type_def, 0)
|
||||
|
||||
# optional
|
||||
def add_function_to_group(self, address: int, group: str):
|
||||
if not FOLDERS_AVAILABLE or True: # enable at your own risk - this is slow
|
||||
return
|
||||
|
||||
if group not in self._folders:
|
||||
self._folders.append(group)
|
||||
self._function_dirtree.mkdir(group)
|
||||
|
||||
name = ida_funcs.get_func_name(address)
|
||||
self._function_dirtree.rename(name, f"{group}/{name}")
|
||||
|
||||
# only required if supports_fake_string_segment == True
|
||||
def create_fake_segment(self, name: str, size: int) -> int:
|
||||
start = ida_ida.inf_get_max_ea()
|
||||
end = start + size
|
||||
|
||||
ida_segment.add_segm(0, start, end, name, "DATA")
|
||||
segment = ida_segment.get_segm_by_name(name)
|
||||
segment.bitness = 1 if self._is_32_bit else 2
|
||||
segment.perm = ida_segment.SEGPERM_READ
|
||||
segment.update()
|
||||
|
||||
return start
|
||||
|
||||
def write_string(self, address: int, value: str):
|
||||
encoded_string = value.encode() + b'\x00'
|
||||
string_length = len(encoded_string)
|
||||
ida_bytes.put_bytes(address, encoded_string)
|
||||
ida_bytes.create_strlit(address, string_length, ida_nalt.STRTYPE_C)
|
||||
|
||||
def write_address(self, address: int, value: int):
|
||||
if self._is_32_bit:
|
||||
ida_bytes.put_dword(address, value)
|
||||
else:
|
||||
ida_bytes.put_qword(address, value)
|
||||
|
||||
# Status handler
|
||||
|
||||
class StatusHandler(BaseStatusHandler):
|
||||
class IDAStatusHandler(BaseStatusHandler):
|
||||
def __init__(self):
|
||||
self.step = "Initializing"
|
||||
self.max_items = 0
|
||||
self.current_items = 0
|
||||
self.start_time = datetime.datetime.now()
|
||||
self.start_time = datetime.now()
|
||||
self.step_start_time = self.start_time
|
||||
self.last_updated_time = datetime.datetime.min
|
||||
self.last_updated_time = datetime.min
|
||||
|
||||
def initialize(self):
|
||||
ida_kernwin.show_wait_box("Processing")
|
||||
@@ -231,7 +232,7 @@ class StatusHandler(BaseStatusHandler):
|
||||
if self.was_cancelled():
|
||||
raise RuntimeError("Cancelled script.")
|
||||
|
||||
current_time = datetime.datetime.now()
|
||||
current_time = datetime.now()
|
||||
if 0.5 > (current_time - self.last_updated_time).total_seconds():
|
||||
return
|
||||
|
||||
@@ -254,8 +255,8 @@ Elapsed: {step_time} ({total_time})
|
||||
self.step = step
|
||||
self.max_items = max_items
|
||||
self.current_items = 0
|
||||
self.step_start_time = datetime.datetime.now()
|
||||
self.last_updated_time = datetime.datetime.min
|
||||
self.step_start_time = datetime.now()
|
||||
self.last_updated_time = datetime.min
|
||||
self.update()
|
||||
|
||||
def update_progress(self, new_progress = 1):
|
||||
@@ -265,5 +266,10 @@ Elapsed: {step_time} ({total_time})
|
||||
def was_cancelled(self):
|
||||
return ida_kernwin.user_cancelled()
|
||||
|
||||
def close(self):
|
||||
ida_kernwin.hide_wait_box()
|
||||
def shutdown(self):
|
||||
ida_kernwin.hide_wait_box()
|
||||
|
||||
status = IDAStatusHandler()
|
||||
backend = IDADisassemblerInterface(status)
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
@@ -1,182 +0,0 @@
|
||||
# Shared interface
|
||||
def from_hex(addr): return int(addr, 0)
|
||||
|
||||
def parse_address(d): return from_hex(d['virtualAddress'])
|
||||
|
||||
def define_il_method(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_function_type(addr, jsonDef['signature'])
|
||||
set_header_comment(addr, jsonDef['dotNetSignature'])
|
||||
add_function_to_group(addr, jsonDef['group'])
|
||||
|
||||
def define_il_method_info(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_comment(addr, jsonDef['dotNetSignature'])
|
||||
set_type(addr, r'struct MethodInfo *')
|
||||
if 'methodAddress' in jsonDef:
|
||||
add_xref(from_hex(jsonDef["methodAddress"]), addr)
|
||||
|
||||
|
||||
def define_cpp_function(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_function_type(addr, jsonDef['signature'])
|
||||
|
||||
def define_string(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_comment(addr, jsonDef['string'])
|
||||
|
||||
def define_field(addr, name, type, ilType = None):
|
||||
addr = from_hex(addr)
|
||||
set_name(addr, name)
|
||||
set_type(addr, type)
|
||||
if ilType is not None:
|
||||
set_comment(addr, ilType)
|
||||
|
||||
def define_field_from_json(jsonDef):
|
||||
define_field(jsonDef['virtualAddress'], jsonDef['name'], jsonDef['type'], jsonDef['dotNetType'])
|
||||
|
||||
def define_array(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
make_array(addr, int(jsonDef['count']), jsonDef['type'])
|
||||
set_name(addr, jsonDef['name'])
|
||||
|
||||
def define_field_with_value(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_comment(addr, jsonDef['value'])
|
||||
|
||||
# Process JSON
|
||||
def process_json(jsonData, status):
|
||||
# Function boundaries
|
||||
functionAddresses = jsonData['functionAddresses']
|
||||
functionAddresses.sort()
|
||||
count = len(functionAddresses)
|
||||
|
||||
status.update_step('Processing function boundaries', count)
|
||||
for i in range(count):
|
||||
start = from_hex(functionAddresses[i])
|
||||
if start == 0:
|
||||
status.update_progress()
|
||||
continue
|
||||
|
||||
end = from_hex(functionAddresses[i + 1]) if i + 1 != count else None
|
||||
|
||||
make_function(start, end)
|
||||
status.update_progress()
|
||||
|
||||
# Method definitions
|
||||
status.update_step('Processing method definitions', len(jsonData['methodDefinitions']))
|
||||
for d in jsonData['methodDefinitions']:
|
||||
define_il_method(d)
|
||||
status.update_progress()
|
||||
|
||||
# Constructed generic methods
|
||||
status.update_step('Processing constructed generic methods', len(jsonData['constructedGenericMethods']))
|
||||
for d in jsonData['constructedGenericMethods']:
|
||||
define_il_method(d)
|
||||
status.update_progress()
|
||||
|
||||
# Custom attributes generators
|
||||
status.update_step('Processing custom attributes generators', len(jsonData['customAttributesGenerators']))
|
||||
for d in jsonData['customAttributesGenerators']:
|
||||
define_cpp_function(d)
|
||||
status.update_progress()
|
||||
|
||||
# Method.Invoke thunks
|
||||
status.update_step('Processing Method.Invoke thunks', len(jsonData['methodInvokers']))
|
||||
for d in jsonData['methodInvokers']:
|
||||
define_cpp_function(d)
|
||||
status.update_progress()
|
||||
|
||||
# String literals for version >= 19
|
||||
if 'virtualAddress' in jsonData['stringLiterals'][0]:
|
||||
status.update_step('Processing string literals (V19+)', len(jsonData['stringLiterals']))
|
||||
|
||||
process_string_literals(status, jsonData)
|
||||
|
||||
# String literals for version < 19
|
||||
else:
|
||||
status.update_step('Processing string literals (pre-V19)')
|
||||
litDecl = 'enum StringLiteralIndex {\n'
|
||||
for d in jsonData['stringLiterals']:
|
||||
litDecl += " " + d['name'] + ",\n"
|
||||
litDecl += '};\n'
|
||||
define_code(litDecl)
|
||||
|
||||
# Il2CppClass (TypeInfo) pointers
|
||||
status.update_step('Processing Il2CppClass (TypeInfo) pointers', len(jsonData['typeInfoPointers']))
|
||||
for d in jsonData['typeInfoPointers']:
|
||||
define_field_from_json(d)
|
||||
status.update_progress()
|
||||
|
||||
# Il2CppType (TypeRef) pointers
|
||||
status.update_step('Processing Il2CppType (TypeRef) pointers', len(jsonData['typeRefPointers']))
|
||||
for d in jsonData['typeRefPointers']:
|
||||
define_field(d['virtualAddress'], d['name'], r'struct Il2CppType *', d['dotNetType'])
|
||||
status.update_progress()
|
||||
|
||||
# MethodInfo pointers
|
||||
status.update_step('Processing MethodInfo pointers', len(jsonData['methodInfoPointers']))
|
||||
for d in jsonData['methodInfoPointers']:
|
||||
define_il_method_info(d)
|
||||
status.update_progress()
|
||||
|
||||
# FieldInfo pointers, add the contents as a comment
|
||||
status.update_step('Processing FieldInfo pointers', len(jsonData['fields']))
|
||||
for d in jsonData['fields']:
|
||||
define_field_with_value(d)
|
||||
status.update_progress()
|
||||
|
||||
# FieldRva pointers, add the contents as a comment
|
||||
status.update_step('Processing FieldRva pointers', len(jsonData['fieldRvas']))
|
||||
for d in jsonData['fieldRvas']:
|
||||
define_field_with_value(d)
|
||||
status.update_progress()
|
||||
|
||||
# IL2CPP type metadata
|
||||
status.update_step('Processing IL2CPP type metadata', len(jsonData['typeMetadata']))
|
||||
for d in jsonData['typeMetadata']:
|
||||
define_field(d['virtualAddress'], d['name'], d['type'])
|
||||
|
||||
# IL2CPP function metadata
|
||||
status.update_step('Processing IL2CPP function metadata', len(jsonData['functionMetadata']))
|
||||
for d in jsonData['functionMetadata']:
|
||||
define_cpp_function(d)
|
||||
|
||||
# IL2CPP array metadata
|
||||
status.update_step('Processing IL2CPP array metadata', len(jsonData['arrayMetadata']))
|
||||
for d in jsonData['arrayMetadata']:
|
||||
define_array(d)
|
||||
|
||||
# IL2CPP API functions
|
||||
status.update_step('Processing IL2CPP API functions', len(jsonData['apis']))
|
||||
for d in jsonData['apis']:
|
||||
define_cpp_function(d)
|
||||
|
||||
# Entry point
|
||||
print('Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty)')
|
||||
status = StatusHandler()
|
||||
status.initialize()
|
||||
|
||||
try:
|
||||
start_time = datetime.datetime.now()
|
||||
|
||||
status.update_step("Running script prologue")
|
||||
script_prologue(status)
|
||||
|
||||
with open(os.path.join(get_script_directory(), "%JSON_METADATA_RELATIVE_PATH%"), "r") as jsonFile:
|
||||
status.update_step("Loading JSON metadata")
|
||||
jsonData = json.load(jsonFile)['addressMap']
|
||||
process_json(jsonData, status)
|
||||
|
||||
status.update_step("Running script epilogue")
|
||||
script_epilogue(status)
|
||||
|
||||
status.update_step('Script execution complete.')
|
||||
print("Took: %s" % (datetime.datetime.now() - start_time))
|
||||
except RuntimeError: pass
|
||||
finally: status.close()
|
||||
@@ -1,14 +0,0 @@
|
||||
# Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty)
|
||||
# Target Unity version: %TARGET_UNITY_VERSION%
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
class BaseStatusHandler:
|
||||
def initialize(self): pass
|
||||
def update_step(self, name, max_items = 0): print(name)
|
||||
def update_progress(self, progress = 1): pass
|
||||
def was_cancelled(self): return False
|
||||
def close(self): pass
|
||||
290
Il2CppInspector.Common/Outputs/ScriptResources/shared_base.py
Normal file
290
Il2CppInspector.Common/Outputs/ScriptResources/shared_base.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty)
|
||||
# Target Unity version: %TARGET_UNITY_VERSION%
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
import abc
|
||||
|
||||
class BaseStatusHandler(abc.ABC):
|
||||
def initialize(self): pass
|
||||
def shutdown(self): pass
|
||||
|
||||
def update_step(self, name: str, max_items: int = 0): print(name)
|
||||
def update_progress(self, progress: int = 1): pass
|
||||
|
||||
def was_cancelled(self): return False
|
||||
|
||||
class BaseDisassemblerInterface(abc.ABC):
|
||||
supports_fake_string_segment: bool = False
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_script_directory(self) -> str: return ""
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_start(self): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_finish(self): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def define_function(self, address: int, end: int | None = None): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def define_data_array(self, address: int, type: str, count: int): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_data_type(self, address: int, type: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_function_type(self, address: int, type: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_data_comment(self, address: int, cmt: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_function_comment(self, address: int, cmt: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_data_name(self, address: int, name: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_function_name(self, address: int, name: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_cross_reference(self, from_address: int, to_address: int): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def import_c_typedef(self, type_def: str): pass
|
||||
|
||||
# optional
|
||||
def add_function_to_group(self, address: int, group: str): pass
|
||||
def cache_function_types(self, function_types: list[str]): pass
|
||||
|
||||
# only required if supports_fake_string_segment == True
|
||||
def create_fake_segment(self, name: str, size: int) -> int: return 0
|
||||
|
||||
def write_string(self, address: int, value: str): pass
|
||||
def write_address(self, address: int, value: int): pass
|
||||
|
||||
class ScriptContext:
|
||||
_backend: BaseDisassemblerInterface
|
||||
_status: BaseStatusHandler
|
||||
|
||||
def __init__(self, backend: BaseDisassemblerInterface, status: BaseStatusHandler) -> None:
|
||||
self._backend = backend
|
||||
self._status = status
|
||||
|
||||
def from_hex(self, addr: str):
|
||||
return int(addr, 0)
|
||||
|
||||
def parse_address(self, d: dict):
|
||||
return self.from_hex(d['virtualAddress'])
|
||||
|
||||
def define_il_method(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_function_name(addr, definition['name'])
|
||||
self._backend.set_function_type(addr, definition['signature'])
|
||||
self._backend.set_function_comment(addr, definition['dotNetSignature'])
|
||||
self._backend.add_function_to_group(addr, definition['group'])
|
||||
|
||||
def define_il_method_info(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_data_type(addr, r'struct MethodInfo *')
|
||||
self._backend.set_data_name(addr, definition['name'])
|
||||
self._backend.set_data_comment(addr, definition['dotNetSignature'])
|
||||
if 'methodAddress' in definition:
|
||||
method_addr = self.from_hex(definition["methodAddress"])
|
||||
self._backend.add_cross_reference(method_addr, addr)
|
||||
|
||||
def define_cpp_function(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_function_name(addr, definition['name'])
|
||||
self._backend.set_function_type(addr, definition['signature'])
|
||||
|
||||
def define_string(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_data_type(addr, r'struct String *')
|
||||
self._backend.set_data_name(addr, definition['name'])
|
||||
self._backend.set_data_comment(addr, definition['string'])
|
||||
|
||||
def define_field(self, addr: str, name: str, type: str, il_type: str | None = None):
|
||||
address = self.from_hex(addr)
|
||||
self._backend.set_data_type(address, type)
|
||||
self._backend.set_data_name(address, name)
|
||||
if il_type is not None:
|
||||
self._backend.set_data_comment(address, il_type)
|
||||
|
||||
def define_field_from_json(self, definition: dict):
|
||||
self.define_field(definition['virtualAddress'], definition['name'], definition['type'], definition['dotNetType'])
|
||||
|
||||
def define_array(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.define_data_array(addr, definition['type'], int(definition['count']))
|
||||
self._backend.set_data_name(addr, definition['name'])
|
||||
|
||||
def define_field_with_value(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_data_name(addr, definition['name'])
|
||||
self._backend.set_data_comment(addr, definition['value'])
|
||||
|
||||
def process_metadata(self, metadata: dict):
|
||||
# Function boundaries
|
||||
function_addresses = metadata['functionAddresses']
|
||||
function_addresses.sort()
|
||||
count = len(function_addresses)
|
||||
|
||||
self._status.update_step('Processing function boundaries', count)
|
||||
for i in range(count):
|
||||
start = self.from_hex(function_addresses[i])
|
||||
if start == 0:
|
||||
self._status.update_progress()
|
||||
continue
|
||||
|
||||
end = self.from_hex(function_addresses[i + 1]) if i + 1 != count else None
|
||||
|
||||
self._backend.define_function(start, end)
|
||||
self._status.update_progress()
|
||||
|
||||
# Method definitions
|
||||
self._status.update_step('Processing method definitions', len(metadata['methodDefinitions']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['methodDefinitions']])
|
||||
for d in metadata['methodDefinitions']:
|
||||
self.define_il_method(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# Constructed generic methods
|
||||
self._status.update_step('Processing constructed generic methods', len(metadata['constructedGenericMethods']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['constructedGenericMethods']])
|
||||
for d in metadata['constructedGenericMethods']:
|
||||
self.define_il_method(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# Custom attributes generators
|
||||
self._status.update_step('Processing custom attributes generators', len(metadata['customAttributesGenerators']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['customAttributesGenerators']])
|
||||
for d in metadata['customAttributesGenerators']:
|
||||
self.define_cpp_function(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# Method.Invoke thunks
|
||||
self._status.update_step('Processing Method.Invoke thunks', len(metadata['methodInvokers']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['methodInvokers']])
|
||||
for d in metadata['methodInvokers']:
|
||||
self.define_cpp_function(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# String literals for version >= 19
|
||||
if 'virtualAddress' in metadata['stringLiterals'][0]:
|
||||
self._status.update_step('Processing string literals (V19+)', len(metadata['stringLiterals']))
|
||||
|
||||
if self._backend.supports_fake_string_segment:
|
||||
total_string_length = 0
|
||||
for d in metadata['stringLiterals']:
|
||||
total_string_length += len(d["string"]) + 1
|
||||
|
||||
aligned_length = total_string_length + (4096 - (total_string_length % 4096))
|
||||
segment_base = self._backend.create_fake_segment(".fake_strings", aligned_length)
|
||||
|
||||
current_string_address = segment_base
|
||||
for d in metadata['stringLiterals']:
|
||||
self.define_string(d)
|
||||
|
||||
ref_addr = self.parse_address(d)
|
||||
self._backend.write_string(current_string_address, d["string"])
|
||||
self._backend.set_data_type(ref_addr, r'const char* const')
|
||||
self._backend.write_address(ref_addr, current_string_address)
|
||||
|
||||
current_string_address += len(d["string"]) + 1
|
||||
self._status.update_progress()
|
||||
else:
|
||||
for d in metadata['stringLiterals']:
|
||||
self.define_string(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# String literals for version < 19
|
||||
else:
|
||||
self._status.update_step('Processing string literals (pre-V19)')
|
||||
litDecl = 'enum StringLiteralIndex {\n'
|
||||
for d in metadata['stringLiterals']:
|
||||
litDecl += " " + d['name'] + ",\n"
|
||||
litDecl += '};\n'
|
||||
|
||||
self._backend.import_c_typedef(litDecl)
|
||||
|
||||
# Il2CppClass (TypeInfo) pointers
|
||||
self._status.update_step('Processing Il2CppClass (TypeInfo) pointers', len(metadata['typeInfoPointers']))
|
||||
for d in metadata['typeInfoPointers']:
|
||||
self.define_field_from_json(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# Il2CppType (TypeRef) pointers
|
||||
self._status.update_step('Processing Il2CppType (TypeRef) pointers', len(metadata['typeRefPointers']))
|
||||
for d in metadata['typeRefPointers']:
|
||||
self.define_field(d['virtualAddress'], d['name'], r'struct Il2CppType *', d['dotNetType'])
|
||||
self._status.update_progress()
|
||||
|
||||
# MethodInfo pointers
|
||||
self._status.update_step('Processing MethodInfo pointers', len(metadata['methodInfoPointers']))
|
||||
for d in metadata['methodInfoPointers']:
|
||||
self.define_il_method_info(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# FieldInfo pointers, add the contents as a comment
|
||||
self._status.update_step('Processing FieldInfo pointers', len(metadata['fields']))
|
||||
for d in metadata['fields']:
|
||||
self.define_field_with_value(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# FieldRva pointers, add the contents as a comment
|
||||
self._status.update_step('Processing FieldRva pointers', len(metadata['fieldRvas']))
|
||||
for d in metadata['fieldRvas']:
|
||||
self.define_field_with_value(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# IL2CPP type metadata
|
||||
self._status.update_step('Processing IL2CPP type metadata', len(metadata['typeMetadata']))
|
||||
for d in metadata['typeMetadata']:
|
||||
self.define_field(d['virtualAddress'], d['name'], d['type'])
|
||||
|
||||
# IL2CPP function metadata
|
||||
self._status.update_step('Processing IL2CPP function metadata', len(metadata['functionMetadata']))
|
||||
for d in metadata['functionMetadata']:
|
||||
self.define_cpp_function(d)
|
||||
|
||||
# IL2CPP array metadata
|
||||
self._status.update_step('Processing IL2CPP array metadata', len(metadata['arrayMetadata']))
|
||||
for d in metadata['arrayMetadata']:
|
||||
self.define_array(d)
|
||||
|
||||
# IL2CPP API functions
|
||||
self._status.update_step('Processing IL2CPP API functions', len(metadata['apis']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['apis']])
|
||||
for d in metadata['apis']:
|
||||
self.define_cpp_function(d)
|
||||
|
||||
def process(self):
|
||||
self._status.initialize()
|
||||
|
||||
try:
|
||||
start_time = datetime.now()
|
||||
|
||||
self._status.update_step("Running script prologue")
|
||||
self._backend.on_start()
|
||||
|
||||
metadata_path = os.path.join(self._backend.get_script_directory(), "%JSON_METADATA_RELATIVE_PATH%")
|
||||
with open(metadata_path, "r") as f:
|
||||
self._status.update_step("Loading JSON metadata")
|
||||
metadata = json.load(f)['addressMap']
|
||||
self.process_metadata(metadata)
|
||||
|
||||
self._status.update_step("Running script epilogue")
|
||||
self._backend.on_finish()
|
||||
|
||||
self._status.update_step('Script execution complete.')
|
||||
|
||||
end_time = datetime.now()
|
||||
print(f"Took: {end_time - start_time}")
|
||||
|
||||
except RuntimeError: pass
|
||||
finally: self._status.shutdown()
|
||||
40
README.md
40
README.md
@@ -27,6 +27,7 @@ This is a continuation of [Il2CppInspector, by djkaty](https://github.com/djkaty
|
||||
- Automatic unloading of conflicting type libraries
|
||||
- Addition of custom fake string segment to show string literal contents in decompiler
|
||||
- A fake xref between MethodInfo instances and their corresponding method to quickly get the correct function
|
||||
* Binary Ninja script output, with all of the IDA-exclusive features
|
||||
|
||||
### Main features
|
||||
|
||||
@@ -36,7 +37,7 @@ This is a continuation of [Il2CppInspector, by djkaty](https://github.com/djkaty
|
||||
|
||||
* Create **[C++ scaffolding](#creating-c-scaffolding-or-a-dll-injection-project)** for all types, methods, function pointers and API functions in an IL2CPP application for use in x64dbg, Cydia Substrate etc.
|
||||
|
||||
* Create **[IDA](#adding-metadata-to-your-ida-workflow) and [Ghidra](#adding-metadata-to-your-ghidra-workflow) Python scripts** to populate symbol, function and type information; includes API hooks to [implement scripts for other targets](#extending-il2cppinspectors-python-output-to-support-other-targets)
|
||||
* Create **[IDA](#adding-metadata-to-your-ida-workflow), [Ghidra](#adding-metadata-to-your-ghidra-workflow) or [Binary Ninja](#adding-metadata-to-your-binary-ninja-workflow) Python scripts** to populate symbol, function and type information; includes API hooks to [implement scripts for other targets](#extending-il2cppinspectors-python-output-to-support-other-targets)
|
||||
|
||||
* Create Visual Studio **[C++ DLL injection projects](#dll-injection-workflow)** directly from IL2CPP files
|
||||
|
||||
@@ -68,7 +69,6 @@ This is a continuation of [Il2CppInspector, by djkaty](https://github.com/djkaty
|
||||
|
||||
* Tested with [every release of IL2CPP](#version-support) since Unity 5.3.0
|
||||
|
||||
|
||||
### Tutorials and Guides
|
||||
|
||||
You can read more about how IL2CPP works in my series IL2CPP Reverse Engineering:
|
||||
@@ -124,7 +124,7 @@ Nice to have:
|
||||
* Automatically defeats certain basic obfuscation methods
|
||||
* Test chassis for automated integration testing of IL2CPP binaries
|
||||
|
||||
Class library targets .NET 8. Built with Visual Studio 2019.
|
||||
Class library targets .NET 8. Built with Visual Studio 2022.
|
||||
|
||||
**NOTE**: Il2CppInspector is not a decompiler. It can provide you with the structure of an application and function addresses for every method so that you can easily jump straight to methods of interest in your disassembler. It does not attempt to recover the entire source code of the application.
|
||||
|
||||
@@ -132,7 +132,7 @@ Class library targets .NET 8. Built with Visual Studio 2019.
|
||||
|
||||
```
|
||||
git clone --recursive https://github.com/LukeFZ/Il2CppInspectorRedux
|
||||
cd Il2CppInspector
|
||||
cd Il2CppInspectorRedux
|
||||
```
|
||||
|
||||
##### Windows
|
||||
@@ -181,9 +181,9 @@ Get all current plugins (optional):
|
||||
|
||||
For other operating systems supporting .NET Core, add `-r xxx` to the final command where `xxx` is a RID from https://docs.microsoft.com/en-us/dotnet/articles/core/rid-catalog
|
||||
|
||||
The output binary for command-line usage is placed in `Il2CppInspector/Il2CppInspector.CLI/bin/Release/net8.0/[win|osx|linux]-x64/publish/Il2CppInspector.exe`.
|
||||
The output binary for command-line usage is placed in `Il2CppInspectorRedux/Il2CppInspector.CLI/bin/Release/net8.0/[win|osx|linux]-x64/publish/Il2CppInspector.exe`.
|
||||
|
||||
The output binary for Windows GUI is placed in `Il2CppInspector/Il2CppInspector.GUI/bin/Release/net8.0-windows/win-x64/publish/Il2CppInspector.exe`.
|
||||
The output binary for Windows GUI is placed in `Il2CppInspectorRedux/Il2CppInspector.GUI/bin/Release/net8.0-windows/win-x64/publish/Il2CppInspector.exe`.
|
||||
|
||||
The `plugins` folder should be placed in the same folder as `Il2CppInspector.exe`.
|
||||
|
||||
@@ -389,6 +389,12 @@ Example Ghidra C++ decompilation after applying Il2CppInspector:
|
||||
|
||||

|
||||
|
||||
### Adding metadata to your Binary Ninja workflow
|
||||
|
||||
Import your binary into Binary Ninja, and let the initial analysis complete.
|
||||
Then run the generated *il2cpp.py* using the "File > Run script..." menu option.
|
||||
You can view the current script progress in the bottom left corner, alongside the total elapsed time.
|
||||
|
||||
### Creating C++ scaffolding or a DLL injection project
|
||||
|
||||
Il2CppInspector generates a series of C++ source files which you can use in a variety of ways, for example:
|
||||
@@ -706,23 +712,14 @@ You can find out more about plugins, and browse the source code of current plugi
|
||||
|
||||
### Extending Il2CppInspector's Python output to support other targets
|
||||
|
||||
The current version of Il2CppInspector can output Python scripts targeting the IDA and Ghidra disassemblers.
|
||||
The current version of Il2CppInspector can output Python scripts targeting the IDA, Ghidra and Binary Ninja disassemblers.
|
||||
|
||||
When Il2CppInspector generates such a script, it generates a concatenation of a shared block of code (`Outputs/ScriptResources/shared-main.py`) which parses the JSON metadata and dispatches it to a set of implementation-specific functions to be processed, and a block of code specific to the target application which implements these functions (a file from `Outputs/ScriptResources/Targets`).
|
||||
When Il2CppInspector generates such a script, it generates a concatenation of a shared block of code (`Outputs/ScriptResources/shared_base_.py`) which parses the JSON metadata and dispatches it to a set of implementation-specific functions to be processed, and a block of code specific to the target application which implements these functions (a file from `Outputs/ScriptResources/Targets`).
|
||||
|
||||
If you would like to add support for a new target application, create a new Python script in `Outputs/ScriptResources/Targets` with the nane `<target-name-without-whitespace>.py` and implement the following functions:
|
||||
|
||||
- `CustomInitializer()` - perform any custom initialization required for the target before applying the metadata
|
||||
- `DefineCode(code)` - parse and apply the specified C++ declaration text (this is not required for Unity 5.3.2 and later; if you don't need to support earlier versions, just specify `pass` as the implementation)
|
||||
- `GetScriptDirectory()` - retrieve the directory that the Python script is running in. This will normally be `os.path.dirname(os.path.realpath(__file__))`
|
||||
- `MakeFunction(start, name=None)` - define address `start` as the start of a function, optionally with name `name`
|
||||
- `SetComment(addr, text)` - place the comment `text` at address `addr`
|
||||
- `SetFunctionType(addr, sig)` - parse the C++ function signature in `sig` and apply it to the function at address `addr`
|
||||
- `SetHeaderComment(addr, text)` - place the header/plate/pre-item comment `text` at address `addr`
|
||||
- `SetName(addr, name)` - set the symbol (or label or name) of address `addr` to `name`
|
||||
- `SetType(addr, type)` - define address `addr` to be an item of the C++ type `type`
|
||||
|
||||
Refer to the source code of `IDA.py` and `Ghidra.py` for examples.
|
||||
If you would like to add support for a new target application, create a new Python script in `Outputs/ScriptResources/Targets` with the nane `<target-name-without-whitespace>.py` and implement the *BaseDisassemblerInterface* class.
|
||||
If you also want to have an updating status display, you also need to implement the *BaseStatusHandler* class.
|
||||
Your target implementation also needs to dispatch analysis at the end, by constructing a *ScriptContext* instance with your disassembler and status implementations, then calling the `process` methon it.
|
||||
For a simple version of this you can view the *IDA* and *Ghidra* targets, and the *BinaryNinja* one for a more specific analysis dispatch.
|
||||
|
||||
When you add a new target and re-compile Il2CppInspector:
|
||||
|
||||
@@ -815,6 +812,7 @@ The following books and documents were also very helpful:
|
||||
- [ARM Architecture Reference Manual ARMv8-A](https://developer.arm.com/docs/ddi0487/latest)
|
||||
- [Intel 64 and IA-32 Architectures Software Developer's Manual](https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf)
|
||||
- [Ghidra API documentation](https://ghidra.re/ghidra_docs/api/)
|
||||
- [Binary Ninja API documentation](https://docs.binary.ninja/dev)
|
||||
|
||||
Pizza spinner animation in the GUI made by Chris Gannon - https://gannon.tv/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user