From 1884ff4ac8ed6b13330e7f93ce168bfb78cc7452 Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Mon, 5 May 2025 17:31:23 +0200 Subject: [PATCH] wip compare class laoder --- frida/theseus_frida/__init__.py | 12 +- frida/theseus_frida/hook.js | 39 ++- patcher/src/dex_types.rs | 12 + patcher/src/reflection_patcher.rs | 299 ++++++++++++------ patcher/src/runtime_data.rs | 2 +- .../com/example/theseus/dynandref/Main.java | 65 +++- .../theseus/dynandref/MainActivity.java | 5 + 7 files changed, 326 insertions(+), 108 deletions(-) diff --git a/frida/theseus_frida/__init__.py b/frida/theseus_frida/__init__.py index f982129..91c4acf 100644 --- a/frida/theseus_frida/__init__.py +++ b/frida/theseus_frida/__init__.py @@ -59,6 +59,8 @@ def on_message(message, data, data_storage: dict, file_storage: Path): elif message["type"] == "send" and message["payload"]["type"] == "classloader-done": global CLASSLOADER_DONE CLASSLOADER_DONE = True + elif message["type"] == "send" and message["payload"]["type"] == "app_info": + handle_app_info(message["payload"]["data"], data_storage) else: print("[-] message:", message) @@ -234,6 +236,14 @@ def handle_load_dex(data, data_storage: dict, file_storage: Path): ) +def handle_app_info(data, data_storage: dict): + data["actualSourceDir"] = data["sourceDir"].removesuffix("/base.apk") + data_storage["app_info"] = data + print("[+] Received app info:") + for k in data.keys(): + print(f" {k}: {data[k]}") + + def setup_frida(device_name: str, env: dict[str, str], adb: str) -> frida.core.Device: if device_name != "": device = frida.get_device(device_name) @@ -424,7 +434,7 @@ def collect_runtime( cls = {} for cl in data_storage["classloaders"]: # This is verry doubious - if cl["cname"] == "dalvik.system.PathClassLoader": + if cl["cname"] == "Ldalvik/system/PathClassLoader;": zip_files = list( map( lambda s: s.removeprefix('zip file "').removesuffix('"'), diff --git a/frida/theseus_frida/hook.js b/frida/theseus_frida/hook.js index a554179..57dab4d 100644 --- a/frida/theseus_frida/hook.js +++ b/frida/theseus_frida/hook.js @@ -1,6 +1,7 @@ const sended_class_loaders = new Set(); function send_class_loader(cl) { + get_app_info(); const System = Java.use('java.lang.System'); let cl_id = System.identityHashCode(cl); while (cl != null && !sended_class_loaders.has(cl_id)) { @@ -9,7 +10,7 @@ function send_class_loader(cl) { "id": cl_id, "parent_id": System.identityHashCode(parent_), "str": cl.toString(), - "cname": cl.$className + "cname": cl.getClass().descriptorString() }}); sended_class_loaders.add(cl_id); cl = parent_; @@ -26,6 +27,37 @@ function dump_classloaders() { }); } +let info_sent = false +function get_app_info() { + if (info_sent) { + return; + } + var app = Java.use('android.app.ActivityThread').currentApplication(); + if (app == null) { + return; + } + var context = app.getApplicationContext(); + if (context == null) { + return; + } + var appinfo = context.getApplicationInfo(); + if (appinfo == null) { + return; + } + send({"type": "app_info", "data": { + "dataDir": appinfo.dataDir.value, + "deviceProtectedDataDir": appinfo.deviceProtectedDataDir.value, + "nativeLibraryDir": appinfo.nativeLibraryDir.value, + "publicSourceDir": appinfo.publicSourceDir.value, + "sharedLibraryFiles": appinfo.sharedLibraryFiles.value, + "sourceDir": appinfo.sourceDir.value, + "splitNames": appinfo.splitNames.value, + "splitPublicSourceDirs": appinfo.splitPublicSourceDirs.value, + "splitSourceDirs": appinfo.splitSourceDirs.value, + }}); + info_sent = true; +} + /* ----- Frida Native Class Loading ----- * Broken, for some ineffable frida-android reason. function registerStackConsumer() { @@ -117,6 +149,8 @@ Java.perform(() => { const System = Java.use('java.lang.System'); */ + + const StackWalker = Java.use('java.lang.StackWalker'); const StackWalkerOptions = Java.use('java.lang.StackWalker$Option'); const StackWalkerOptionsShowHidden = StackWalkerOptions.valueOf("SHOW_HIDDEN_FRAMES"); @@ -304,7 +338,8 @@ Java.perform(() => { let classloader_class = null; let classloader_id = System.identityHashCode(loader); if (loader !== null) { - send_class_loader(loader); + // send_class_loader(loader); // Sending names before the end of the initialization + // collect the wrong string representation ! classloader_class = loader.getClass().descriptorString(); } send({ diff --git a/patcher/src/dex_types.rs b/patcher/src/dex_types.rs index 67bfe9c..dedb88f 100644 --- a/patcher/src/dex_types.rs +++ b/patcher/src/dex_types.rs @@ -102,15 +102,27 @@ pub(crate) static SCAL_TO_OBJ_DOUBLE: LazyLock = LazyLock::new(|| { pub(crate) static GET_CLASS_LOADER: LazyLock = LazyLock::new(|| { IdMethod::from_smali("Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;").unwrap() }); +pub(crate) static GET_PARENT: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/ClassLoader;->getParent()Ljava/lang/ClassLoader;").unwrap() +}); +pub(crate) static GET_CLASS: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Object;->getClass()Ljava/lang/Class;").unwrap() +}); pub(crate) static TO_STRING: LazyLock = LazyLock::new(|| { IdMethod::from_smali("Ljava/lang/Object;->toString()Ljava/lang/String;").unwrap() }); +pub(crate) static BOOT_CLASS_LOADER_TY: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/BootClassLoader;").unwrap()); pub(crate) static OBJECT_TY: LazyLock = LazyLock::new(|| IdType::from_smali("Ljava/lang/Object;").unwrap()); pub(crate) static DELEGATE_LAST_CLASS_LOADER: LazyLock = LazyLock::new(|| IdType::from_smali("Ldalvik/system/DelegateLastClassLoader;").unwrap()); +pub(crate) static LOG_INFO: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I").unwrap() +}); + /// Get the method that convert a object to its scalar conterpart (eg `java.lang.Integer` to `int` with /// `Ljava/lang/Integer;->intValue()I`) /// diff --git a/patcher/src/reflection_patcher.rs b/patcher/src/reflection_patcher.rs index 435cd2a..bf9fd5c 100644 --- a/patcher/src/reflection_patcher.rs +++ b/patcher/src/reflection_patcher.rs @@ -8,6 +8,8 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use crate::{dex_types::*, register_manipulation::*, runtime_data::*}; +const DEBUG: bool = false; + // Interesting stuff: https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/reg_type.h;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=94 // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/method_verifier.cc;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=5328 /// `meth`: the method that make reflectif calls. This is the method to patch. @@ -340,11 +342,12 @@ fn gen_tester_method( tester_methods_class: IdType, method_to_test: IdMethod, is_constructor: bool, - classloader: Option<&ClassLoaderData>, + classloader: Option, + classloaders: &HashMap, ) -> Result { let mut hasher = DefaultHasher::new(); - if let Some(classloader) = classloader { - classloader.id.hash(&mut hasher); + if let Some(ref id) = classloader { + id.hash(&mut hasher); } else { "00000000".hash(&mut hasher); } @@ -363,8 +366,9 @@ fn gen_tester_method( } }; + let method_test_name = format!("check_is_{c_name}_{m_name}_{hash:016x}"); let descriptor = IdMethod::new( - format!("check_is_{c_name}_{m_name}_{hash:016x}").into(), + method_test_name.as_str().into(), IdMethodType::new( IdType::boolean(), vec![if is_constructor { @@ -379,8 +383,11 @@ fn gen_tester_method( let no_label: String = "lable_no".into(); let reg_arr = 0; let reg_arr_idx = 1; - let reg_arr_val = 2; - let reg_ref_method = 3; + let reg_tst_val = 2; + let reg_def_type = 3; + let reg_cmp_val = 4; + let reg_class_loader = 5; + let reg_ref_method = 6; // Check for arg type let mut insns = if !is_constructor { vec![ @@ -407,12 +414,12 @@ fn gen_tester_method( arr: reg_arr, }, Instruction::Const { - reg: reg_arr_val, + reg: reg_tst_val, lit: method_to_test.proto.get_parameters().len() as i32, }, Instruction::IfNe { a: reg_arr_idx, - b: reg_arr_val, + b: reg_tst_val, label: no_label.clone(), }, ]); @@ -428,40 +435,38 @@ fn gen_tester_method( lit: i as i32, }); insns.push(Instruction::AGetObject { - dest: reg_arr_val, + dest: reg_tst_val, arr: reg_arr, idx: reg_arr_idx, }); insns.push(Instruction::ConstClass { - reg: reg_arr_idx, // wrong name, but available for tmp val + reg: reg_cmp_val, lit: param, }); insns.push(Instruction::InvokeVirtual { method: CLT_GET_DESCR_STRING.clone(), - args: vec![reg_arr_idx as u16], + args: vec![reg_cmp_val as u16], }); - insns.push(Instruction::MoveResultObject { to: reg_arr_idx }); + insns.push(Instruction::MoveResultObject { to: reg_cmp_val }); insns.push(Instruction::InvokeVirtual { method: CLT_GET_DESCR_STRING.clone(), - args: vec![reg_arr_val as u16], + args: vec![reg_tst_val as u16], }); - insns.push(Instruction::MoveResultObject { to: reg_arr_val }); + insns.push(Instruction::MoveResultObject { to: reg_tst_val }); insns.push(Instruction::InvokeVirtual { method: STR_EQ.clone(), - args: vec![reg_arr_idx as u16, reg_arr_val as u16], - }); - insns.push(Instruction::MoveResult { - to: reg_arr_idx, // wrong name, but available for tmp val + args: vec![reg_cmp_val as u16, reg_tst_val as u16], }); + insns.push(Instruction::MoveResult { to: reg_cmp_val }); insns.push(Instruction::IfEqZ { - a: reg_arr_idx, + a: reg_cmp_val, label: no_label.clone(), }); // Comparing Type does not work when different types share the same name (eg type from // another class loader) //insns.push(Instruction::IfNe { // a: reg_arr_idx, - // b: reg_arr_val, + // b: reg_tst_val, // label: no_label.clone(), //}) } @@ -473,22 +478,18 @@ fn gen_tester_method( method: MTH_GET_NAME.clone(), args: vec![reg_ref_method], }, - Instruction::MoveResultObject { - to: reg_arr_idx, // wrong name, but available for tmp val - }, + Instruction::MoveResultObject { to: reg_tst_val }, Instruction::ConstString { - reg: reg_arr_val, // wrong name, but available for tmp val + reg: reg_cmp_val, lit: method_to_test.name.clone(), }, Instruction::InvokeVirtual { method: STR_EQ.clone(), - args: vec![reg_arr_idx as u16, reg_arr_val as u16], - }, - Instruction::MoveResult { - to: reg_arr_idx, // wrong name, but available for tmp val + args: vec![reg_tst_val as u16, reg_cmp_val as u16], }, + Instruction::MoveResult { to: reg_cmp_val }, Instruction::IfEqZ { - a: reg_arr_idx, + a: reg_cmp_val, label: no_label.clone(), }, // Check Return Type @@ -496,39 +497,35 @@ fn gen_tester_method( method: MTH_GET_RET_TY.clone(), args: vec![reg_ref_method], }, - Instruction::MoveResultObject { - to: reg_arr_idx, // wrong name, but available for tmp val + Instruction::MoveResultObject { to: reg_tst_val }, + Instruction::InvokeVirtual { + method: CLT_GET_DESCR_STRING.clone(), + args: vec![reg_tst_val as u16], }, + Instruction::MoveResultObject { to: reg_tst_val }, Instruction::ConstClass { - reg: reg_arr_val, // wrong name, but available for tmp val + reg: reg_cmp_val, lit: method_to_test.proto.get_return_type(), }, Instruction::InvokeVirtual { method: CLT_GET_DESCR_STRING.clone(), - args: vec![reg_arr_idx as u16], + args: vec![reg_cmp_val as u16], }, - Instruction::MoveResultObject { to: reg_arr_idx }, - Instruction::InvokeVirtual { - method: CLT_GET_DESCR_STRING.clone(), - args: vec![reg_arr_val as u16], - }, - Instruction::MoveResultObject { to: reg_arr_val }, + Instruction::MoveResultObject { to: reg_cmp_val }, Instruction::InvokeVirtual { method: STR_EQ.clone(), - args: vec![reg_arr_idx as u16, reg_arr_val as u16], - }, - Instruction::MoveResult { - to: reg_arr_idx, // wrong name, but available for tmp val + args: vec![reg_cmp_val as u16, reg_tst_val as u16], }, + Instruction::MoveResult { to: reg_cmp_val }, Instruction::IfEqZ { - a: reg_arr_idx, + a: reg_cmp_val, label: no_label.clone(), }, // Comparing Type does not work when different types share the same name (eg type from // another class loader) //Instruction::IfNe { // a: reg_arr_idx, - // b: reg_arr_val, + // b: reg_tst_val, // label: no_label.clone(), //}, ]); @@ -545,109 +542,211 @@ fn gen_tester_method( args: vec![reg_ref_method], }); } + insns.push(Instruction::MoveResultObject { to: reg_def_type }); + //Check the classloader - if let Some(classloader) = classloader { + let mut current_classloader = classloader.as_ref().and_then(|id| classloaders.get(id)); + let check_class_loader = current_classloader.is_some(); + if check_class_loader { insns.append(&mut vec![ // Get the string representation of the classloader. // Not the ideal, but best cross execution classloader identifier we have. - Instruction::MoveResultObject { - to: reg_arr_idx, // wrong name, but available for tmp val - }, Instruction::InvokeVirtual { method: GET_CLASS_LOADER.clone(), - args: vec![reg_arr_idx as u16], - }, - Instruction::MoveResultObject { to: reg_arr_idx }, - Instruction::InvokeVirtual { - method: TO_STRING.clone(), - args: vec![reg_arr_idx as u16], + args: vec![reg_def_type as u16], }, Instruction::MoveResultObject { - to: reg_arr_idx, // wrong name, but available for tmp val + to: reg_class_loader, }, + ]); + } + while let Some(classloader) = current_classloader { + if classloader.cname == *BOOT_CLASS_LOADER_TY { + // Ljava/lang/BootClassLoader; is complicated. + // It's string rep is "java.lang.BootClassLoader@7e2aeab" where "7e2aeab" is it's + // runtime hash id: the name change at each run. We need to compare with its type (it's + // ok, it's supposed to be a singleton). + // Also, it can be represented at runtime by the null pointer, so we need to accept the + // null pointer as a valid value. + insns.append(&mut vec![ + Instruction::IfEqZ { + a: reg_class_loader, + label: "label_end_classloader_test".into(), + }, + Instruction::InvokeVirtual { + method: GET_CLASS.clone(), + args: vec![reg_class_loader as u16], + }, + Instruction::MoveResultObject { to: reg_tst_val }, + Instruction::InvokeVirtual { + method: CLT_GET_DESCR_STRING.clone(), + args: vec![reg_tst_val as u16], + }, + Instruction::MoveResultObject { to: reg_tst_val }, + Instruction::ConstClass { + reg: reg_cmp_val, + lit: BOOT_CLASS_LOADER_TY.clone(), + }, + Instruction::InvokeVirtual { + method: CLT_GET_DESCR_STRING.clone(), + args: vec![reg_cmp_val as u16], + }, + Instruction::MoveResultObject { to: reg_cmp_val }, + Instruction::InvokeVirtual { + method: STR_EQ.clone(), + args: vec![reg_cmp_val as u16, reg_tst_val as u16], + }, + Instruction::MoveResult { to: reg_cmp_val }, + Instruction::IfEqZ { + a: reg_cmp_val, + label: no_label.clone(), + }, + Instruction::Label { + name: "label_end_classloader_test".into(), + }, + ]); + break; + } + insns.append(&mut vec![ + Instruction::IfEqZ { + a: reg_class_loader, + label: no_label.clone(), + }, + Instruction::InvokeVirtual { + method: TO_STRING.clone(), + args: vec![reg_class_loader as u16], + }, + Instruction::MoveResultObject { to: reg_tst_val }, Instruction::ConstString { - reg: reg_arr_val, + reg: reg_cmp_val, lit: classloader.string_representation.as_str().into(), }, Instruction::InvokeVirtual { method: STR_EQ.clone(), - args: vec![reg_arr_idx as u16, reg_arr_val as u16], - }, - Instruction::MoveResult { - to: reg_arr_idx, // wrong name, but available for tmp val + args: vec![reg_cmp_val as u16, reg_tst_val as u16], }, + Instruction::MoveResult { to: reg_cmp_val }, Instruction::IfEqZ { - a: reg_arr_idx, + a: reg_cmp_val, label: no_label.clone(), }, + Instruction::InvokeVirtual { + method: GET_PARENT.clone(), + args: vec![reg_class_loader as u16], + }, + Instruction::MoveResultObject { + to: reg_class_loader, + }, ]); - // Get Declaring Type - if is_constructor { - insns.push(Instruction::InvokeVirtual { - method: CNSTR_GET_DEC_CLS.clone(), - args: vec![reg_ref_method], - }); + let parent_id = classloader.parent_id.clone(); + // If parent_id is None, the parent is in fact the boot class loader (except for the + // boot class loader itself, already handled at the start of the loop). + current_classloader = if let Some(ref id) = parent_id { + classloaders.get(id) } else { - insns.push(Instruction::InvokeVirtual { - method: MTH_GET_DEC_CLS.clone(), - args: vec![reg_ref_method], - }); - } + classloaders + .values() + .find(|cl| cl.cname == *BOOT_CLASS_LOADER_TY) + }; } + // Check Declaring Type insns.append(&mut vec![ - Instruction::MoveResultObject { - to: reg_arr_idx, // wrong name, but available for tmp val - }, Instruction::ConstClass { - reg: reg_arr_val, // wrong name, but available for tmp val + reg: reg_cmp_val, lit: method_to_test.class_.clone(), }, Instruction::InvokeVirtual { method: CLT_GET_DESCR_STRING.clone(), - args: vec![reg_arr_idx as u16], + args: vec![reg_cmp_val as u16], }, - Instruction::MoveResultObject { to: reg_arr_idx }, + Instruction::MoveResultObject { to: reg_cmp_val }, Instruction::InvokeVirtual { method: CLT_GET_DESCR_STRING.clone(), - args: vec![reg_arr_val as u16], + args: vec![reg_def_type as u16], }, - Instruction::MoveResultObject { to: reg_arr_val }, + Instruction::MoveResultObject { to: reg_tst_val }, Instruction::InvokeVirtual { method: STR_EQ.clone(), - args: vec![reg_arr_idx as u16, reg_arr_val as u16], - }, - Instruction::MoveResult { - to: reg_arr_idx, // wrong name, but available for tmp val + args: vec![reg_cmp_val as u16, reg_tst_val as u16], }, + Instruction::MoveResult { to: reg_cmp_val }, Instruction::IfEqZ { - a: reg_arr_idx, + a: reg_cmp_val, label: no_label.clone(), }, // Comparing Type does not work when different types share the same name (eg type from // another class loader) //Instruction::IfNe { // a: reg_arr_idx, - // b: reg_arr_val, + // b: reg_tst_val, // label: no_label.clone(), //}, + ]); + if DEBUG { + insns.append(&mut vec![ + Instruction::ConstString { + reg: reg_tst_val, + lit: "THESEUS".into(), + }, + Instruction::ConstString { + reg: reg_cmp_val, + lit: format!( + "T.{method_test_name}() (test of {}) returned true", + method_to_test + .try_to_smali() + .unwrap_or("failed to convert".into()) + ) + .into(), + }, + Instruction::InvokeStatic { + method: LOG_INFO.clone(), + args: vec![reg_tst_val as u16, reg_cmp_val as u16], + }, + ]); + } + insns.append(&mut vec![ Instruction::Const { - reg: reg_arr_val, + reg: reg_cmp_val, lit: 1, }, - Instruction::Return { reg: reg_arr_val }, + Instruction::Return { reg: reg_cmp_val }, Instruction::Label { name: no_label }, + ]); + if DEBUG { + insns.append(&mut vec![ + Instruction::ConstString { + reg: reg_tst_val, + lit: "THESEUS".into(), + }, + Instruction::ConstString { + reg: reg_cmp_val, + lit: format!( + "T.{method_test_name}() (test of {}) returned false", + method_to_test + .try_to_smali() + .unwrap_or("failed to convert".into()) + ) + .into(), + }, + Instruction::InvokeStatic { + method: LOG_INFO.clone(), + args: vec![reg_tst_val as u16, reg_cmp_val as u16], + }, + ]); + } + insns.append(&mut vec![ Instruction::Const { - reg: reg_arr_val, + reg: reg_cmp_val, lit: 0, }, - Instruction::Return { reg: reg_arr_val }, + Instruction::Return { reg: reg_cmp_val }, ]); method.is_static = true; method.is_final = true; method.code = Some(Code::new( - 4, //registers_size, 3 reg + 1 parameter reg + 7, //registers_size, 6 reg + 1 parameter reg insns, Some(vec![Some("meth".into())]), // parameter_names )); @@ -667,6 +766,7 @@ fn gen_tester_method( /// reflected method. If None, the classloader is not tested. Platform classes should probably /// not be tested (the bootclassloader can be represented with a null reference, which may /// lead to a null pointer exception). +#[allow(clippy::too_many_arguments)] fn test_method( method_obj_reg: u16, id_method: IdMethod, @@ -674,7 +774,8 @@ fn test_method( reg_inf: &mut RegistersInfo, tester_methods_class: IdType, tester_methods: &mut HashMap, - classloader: Option<&ClassLoaderData>, + classloader: Option, + classloaders: &HashMap, ) -> Result> { use std::collections::hash_map::Entry; let tst_descriptor = match tester_methods.entry(id_method.clone()) { @@ -684,6 +785,7 @@ fn test_method( id_method, false, classloader, + classloaders, )?), } .descriptor @@ -765,7 +867,7 @@ fn get_invoke_block( let classloader = if ref_data.method.class_.is_platform_class() { None } else { - classloaders.get(&ref_data.method_cl_id) + Some(ref_data.method_cl_id.clone()) }; let mut insns = test_method( method_obj, @@ -775,6 +877,7 @@ fn get_invoke_block( tester_methods_class, tester_methods, classloader, + classloaders, )?; if !ref_data.is_static { @@ -979,7 +1082,7 @@ fn get_cnstr_new_inst_block( let classloader = if ref_data.constructor.class_.is_platform_class() { None } else { - classloaders.get(&ref_data.constructor_cl_id) + Some(ref_data.constructor_cl_id.clone()) }; let mut insns = test_cnstr( cnst_reg, @@ -989,6 +1092,7 @@ fn get_cnstr_new_inst_block( tester_methods_class, tester_methods, classloader, + classloaders, )?; insns.append(&mut get_args_from_obj_arr( &ref_data.constructor.proto.get_parameters(), // TODO: what if args are renammed? @@ -1039,6 +1143,7 @@ fn get_cnstr_new_inst_block( /// - `classloader`: is the runtime data of the classloader that loaded the. If None, the classloader /// is not tested. Platform classes should probably not be tested (the bootclassloader can be /// represented with a null reference, which may lead to a null pointer exception). +#[allow(clippy::too_many_arguments)] fn test_cnstr( cnst_reg: u16, id_method: IdMethod, @@ -1046,7 +1151,8 @@ fn test_cnstr( reg_inf: &mut RegistersInfo, tester_methods_class: IdType, tester_methods: &mut HashMap, - classloader: Option<&ClassLoaderData>, + classloader: Option, + classloaders: &HashMap, ) -> Result> { use std::collections::hash_map::Entry; let tst_descriptor = match tester_methods.entry(id_method.clone()) { @@ -1056,6 +1162,7 @@ fn test_cnstr( id_method, true, classloader, + classloaders, )?), } .descriptor diff --git a/patcher/src/runtime_data.rs b/patcher/src/runtime_data.rs index e98c70b..c52a223 100644 --- a/patcher/src/runtime_data.rs +++ b/patcher/src/runtime_data.rs @@ -210,5 +210,5 @@ pub struct ClassLoaderData { #[serde(rename = "str")] pub string_representation: String, /// The class of the class loader. - pub cname: String, // TODO: IdType, + pub cname: IdType, } diff --git a/test_apks/dyn_and_ref/java/classes/com/example/theseus/dynandref/Main.java b/test_apks/dyn_and_ref/java/classes/com/example/theseus/dynandref/Main.java index 2ab6e38..75a880f 100644 --- a/test_apks/dyn_and_ref/java/classes/com/example/theseus/dynandref/Main.java +++ b/test_apks/dyn_and_ref/java/classes/com/example/theseus/dynandref/Main.java @@ -14,6 +14,14 @@ import java.lang.reflect.Method; import com.example.theseus.Utils; public class Main { + static ClassLoader delegateLastClassLoaderParent = null; + static ClassLoader delegateLastClassLoader = null; + static ClassLoader dexClassLoaderParent = null; + static ClassLoader dexClassLoader = null; + static ClassLoader inMemoryDexClassLoaderParent = null; + static ClassLoader inMemoryDexClassLoader = null; + static ClassLoader pathClassLoaderParent = null; + static ClassLoader pathClassLoader = null; public static String getdexfile(Activity ac, String name) throws Exception { File dexfile = new File(ac.getCacheDir(), name); @@ -30,25 +38,65 @@ public class Main { } public static void run(Activity ac, String clname, boolean hasCollision, boolean hasParent, String methodType) { + ClassLoader cl = Main.class.getClassLoader(); + ClassLoader parent; try { Log.i("THESEUS", "clname: " + clname + ", hasCollision: " + hasCollision + ", hasParent: " + hasParent + ", methodType: " + methodType); - ClassLoader cl; - ClassLoader parent; + String name = "a.dex"; if (hasParent) { parent = Main.class.getClassLoader(); + name = "b.dex"; } else { parent = null; } if (clname.equals("DelegateLastClassLoader")) { - cl = new DelegateLastClassLoader(getdexfile(ac, "a.dex"), parent); + if (parent == null) { + if (delegateLastClassLoader == null) { + delegateLastClassLoader = new DelegateLastClassLoader(getdexfile(ac, name), parent); + } + cl = delegateLastClassLoader; + } else { + if (delegateLastClassLoaderParent == null) { + delegateLastClassLoaderParent = new DelegateLastClassLoader(getdexfile(ac, name), parent); + } + cl = delegateLastClassLoaderParent; + } } else if (clname.equals("DexClassLoader")) { - cl = new DexClassLoader(getdexfile(ac, "a.dex"), null, null, parent); + if (parent == null) { + if (dexClassLoader == null) { + dexClassLoader = new DexClassLoader(getdexfile(ac, name), null, null, parent); + } + cl = dexClassLoader; + } else { + if (dexClassLoaderParent == null) { + dexClassLoaderParent = new DexClassLoader(getdexfile(ac, name), null, null, parent); + } + cl = dexClassLoaderParent; + } } else if (clname.equals("InMemoryDexClassLoader")) { - cl = new InMemoryDexClassLoader(getdexbuffer(ac, "a.dex"), parent); + if (parent == null) { + if (inMemoryDexClassLoader == null) { + inMemoryDexClassLoader = new InMemoryDexClassLoader(getdexbuffer(ac, name), parent); + } + cl = inMemoryDexClassLoader; + } else { + if (inMemoryDexClassLoaderParent == null) { + inMemoryDexClassLoaderParent = new InMemoryDexClassLoader(getdexbuffer(ac, name), parent); + } + cl = inMemoryDexClassLoaderParent; + } } else if (clname.equals("PathClassLoader")) { - cl = new PathClassLoader(getdexfile(ac, "a.dex"), parent); - } else { - cl = Main.class.getClassLoader(); + if (parent == null) { + if (pathClassLoader == null) { + pathClassLoader = new PathClassLoader(getdexfile(ac, name), parent); + } + cl = pathClassLoader; + } else { + if (pathClassLoaderParent == null) { + pathClassLoaderParent = new PathClassLoader(getdexfile(ac, name), parent); + } + cl = pathClassLoaderParent; + } } Class clz = null; @@ -194,6 +242,7 @@ public class Main { return; }; } catch (Exception e) { + Log.e("THESEUS", "class loader name: " + cl.toString()); Log.e("THESEUS", "error:", e); } } diff --git a/test_apks/dyn_and_ref/java/classes/com/example/theseus/dynandref/MainActivity.java b/test_apks/dyn_and_ref/java/classes/com/example/theseus/dynandref/MainActivity.java index f10871d..afa7eef 100644 --- a/test_apks/dyn_and_ref/java/classes/com/example/theseus/dynandref/MainActivity.java +++ b/test_apks/dyn_and_ref/java/classes/com/example/theseus/dynandref/MainActivity.java @@ -49,6 +49,11 @@ public class MainActivity extends Activity { out = new FileOutputStream(outFile); Utils.copy(in, out); outFile.renameTo(new File(getCacheDir(), "a.dex")); // security? + in = assetManager.open("a.dex"); + outFile = new File(getCacheDir(), "b.dex_"); + out = new FileOutputStream(outFile); + Utils.copy(in, out); + outFile.renameTo(new File(getCacheDir(), "b.dex")); } catch (IOException e) {} try { in.close();