Last active
October 28, 2022 17:47
-
-
Save Areizen/a1b2b52b44bdb6460dd956cf0bcb439f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import json | |
predefined_struct = { | |
"UnityEngine_Vector3_o" : [("float","x"),("float","y"),("float","z")] | |
} | |
def extract_class_methods(methods_informations, clazz): | |
""" | |
We check if the signature start with the class we want to hook, if so | |
we add the current method to the array | |
:param methods_informations: list of method objects | |
:param clazz: class to hook | |
:return array(methods): return filtered methods | |
""" | |
clazz_methods = [] | |
for method in methods_informations: | |
method_name = method["Name"] | |
if(method_name.startswith(f'{clazz}$$')): | |
#print(method_name) | |
clazz_methods.append(method) | |
return clazz_methods | |
def extract_signature(method): | |
""" | |
Extract the signature to have an understable way to get them | |
""" | |
if( "." not in method["Name"] and "<" not in method["Name"]): | |
signature = { | |
"initial" : method["Signature"], | |
"addr" : method["Address"], | |
"method_name" : method["Name"], | |
"return" : "void", | |
"parameters" : [] | |
} | |
# extract all type/name associations | |
parameters = method["Signature"].split('(')[1] .split(')')[-2].split(",") | |
return_ = method["Signature"].split(" ")[0] | |
if("*" in return_): | |
return_ = "pointer" | |
elif("_t" in return_): | |
return_ = return_.replace("_t","") | |
signature["return"] = return_ | |
for parameter in parameters: | |
parameter = parameter.strip() | |
if(len(parameter.split(" ")) == 2): | |
type_, name = parameter.split(" ") | |
# make type usable by Frida | |
if("*" in type_): | |
type_ = "pointer" | |
name = name + "_ptr" | |
signature["parameters"].append({"type":type_,"name":name}) | |
elif("_t" in type_): | |
type_ = type_.replace("_t","") | |
signature["parameters"].append({"type":type_,"name":name}) | |
elif(type_ in predefined_struct): | |
for type_,name in predefined_struct[type_]: | |
signature["parameters"].append({"type":type_,"name":name}) | |
else: | |
signature["parameters"].append({"type":type_,"name":name}) | |
return signature | |
def generate_hook_method(signature): | |
name = signature["method_name"].split("$$")[1] | |
types_parameter = list(map(lambda x: x["type"], signature["parameters"])) | |
names = list(map(lambda x: x["name"], signature["parameters"])) | |
console_logs = "" | |
for i in range(len(types_parameter)): | |
console_logs += f'\tconsole.log("\\t - {names[i]} : " + {names[i]} );\n' | |
#print(types_parameter) | |
hooks = f""" | |
// {signature["initial"]} | |
var {name}_pointer = il2cpp_addr.add({hex(signature["addr"])}) | |
const {name} = new NativeFunction({name}_pointer, "{signature["return"]}",{types_parameter}); | |
Interceptor.replace({name}_pointer, | |
new NativeCallback(function({",".join(names)}) {{ | |
console.log("[+] {name} : "); | |
{console_logs} | |
{name}({",".join(names)}); | |
}}, | |
"{signature["return"]}",{types_parameter}) | |
); | |
""" | |
return hooks | |
def generate_hooks(methods): | |
base = """ | |
var library_name = "libil2cpp.so"; | |
var library_loaded = 0; | |
var base_address = 0; | |
// frida -U -l <this_file> -f <package> --no-pause | |
function hookFunction(){ | |
var il2cpp_addr = Module.findBaseAddress(library_name); | |
""" | |
for method in methods: | |
signature = extract_signature(method=method) | |
if(signature): | |
base += generate_hook_method(signature=signature) | |
base+=""" | |
} | |
// First Step : waiting for the application to load the good library | |
// https://android.googlesource.com/platform/system/core/+/master/libnativeloader/native_loader.cpp#746 | |
// | |
// OpenNativeLibrary is called when you loadLibrary from Java, it then call android_dlopen_ext | |
Interceptor.attach(Module.findExportByName(null, 'android_dlopen_ext'),{ | |
onEnter: function(args){ | |
// first arg is the path to the library loaded | |
var library_path = Memory.readCString(args[0]); | |
if( library_path.includes(library_name)){ | |
console.log("[...] Loading library : " + library_path); | |
library_loaded = 1; | |
} | |
}, | |
onLeave: function(return_val){ | |
// if it's the library we want to hook, hooking it | |
if(library_loaded == 1){ | |
console.log("[+] Loaded") | |
library_loaded = 0; | |
hookFunction(); | |
} | |
} | |
}) | |
""" | |
print(base) | |
def main(script, clazz): | |
# Loading script.json | |
il2cpp_informations = json.load(open(script,"rb")) | |
methods_informations = il2cpp_informations["ScriptMethod"] | |
# Extract the method used by the class we want to hook | |
clazz_methods = extract_class_methods(methods_informations=methods_informations, clazz=clazz) | |
hooks = generate_hooks(methods=clazz_methods) | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(description="Generate Frida Hooks for a given Unity application") | |
parser.add_argument("script", help="script.json file") | |
parser.add_argument("clazz", help="Class to hook functions") | |
args = parser.parse_args() | |
main(script=args.script, clazz=args.clazz) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment