diff --git a/patcher/src/code_loading_patcher.rs b/patcher/src/code_loading_patcher.rs index 43875b6..055cb13 100644 --- a/patcher/src/code_loading_patcher.rs +++ b/patcher/src/code_loading_patcher.rs @@ -4,6 +4,7 @@ use std::fs::File; use androscalpel::{Apk, DexString, IdType, VisitorMut}; use anyhow::{Context, Result}; use clap::ValueEnum; +use log::{debug, info}; use crate::dex_types::DELEGATE_LAST_CLASS_LOADER; use crate::runtime_data::RuntimeData; @@ -262,9 +263,9 @@ impl ClassLoader<'_> { }; let new_name = loop { let prefix: DexString = if i == 0 { - format!("theseus-dedup/{}/", self.id).into() + format!("theseus/dedup/{}/", self.id).into() } else { - format!("theseus-dedup/{}-{i}/", self.id).into() + format!("theseus/dedup/{}/{i}/", self.id).into() }; let new_name = IdType::class_from_dex_string(&prefix.concatenate(&name)); if self.apk().get_class(&new_name).is_none() { @@ -305,9 +306,9 @@ impl ClassLoader<'_> { ) }) .collect(); - println!("rename for {}", self.id); + info!("types rename for {}", self.id); for (old, new) in &r { - println!(" {} -> {}", old.__str__(), new.__str__()); + info!(" {} -> {}", old.__str__(), new.__str__()); } r } @@ -326,18 +327,42 @@ impl ClassLoader<'_> { ) -> Option { if ty.is_platform_class() { // Platform classes have precedence for all android SDK classloader. + debug!("Class {} is a platform class, no renaming", ty.__str__()); return Some(ty.clone()); } if self.class == *DELEGATE_LAST_CLASS_LOADER { if let Some(new_ty) = self.renamed_classes.get(ty) { + debug!( + "Class {} found in {} ({}) in renamed class before delagation, use name {}", + ty.__str__(), + self.class.__str__(), + self.id, + new_ty.__str__() + ); return Some(new_ty.clone()); } else if self.apk().get_class(ty).is_some() { + debug!( + "Class {} found in {} ({}) in unique classes before delagation, use name {}", + ty.__str__(), + self.class.__str__(), + self.id, + ty.__str__() + ); return Some(ty.clone()); } } if let Some(ref parent_id) = self.parent { if let Some(parent) = class_loaders.get(parent_id) { if let Some(new_ty) = parent.get_ref_new_name(ty, class_loaders) { + debug!( + "Class {} found by delegating to parent ({}) \ + of {}({}), use name {}", + ty.__str__(), + parent_id, + self.class.__str__(), + self.id, + new_ty.__str__() + ); return Some(new_ty); } } else { @@ -350,13 +375,39 @@ impl ClassLoader<'_> { } } if self.class == *DELEGATE_LAST_CLASS_LOADER { + debug!( + "Class {} not found by {}({})", + ty.__str__(), + self.class.__str__(), + self.id + ); return None; } if let Some(new_ty) = self.renamed_classes.get(ty) { + debug!( + "Class {} found in {} ({}) in renamed class after delagation, use name {}", + ty.__str__(), + self.class.__str__(), + self.id, + new_ty.__str__() + ); Some(new_ty.clone()) } else if self.apk().get_class(ty).is_some() { + debug!( + "Class {} found in {} ({}) in unique classes after delagation, use name {}", + ty.__str__(), + self.class.__str__(), + self.id, + ty.__str__() + ); Some(ty.clone()) } else { + debug!( + "Class {} not found by {}({})", + ty.__str__(), + self.class.__str__(), + self.id + ); None } } diff --git a/patcher/src/dex_types.rs b/patcher/src/dex_types.rs index 9fb1328..67bfe9c 100644 --- a/patcher/src/dex_types.rs +++ b/patcher/src/dex_types.rs @@ -1,5 +1,5 @@ use androscalpel::{IdMethod, IdType}; -use anyhow::{Result, bail}; +use anyhow::{bail, Result}; use std::sync::LazyLock; pub(crate) static MTH_INVOKE: LazyLock = LazyLock::new(|| { @@ -99,6 +99,12 @@ pub(crate) static SCAL_TO_OBJ_FLOAT: LazyLock = LazyLock::new(|| { pub(crate) static SCAL_TO_OBJ_DOUBLE: LazyLock = LazyLock::new(|| { IdMethod::from_smali("Ljava/lang/Double;->valueOf(D)Ljava/lang/Double;").unwrap() }); +pub(crate) static GET_CLASS_LOADER: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;").unwrap() +}); +pub(crate) static TO_STRING: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Object;->toString()Ljava/lang/String;").unwrap() +}); pub(crate) static OBJECT_TY: LazyLock = LazyLock::new(|| IdType::from_smali("Ljava/lang/Object;").unwrap()); diff --git a/patcher/src/reflection_patcher.rs b/patcher/src/reflection_patcher.rs index cb8781c..435cd2a 100644 --- a/patcher/src/reflection_patcher.rs +++ b/patcher/src/reflection_patcher.rs @@ -27,6 +27,7 @@ pub fn transform_method( let invoke_data = ref_data.get_invoke_data_for(&meth.descriptor); let class_new_inst_data = ref_data.get_class_new_instance_data_for(&meth.descriptor); let cnstr_new_inst_data = ref_data.get_cnstr_new_instance_data_for(&meth.descriptor); + let classloaders = ref_data.get_classloader_data(); let code = meth .code @@ -189,6 +190,7 @@ pub fn transform_method( move_ret.clone(), tester_methods_class.clone(), tester_methods, + &classloaders, )? { new_insns.push(ins); } @@ -227,6 +229,7 @@ pub fn transform_method( move_ret.clone(), tester_methods_class.clone(), tester_methods, + &classloaders, )? { new_insns.push(ins); } @@ -337,8 +340,14 @@ fn gen_tester_method( tester_methods_class: IdType, method_to_test: IdMethod, is_constructor: bool, + classloader: Option<&ClassLoaderData>, ) -> Result { let mut hasher = DefaultHasher::new(); + if let Some(classloader) = classloader { + classloader.id.hash(&mut hasher); + } else { + "00000000".hash(&mut hasher); + } method_to_test.hash(&mut hasher); let hash = hasher.finish(); let m_name: String = (&method_to_test.name).try_into()?; @@ -522,20 +531,70 @@ fn gen_tester_method( // b: reg_arr_val, // label: no_label.clone(), //}, - // Check Declaring Type - Instruction::InvokeVirtual { - method: MTH_GET_DEC_CLS.clone(), - args: vec![reg_ref_method], - }, ]); } + // Get Declaring Type if is_constructor { - // Check Declaring Type insns.push(Instruction::InvokeVirtual { method: CNSTR_GET_DEC_CLS.clone(), args: vec![reg_ref_method], }); + } else { + insns.push(Instruction::InvokeVirtual { + method: MTH_GET_DEC_CLS.clone(), + args: vec![reg_ref_method], + }); } + //Check the classloader + if let Some(classloader) = classloader { + 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], + }, + Instruction::MoveResultObject { + to: reg_arr_idx, // wrong name, but available for tmp val + }, + Instruction::ConstString { + reg: reg_arr_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 + }, + Instruction::IfEqZ { + a: reg_arr_idx, + label: no_label.clone(), + }, + ]); + // Get Declaring Type + if is_constructor { + insns.push(Instruction::InvokeVirtual { + method: CNSTR_GET_DEC_CLS.clone(), + args: vec![reg_ref_method], + }); + } else { + insns.push(Instruction::InvokeVirtual { + method: MTH_GET_DEC_CLS.clone(), + args: vec![reg_ref_method], + }); + } + } + // Check Declaring Type insns.append(&mut vec![ Instruction::MoveResultObject { to: reg_arr_idx, // wrong name, but available for tmp val @@ -604,6 +663,10 @@ fn gen_tester_method( /// - `tester_methods`: the methods used to test if a `java.lang.reflect.Method` is a specific method. /// Methods are indexed by the IdMethod they detect, and have a name derived from the method /// they detect. +/// - `classloader`: is the runtime data of the classloader that loaded the class defining the +/// 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). fn test_method( method_obj_reg: u16, id_method: IdMethod, @@ -611,11 +674,17 @@ fn test_method( reg_inf: &mut RegistersInfo, tester_methods_class: IdType, tester_methods: &mut HashMap, + classloader: Option<&ClassLoaderData>, ) -> Result> { use std::collections::hash_map::Entry; let tst_descriptor = match tester_methods.entry(id_method.clone()) { Entry::Occupied(e) => e.into_mut(), - Entry::Vacant(e) => e.insert(gen_tester_method(tester_methods_class, id_method, false)?), + Entry::Vacant(e) => e.insert(gen_tester_method( + tester_methods_class, + id_method, + false, + classloader, + )?), } .descriptor .clone(); @@ -658,6 +727,7 @@ fn get_move_result<'a>( (vec![], None) } +#[allow(clippy::too_many_arguments)] fn get_invoke_block( ref_data: &ReflectionInvokeData, invoke_arg: &[u16], @@ -666,6 +736,7 @@ fn get_invoke_block( move_result: Option, tester_methods_class: IdType, tester_methods: &mut HashMap, + classloaders: &HashMap, ) -> Result> { let (method_obj, obj_inst, arg_arr) = if let &[a, b, c] = invoke_arg { (a, b, c) @@ -691,6 +762,11 @@ fn get_invoke_block( ref_data.method.try_to_smali()?, ref_data.addr ); + let classloader = if ref_data.method.class_.is_platform_class() { + None + } else { + classloaders.get(&ref_data.method_cl_id) + }; let mut insns = test_method( method_obj, ref_data.method.clone(), @@ -698,6 +774,7 @@ fn get_invoke_block( reg_inf, tester_methods_class, tester_methods, + classloader, )?; if !ref_data.is_static { @@ -868,6 +945,7 @@ fn get_args_from_obj_arr( insns } +#[allow(clippy::too_many_arguments)] fn get_cnstr_new_inst_block( ref_data: &ReflectionCnstrNewInstData, invoke_arg: &[u16], @@ -876,6 +954,7 @@ fn get_cnstr_new_inst_block( move_result: Option, tester_methods_class: IdType, tester_methods: &mut HashMap, + classloaders: &HashMap, ) -> Result> { let (cnst_reg, arg_arr) = if let &[a, b] = invoke_arg { (a, b) @@ -897,6 +976,11 @@ fn get_cnstr_new_inst_block( ref_data.addr ); + let classloader = if ref_data.constructor.class_.is_platform_class() { + None + } else { + classloaders.get(&ref_data.constructor_cl_id) + }; let mut insns = test_cnstr( cnst_reg, ref_data.constructor.clone(), // TODO: what if args are renammed? @@ -904,6 +988,7 @@ fn get_cnstr_new_inst_block( reg_inf, tester_methods_class, tester_methods, + classloader, )?; insns.append(&mut get_args_from_obj_arr( &ref_data.constructor.proto.get_parameters(), // TODO: what if args are renammed? @@ -947,6 +1032,13 @@ fn get_cnstr_new_inst_block( /// - `method_obj_reg`: the register containing the `java.lang.reflect.Method` /// - `id_method`: the expected [`IdMethod`]. /// - `abort_label`: the label where to jump if the method does not match `id_method`. +/// - `tester_methods_class`: the class used to define the methods in `tester_methods` +/// - `tester_methods`: the methods used to test if a `java.lang.reflect.Method` is a specific method. +/// Methods are indexed by the IdMethod they detect, and have a name derived from the method +/// they detect. +/// - `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). fn test_cnstr( cnst_reg: u16, id_method: IdMethod, @@ -954,11 +1046,17 @@ fn test_cnstr( reg_inf: &mut RegistersInfo, tester_methods_class: IdType, tester_methods: &mut HashMap, + classloader: Option<&ClassLoaderData>, ) -> Result> { use std::collections::hash_map::Entry; let tst_descriptor = match tester_methods.entry(id_method.clone()) { Entry::Occupied(e) => e.into_mut(), - Entry::Vacant(e) => e.insert(gen_tester_method(tester_methods_class, id_method, true)?), + Entry::Vacant(e) => e.insert(gen_tester_method( + tester_methods_class, + id_method, + true, + classloader, + )?), } .descriptor .clone(); diff --git a/patcher/src/register_manipulation.rs b/patcher/src/register_manipulation.rs index 2f4bfea..e72db16 100644 --- a/patcher/src/register_manipulation.rs +++ b/patcher/src/register_manipulation.rs @@ -1,5 +1,5 @@ use androscalpel::{Instruction, RegType}; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use log::debug; /// Information about the register used. diff --git a/patcher/src/runtime_data.rs b/patcher/src/runtime_data.rs index 6362496..e98c70b 100644 --- a/patcher/src/runtime_data.rs +++ b/patcher/src/runtime_data.rs @@ -12,6 +12,8 @@ pub struct RuntimeData { pub dyn_code_load: Vec, /// The id of the class loader of the apk (the main classloader) pub apk_cl_id: Option, + /// Additionnal classloader data. + pub classloaders: Vec, } impl RuntimeData { @@ -87,6 +89,14 @@ impl RuntimeData { } data } + + /// Get classloader data, indexed by id. + pub fn get_classloader_data(&self) -> HashMap { + self.classloaders + .iter() + .map(|data| (data.id.clone(), data.clone())) + .collect() + } } /// Structure storing the runtime information of a reflection call using @@ -187,3 +197,18 @@ pub struct DynamicCodeLoadingData { /// The path to the files storing the .dex/.apk/other bytecode loaded. pub files: Vec, } + +/// Structure storing the runtime information of a classloader. +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct ClassLoaderData { + /// Id of the classloader. This value is unique for *one* run of the apk. + pub id: String, + /// The Id of the parent classloader if it exists. + pub parent_id: Option, + /// The string representation of the classloader. Not verry relayable but our best option to + /// distinguish classloader at runtime. + #[serde(rename = "str")] + pub string_representation: String, + /// The class of the class loader. + pub cname: String, // TODO: IdType, +}