From 23d1f51591bf0c6dcec0ccd45faee915f8f65959 Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Wed, 19 Feb 2025 16:18:44 +0100 Subject: [PATCH] handle scalar in callee args and ret ty --- patcher/src/lib.rs | 285 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 231 insertions(+), 54 deletions(-) diff --git a/patcher/src/lib.rs b/patcher/src/lib.rs index 3470055..54e9789 100644 --- a/patcher/src/lib.rs +++ b/patcher/src/lib.rs @@ -1,5 +1,5 @@ use androscalpel::SmaliName; -use androscalpel::{IdMethod, Instruction, Method}; +use androscalpel::{IdMethod, IdType, Instruction, Method}; use anyhow::{bail, Context, Result}; use log::warn; use std::collections::{HashMap, HashSet}; @@ -135,7 +135,7 @@ pub struct ReflectionCnstrNewInstData { pub struct RegistersInfo { pub array_index: u8, //pub array: u8, - pub array_val: u8, + pub array_val: u8, // Reserver 2 reg here, for wide operation pub array: u8, //pub original_array_index_reg: Option, //pub original_array_reg: Option, @@ -144,9 +144,9 @@ pub struct RegistersInfo { } impl RegistersInfo { - const NB_U8_REG: u16 = 3; + const NB_U8_REG: u16 = 4; // array_val is a double register fn get_nb_added_reg(&self) -> u16 { - 3 + self.nb_arg_reg + 4 + self.nb_arg_reg } } @@ -191,6 +191,48 @@ static CNSTR_GET_DEC_CLS: LazyLock = LazyLock::new(|| { .unwrap() }); +static OBJ_TO_SCAL_BOOL: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Boolean;->booleanValue()Z").unwrap()); +static OBJ_TO_SCAL_BYTE: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Byte;->byteValue()B").unwrap()); +static OBJ_TO_SCAL_SHORT: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Short;->shortValue()S").unwrap()); +static OBJ_TO_SCAL_CHAR: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Character;->charValue()C").unwrap()); +static OBJ_TO_SCAL_INT: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Integer;->intValue()I").unwrap()); +static OBJ_TO_SCAL_LONG: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Long;->longValue()J").unwrap()); +static OBJ_TO_SCAL_FLOAT: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Float;->floatValue()F").unwrap()); +static OBJ_TO_SCAL_DOUBLE: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Double;->doubleValue()D").unwrap()); +static SCAL_TO_OBJ_BOOL: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;").unwrap() +}); +static SCAL_TO_OBJ_BYTE: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte;").unwrap()); +static SCAL_TO_OBJ_SHORT: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Short;->valueOf(S)Ljava/lang/Short;").unwrap() +}); +static SCAL_TO_OBJ_CHAR: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Character;->valueOf(C)Ljava/lang/Character;").unwrap() +}); +static SCAL_TO_OBJ_INT: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;").unwrap() +}); +static SCAL_TO_OBJ_LONG: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;").unwrap()); +static SCAL_TO_OBJ_FLOAT: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Float;->valueOf(F)Ljava/lang/Float;").unwrap() +}); +static SCAL_TO_OBJ_DOUBLE: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Double;->valueOf(D)Ljava/lang/Double;").unwrap() +}); + +static OBJECT_TY: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Object;").unwrap()); + /// Function passed to [`androscalpel::Apk::load_apk`] to label the instructions of interest. pub fn labeling(_mth: &IdMethod, ins: &Instruction, addr: usize) -> Option { match ins { @@ -225,9 +267,9 @@ pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result< let mut register_info = RegistersInfo { array_index: code.registers_size as u8, array_val: (code.registers_size + 1) as u8, - array: (code.registers_size + 2) as u8, + array: (code.registers_size + 3) as u8, //array: 0, - first_arg: code.registers_size + 3, + first_arg: code.registers_size + 4, nb_arg_reg: 0, }; let mut new_insns = vec![]; @@ -357,14 +399,23 @@ pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result< /// 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, + iter: impl Iterator, ) -> (Vec, Option) { - if let Some(ins) = iter.next() { + let mut pseudo_insns = vec![]; + for ins in iter { + /* match ins { Instruction::MoveResult { .. } | Instruction::MoveResultWide { .. } | Instruction::MoveResultObject { .. } => return (vec![], Some(ins.clone())), _ => (), // break, + }*/ + if ins.is_pseudo_ins() { + pseudo_insns.push(ins.clone()); + } else if let Instruction::MoveResultObject { .. } = ins { + return (pseudo_insns, Some(ins.clone())); + } else { + break; } } (vec![], None) @@ -389,7 +440,13 @@ fn get_invoke_block( // TODO bail!("Cannot transform invoke calls to a method using 16 bits register for its argument"); } - let nb_args = ref_data.method.proto.get_parameters().len(); + let nb_args: usize = ref_data + .method + .proto + .get_parameters() + .iter() + .map(|ty| if ty.is_double() || ty.is_long() { 2 } else { 1 }) + .sum(); if reg_inf.nb_arg_reg < nb_args as u16 + 1 { reg_inf.nb_arg_reg = nb_args as u16 + 1; } @@ -421,31 +478,55 @@ fn get_invoke_block( from: reg_inf.array_val as u16, to: reg_inf.first_arg, }); - for (i, param) in ref_data.method.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 + 1 + i as u16, - }); - } + insns.append(&mut get_args_from_obj_arr( + &ref_data.method.proto.get_parameters(), + arg_arr as u8, // TODO + reg_inf.first_arg + 1, + reg_inf, + )); insns.push(Instruction::InvokeVirtual { method: ref_data.method.clone(), args: (reg_inf.first_arg..reg_inf.first_arg + 1 + nb_args as u16).collect(), }); if let Some(move_result) = move_result { - insns.push(move_result); + let ret_ty = ref_data.method.proto.get_return_type(); + let res_reg = if let Instruction::MoveResultObject { to } = &move_result { + *to + } else { + panic!( + "`move_result` shloud always be a MoveResultObject, found {}", + move_result.__str__() + ) + }; + if ret_ty.is_class() || ret_ty.is_array() { + insns.push(move_result); + } else if ret_ty.is_double() || ret_ty.is_long() { + insns.push(Instruction::MoveResultWide { + to: reg_inf.array_val, + }); + insns.push(Instruction::InvokeStatic { + method: get_scalar_to_obj_method(&ret_ty).unwrap(), + args: vec![reg_inf.array_val as u16], + }); + insns.push(move_result); + insns.push(Instruction::CheckCast { + reg: res_reg, + lit: OBJECT_TY.clone(), + }); + } else { + insns.push(Instruction::MoveResult { + to: reg_inf.array_val, + }); + insns.push(Instruction::InvokeStatic { + method: get_scalar_to_obj_method(&ret_ty).unwrap(), + args: vec![reg_inf.array_val as u16], + }); + insns.push(move_result); + insns.push(Instruction::CheckCast { + reg: res_reg, + lit: OBJECT_TY.clone(), + }); + } } insns.push(Instruction::Goto { label: end_label.to_string(), @@ -455,6 +536,121 @@ fn get_invoke_block( Ok(insns) } +/// Get the method that convert a object to its scalar conterpart (eg `java.lang.Integer` to `int` with +/// `Ljava/lang/Integer;->intValue()I`) +/// +/// `scalar_ty` is the type of the scalar (eg `I`) +pub fn get_obj_to_scalar_method(scalar_ty: &IdType) -> Result { + if scalar_ty == &IdType::boolean() { + Ok(OBJ_TO_SCAL_BOOL.clone()) + } else if scalar_ty == &IdType::byte() { + Ok(OBJ_TO_SCAL_BYTE.clone()) + } else if scalar_ty == &IdType::short() { + Ok(OBJ_TO_SCAL_SHORT.clone()) + } else if scalar_ty == &IdType::char() { + Ok(OBJ_TO_SCAL_CHAR.clone()) + } else if scalar_ty == &IdType::int() { + Ok(OBJ_TO_SCAL_INT.clone()) + } else if scalar_ty == &IdType::long() { + Ok(OBJ_TO_SCAL_LONG.clone()) + } else if scalar_ty == &IdType::float() { + Ok(OBJ_TO_SCAL_FLOAT.clone()) + } else if scalar_ty == &IdType::double() { + Ok(OBJ_TO_SCAL_DOUBLE.clone()) + } else { + bail!("{} is not a scalar", scalar_ty.__str__()) + } +} + +/// Get the method that convert a scalar to its object conterpart (eg `int` to `java.lang.Integer` with +/// `Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;`) +/// +/// `scalar_ty` is the type of the scalar (eg `I`) +pub fn get_scalar_to_obj_method(scalar_ty: &IdType) -> Result { + if scalar_ty == &IdType::boolean() { + Ok(SCAL_TO_OBJ_BOOL.clone()) + } else if scalar_ty == &IdType::byte() { + Ok(SCAL_TO_OBJ_BYTE.clone()) + } else if scalar_ty == &IdType::short() { + Ok(SCAL_TO_OBJ_SHORT.clone()) + } else if scalar_ty == &IdType::char() { + Ok(SCAL_TO_OBJ_CHAR.clone()) + } else if scalar_ty == &IdType::int() { + Ok(SCAL_TO_OBJ_INT.clone()) + } else if scalar_ty == &IdType::long() { + Ok(SCAL_TO_OBJ_LONG.clone()) + } else if scalar_ty == &IdType::float() { + Ok(SCAL_TO_OBJ_FLOAT.clone()) + } else if scalar_ty == &IdType::double() { + Ok(SCAL_TO_OBJ_DOUBLE.clone()) + } else { + bail!("{} is not a scalar", scalar_ty.__str__()) + } +} + +/// Generate bytecode that put the arguments of types `params` from an [java.lang.Object to +/// types consecutive registers starting at `first_arg_reg`. +/// `first_arg_reg` sould be `reg_inf.first_arg` or `reg_inf.first_arg+1` depending on if this +/// is for a static or virtual call. +pub fn get_args_from_obj_arr( + params: &[IdType], + array_reg: u8, + first_arg_reg: u16, + reg_inf: &mut RegistersInfo, +) -> Vec { + let mut insns = vec![]; + let mut reg_count = 0; + for (i, param) in params.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: array_reg, + idx: reg_inf.array_index, + }); + if param.is_class() || param.is_array() { + insns.push(Instruction::CheckCast { + reg: reg_inf.array_val, + lit: param.clone(), + }); + insns.push(Instruction::MoveObject { + from: reg_inf.array_val as u16, + to: first_arg_reg + reg_count, + }); + reg_count += 1; + } else if param.is_double() || param.is_long() { + insns.push(Instruction::InvokeVirtual { + method: get_obj_to_scalar_method(param).unwrap(), + args: vec![reg_inf.array_val as u16], + }); + insns.push(Instruction::MoveResultWide { + to: reg_inf.array_val, + }); + insns.push(Instruction::MoveWide { + from: reg_inf.array_val as u16, + to: first_arg_reg + reg_count, + }); + reg_count += 2; + } else { + insns.push(Instruction::InvokeVirtual { + method: get_obj_to_scalar_method(param).unwrap(), + args: vec![reg_inf.array_val as u16], + }); + insns.push(Instruction::MoveResult { + to: reg_inf.array_val, + }); + insns.push(Instruction::Move { + from: reg_inf.array_val as u16, + to: first_arg_reg + reg_count, + }); + reg_count += 1; + } + } + 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` @@ -614,31 +810,12 @@ fn get_cnstr_new_inst_block( 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.append(&mut get_args_from_obj_arr( + &ref_data.constructor.proto.get_parameters(), + arg_arr as u8, // TODO + reg_inf.first_arg + 1, + reg_inf, + )); insns.push(Instruction::NewInstance { reg: reg_inf.first_arg as u8, lit: ref_data.constructor.class_.clone(),