From b476d04b78fc3b88c84097affe632facd4534862 Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Tue, 11 Mar 2025 11:32:53 +0100 Subject: [PATCH] refactoring --- frida/theseus_frida/__init__.py | 6 +- frida/theseus_frida/hook.js | 2 +- patcher/src/bin/patcher.rs | 8 +- patcher/src/dex_types.rs | 178 +++ patcher/src/lib.rs | 1514 +------------------------- patcher/src/reflection_patcher.rs | 802 ++++++++++++++ patcher/src/register_manipulation.rs | 408 +++++++ patcher/src/runtime_data.rs | 138 +++ 8 files changed, 1542 insertions(+), 1514 deletions(-) create mode 100644 patcher/src/dex_types.rs create mode 100644 patcher/src/reflection_patcher.rs create mode 100644 patcher/src/register_manipulation.rs create mode 100644 patcher/src/runtime_data.rs diff --git a/frida/theseus_frida/__init__.py b/frida/theseus_frida/__init__.py index 2497aaa..8e55fb5 100644 --- a/frida/theseus_frida/__init__.py +++ b/frida/theseus_frida/__init__.py @@ -16,6 +16,10 @@ logger.remove() # remove androguard logs FRIDA_SCRIPT = Path(__file__).parent / "hook.js" STACK_CONSUMER_B64 = Path(__file__).parent / "StackConsumer.dex.b64" +# The number of bytes used to encode a java hash (from Object.hashCode or System.identiyHashCode) +# The type is 'int', so it sould be a 32bit signed value? +HASH_NB_BYTES = 4 + # Define handler to event generated by the scripts def on_message(message, data, data_storage: dict, file_storage: Path): @@ -137,7 +141,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"] + classloader = data["classloader"].hex() short_class = classloader_class.split("/")[-1].removesuffix(";") files = [] print("DEX file loaded:") diff --git a/frida/theseus_frida/hook.js b/frida/theseus_frida/hook.js index 2f7c08e..51654df 100644 --- a/frida/theseus_frida/hook.js +++ b/frida/theseus_frida/hook.js @@ -183,7 +183,7 @@ Java.perform(() => { let path = Path.of(sourceName, []); let dex = Files.readAllBytes(path); let b64 = Base64.encodeToString(dex, Base64.DEFAULT.value); - let classloader_class = ""; + let classloader_class = null; let classloader_id = System.identityHashCode(loader); if (loader !== null) { classloader_class = loader.getClass().descriptorString(); diff --git a/patcher/src/bin/patcher.rs b/patcher/src/bin/patcher.rs index 9d04400..afa437e 100644 --- a/patcher/src/bin/patcher.rs +++ b/patcher/src/bin/patcher.rs @@ -7,8 +7,8 @@ use androscalpel::Apk; use patcher::{ labeling, - transform_method, - ReflectionData, // ReflectionInvokeData, ReflectionClassNewInstData, ReflectionCnstrNewInstData, + reflection_patcher::transform_method, + runtime_data::RuntimeData, // ReflectionInvokeData, ReflectionClassNewInstData, ReflectionCnstrNewInstData, }; use clap::Parser; @@ -40,9 +40,9 @@ fn main() { .unwrap() .read_to_string(&mut json) .unwrap(); - let reflection_data: ReflectionData = serde_json::from_str(&json).unwrap(); + let reflection_data: RuntimeData = serde_json::from_str(&json).unwrap(); /* - let reflection_data = ReflectionData { + let reflection_data = RuntimeData { invoke_data: vec![ ReflectionInvokeData { method: IdMethod::from_smali( diff --git a/patcher/src/dex_types.rs b/patcher/src/dex_types.rs new file mode 100644 index 0000000..6d58314 --- /dev/null +++ b/patcher/src/dex_types.rs @@ -0,0 +1,178 @@ +use androscalpel::{IdMethod, IdType}; +use anyhow::{bail, Result}; +use std::sync::LazyLock; + +pub(crate) static MTH_INVOKE: LazyLock = LazyLock::new(|| { + IdMethod::from_smali( + "Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", +) +.unwrap() +}); +pub(crate) static MTH_GET_NAME: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/reflect/Method;->getName()Ljava/lang/String;").unwrap() +}); +pub(crate) static MTH_GET_PARAMS_TY: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/reflect/Method;->getParameterTypes()[Ljava/lang/Class;") + .unwrap() +}); +pub(crate) static MTH_GET_RET_TY: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/reflect/Method;->getReturnType()Ljava/lang/Class;").unwrap() +}); +pub(crate) static MTH_GET_DEC_CLS: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/reflect/Method;->getDeclaringClass()Ljava/lang/Class;") + .unwrap() +}); +pub(crate) static STR_EQ: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/String;->equals(Ljava/lang/Object;)Z").unwrap() +}); +pub(crate) static CLASS_NEW_INST: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Class;->newInstance()Ljava/lang/Object;").unwrap() +}); +pub(crate) static CNSTR_NEW_INST: LazyLock = LazyLock::new(|| { + IdMethod::from_smali( + "Ljava/lang/reflect/Constructor;->newInstance([Ljava/lang/Object;)Ljava/lang/Object;", + ) + .unwrap() +}); +pub(crate) static CNSTR_GET_PARAMS_TY: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/reflect/Constructor;->getParameterTypes()[Ljava/lang/Class;") + .unwrap() +}); +pub(crate) static CNSTR_GET_DEC_CLS: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/reflect/Constructor;->getDeclaringClass()Ljava/lang/Class;") + .unwrap() +}); +pub(crate) static OBJ_TO_SCAL_BOOL: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Boolean;->booleanValue()Z").unwrap()); +pub(crate) static OBJ_TO_SCAL_BYTE: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Byte;->byteValue()B").unwrap()); +pub(crate) static OBJ_TO_SCAL_SHORT: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Short;->shortValue()S").unwrap()); +pub(crate) static OBJ_TO_SCAL_CHAR: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Character;->charValue()C").unwrap()); +pub(crate) static OBJ_TO_SCAL_INT: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Integer;->intValue()I").unwrap()); +pub(crate) static OBJ_TO_SCAL_LONG: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Long;->longValue()J").unwrap()); +pub(crate) static OBJ_TO_SCAL_FLOAT: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Float;->floatValue()F").unwrap()); +pub(crate) static OBJ_TO_SCAL_DOUBLE: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Double;->doubleValue()D").unwrap()); +pub(crate) static OBJ_OF_SCAL_BOOL: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Boolean;").unwrap()); +pub(crate) static OBJ_OF_SCAL_BYTE: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Byte;").unwrap()); +pub(crate) static OBJ_OF_SCAL_SHORT: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Short;").unwrap()); +pub(crate) static OBJ_OF_SCAL_CHAR: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Character;").unwrap()); +pub(crate) static OBJ_OF_SCAL_INT: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Integer;").unwrap()); +pub(crate) static OBJ_OF_SCAL_LONG: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Long;").unwrap()); +pub(crate) static OBJ_OF_SCAL_FLOAT: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Float;").unwrap()); +pub(crate) static OBJ_OF_SCAL_DOUBLE: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Double;").unwrap()); +pub(crate) static SCAL_TO_OBJ_BOOL: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;").unwrap() +}); +pub(crate) static SCAL_TO_OBJ_BYTE: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte;").unwrap()); +pub(crate) static SCAL_TO_OBJ_SHORT: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Short;->valueOf(S)Ljava/lang/Short;").unwrap() +}); +pub(crate) static SCAL_TO_OBJ_CHAR: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Character;->valueOf(C)Ljava/lang/Character;").unwrap() +}); +pub(crate) static SCAL_TO_OBJ_INT: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;").unwrap() +}); +pub(crate) static SCAL_TO_OBJ_LONG: LazyLock = + LazyLock::new(|| IdMethod::from_smali("Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;").unwrap()); +pub(crate) static SCAL_TO_OBJ_FLOAT: LazyLock = LazyLock::new(|| { + IdMethod::from_smali("Ljava/lang/Float;->valueOf(F)Ljava/lang/Float;").unwrap() +}); +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 OBJECT_TY: LazyLock = + LazyLock::new(|| IdType::from_smali("Ljava/lang/Object;").unwrap()); + +/// 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 object associated to a scalar (eg `java.lang.Integer` for `int`) +/// +/// `scalar_ty` is the type of the scalar (eg `I`) +pub fn get_obj_of_scalar(scalar_ty: &IdType) -> Result { + if scalar_ty == &IdType::boolean() { + Ok(OBJ_OF_SCAL_BOOL.clone()) + } else if scalar_ty == &IdType::byte() { + Ok(OBJ_OF_SCAL_BYTE.clone()) + } else if scalar_ty == &IdType::short() { + Ok(OBJ_OF_SCAL_SHORT.clone()) + } else if scalar_ty == &IdType::char() { + Ok(OBJ_OF_SCAL_CHAR.clone()) + } else if scalar_ty == &IdType::int() { + Ok(OBJ_OF_SCAL_INT.clone()) + } else if scalar_ty == &IdType::long() { + Ok(OBJ_OF_SCAL_LONG.clone()) + } else if scalar_ty == &IdType::float() { + Ok(OBJ_OF_SCAL_FLOAT.clone()) + } else if scalar_ty == &IdType::double() { + Ok(OBJ_OF_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__()) + } +} diff --git a/patcher/src/lib.rs b/patcher/src/lib.rs index 3fd85c8..80ac18e 100644 --- a/patcher/src/lib.rs +++ b/patcher/src/lib.rs @@ -1,11 +1,10 @@ -use androscalpel::SmaliName; -use androscalpel::{IdMethod, IdType, Instruction, Method, RegType}; -use anyhow::{bail, Context, Result}; -use log::warn; -use std::collections::{HashMap, HashSet}; -use std::sync::LazyLock; +use androscalpel::{IdMethod, Instruction}; -use serde::{Deserialize, Serialize}; +pub mod dex_types; +pub mod reflection_patcher; +pub mod register_manipulation; +pub mod runtime_data; +use dex_types::*; // TODO: // Check what @@ -22,632 +21,6 @@ fn _debug_info(data: &str) -> Vec { .collect() } -#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] -pub struct ReflectionData { - pub invoke_data: Vec, - pub class_new_inst_data: Vec, - pub cnstr_new_inst_data: Vec, -} - -impl ReflectionData { - /// List all the methods that made reflection calls. - pub fn get_method_referenced(&self) -> HashSet { - self.invoke_data - .iter() - .map(|data| data.caller_method.clone()) - .chain( - self.class_new_inst_data - .iter() - .map(|data| data.caller_method.clone()) - .chain( - self.cnstr_new_inst_data - .iter() - .map(|data| data.caller_method.clone()), - ), - ) - .collect() - } - - /// List all data collected from called to `java.lang.reflect.Method.invoke()` made by - /// `method`. - pub fn get_invoke_data_for( - &self, - method: &IdMethod, - ) -> HashMap> { - let mut data = HashMap::new(); - for val in self - .invoke_data - .iter() - .filter(|data| &data.caller_method == method) - { - let key = format!("THESEUS_ADDR_{:08X}", val.addr); - let entry = data.entry(key).or_insert(vec![]); - entry.push(val.clone()); - } - data - } - /// List all data collected from called to `java.lang.Class.newInstance()` made by - /// `method`. - pub fn get_class_new_instance_data_for( - &self, - method: &IdMethod, - ) -> HashMap> { - let mut data = HashMap::new(); - for val in self - .class_new_inst_data - .iter() - .filter(|data| &data.caller_method == method) - { - let key = format!("THESEUS_ADDR_{:08X}", val.addr); - let entry = data.entry(key).or_insert(vec![]); - entry.push(val.clone()); - } - data - } - /// List all data collected from called to `java.lang.reflect.Constructor.newInstance()` made by - /// `method`. - pub fn get_cnstr_new_instance_data_for( - &self, - method: &IdMethod, - ) -> HashMap> { - let mut data = HashMap::new(); - for val in self - .cnstr_new_inst_data - .iter() - .filter(|data| &data.caller_method == method) - { - let key = format!("THESEUS_ADDR_{:08X}", val.addr); - let entry = data.entry(key).or_insert(vec![]); - entry.push(val.clone()); - } - data - } -} - -/// Structure storing the runtime information of a reflection call using -/// `java.lang.reflect.Method.invoke()`. -#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] -pub struct ReflectionInvokeData { - /// The method called by `java.lang.reflect.Method.invoke()` - pub method: IdMethod, - /// The method calling `java.lang.reflect.Method.invoke()` - pub caller_method: IdMethod, - /// Address where the call to `java.lang.reflect.Method.invoke()` was made in `caller_method`. - pub addr: usize, - /// If the method is static (static method don't take 'this' as argument) - pub is_static: bool, - // TODO: type of invoke? -} - -/// Structure storing the runtime information of a reflection instanciation using -/// `java.lang.Class.newInstance()`. -#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] -pub struct ReflectionClassNewInstData { - /// The constructor called by `java.lang.Class.newInstance()` - pub constructor: IdMethod, - /// The method calling `java.lang.Class.newInstance()` - pub caller_method: IdMethod, - /// Address where the call to `java.lang.Class.newInstance()` was made in `caller_method`. - pub addr: usize, -} - -/// Structure storing the runtime information of a reflection instanciation using -/// `java.lang.reflect.Constructor.newInstance()`. -#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] -pub struct ReflectionCnstrNewInstData { - /// The constructor calleb by `java.lang.reflect.Constructor.newInstance()` - pub constructor: IdMethod, - /// The method calling `java.lang.reflect.Constructor.newInstance()` - pub caller_method: IdMethod, - /// Address where the call to `java.lang.Class.newInstance()` was made in `caller_method`. - pub addr: usize, -} - -/// Information about the register used. -/// -/// `array_index` and `array` are simple 4 bits register (that is, registers between 0 and 15 -/// included that store 32 bit scalar or object depending on the situation) and `pub array_val` is -/// a wide 4 bit register (that is, a register between 0 and 15 included plus the next register, so -/// that it can store 64 bits sclarars in addition to 32 bits scalars and objects depending on the -/// situation). In theory, those should be encoded in u4 types, but rust does not have those. -/// -/// Because we can rarely reserved 4 bits registers for a whole method, `array_index_save`, `array_val_save` -/// and `array_save` are 16 bits registers where we can save the previous contant of the registers -/// before using them. -/// -/// `first_arg` is the first register of plage of `nb_arg_reg` use to invoke method. -#[derive(PartialEq, Debug, Default)] -struct RegistersInfo { - pub array_index: u8, - pub array: u8, - pub array_val: u8, // Reserver 2 reg here, for wide operation - pub array_index_save: Option, - pub array_save: Option, - pub array_val_save: Option, // Reserver 2 reg here, for wide operation - pub first_arg: u16, - pub nb_arg_reg: u16, -} - -impl RegistersInfo { - pub fn get_nb_added_reg(&self) -> u16 { - self.nb_arg_reg + 4 - } - - /// Set the values for `array_index`, `array` and `array_val` when the methode already use more - /// than 12 registers. This means already used registers need to be saved in order to be used. - /// The first instruction vec return contains the instructions to save the registers, the - /// second the instructions to restore the registers to their old values. - /// - /// `used_reg` is a list of register that cannot be used because directly used by the invoke - /// instruction or the move-result ibstruction. - /// `regs_type` is the type of the registers at this point in the code of the method. - pub fn tmp_reserve_reg( - &mut self, - used_reg: &[u16], - regs_type: &[RegType], - ) -> Result<(Vec, Vec)> { - let mut used_reg = used_reg.to_vec(); - let mut save_reg_insns = vec![]; - let mut restore_reg_insns = vec![]; - if let Some(reg_save) = self.array_val_save { - let mut found = false; - if reg_save <= 0b1110 { - // This should not happend, but who knows? - found = true; - } - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) - && !used_reg.contains(&((i + 1) as u16)) - && regs_type[i] == RegType::FirstWideScalar - && regs_type[i + 1] == RegType::SecondWideScalar - { - self.array_val = i as u8; - used_reg.push(i as u16); - used_reg.push((i + 1) as u16); - save_reg_insns.push(Instruction::MoveWide { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::MoveWide { - from: reg_save, - to: i as u16, - }); - found = true; - break; - } - } - } - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) - && !used_reg.contains(&((i + 1) as u16)) - && (regs_type[i] == RegType::Object - || regs_type[i] == RegType::SimpleScalar - || regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - || regs_type[i] == RegType::Undefined) - && (regs_type[i + 1] == RegType::Object - || regs_type[i + 1] == RegType::SimpleScalar - || regs_type[i + 1] == RegType::FirstWideScalar - || regs_type[i + 1] == RegType::SecondWideScalar - || regs_type[i + 1] == RegType::Undefined) - { - self.array_val = i as u8; - used_reg.push(i as u16); - used_reg.push((i + 1) as u16); - if regs_type[i] == RegType::Object { - save_reg_insns.push(Instruction::MoveObject { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::MoveObject { - from: reg_save, - to: i as u16, - }); - } else if regs_type[i] == RegType::SimpleScalar - || regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - { - save_reg_insns.push(Instruction::Move { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: i as u16, - }); - } // else RegType::Undefined, do nothing, just use it - if regs_type[i + 1] == RegType::Object { - save_reg_insns.push(Instruction::MoveObject { - from: (i + 1) as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::MoveObject { - from: reg_save, - to: (i + 1) as u16, - }); - } else if regs_type[i + 1] == RegType::SimpleScalar - || regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - { - save_reg_insns.push(Instruction::Move { - from: (i + 1) as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: (i + 1) as u16, - }); - } // else RegType::Undefined, do nothing, just use it - found = true; - break; - } - } - } - // Last resort - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) - && !used_reg.contains(&((i + 1) as u16)) - && (regs_type[i] == RegType::Object - || regs_type[i] == RegType::SimpleScalar - || regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - || regs_type[i] == RegType::Any - || regs_type[i] == RegType::Undefined) - && (regs_type[i + 1] == RegType::Object - || regs_type[i + 1] == RegType::SimpleScalar - || regs_type[i + 1] == RegType::FirstWideScalar - || regs_type[i + 1] == RegType::SecondWideScalar - || regs_type[i] == RegType::Any - || regs_type[i + 1] == RegType::Undefined) - { - self.array_val = i as u8; - used_reg.push(i as u16); - used_reg.push((i + 1) as u16); - if regs_type[i] == RegType::Object { - save_reg_insns.push(Instruction::MoveObject { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::MoveObject { - from: reg_save, - to: i as u16, - }); - } else if regs_type[i] == RegType::SimpleScalar - || regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - || regs_type[i] == RegType::Any - { - save_reg_insns.push(Instruction::Move { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: i as u16, - }); - } // else RegType::Undefined, do nothing, just use it - if regs_type[i + 1] == RegType::Object { - save_reg_insns.push(Instruction::MoveObject { - from: (i + 1) as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::MoveObject { - from: reg_save, - to: (i + 1) as u16, - }); - } else if regs_type[i + 1] == RegType::SimpleScalar - || regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - { - save_reg_insns.push(Instruction::Move { - from: (i + 1) as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: (i + 1) as u16, - }); - } // else RegType::Undefined, do nothing, just use it - found = true; - break; - } - } - if !found { - bail!("Could not found enough usable registers to patch the method") - } - } - } - if let Some(reg_save) = self.array_index_save { - let mut found = false; - if reg_save <= 0b1111 { - // This should not happend, but who knows? - found = true; - } - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) && regs_type[i] == RegType::SimpleScalar { - self.array_index = i as u8; - used_reg.push(i as u16); - save_reg_insns.push(Instruction::Move { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: i as u16, - }); - found = true; - break; - } - } - } - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) - && (regs_type[i] == RegType::Object - || regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - || regs_type[i] == RegType::Undefined) - { - self.array_index = i as u8; - used_reg.push(i as u16); - if regs_type[i] == RegType::Object { - save_reg_insns.push(Instruction::MoveObject { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::MoveObject { - from: reg_save, - to: i as u16, - }); - } else if regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - { - save_reg_insns.push(Instruction::Move { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: i as u16, - }); - } // else RegType::Undefined, do nothing, just use it - found = true; - break; - } - } - } - // Last resort - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) && regs_type[i] == RegType::Any { - self.array_index = i as u8; - used_reg.push(i as u16); - save_reg_insns.push(Instruction::Move { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: i as u16, - }); - found = true; - break; - } - } - if !found { - bail!("Could not found enough usable registers to patch the method") - } - } - } - if let Some(reg_save) = self.array_save { - let mut found = false; - if reg_save <= 0b1111 { - // This should not happend, but who knows? - found = true; - } - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) && regs_type[i] == RegType::Object { - self.array = i as u8; - used_reg.push(i as u16); - save_reg_insns.push(Instruction::MoveObject { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::MoveObject { - from: reg_save, - to: i as u16, - }); - found = true; - break; - } - } - } - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) - && (regs_type[i] == RegType::SimpleScalar - || regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - || regs_type[i] == RegType::Undefined) - { - self.array = i as u8; - used_reg.push(i as u16); - if regs_type[i] == RegType::FirstWideScalar - || regs_type[i] == RegType::SecondWideScalar - || regs_type[i] == RegType::SimpleScalar - { - save_reg_insns.push(Instruction::Move { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: i as u16, - }); - } // else RegType::Undefined, do nothing, just use it - found = true; - break; - } - } - } - // Last resort - if !found { - for i in 0..15 { - if i >= regs_type.len() { - break; - } - if !used_reg.contains(&(i as u16)) && regs_type[i] == RegType::Any { - self.array = i as u8; - used_reg.push(i as u16); - save_reg_insns.push(Instruction::Move { - from: i as u16, - to: reg_save, - }); - restore_reg_insns.push(Instruction::Move { - from: reg_save, - to: i as u16, - }); - found = true; - break; - } - } - if !found { - bail!("Could not found enough usable registers to patch the method") - } - } - } - Ok((save_reg_insns, restore_reg_insns)) - } -} - -static MTH_INVOKE: LazyLock = LazyLock::new(|| { - IdMethod::from_smali( - "Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", -) -.unwrap() -}); -static MTH_GET_NAME: LazyLock = LazyLock::new(|| { - IdMethod::from_smali("Ljava/lang/reflect/Method;->getName()Ljava/lang/String;").unwrap() -}); -static MTH_GET_PARAMS_TY: LazyLock = LazyLock::new(|| { - IdMethod::from_smali("Ljava/lang/reflect/Method;->getParameterTypes()[Ljava/lang/Class;") - .unwrap() -}); -static MTH_GET_RET_TY: LazyLock = LazyLock::new(|| { - IdMethod::from_smali("Ljava/lang/reflect/Method;->getReturnType()Ljava/lang/Class;").unwrap() -}); -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() -}); - -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 OBJ_OF_SCAL_BOOL: LazyLock = - LazyLock::new(|| IdType::from_smali("Ljava/lang/Boolean;").unwrap()); -static OBJ_OF_SCAL_BYTE: LazyLock = - LazyLock::new(|| IdType::from_smali("Ljava/lang/Byte;").unwrap()); -static OBJ_OF_SCAL_SHORT: LazyLock = - LazyLock::new(|| IdType::from_smali("Ljava/lang/Short;").unwrap()); -static OBJ_OF_SCAL_CHAR: LazyLock = - LazyLock::new(|| IdType::from_smali("Ljava/lang/Character;").unwrap()); -static OBJ_OF_SCAL_INT: LazyLock = - LazyLock::new(|| IdType::from_smali("Ljava/lang/Integer;").unwrap()); -static OBJ_OF_SCAL_LONG: LazyLock = - LazyLock::new(|| IdType::from_smali("Ljava/lang/Long;").unwrap()); -static OBJ_OF_SCAL_FLOAT: LazyLock = - LazyLock::new(|| IdType::from_smali("Ljava/lang/Float;").unwrap()); -static OBJ_OF_SCAL_DOUBLE: LazyLock = - LazyLock::new(|| IdType::from_smali("Ljava/lang/Double;").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 { @@ -661,878 +34,3 @@ pub fn labeling(_mth: &IdMethod, ins: &Instruction, addr: usize) -> Option None, } } - -// 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<()> { - // checking meth.annotations might be usefull at some point - //println!("{}", meth.descriptor.__str__()); - 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 code = meth - .code - .as_ref() - .with_context(|| format!("Code not found in {}", meth.descriptor.__str__()))?; - - // Get the available registers at the method level - let mut register_info = RegistersInfo::default(); - // register_info.array_val is a wide reg, so need at least 0b1110 and 0b1111 - if code.registers_size < 0b1111 { - register_info.array_val = code.registers_size as u8; - } else { - register_info.array_val = 0; - register_info.array_val_save = Some(code.registers_size); - } - if code.registers_size + 2 <= 0b1111 { - register_info.array_index = (code.registers_size + 2) as u8; - } else { - register_info.array_index = 0; - register_info.array_index_save = Some(code.registers_size + 2); - } - if code.registers_size + 3 <= 0b1111 { - register_info.array = (code.registers_size + 3) as u8; - } else { - register_info.array = 0; - register_info.array_save = Some(code.registers_size + 3); - } - register_info.first_arg = code.registers_size + 4; - register_info.nb_arg_reg = 0; - - let regs_type = if register_info.array_val_save.is_some() - || register_info.array_index_save.is_some() - || register_info.array_save.is_some() - { - Some(meth.get_cfg()?.get_reg_types()) - } else { - None - }; - - let mut new_insns = vec![]; - let mut iter = code.insns.iter(); - let mut current_addr_label: Option = None; - while let Some(ins) = iter.next() { - match ins { - Instruction::InvokeVirtual { method, args } - if (method == &*MTH_INVOKE - || method == &*CLASS_NEW_INST - || method == &*CNSTR_NEW_INST) - && current_addr_label.is_some() => - { - let addr_label = current_addr_label.as_ref().unwrap(); - 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!") - }; - let mut restore_reg = vec![]; - if let Some(regs_type) = regs_type.as_ref() { - if (method == &*MTH_INVOKE && invoke_data.contains_key(addr_label)) - || (method == &*CLASS_NEW_INST - && class_new_inst_data.contains_key(addr_label)) - || (method == &*CNSTR_NEW_INST - && cnstr_new_inst_data.contains_key(addr_label)) - { - let regs_type = regs_type.get(addr_label).unwrap(); - let mut used_reg = args.clone(); - match move_ret { - Some(Instruction::MoveResult { to }) => used_reg.push(to as u16), - Some(Instruction::MoveResultObject { to }) => used_reg.push(to as u16), - Some(Instruction::MoveResultWide { to }) => used_reg.push(to as u16), - _ => (), - } - match register_info.tmp_reserve_reg(&used_reg, regs_type) { - Ok((mut save_insns, restore_insns)) => { - restore_reg = restore_insns; - new_insns.append(&mut save_insns); - } - Err(err) => { - warn!( - "Failed to instrument reflection in {} at {}: {}", - method.__str__(), - addr_label, - err, - ); - new_insns.push(ins.clone()); - if let Some(move_ret) = move_ret.as_ref() { - for ins in pseudo_insns.iter() { - new_insns.push(ins.clone()); - } - new_insns.push(move_ret.clone()); - } - current_addr_label = None; - continue; - } - } - } - } - // TODO: recover from failure - if method == &*MTH_INVOKE { - for ref_data in invoke_data.get(addr_label).unwrap_or(&vec![]) { - for ins in get_invoke_block( - ref_data, - args.as_slice(), - &mut register_info, - &end_label, - move_ret.clone(), - )? { - new_insns.push(ins); - } - } - } else if method == &*CLASS_NEW_INST { - for ref_data in class_new_inst_data.get(addr_label).unwrap_or(&vec![]) { - for ins in get_class_new_inst_block( - ref_data, - args.as_slice(), - &mut register_info, - &end_label, - move_ret.clone(), - )? { - new_insns.push(ins); - } - } - } else if method == &*CNSTR_NEW_INST { - for ref_data in cnstr_new_inst_data.get(addr_label).unwrap_or(&vec![]) { - for ins in get_cnstr_new_inst_block( - ref_data, - args.as_slice(), - &mut register_info, - &end_label, - move_ret.clone(), - )? { - new_insns.push(ins); - } - } - } else { - panic!("Should not happen!") - }; - 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); - } - let end_label = Instruction::Label { name: end_label }; - new_insns.push(end_label.clone()); - new_insns.append(&mut restore_reg); - current_addr_label = None; - } - Instruction::Label { name } if name.starts_with("THESEUS_ADDR_") => { - current_addr_label = Some(name.clone()); - new_insns.push(ins.clone()); - } - ins => { - if !ins.is_pseudo_ins() { - current_addr_label = None; - } - new_insns.push(ins.clone()); - } - } - } - let ins_size = code.ins_size(meth); - let code = meth - .code - .as_mut() - .with_context(|| format!("Code not found in {}", meth.descriptor.__str__()))?; - - code.insns = vec![]; - // Start the method by moving the parameter to their registers pre-transformation. - let mut i = 0; - if !meth.is_static { - // Non static method take 'this' as first argument - code.insns.push(Instruction::MoveObject { - from: code.registers_size - ins_size + i + register_info.get_nb_added_reg(), - to: code.registers_size - ins_size + i, - }); - i += 1; - } - for arg in &meth.descriptor.proto.get_parameters() { - if arg.is_class() || arg.is_array() { - code.insns.push(Instruction::MoveObject { - from: code.registers_size - ins_size + i + register_info.get_nb_added_reg(), - to: code.registers_size - ins_size + i, - }); - i += 1; - } else if arg.is_long() || arg.is_double() { - code.insns.push(Instruction::MoveWide { - from: code.registers_size - ins_size + i + register_info.get_nb_added_reg(), - to: code.registers_size - ins_size + i, - }); - i += 2; - } else { - code.insns.push(Instruction::Move { - from: code.registers_size - ins_size + i + register_info.get_nb_added_reg(), - to: code.registers_size - ins_size + i, - }); - i += 1; - } - } - if i != ins_size { - warn!( - "Method {} argument do not match code ins_size ({})", - meth.descriptor.__str__(), - ins_size - ); - } - // Add the new code - code.insns.append(&mut new_insns); - code.registers_size += register_info.get_nb_added_reg(); - - Ok(()) -} - -/// Return the MoveResult{,Wide,Object} associated to the last instruction of the iterator. -fn get_move_result<'a>( - iter: impl Iterator, -) -> (Vec, Option) { - 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) -} - -fn get_invoke_block( - ref_data: &ReflectionInvokeData, - invoke_arg: &[u16], - reg_inf: &mut RegistersInfo, - end_label: &str, - move_result: Option, -) -> Result> { - let (method_obj, obj_inst, arg_arr) = if let &[a, b, c] = invoke_arg { - (a, b, c) - } else { - bail!( - "Method;->invoke arg should have exactly 3 arguments, found {}", - invoke_arg.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 + if ref_data.is_static { 0 } else { 1 } { - reg_inf.nb_arg_reg = nb_args as u16 + if ref_data.is_static { 0 } else { 1 }; - } - - let abort_label = format!( - "end_static_call_to_{}_at_{:08X}", - ref_data.method.try_to_smali()?, - ref_data.addr - ); - let mut insns = test_method( - method_obj, - ref_data.method.clone(), - abort_label.clone(), - reg_inf, - ); - - if !ref_data.is_static { - // 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, - }); - } - insns.append(&mut get_args_from_obj_arr( - &ref_data.method.proto.get_parameters(), - 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(), - args: (reg_inf.first_arg..reg_inf.first_arg + nb_args as u16).collect(), - }); - } else { - 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 { - 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(), - }); - 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) -} - -/// 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 object associated to a scalar (eg `java.lang.Integer` for `int`) -/// -/// `scalar_ty` is the type of the scalar (eg `I`) -pub fn get_obj_of_scalar(scalar_ty: &IdType) -> Result { - if scalar_ty == &IdType::boolean() { - Ok(OBJ_OF_SCAL_BOOL.clone()) - } else if scalar_ty == &IdType::byte() { - Ok(OBJ_OF_SCAL_BYTE.clone()) - } else if scalar_ty == &IdType::short() { - Ok(OBJ_OF_SCAL_SHORT.clone()) - } else if scalar_ty == &IdType::char() { - Ok(OBJ_OF_SCAL_CHAR.clone()) - } else if scalar_ty == &IdType::int() { - Ok(OBJ_OF_SCAL_INT.clone()) - } else if scalar_ty == &IdType::long() { - Ok(OBJ_OF_SCAL_LONG.clone()) - } else if scalar_ty == &IdType::float() { - Ok(OBJ_OF_SCAL_FLOAT.clone()) - } else if scalar_ty == &IdType::double() { - Ok(OBJ_OF_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. -fn get_args_from_obj_arr( - params: &[IdType], - array_reg: u16, - first_arg_reg: u16, - reg_inf: &mut RegistersInfo, -) -> Vec { - let mut insns = vec![]; - let mut restore_array = vec![]; - let mut reg_count = 0; - let array_reg = if array_reg <= 0b1111 { - array_reg as u8 - } else { - insns.push(Instruction::MoveObject { - from: array_reg, - to: reg_inf.array as u16, - }); - restore_array.push(Instruction::MoveObject { - from: reg_inf.array as u16, - to: array_reg, - }); - reg_inf.array - }; - 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::CheckCast { - reg: reg_inf.array_val, - lit: get_obj_of_scalar(param).unwrap(), - }); - 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::CheckCast { - reg: reg_inf.array_val, - lit: get_obj_of_scalar(param).unwrap(), - }); - 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.append(&mut restore_array); - 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`. -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 - // TODO: remove, test - // -------------------- - insns.append(&mut vec![ - 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, - ); - insns.append(&mut get_args_from_obj_arr( - &ref_data.constructor.proto.get_parameters(), - arg_arr, - reg_inf.first_arg + 1, - reg_inf, - )); - 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(), - }); - } else { - insns.push(Instruction::NewInstance { - reg: reg_inf.array_val, - lit: ref_data.constructor.class_.clone(), - }); - insns.push(Instruction::MoveObject { - from: reg_inf.array_val as u16, - to: reg_inf.first_arg, - }); - } - 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`. -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/reflection_patcher.rs b/patcher/src/reflection_patcher.rs new file mode 100644 index 0000000..9d68cc7 --- /dev/null +++ b/patcher/src/reflection_patcher.rs @@ -0,0 +1,802 @@ +use androscalpel::SmaliName; +use androscalpel::{IdMethod, IdType, Instruction, Method}; +use anyhow::{bail, Context, Result}; +use log::warn; + +use crate::{dex_types::*, register_manipulation::*, runtime_data::*}; + +// 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: &RuntimeData) -> Result<()> { + // checking meth.annotations might be usefull at some point + //println!("{}", meth.descriptor.__str__()); + 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 code = meth + .code + .as_ref() + .with_context(|| format!("Code not found in {}", meth.descriptor.__str__()))?; + + // Get the available registers at the method level + let mut register_info = RegistersInfo::default(); + // register_info.array_val is a wide reg, so need at least 0b1110 and 0b1111 + if code.registers_size < 0b1111 { + register_info.array_val = code.registers_size as u8; + } else { + register_info.array_val = 0; + register_info.array_val_save = Some(code.registers_size); + } + if code.registers_size + 2 <= 0b1111 { + register_info.array_index = (code.registers_size + 2) as u8; + } else { + register_info.array_index = 0; + register_info.array_index_save = Some(code.registers_size + 2); + } + if code.registers_size + 3 <= 0b1111 { + register_info.array = (code.registers_size + 3) as u8; + } else { + register_info.array = 0; + register_info.array_save = Some(code.registers_size + 3); + } + register_info.first_arg = code.registers_size + 4; + register_info.nb_arg_reg = 0; + + let regs_type = if register_info.array_val_save.is_some() + || register_info.array_index_save.is_some() + || register_info.array_save.is_some() + { + Some(meth.get_cfg()?.get_reg_types()) + } else { + None + }; + + let mut new_insns = vec![]; + let mut iter = code.insns.iter(); + let mut current_addr_label: Option = None; + while let Some(ins) = iter.next() { + match ins { + Instruction::InvokeVirtual { method, args } + if (method == &*MTH_INVOKE + || method == &*CLASS_NEW_INST + || method == &*CNSTR_NEW_INST) + && current_addr_label.is_some() => + { + let addr_label = current_addr_label.as_ref().unwrap(); + 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!") + }; + let mut restore_reg = vec![]; + if let Some(regs_type) = regs_type.as_ref() { + if (method == &*MTH_INVOKE && invoke_data.contains_key(addr_label)) + || (method == &*CLASS_NEW_INST + && class_new_inst_data.contains_key(addr_label)) + || (method == &*CNSTR_NEW_INST + && cnstr_new_inst_data.contains_key(addr_label)) + { + let regs_type = regs_type.get(addr_label).unwrap(); + let mut used_reg = args.clone(); + match move_ret { + Some(Instruction::MoveResult { to }) => used_reg.push(to as u16), + Some(Instruction::MoveResultObject { to }) => used_reg.push(to as u16), + Some(Instruction::MoveResultWide { to }) => used_reg.push(to as u16), + _ => (), + } + match register_info.tmp_reserve_reg(&used_reg, regs_type) { + Ok((mut save_insns, restore_insns)) => { + restore_reg = restore_insns; + new_insns.append(&mut save_insns); + } + Err(err) => { + warn!( + "Failed to instrument reflection in {} at {}: {}", + method.__str__(), + addr_label, + err, + ); + new_insns.push(ins.clone()); + if let Some(move_ret) = move_ret.as_ref() { + for ins in pseudo_insns.iter() { + new_insns.push(ins.clone()); + } + new_insns.push(move_ret.clone()); + } + current_addr_label = None; + continue; + } + } + } + } + // TODO: recover from failure + if method == &*MTH_INVOKE { + for ref_data in invoke_data.get(addr_label).unwrap_or(&vec![]) { + for ins in get_invoke_block( + ref_data, + args.as_slice(), + &mut register_info, + &end_label, + move_ret.clone(), + )? { + new_insns.push(ins); + } + } + } else if method == &*CLASS_NEW_INST { + for ref_data in class_new_inst_data.get(addr_label).unwrap_or(&vec![]) { + for ins in get_class_new_inst_block( + ref_data, + args.as_slice(), + &mut register_info, + &end_label, + move_ret.clone(), + )? { + new_insns.push(ins); + } + } + } else if method == &*CNSTR_NEW_INST { + for ref_data in cnstr_new_inst_data.get(addr_label).unwrap_or(&vec![]) { + for ins in get_cnstr_new_inst_block( + ref_data, + args.as_slice(), + &mut register_info, + &end_label, + move_ret.clone(), + )? { + new_insns.push(ins); + } + } + } else { + panic!("Should not happen!") + }; + 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); + } + let end_label = Instruction::Label { name: end_label }; + new_insns.push(end_label.clone()); + new_insns.append(&mut restore_reg); + current_addr_label = None; + } + Instruction::Label { name } if name.starts_with("THESEUS_ADDR_") => { + current_addr_label = Some(name.clone()); + new_insns.push(ins.clone()); + } + ins => { + if !ins.is_pseudo_ins() { + current_addr_label = None; + } + new_insns.push(ins.clone()); + } + } + } + let ins_size = code.ins_size(meth); + let code = meth + .code + .as_mut() + .with_context(|| format!("Code not found in {}", meth.descriptor.__str__()))?; + + code.insns = vec![]; + // Start the method by moving the parameter to their registers pre-transformation. + let mut i = 0; + if !meth.is_static { + // Non static method take 'this' as first argument + code.insns.push(Instruction::MoveObject { + from: code.registers_size - ins_size + i + register_info.get_nb_added_reg(), + to: code.registers_size - ins_size + i, + }); + i += 1; + } + for arg in &meth.descriptor.proto.get_parameters() { + if arg.is_class() || arg.is_array() { + code.insns.push(Instruction::MoveObject { + from: code.registers_size - ins_size + i + register_info.get_nb_added_reg(), + to: code.registers_size - ins_size + i, + }); + i += 1; + } else if arg.is_long() || arg.is_double() { + code.insns.push(Instruction::MoveWide { + from: code.registers_size - ins_size + i + register_info.get_nb_added_reg(), + to: code.registers_size - ins_size + i, + }); + i += 2; + } else { + code.insns.push(Instruction::Move { + from: code.registers_size - ins_size + i + register_info.get_nb_added_reg(), + to: code.registers_size - ins_size + i, + }); + i += 1; + } + } + if i != ins_size { + warn!( + "Method {} argument do not match code ins_size ({})", + meth.descriptor.__str__(), + ins_size + ); + } + // Add the new code + code.insns.append(&mut new_insns); + code.registers_size += register_info.get_nb_added_reg(); + + Ok(()) +} + +/// Return the MoveResult{,Wide,Object} associated to the last instruction of the iterator. +fn get_move_result<'a>( + iter: impl Iterator, +) -> (Vec, Option) { + 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) +} + +fn get_invoke_block( + ref_data: &ReflectionInvokeData, + invoke_arg: &[u16], + reg_inf: &mut RegistersInfo, + end_label: &str, + move_result: Option, +) -> Result> { + let (method_obj, obj_inst, arg_arr) = if let &[a, b, c] = invoke_arg { + (a, b, c) + } else { + bail!( + "Method;->invoke arg should have exactly 3 arguments, found {}", + invoke_arg.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 + if ref_data.is_static { 0 } else { 1 } { + reg_inf.nb_arg_reg = nb_args as u16 + if ref_data.is_static { 0 } else { 1 }; + } + + let abort_label = format!( + "end_static_call_to_{}_at_{:08X}", + ref_data.method.try_to_smali()?, + ref_data.addr + ); + let mut insns = test_method( + method_obj, + ref_data.method.clone(), + abort_label.clone(), + reg_inf, + ); + + if !ref_data.is_static { + // 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, + }); + } + insns.append(&mut get_args_from_obj_arr( + &ref_data.method.proto.get_parameters(), + 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(), + args: (reg_inf.first_arg..reg_inf.first_arg + nb_args as u16).collect(), + }); + } else { + 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 { + 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(), + }); + 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 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. +fn get_args_from_obj_arr( + params: &[IdType], + array_reg: u16, + first_arg_reg: u16, + reg_inf: &mut RegistersInfo, +) -> Vec { + let mut insns = vec![]; + let mut restore_array = vec![]; + let mut reg_count = 0; + let array_reg = if array_reg <= 0b1111 { + array_reg as u8 + } else { + insns.push(Instruction::MoveObject { + from: array_reg, + to: reg_inf.array as u16, + }); + restore_array.push(Instruction::MoveObject { + from: reg_inf.array as u16, + to: array_reg, + }); + reg_inf.array + }; + 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::CheckCast { + reg: reg_inf.array_val, + lit: get_obj_of_scalar(param).unwrap(), + }); + 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::CheckCast { + reg: reg_inf.array_val, + lit: get_obj_of_scalar(param).unwrap(), + }); + 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.append(&mut restore_array); + 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`. +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 + // TODO: remove, test + // -------------------- + insns.append(&mut vec![ + 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, + ); + insns.append(&mut get_args_from_obj_arr( + &ref_data.constructor.proto.get_parameters(), + arg_arr, + reg_inf.first_arg + 1, + reg_inf, + )); + 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(), + }); + } else { + insns.push(Instruction::NewInstance { + reg: reg_inf.array_val, + lit: ref_data.constructor.class_.clone(), + }); + insns.push(Instruction::MoveObject { + from: reg_inf.array_val as u16, + to: reg_inf.first_arg, + }); + } + 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`. +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/register_manipulation.rs b/patcher/src/register_manipulation.rs new file mode 100644 index 0000000..823cca2 --- /dev/null +++ b/patcher/src/register_manipulation.rs @@ -0,0 +1,408 @@ +use androscalpel::{Instruction, RegType}; +use anyhow::{bail, Result}; + +/// Information about the register used. +/// +/// `array_index` and `array` are simple 4 bits register (that is, registers between 0 and 15 +/// included that store 32 bit scalar or object depending on the situation) and `pub array_val` is +/// a wide 4 bit register (that is, a register between 0 and 15 included plus the next register, so +/// that it can store 64 bits sclarars in addition to 32 bits scalars and objects depending on the +/// situation). In theory, those should be encoded in u4 types, but rust does not have those. +/// +/// Because we can rarely reserved 4 bits registers for a whole method, `array_index_save`, `array_val_save` +/// and `array_save` are 16 bits registers where we can save the previous contant of the registers +/// before using them. +/// +/// `first_arg` is the first register of plage of `nb_arg_reg` use to invoke method. +#[derive(PartialEq, Debug, Default)] +pub(crate) struct RegistersInfo { + pub array_index: u8, + pub array: u8, + pub array_val: u8, // Reserver 2 reg here, for wide operation + pub array_index_save: Option, + pub array_save: Option, + pub array_val_save: Option, // Reserver 2 reg here, for wide operation + pub first_arg: u16, + pub nb_arg_reg: u16, +} + +impl RegistersInfo { + pub fn get_nb_added_reg(&self) -> u16 { + self.nb_arg_reg + 4 + } + + /// Set the values for `array_index`, `array` and `array_val` when the methode already use more + /// than 12 registers. This means already used registers need to be saved in order to be used. + /// The first instruction vec return contains the instructions to save the registers, the + /// second the instructions to restore the registers to their old values. + /// + /// `used_reg` is a list of register that cannot be used because directly used by the invoke + /// instruction or the move-result ibstruction. + /// `regs_type` is the type of the registers at this point in the code of the method. + pub fn tmp_reserve_reg( + &mut self, + used_reg: &[u16], + regs_type: &[RegType], + ) -> Result<(Vec, Vec)> { + let mut used_reg = used_reg.to_vec(); + let mut save_reg_insns = vec![]; + let mut restore_reg_insns = vec![]; + if let Some(reg_save) = self.array_val_save { + let mut found = false; + if reg_save <= 0b1110 { + // This should not happend, but who knows? + found = true; + } + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) + && !used_reg.contains(&((i + 1) as u16)) + && regs_type[i] == RegType::FirstWideScalar + && regs_type[i + 1] == RegType::SecondWideScalar + { + self.array_val = i as u8; + used_reg.push(i as u16); + used_reg.push((i + 1) as u16); + save_reg_insns.push(Instruction::MoveWide { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::MoveWide { + from: reg_save, + to: i as u16, + }); + found = true; + break; + } + } + } + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) + && !used_reg.contains(&((i + 1) as u16)) + && (regs_type[i] == RegType::Object + || regs_type[i] == RegType::SimpleScalar + || regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + || regs_type[i] == RegType::Undefined) + && (regs_type[i + 1] == RegType::Object + || regs_type[i + 1] == RegType::SimpleScalar + || regs_type[i + 1] == RegType::FirstWideScalar + || regs_type[i + 1] == RegType::SecondWideScalar + || regs_type[i + 1] == RegType::Undefined) + { + self.array_val = i as u8; + used_reg.push(i as u16); + used_reg.push((i + 1) as u16); + if regs_type[i] == RegType::Object { + save_reg_insns.push(Instruction::MoveObject { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::MoveObject { + from: reg_save, + to: i as u16, + }); + } else if regs_type[i] == RegType::SimpleScalar + || regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + { + save_reg_insns.push(Instruction::Move { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: i as u16, + }); + } // else RegType::Undefined, do nothing, just use it + if regs_type[i + 1] == RegType::Object { + save_reg_insns.push(Instruction::MoveObject { + from: (i + 1) as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::MoveObject { + from: reg_save, + to: (i + 1) as u16, + }); + } else if regs_type[i + 1] == RegType::SimpleScalar + || regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + { + save_reg_insns.push(Instruction::Move { + from: (i + 1) as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: (i + 1) as u16, + }); + } // else RegType::Undefined, do nothing, just use it + found = true; + break; + } + } + } + // Last resort + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) + && !used_reg.contains(&((i + 1) as u16)) + && (regs_type[i] == RegType::Object + || regs_type[i] == RegType::SimpleScalar + || regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + || regs_type[i] == RegType::Any + || regs_type[i] == RegType::Undefined) + && (regs_type[i + 1] == RegType::Object + || regs_type[i + 1] == RegType::SimpleScalar + || regs_type[i + 1] == RegType::FirstWideScalar + || regs_type[i + 1] == RegType::SecondWideScalar + || regs_type[i] == RegType::Any + || regs_type[i + 1] == RegType::Undefined) + { + self.array_val = i as u8; + used_reg.push(i as u16); + used_reg.push((i + 1) as u16); + if regs_type[i] == RegType::Object { + save_reg_insns.push(Instruction::MoveObject { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::MoveObject { + from: reg_save, + to: i as u16, + }); + } else if regs_type[i] == RegType::SimpleScalar + || regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + || regs_type[i] == RegType::Any + { + save_reg_insns.push(Instruction::Move { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: i as u16, + }); + } // else RegType::Undefined, do nothing, just use it + if regs_type[i + 1] == RegType::Object { + save_reg_insns.push(Instruction::MoveObject { + from: (i + 1) as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::MoveObject { + from: reg_save, + to: (i + 1) as u16, + }); + } else if regs_type[i + 1] == RegType::SimpleScalar + || regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + { + save_reg_insns.push(Instruction::Move { + from: (i + 1) as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: (i + 1) as u16, + }); + } // else RegType::Undefined, do nothing, just use it + found = true; + break; + } + } + if !found { + bail!("Could not found enough usable registers to patch the method") + } + } + } + if let Some(reg_save) = self.array_index_save { + let mut found = false; + if reg_save <= 0b1111 { + // This should not happend, but who knows? + found = true; + } + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) && regs_type[i] == RegType::SimpleScalar { + self.array_index = i as u8; + used_reg.push(i as u16); + save_reg_insns.push(Instruction::Move { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: i as u16, + }); + found = true; + break; + } + } + } + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) + && (regs_type[i] == RegType::Object + || regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + || regs_type[i] == RegType::Undefined) + { + self.array_index = i as u8; + used_reg.push(i as u16); + if regs_type[i] == RegType::Object { + save_reg_insns.push(Instruction::MoveObject { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::MoveObject { + from: reg_save, + to: i as u16, + }); + } else if regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + { + save_reg_insns.push(Instruction::Move { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: i as u16, + }); + } // else RegType::Undefined, do nothing, just use it + found = true; + break; + } + } + } + // Last resort + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) && regs_type[i] == RegType::Any { + self.array_index = i as u8; + used_reg.push(i as u16); + save_reg_insns.push(Instruction::Move { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: i as u16, + }); + found = true; + break; + } + } + if !found { + bail!("Could not found enough usable registers to patch the method") + } + } + } + if let Some(reg_save) = self.array_save { + let mut found = false; + if reg_save <= 0b1111 { + // This should not happend, but who knows? + found = true; + } + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) && regs_type[i] == RegType::Object { + self.array = i as u8; + used_reg.push(i as u16); + save_reg_insns.push(Instruction::MoveObject { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::MoveObject { + from: reg_save, + to: i as u16, + }); + found = true; + break; + } + } + } + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) + && (regs_type[i] == RegType::SimpleScalar + || regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + || regs_type[i] == RegType::Undefined) + { + self.array = i as u8; + used_reg.push(i as u16); + if regs_type[i] == RegType::FirstWideScalar + || regs_type[i] == RegType::SecondWideScalar + || regs_type[i] == RegType::SimpleScalar + { + save_reg_insns.push(Instruction::Move { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: i as u16, + }); + } // else RegType::Undefined, do nothing, just use it + found = true; + break; + } + } + } + // Last resort + if !found { + for i in 0..15 { + if i >= regs_type.len() { + break; + } + if !used_reg.contains(&(i as u16)) && regs_type[i] == RegType::Any { + self.array = i as u8; + used_reg.push(i as u16); + save_reg_insns.push(Instruction::Move { + from: i as u16, + to: reg_save, + }); + restore_reg_insns.push(Instruction::Move { + from: reg_save, + to: i as u16, + }); + found = true; + break; + } + } + if !found { + bail!("Could not found enough usable registers to patch the method") + } + } + } + Ok((save_reg_insns, restore_reg_insns)) + } +} diff --git a/patcher/src/runtime_data.rs b/patcher/src/runtime_data.rs new file mode 100644 index 0000000..be60a78 --- /dev/null +++ b/patcher/src/runtime_data.rs @@ -0,0 +1,138 @@ +use androscalpel::{IdMethod, IdType}; +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct RuntimeData { + pub invoke_data: Vec, + pub class_new_inst_data: Vec, + pub cnstr_new_inst_data: Vec, + pub dyn_code_load: Vec, +} + +impl RuntimeData { + /// List all the methods that made reflection calls. + pub fn get_method_referenced(&self) -> HashSet { + self.invoke_data + .iter() + .map(|data| data.caller_method.clone()) + .chain( + self.class_new_inst_data + .iter() + .map(|data| data.caller_method.clone()) + .chain( + self.cnstr_new_inst_data + .iter() + .map(|data| data.caller_method.clone()), + ), + ) + .collect() + } + + /// List all data collected from called to `java.lang.reflect.Method.invoke()` made by + /// `method`. + pub fn get_invoke_data_for( + &self, + method: &IdMethod, + ) -> HashMap> { + let mut data = HashMap::new(); + for val in self + .invoke_data + .iter() + .filter(|data| &data.caller_method == method) + { + let key = format!("THESEUS_ADDR_{:08X}", val.addr); + let entry = data.entry(key).or_insert(vec![]); + entry.push(val.clone()); + } + data + } + /// List all data collected from called to `java.lang.Class.newInstance()` made by + /// `method`. + pub fn get_class_new_instance_data_for( + &self, + method: &IdMethod, + ) -> HashMap> { + let mut data = HashMap::new(); + for val in self + .class_new_inst_data + .iter() + .filter(|data| &data.caller_method == method) + { + let key = format!("THESEUS_ADDR_{:08X}", val.addr); + let entry = data.entry(key).or_insert(vec![]); + entry.push(val.clone()); + } + data + } + /// List all data collected from called to `java.lang.reflect.Constructor.newInstance()` made by + /// `method`. + pub fn get_cnstr_new_instance_data_for( + &self, + method: &IdMethod, + ) -> HashMap> { + let mut data = HashMap::new(); + for val in self + .cnstr_new_inst_data + .iter() + .filter(|data| &data.caller_method == method) + { + let key = format!("THESEUS_ADDR_{:08X}", val.addr); + let entry = data.entry(key).or_insert(vec![]); + entry.push(val.clone()); + } + data + } +} + +/// Structure storing the runtime information of a reflection call using +/// `java.lang.reflect.Method.invoke()`. +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct ReflectionInvokeData { + /// The method called by `java.lang.reflect.Method.invoke()` + pub method: IdMethod, + /// The method calling `java.lang.reflect.Method.invoke()` + pub caller_method: IdMethod, + /// Address where the call to `java.lang.reflect.Method.invoke()` was made in `caller_method`. + pub addr: usize, + /// If the method is static (static method don't take 'this' as argument) + pub is_static: bool, + // TODO: type of invoke? +} + +/// Structure storing the runtime information of a reflection instanciation using +/// `java.lang.Class.newInstance()`. +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct ReflectionClassNewInstData { + /// The constructor called by `java.lang.Class.newInstance()` + pub constructor: IdMethod, + /// The method calling `java.lang.Class.newInstance()` + pub caller_method: IdMethod, + /// Address where the call to `java.lang.Class.newInstance()` was made in `caller_method`. + pub addr: usize, +} + +/// Structure storing the runtime information of a reflection instanciation using +/// `java.lang.reflect.Constructor.newInstance()`. +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct ReflectionCnstrNewInstData { + /// The constructor calleb by `java.lang.reflect.Constructor.newInstance()` + pub constructor: IdMethod, + /// The method calling `java.lang.reflect.Constructor.newInstance()` + pub caller_method: IdMethod, + /// Address where the call to `java.lang.Class.newInstance()` was made in `caller_method`. + pub addr: usize, +} + +/// Structure storing the runtime information of a dynamic code loading. +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct DynamicCodeLoadingData { + /// The type of the class loader used to load the code. + pub classloader_class: IdType, + /// An identifier for the classloader, valid for one specific run of the application. + pub classloader: String, + /// The path to the files storing the .dex/.apk/other bytecode loaded. + pub files: Vec, +}