From 5fdeb256826b0c9610f274d047725ec9cd79bcb2 Mon Sep 17 00:00:00 2001 From: Jean-Marie 'Histausse' Mineau Date: Tue, 4 Feb 2025 19:01:07 +0100 Subject: [PATCH] staticalyze object instanciation --- patcher/src/lib.rs | 623 +++++++++++++----- patcher/src/main.rs | 39 +- .../example/theseus/tests/MainActivity.java | 24 +- 3 files changed, 518 insertions(+), 168 deletions(-) diff --git a/patcher/src/lib.rs b/patcher/src/lib.rs index 6eab25a..161882d 100644 --- a/patcher/src/lib.rs +++ b/patcher/src/lib.rs @@ -1,3 +1,4 @@ +use androscalpel::SmaliName; use androscalpel::{IdMethod, Instruction, Method}; use anyhow::{bail, Context, Result}; use std::sync::LazyLock; @@ -10,13 +11,21 @@ pub mod get_apk; // does. /// Structure storing the runtime information of a reflection call. -pub struct ReflectionData { +pub struct ReflectionInvokeData { pub method: IdMethod, // TODO: variable number of args? // TODO: type of invoke? } -struct RegistersInfo { +pub struct ReflectionClassNewInstData { + pub constructor: IdMethod, +} + +pub struct ReflectionCnstrNewInstData { + pub constructor: IdMethod, +} + +pub struct RegistersInfo { pub array_index: u8, //pub array: u8, pub array_val: u8, @@ -50,26 +59,44 @@ static MTH_GET_PARAMS_TY: LazyLock = LazyLock::new(|| { static MTH_GET_RET_TY: LazyLock = LazyLock::new(|| { IdMethod::from_smali("Ljava/lang/reflect/Method;->getReturnType()Ljava/lang/Class;").unwrap() }); -static MTH_GET_DEC_TY: LazyLock = LazyLock::new(|| { +static MTH_GET_DEC_CLS: LazyLock = LazyLock::new(|| { IdMethod::from_smali("Ljava/lang/reflect/Method;->getDeclaringClass()Ljava/lang/Class;") .unwrap() }); static STR_EQ: LazyLock = LazyLock::new(|| { IdMethod::from_smali("Ljava/lang/String;->equals(Ljava/lang/Object;)Z").unwrap() }); +static CLASS_NEW_INST: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Class;->newInstance()Ljava/lang/Object;").unwrap() +}); +static CNSTR_NEW_INST: LazyLock = LazyLock::new(|| { + IdMethod::from_smali( + "Ljava/lang/reflect/Constructor;->newInstance([Ljava/lang/Object;)Ljava/lang/Object;", + ) + .unwrap() +}); +static CNSTR_GET_PARAMS_TY: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/reflect/Constructor;->getParameterTypes()[Ljava/lang/Class;") + .unwrap() +}); +static CNSTR_GET_DEC_CLS: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/reflect/Constructor;->getDeclaringClass()Ljava/lang/Class;") + .unwrap() +}); // 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 -pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result<()> { +pub fn transform_method( + meth: &mut Method, + ref_invoke_data: &ReflectionInvokeData, + ref_class_new_inst_data: &ReflectionClassNewInstData, + ref_cnstr_new_inst_data: &ReflectionCnstrNewInstData, +) -> Result<()> { // checking meth.annotations might be usefull at some point let code = meth .code .as_mut() .with_context(|| format!("Code not found in {}", meth.descriptor.__str__()))?; - println!( - "registers_size: {}\nins_size: {}\nouts_size: {}", - code.registers_size, code.ins_size, code.outs_size, - ); // TODO if code.registers_size + RegistersInfo::NB_U8_REG > u8::MAX as u16 { bail!("To many registers"); @@ -83,43 +110,67 @@ pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result< nb_arg_reg: 0, }; let mut new_insns = vec![]; - let mut iter = code.insns.iter().peekable(); + let mut iter = code.insns.iter(); while let Some(ins) = iter.next() { match ins { - Instruction::InvokeVirtual { method, args } if method == &*MTH_INVOKE => { - let move_ret = match iter.peek() { - Some(Instruction::MoveResult { .. }) - | Some(Instruction::MoveResultWide { .. }) - | Some(Instruction::MoveResultObject { .. }) => iter.next().cloned(), - _ => None, + Instruction::InvokeVirtual { method, args } + if method == &*MTH_INVOKE + || method == &*CLASS_NEW_INST + || method == &*CNSTR_NEW_INST => + { + let (pseudo_insns, move_ret) = get_move_result(iter.clone()); + if move_ret.is_some() { + while move_ret.as_ref() != iter.next() {} + } + let end_label = if method == &*MTH_INVOKE { + format!("end_reflection_call_at_{}", "TODO_ADDR") + } else if method == &*CLASS_NEW_INST || method == &*CNSTR_NEW_INST { + format!("end_reflection_instanciation_at_{}", "TODO_ADDR") + } else { + panic!("Should not happen!") }; - // TODO: rever from get_invoke_block failure - let label: String = "TODO_NAME_THIS".into(); - for ins in get_invoke_block( - ref_data, - args.as_slice(), - &mut register_info, - &label, - move_ret.clone(), - )? - .into_iter() - { - println!(" \x1b[92m{}\x1b[0m", ins.__str__()); + // TODO: recover from failure + let ins_block = if method == &*MTH_INVOKE { + get_invoke_block( + ref_invoke_data, + args.as_slice(), + &mut register_info, + &end_label, + move_ret.clone(), + )? + } else if method == &*CLASS_NEW_INST { + get_class_new_inst_block( + ref_class_new_inst_data, + args.as_slice(), + &mut register_info, + &end_label, + move_ret.clone(), + )? + } else if method == &*CNSTR_NEW_INST { + get_cnstr_new_inst_block( + ref_cnstr_new_inst_data, + args.as_slice(), + &mut register_info, + &end_label, + move_ret.clone(), + )? + } else { + panic!("Should not happen!") + }; + for ins in ins_block.into_iter() { new_insns.push(ins); } new_insns.push(ins.clone()); if let Some(move_ret) = move_ret { + for ins in pseudo_insns.into_iter() { + new_insns.push(ins); + } new_insns.push(move_ret); } - println!(" \x1b[91m{}\x1b[0m", ins.__str__()); - let lab = Instruction::Label { - name: format!("{label}_END"), - }; - new_insns.push(lab.clone()); - println!(" \x1b[91m{}\x1b[0m", lab.__str__()); + let end_label = Instruction::Label { name: end_label }; + new_insns.push(end_label.clone()); } ins => { - println!(" {}", ins.__str__()); new_insns.push(ins.clone()); } } @@ -137,18 +188,30 @@ pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result< code.insns.append(&mut new_insns); code.registers_size += register_info.get_nb_added_reg(); - println!("\nnew code:\n"); - for ins in &code.insns { - println!(" {}", ins.__str__()); - } Ok(()) } +/// Return the MoveResult{,Wide,Object} associated to the last instruction of the iterator. +/// TODO: return the list of pseudo instruction between the last instruction and the move result. +fn get_move_result<'a>( + mut iter: impl Iterator, +) -> (Vec, Option) { + if let Some(ins) = iter.next() { + match ins { + Instruction::MoveResult { .. } + | Instruction::MoveResultWide { .. } + | Instruction::MoveResultObject { .. } => return (vec![], Some(ins.clone())), + _ => (), // break, + } + } + (vec![], None) +} + fn get_invoke_block( - ref_data: &ReflectionData, + ref_data: &ReflectionInvokeData, invoke_arg: &[u16], reg_inf: &mut RegistersInfo, - label: &str, + end_label: &str, move_result: Option, ) -> Result> { let (method_obj, obj_inst, arg_arr) = if let &[a, b, c] = invoke_arg { @@ -167,117 +230,32 @@ fn get_invoke_block( if reg_inf.nb_arg_reg < nb_args as u16 + 1 { reg_inf.nb_arg_reg = nb_args as u16 + 1; } - let mut insns = vec![ - // Check the runtime method is the right one - // Check Name - Instruction::InvokeVirtual { - method: MTH_GET_NAME.clone(), - args: vec![method_obj], - }, - Instruction::MoveResultObject { - to: reg_inf.array_index, // wrong name, but available for tmp val - }, - Instruction::ConstString { - reg: reg_inf.array_val, // wrong name, but available for tmp val - lit: ref_data.method.name.clone(), - }, - Instruction::InvokeVirtual { - method: STR_EQ.clone(), - args: vec![reg_inf.array_index as u16, reg_inf.array_val as u16], - }, - Instruction::MoveResult { - to: reg_inf.array_index, // wrong name, but available for tmp val - }, - Instruction::IfEqZ { - a: reg_inf.array_index, - label: format!("{label}_END_OF_CALL_1"), // TODO: rename 1 - }, - // Check Return Type - Instruction::InvokeVirtual { - method: MTH_GET_RET_TY.clone(), - args: vec![method_obj], - }, - Instruction::MoveResultObject { - to: reg_inf.array_index, // wrong name, but available for tmp val - }, - Instruction::ConstClass { - reg: reg_inf.array_val, // wrong name, but available for tmp val - lit: ref_data.method.proto.get_return_type(), - }, - Instruction::IfNe { - a: reg_inf.array_index, - b: reg_inf.array_val, - label: format!("{label}_END_OF_CALL_1"), // TODO: rename 1 - }, - // Check Declaring Type - Instruction::InvokeVirtual { - method: MTH_GET_DEC_TY.clone(), - args: vec![method_obj], - }, - Instruction::MoveResultObject { - to: reg_inf.array_index, // wrong name, but available for tmp val - }, - Instruction::ConstClass { - reg: reg_inf.array_val, // wrong name, but available for tmp val - lit: ref_data.method.class_.clone(), - }, - Instruction::IfNe { - a: reg_inf.array_index, - b: reg_inf.array_val, - label: format!("{label}_END_OF_CALL_1"), // TODO: rename 1 - }, - ]; - // Check for arg type - insns.push(Instruction::InvokeVirtual { - method: MTH_GET_PARAMS_TY.clone(), - args: vec![method_obj], - }); - insns.push(Instruction::MoveResultObject { to: reg_inf.array }); - // First check the number of args - insns.push(Instruction::ArrayLength { - dest: reg_inf.array_index, - arr: reg_inf.array, - }); - insns.push(Instruction::Const { - reg: reg_inf.array_val, - lit: ref_data.method.proto.get_parameters().len() as i32, - }); - insns.push(Instruction::IfNe { - a: reg_inf.array_index, - b: reg_inf.array_val, - label: format!("{label}_END_OF_CALL_1"), // TODO: rename 1 - }); - // then the type of each arg - for (i, param) in ref_data - .method - .proto - .get_parameters() - .into_iter() - .enumerate() - { - insns.push(Instruction::Const { - reg: reg_inf.array_index, - lit: i as i32, - }); - insns.push(Instruction::AGetObject { - dest: reg_inf.array_val, - arr: reg_inf.array, - idx: reg_inf.array_index, - }); - insns.push(Instruction::ConstClass { - reg: reg_inf.array_index, // wrong name, but available for tmp val - lit: param, - }); - insns.push(Instruction::IfNe { - a: reg_inf.array_index, - b: reg_inf.array_val, - label: format!("{label}_END_OF_CALL_1"), // TODO: rename 1 - }) - } + + let abort_label = format!( + "end_static_call_to_{}_at_{}", + ref_data.method.try_to_smali()?, + "TODO_ADDR" + ); + let mut insns = test_method( + method_obj, + ref_data.method.clone(), + abort_label.clone(), + reg_inf, + ); // Move 'this' to fist arg + // We do a small detour to `reg_inf.array_val` because we need a u8 reg to down cast the + // Object reference to the right Class insns.push(Instruction::MoveObject { from: obj_inst, + to: reg_inf.array_val as u16, + }); + insns.push(Instruction::CheckCast { + reg: reg_inf.array_val, + lit: ref_data.method.class_.clone(), + }); + insns.push(Instruction::MoveObject { + from: reg_inf.array_val as u16, to: reg_inf.first_arg, }); for (i, param) in ref_data.method.proto.get_parameters().iter().enumerate() { @@ -307,11 +285,354 @@ fn get_invoke_block( insns.push(move_result); } insns.push(Instruction::Goto { - label: format!("{label}_END"), - }); - insns.push(Instruction::Label { - name: format!("{label}_END_OF_CALL_1"), + label: end_label.to_string(), }); + insns.push(Instruction::Label { name: abort_label }); // We need a few u8 regs here. For now, we assumes we work with less than 256 reg. Ok(insns) } + +/// Generate bytecode that test if a `java.lang.reflect.Method` is equal to an [`IdMethod`] +/// +/// - `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`. +pub fn test_method( + method_obj_reg: u16, + id_method: IdMethod, + abort_label: String, + reg_inf: &mut RegistersInfo, +) -> Vec { + // Check for arg type + let mut insns = vec![ + Instruction::InvokeVirtual { + method: MTH_GET_PARAMS_TY.clone(), + args: vec![method_obj_reg], + }, + Instruction::MoveResultObject { to: reg_inf.array }, + // First check the number of args + Instruction::ArrayLength { + dest: reg_inf.array_index, + arr: reg_inf.array, + }, + Instruction::Const { + reg: reg_inf.array_val, + lit: id_method.proto.get_parameters().len() as i32, + }, + Instruction::IfNe { + a: reg_inf.array_index, + b: reg_inf.array_val, + label: abort_label.clone(), + }, + ]; + // then the type of each arg + for (i, param) in id_method.proto.get_parameters().into_iter().enumerate() { + insns.push(Instruction::Const { + reg: reg_inf.array_index, + lit: i as i32, + }); + insns.push(Instruction::AGetObject { + dest: reg_inf.array_val, + arr: reg_inf.array, + idx: reg_inf.array_index, + }); + insns.push(Instruction::ConstClass { + reg: reg_inf.array_index, // wrong name, but available for tmp val + lit: param, + }); + insns.push(Instruction::IfNe { + a: reg_inf.array_index, + b: reg_inf.array_val, + label: abort_label.clone(), + }) + } + insns.append(&mut vec![ + // Check the runtime method is the right one + // Check Name + Instruction::InvokeVirtual { + method: MTH_GET_NAME.clone(), + args: vec![method_obj_reg], + }, + Instruction::MoveResultObject { + to: reg_inf.array_index, // wrong name, but available for tmp val + }, + Instruction::ConstString { + reg: reg_inf.array_val, // wrong name, but available for tmp val + lit: id_method.name.clone(), + }, + Instruction::InvokeVirtual { + method: STR_EQ.clone(), + args: vec![reg_inf.array_index as u16, reg_inf.array_val as u16], + }, + Instruction::MoveResult { + to: reg_inf.array_index, // wrong name, but available for tmp val + }, + Instruction::IfEqZ { + a: reg_inf.array_index, + label: abort_label.clone(), + }, + // Check Return Type + Instruction::InvokeVirtual { + method: MTH_GET_RET_TY.clone(), + args: vec![method_obj_reg], + }, + Instruction::MoveResultObject { + to: reg_inf.array_index, // wrong name, but available for tmp val + }, + Instruction::ConstClass { + reg: reg_inf.array_val, // wrong name, but available for tmp val + lit: id_method.proto.get_return_type(), + }, + Instruction::IfNe { + a: reg_inf.array_index, + b: reg_inf.array_val, + label: abort_label.clone(), + }, + // Check Declaring Type + Instruction::InvokeVirtual { + method: MTH_GET_DEC_CLS.clone(), + args: vec![method_obj_reg], + }, + Instruction::MoveResultObject { + to: reg_inf.array_index, // wrong name, but available for tmp val + }, + Instruction::ConstClass { + reg: reg_inf.array_val, // wrong name, but available for tmp val + lit: id_method.class_.clone(), + }, + Instruction::IfNe { + a: reg_inf.array_index, + b: reg_inf.array_val, + label: abort_label.clone(), + }, + ]); + insns +} + +fn get_cnstr_new_inst_block( + ref_data: &ReflectionCnstrNewInstData, + invoke_arg: &[u16], + reg_inf: &mut RegistersInfo, + end_label: &str, + move_result: Option, +) -> Result> { + let (cnst_reg, arg_arr) = if let &[a, b] = invoke_arg { + (a, b) + } else { + bail!( + "Method;->invoke arg should have exactrly 2 arguments, found {}", + invoke_arg.len() + ); + }; + if cnst_reg > u8::MAX as u16 { + // TODO + bail!("Cannot transform instantiation calls to a class stored in 16 bits register"); + } + if reg_inf.first_arg > u8::MAX as u16 { + // TODO + bail!("Cannot transform instantiation calls to a class with first argument register greater than 255."); + } + //let cnst_reg = cnst_reg as u8; + + let nb_args = ref_data.constructor.proto.get_parameters().len(); + if reg_inf.nb_arg_reg < nb_args as u16 + 1 { + reg_inf.nb_arg_reg = nb_args as u16 + 1; + } + + let abort_label = format!( + "end_static_instance_with_{}_at_{}", + ref_data.constructor.try_to_smali()?, + "TODO_ADDR" + ); + + let mut insns = test_cnstr( + cnst_reg, + ref_data.constructor.clone(), + abort_label.clone(), + reg_inf, + ); + for (i, param) in ref_data + .constructor + .proto + .get_parameters() + .iter() + .enumerate() + { + insns.push(Instruction::Const { + reg: reg_inf.array_index, + lit: i as i32, + }); + insns.push(Instruction::AGetObject { + dest: reg_inf.array_val, + arr: arg_arr as u8, // TODO + idx: reg_inf.array_index, + }); + insns.push(Instruction::CheckCast { + reg: reg_inf.array_val, + lit: param.clone(), + }); + insns.push(Instruction::MoveObject { + from: reg_inf.array_val as u16, + to: reg_inf.first_arg + i as u16 + 1, + }); + } + insns.push(Instruction::NewInstance { + reg: reg_inf.first_arg as u8, + lit: ref_data.constructor.class_.clone(), + }); + insns.push(Instruction::InvokeDirect { + method: ref_data.constructor.clone(), + args: (reg_inf.first_arg..reg_inf.first_arg + nb_args as u16 + 1).collect(), + }); + if let Some(Instruction::MoveResultObject { to }) = move_result { + insns.push(Instruction::MoveObject { + from: reg_inf.first_arg, + to: to as u16, + }); + } + insns.push(Instruction::Goto { + label: end_label.to_string(), + }); + insns.push(Instruction::Label { name: abort_label }); + Ok(insns) +} + +/// Generate bytecode that test if a `java.lang.reflect.Constructor` is equal to an [`IdMethod`] +/// +/// - `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`. +pub fn test_cnstr( + cnst_reg: u16, + id_method: IdMethod, + abort_label: String, + reg_inf: &mut RegistersInfo, +) -> Vec { + // Check for arg type + let mut insns = vec![ + Instruction::InvokeVirtual { + method: CNSTR_GET_PARAMS_TY.clone(), + args: vec![cnst_reg], + }, + Instruction::MoveResultObject { to: reg_inf.array }, + // First check the number of args + Instruction::ArrayLength { + dest: reg_inf.array_index, + arr: reg_inf.array, + }, + Instruction::Const { + reg: reg_inf.array_val, + lit: id_method.proto.get_parameters().len() as i32, + }, + Instruction::IfNe { + a: reg_inf.array_index, + b: reg_inf.array_val, + label: abort_label.clone(), + }, + ]; + // then the type of each arg + for (i, param) in id_method.proto.get_parameters().into_iter().enumerate() { + insns.push(Instruction::Const { + reg: reg_inf.array_index, + lit: i as i32, + }); + insns.push(Instruction::AGetObject { + dest: reg_inf.array_val, + arr: reg_inf.array, + idx: reg_inf.array_index, + }); + insns.push(Instruction::ConstClass { + reg: reg_inf.array_index, // wrong name, but available for tmp val + lit: param, + }); + insns.push(Instruction::IfNe { + a: reg_inf.array_index, + b: reg_inf.array_val, + label: abort_label.clone(), + }) + } + insns.append(&mut vec![ + // Check Declaring Type + Instruction::InvokeVirtual { + method: CNSTR_GET_DEC_CLS.clone(), + args: vec![cnst_reg], + }, + Instruction::MoveResultObject { + to: reg_inf.array_index, // wrong name, but available for tmp val + }, + Instruction::ConstClass { + reg: reg_inf.array_val, // wrong name, but available for tmp val + lit: id_method.class_.clone(), + }, + Instruction::IfNe { + a: reg_inf.array_index, + b: reg_inf.array_val, + label: abort_label.clone(), + }, + ]); + insns +} + +fn get_class_new_inst_block( + ref_data: &ReflectionClassNewInstData, + invoke_arg: &[u16], + reg_inf: &mut RegistersInfo, + end_label: &str, + move_result: Option, +) -> Result> { + let class_reg = if let &[a] = invoke_arg { + a + } else { + bail!( + "Method;->invoke arg should have exactrly 3 arguments, found {}", + invoke_arg.len() + ); + }; + if !ref_data.constructor.proto.get_parameters().is_empty() { + bail!( + "Class.newInstance can only initialize instance with zero args constructor, found {}", + ref_data.constructor.__str__() + ); + } + + 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!( + "end_static_instance_with_{}_at_{}", + ref_data.constructor.try_to_smali()?, + "TODO_ADDR" + ); + + let obj_reg = match move_result { + Some(Instruction::MoveResultObject { to }) => to, + _ => reg_inf.array_index, + }; + + Ok(vec![ + Instruction::ConstClass { + reg: reg_inf.array_index, // wrong name, but available for tmp val + lit: ref_data.constructor.class_.clone(), + }, + Instruction::IfNe { + a: reg_inf.array_index, + b: class_reg, + label: abort_label.clone(), + }, + Instruction::NewInstance { + reg: obj_reg, + lit: ref_data.constructor.class_.clone(), + }, + Instruction::InvokeDirect { + method: ref_data.constructor.clone(), + args: vec![obj_reg as u16], + }, + Instruction::Goto { + label: end_label.to_string(), + }, + Instruction::Label { name: abort_label }, + ]) +} diff --git a/patcher/src/main.rs b/patcher/src/main.rs index 0e50e35..9216d2c 100644 --- a/patcher/src/main.rs +++ b/patcher/src/main.rs @@ -5,7 +5,9 @@ use std::path::PathBuf; use androscalpel::{IdMethod, IdType}; use patcher::get_apk::{get_apk, ApkLocation}; -use patcher::{transform_method, ReflectionData}; +use patcher::{ + transform_method, ReflectionClassNewInstData, ReflectionCnstrNewInstData, ReflectionInvokeData, +}; use clap::Parser; @@ -36,25 +38,38 @@ fn main() { .unwrap(); //println!("{:#?}", class.direct_methods.keys()); //println!("{:#?}", class.virtual_methods.keys()); - let method = class - .virtual_methods - .get_mut( - &IdMethod::from_smali( - "Lcom/example/theseus/reflection/MainActivity;->callVirtualMethodReflectCall()V", - ) - .unwrap(), - ) - .unwrap(); - transform_method( + for m in [ + "Lcom/example/theseus/reflection/MainActivity;->callVirtualMethodReflectCall()V", + "Lcom/example/theseus/reflection/MainActivity;->callConstructorVirtualMethodReflectConstr()V", + "Lcom/example/theseus/reflection/MainActivity;->callVirtualMethodReflectOldConst()V", + ] { + let method = class + .virtual_methods + .get_mut(&IdMethod::from_smali(m).unwrap()) + .unwrap(); + transform_method( method, - &ReflectionData { + &ReflectionInvokeData { method: IdMethod::from_smali( "Lcom/example/theseus/reflection/Reflectee;->transfer(Ljava/lang/String;)Ljava/lang/String;", ) .unwrap(), }, + &ReflectionClassNewInstData { + constructor: IdMethod::from_smali( + "Lcom/example/theseus/reflection/Reflectee;->()V", + ) + .unwrap(), + }, + &ReflectionCnstrNewInstData{ + constructor: IdMethod::from_smali( + "Lcom/example/theseus/reflection/Reflectee;->(Ljava/lang/String;)V", + ) + .unwrap(), + }, ) .unwrap(); + } let mut dex_files = vec![]; let mut files = apk.gen_raw_dex().unwrap(); let mut i = 0; diff --git a/test_apks/tests/java/classes/com/example/theseus/tests/MainActivity.java b/test_apks/tests/java/classes/com/example/theseus/tests/MainActivity.java index 917caca..7239cd0 100644 --- a/test_apks/tests/java/classes/com/example/theseus/tests/MainActivity.java +++ b/test_apks/tests/java/classes/com/example/theseus/tests/MainActivity.java @@ -65,10 +65,11 @@ public class MainActivity extends Activity { Class[] params = mth.getParameterTypes(); if ( mth.getName().equals("transfer") && - ret == String.class && - dec == Reflectee.class && + mth.getReturnType() == String.class && + mth.getDeclaringClass() == Reflectee.class && params.length == 1 && - params[0] == String.class { + params[0] == String.class + ) { Log.e("[TEST]", "OK"); } String newData = (String) mth.invoke(r, data); @@ -88,7 +89,13 @@ public class MainActivity extends Activity { ClassLoader cl = MainActivity.class.getClassLoader(); Class clz = cl.loadClass("com.example.theseus.reflection.Reflectee"); Constructor cst = clz.getDeclaredConstructor(String.class); - Object r = cst.newInstance(data); + Object r; + Class[] args = cst.getParameterTypes(); + if (args.length == 1 && args[0] == String.class && cst.getDeclaringClass() == Reflectee.class) { + r = new Reflectee(data); + } else { + r = cst.newInstance(data); + } Method mth = clz.getMethod("transfer", String.class); String newData = (String) mth.invoke(r, ""); Utils.sink(this, newData); @@ -106,7 +113,14 @@ public class MainActivity extends Activity { String data = Utils.source("no reflect constr"); ClassLoader cl = MainActivity.class.getClassLoader(); Class clz = cl.loadClass("com.example.theseus.reflection.Reflectee"); - Object r = clz.newInstance(); + Object r; + if ( + clz == Reflectee.class + ) { + r = new Reflectee(); + } else { + r = clz.newInstance(); + } Method mth = clz.getMethod("transfer", String.class); String newData = (String) mth.invoke(r, data); Utils.sink(this, newData);