From edd15fce67a34c2fb1dd2ea347c5c6284da07a6f Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Tue, 1 Apr 2025 15:55:33 +0200 Subject: [PATCH] wip --- frida/theseus_frida/__init__.py | 28 +++++++++++-------- patcher/src/reflection_patcher.rs | 28 ++++++++----------- patcher/src/runtime_data.rs | 24 ++++++++++++++++ .../src/theseus_autopatcher/__init__.py | 2 ++ 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/frida/theseus_frida/__init__.py b/frida/theseus_frida/__init__.py index b1a1332..4fa21ac 100644 --- a/frida/theseus_frida/__init__.py +++ b/frida/theseus_frida/__init__.py @@ -45,7 +45,7 @@ def on_message(message, data, data_storage: dict, file_storage: Path): handle_cnstr_new_inst_data(message["payload"]["data"], data_storage) elif message["type"] == "send" and message["payload"]["type"] == "load-dex": handle_load_dex(message["payload"]["data"], data_storage, file_storage) - elif message["type"] == "send" and message["payload"]["type"] == "apk-cl": + elif message["type"] == "send" and message["payload"]["type"] == "classloader": handle_classloader_data(message["payload"]["data"], data_storage) else: print("[-] message:", message) @@ -59,13 +59,20 @@ def print_stack(stack, prefix: str): print(f" {prefix}{frame['method']}:{frame['bytecode_index']}{native}") +def cl_id_to_string(classloader: int) -> str: + if classloader < 0: + classloader += 2 << (HASH_NB_BYTES * 8 - 1) + return classloader.to_bytes(HASH_NB_BYTES).hex() + + def handle_classloader_data(data: dict, data_storage: dict): + data["id"] = cl_id_to_string(data["id"]) data_storage["initial_classloaders"].append(data) def handle_invoke_data(data, data_storage: dict): method = data["method"] - method_cl_id = data["method_cl_id"] + method_cl_id = cl_id_to_string(data["method_cl_id"]) # TODO: good idea? if method in [ "Landroid/view/View;->getTranslationZ()F", @@ -75,7 +82,7 @@ def handle_invoke_data(data, data_storage: dict): if len(data["stack"]) == 0: return caller_method = data["stack"][0]["method"] - caller_cl_id = data["stack"][0]["cl_id"] + caller_cl_id = cl_id_to_string(data["stack"][0]["cl_id"]) addr = data["stack"][0]["bytecode_index"] is_static = data["is_static"] if is_static: @@ -106,7 +113,7 @@ def handle_invoke_data(data, data_storage: dict): def handle_class_new_inst_data(data, data_storage: dict): constructor = data["constructor"] - constructor_cl_id = data["constructor_cl_id"] + constructor_cl_id = cl_id_to_string(data["constructor_cl_id"]) if len(data["stack"]) == 0: return if ( @@ -119,7 +126,7 @@ def handle_class_new_inst_data(data, data_storage: dict): else: return caller_method = frame["method"] - caller_cl_id = frame["cl_id"] + caller_cl_id = cl_id_to_string(frame["cl_id"]) addr = frame["bytecode_index"] print("[+] Class.NewInstance:") print(f" called: [{constructor_cl_id}]{constructor}") @@ -144,13 +151,13 @@ def handle_class_new_inst_data(data, data_storage: dict): def handle_cnstr_new_inst_data(data, data_storage: dict): constructor = data["constructor"] - constructor_cl_id = data["constructor_cl_id"] + constructor_cl_id = cl_id_to_string(data["constructor_cl_id"]) if not constructor.startswith("Lcom/example/theseus"): return if len(data["stack"]) == 0: return caller_method = data["stack"][0]["method"] - caller_cl_id = data["stack"][0]["cl_id"] + caller_cl_id = cl_id_to_string(data["stack"][0]["cl_id"]) addr = data["stack"][0]["bytecode_index"] print("[+] Constructor.newInstance:") print(f" called: [{constructor_cl_id}]{constructor}") @@ -176,10 +183,7 @@ def handle_cnstr_new_inst_data(data, data_storage: dict): def handle_load_dex(data, data_storage: dict, file_storage: Path): dex = data["dex"] classloader_class = data["classloader_class"] - classloader = data["classloader"] - if classloader < 0: - classloader += 2 << (HASH_NB_BYTES * 8 - 1) - classloader = classloader.to_bytes(HASH_NB_BYTES).hex() + classloader = cl_id_to_string(data["classloader"]) short_class = classloader_class.split("/")[-1].removesuffix(";") files = [] print("[+] DEX file loaded:") @@ -360,7 +364,7 @@ def collect_runtime(apk: Path, device_name: str, file_storage: Path, output: Tex for load_data in data_storage["dyn_code_load"]: if load_data["classloader"] in cls: del cls[load_data["classloader"]] - for id_ in cls.keys(): + for id_ in list(cls.keys()): if ( 'dalvik.system.PathClassLoader[DexPathList[[directory "."],' in cls[id_]["str"] diff --git a/patcher/src/reflection_patcher.rs b/patcher/src/reflection_patcher.rs index bb85e86..9e42bbd 100644 --- a/patcher/src/reflection_patcher.rs +++ b/patcher/src/reflection_patcher.rs @@ -627,7 +627,7 @@ fn get_invoke_block( }); insns.push(Instruction::CheckCast { reg: reg_inf.array_val, - lit: ref_data.method.class_.clone(), + lit: ref_data.get_static_callee().class_, }); insns.push(Instruction::MoveObject { from: reg_inf.array_val as u16, @@ -635,24 +635,24 @@ fn get_invoke_block( }); } insns.append(&mut get_args_from_obj_arr( - &ref_data.method.proto.get_parameters(), + &ref_data.get_static_callee().proto.get_parameters(), // TODO: what if renambed args? arg_arr, reg_inf.first_arg + if ref_data.is_static { 0 } else { 1 }, reg_inf, )); if ref_data.is_static { insns.push(Instruction::InvokeStatic { - method: ref_data.method.clone(), + method: ref_data.get_static_callee(), args: (reg_inf.first_arg..reg_inf.first_arg + nb_args as u16).collect(), }); } else { insns.push(Instruction::InvokeVirtual { - method: ref_data.method.clone(), + method: ref_data.get_static_callee(), args: (reg_inf.first_arg..reg_inf.first_arg + 1 + nb_args as u16).collect(), }); } if let Some(move_result) = move_result { - let ret_ty = ref_data.method.proto.get_return_type(); + let ret_ty = ref_data.get_static_callee().proto.get_return_type(); let res_reg = if let Instruction::MoveResultObject { to } = &move_result { *to } else { @@ -816,14 +816,14 @@ fn get_cnstr_new_inst_block( let mut insns = test_cnstr( cnst_reg, - ref_data.constructor.clone(), + ref_data.constructor.clone(), // TODO: what if args are renammed? abort_label.clone(), reg_inf, tester_methods_class, tester_methods, )?; insns.append(&mut get_args_from_obj_arr( - &ref_data.constructor.proto.get_parameters(), + &ref_data.constructor.proto.get_parameters(), // TODO: what if args are renammed? arg_arr, reg_inf.first_arg + 1, reg_inf, @@ -831,12 +831,12 @@ fn get_cnstr_new_inst_block( if reg_inf.first_arg < u8::MAX as u16 { insns.push(Instruction::NewInstance { reg: reg_inf.first_arg as u8, - lit: ref_data.constructor.class_.clone(), + lit: ref_data.get_static_constructor().class_, }); } else { insns.push(Instruction::NewInstance { reg: reg_inf.array_val, - lit: ref_data.constructor.class_.clone(), + lit: ref_data.get_static_constructor().class_, }); insns.push(Instruction::MoveObject { from: reg_inf.array_val as u16, @@ -844,7 +844,7 @@ fn get_cnstr_new_inst_block( }); } insns.push(Instruction::InvokeDirect { - method: ref_data.constructor.clone(), + method: ref_data.get_static_constructor(), args: (reg_inf.first_arg..reg_inf.first_arg + nb_args as u16 + 1).collect(), }); if let Some(Instruction::MoveResultObject { to }) = move_result { @@ -916,10 +916,6 @@ fn get_class_new_inst_block( ); } - if class_reg > u8::MAX as u16 { - // TODO - bail!("Cannot transform instantiation calls to a class stored in 16 bits register"); - } let class_reg = class_reg as u8; let abort_label = format!( @@ -970,10 +966,10 @@ fn get_class_new_inst_block( //}, Instruction::NewInstance { reg: obj_reg, - lit: ref_data.constructor.class_.clone(), + lit: ref_data.get_static_constructor().class_.clone(), }, Instruction::InvokeDirect { - method: ref_data.constructor.clone(), + method: ref_data.get_static_constructor().clone(), args: vec![obj_reg as u16], }, Instruction::Goto { diff --git a/patcher/src/runtime_data.rs b/patcher/src/runtime_data.rs index aa32f09..db5a7dd 100644 --- a/patcher/src/runtime_data.rs +++ b/patcher/src/runtime_data.rs @@ -112,6 +112,14 @@ pub struct ReflectionInvokeData { // TODO: type of invoke? } +impl ReflectionInvokeData { + pub fn get_static_callee(&self) -> IdMethod { + self.renamed_method + .clone() + .unwrap_or_else(|| self.method.clone()) + } +} + /// Structure storing the runtime information of a reflection instanciation using /// `java.lang.Class.newInstance()`. #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] @@ -132,6 +140,14 @@ pub struct ReflectionClassNewInstData { pub addr: usize, } +impl ReflectionClassNewInstData { + pub fn get_static_constructor(&self) -> IdMethod { + self.renamed_constructor + .clone() + .unwrap_or_else(|| self.constructor.clone()) + } +} + /// Structure storing the runtime information of a reflection instanciation using /// `java.lang.reflect.Constructor.newInstance()`. #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] @@ -152,6 +168,14 @@ pub struct ReflectionCnstrNewInstData { pub addr: usize, } +impl ReflectionCnstrNewInstData { + pub fn get_static_constructor(&self) -> IdMethod { + self.renamed_constructor + .clone() + .unwrap_or_else(|| self.constructor.clone()) + } +} + /// Structure storing the runtime information of a dynamic code loading. #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct DynamicCodeLoadingData { diff --git a/theseus_autopatcher/src/theseus_autopatcher/__init__.py b/theseus_autopatcher/src/theseus_autopatcher/__init__.py index 1a67523..9278d23 100644 --- a/theseus_autopatcher/src/theseus_autopatcher/__init__.py +++ b/theseus_autopatcher/src/theseus_autopatcher/__init__.py @@ -120,6 +120,8 @@ def patch_apk( str(zipalign.absolute()), "-a", str(apksigner.absolute()), + "--code-loading-patch-strategy", + "model-class-loaders", ] ) )