From 540e0223fcaa2e43c9df3a0d2d844a16da63df2d Mon Sep 17 00:00:00 2001 From: Jean-Marie 'Histausse' Mineau Date: Wed, 26 Feb 2025 14:12:00 +0100 Subject: [PATCH] should remive the 12 reg limit --- patcher/Cargo.lock | 8 +- patcher/Cargo.toml | 4 +- patcher/src/lib.rs | 520 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 504 insertions(+), 28 deletions(-) diff --git a/patcher/Cargo.lock b/patcher/Cargo.lock index f631537..66fdaae 100644 --- a/patcher/Cargo.lock +++ b/patcher/Cargo.lock @@ -35,7 +35,7 @@ dependencies = [ [[package]] name = "androscalpel" version = "0.1.0" -source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=95c6b62#95c6b624ae31254d2f67c2f3d13c0a96217a6ec1" +source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=f15ad78#f15ad78d44d78d188580698a2cfd3da1b0f4389d" dependencies = [ "adler", "androscalpel_serializer", @@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "androscalpel_serializer" version = "0.1.0" -source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=95c6b62#95c6b624ae31254d2f67c2f3d13c0a96217a6ec1" +source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=f15ad78#f15ad78d44d78d188580698a2cfd3da1b0f4389d" dependencies = [ "androscalpel_serializer_derive", "log", @@ -60,7 +60,7 @@ dependencies = [ [[package]] name = "androscalpel_serializer_derive" version = "0.1.0" -source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=95c6b62#95c6b624ae31254d2f67c2f3d13c0a96217a6ec1" +source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=f15ad78#f15ad78d44d78d188580698a2cfd3da1b0f4389d" dependencies = [ "proc-macro2", "quote", @@ -129,7 +129,7 @@ dependencies = [ [[package]] name = "apk_frauder" version = "0.1.0" -source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=95c6b62#95c6b624ae31254d2f67c2f3d13c0a96217a6ec1" +source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=f15ad78#f15ad78d44d78d188580698a2cfd3da1b0f4389d" dependencies = [ "androscalpel_serializer", "flate2", diff --git a/patcher/Cargo.toml b/patcher/Cargo.toml index e9da891..7e31b03 100644 --- a/patcher/Cargo.toml +++ b/patcher/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -androscalpel = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git", rev = "95c6b62", features = ["code-analysis"] } -apk_frauder = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git", rev = "95c6b62"} +androscalpel = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git", rev = "f15ad78", features = ["code-analysis"] } +apk_frauder = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git", rev = "f15ad78"} anyhow = "1.0.95" clap = { version = "4.5.27", features = ["derive"] } env_logger = "0.11.6" diff --git a/patcher/src/lib.rs b/patcher/src/lib.rs index 44a5263..66ff280 100644 --- a/patcher/src/lib.rs +++ b/patcher/src/lib.rs @@ -1,5 +1,5 @@ use androscalpel::SmaliName; -use androscalpel::{IdMethod, IdType, Instruction, Method}; +use androscalpel::{IdMethod, IdType, Instruction, Method, RegType}; use anyhow::{bail, Context, Result}; use log::warn; use std::collections::{HashMap, HashSet}; @@ -145,6 +145,7 @@ pub struct ReflectionCnstrNewInstData { /// 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, @@ -156,6 +157,386 @@ struct RegistersInfo { 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, + }); + } + 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, + }); + } + 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, + }); + } + 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, + }); + } + 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, + }); + } + 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 + { + 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; + } + } + } + // 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;", @@ -264,20 +645,42 @@ pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result< let code = meth .code - .as_mut() + .as_ref() .with_context(|| format!("Code not found in {}", meth.descriptor.__str__()))?; - // TODO - if code.registers_size + RegistersInfo::NB_U8_REG > u8::MAX as u16 { - bail!("To many registers"); + + // 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); } - let mut register_info = RegistersInfo { - array_index: code.registers_size as u8, - array_val: (code.registers_size + 1) as u8, - array: (code.registers_size + 3) as u8, - //array: 0, - first_arg: code.registers_size + 4, - nb_arg_reg: 0, + 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_none() + || register_info.array_index_save.is_none() + || register_info.array_save.is_none() + { + 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; @@ -301,6 +704,47 @@ pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result< } 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![]) { @@ -350,6 +794,7 @@ pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result< } 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_") => { @@ -364,6 +809,11 @@ pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result< } } } + 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; @@ -486,7 +936,7 @@ fn get_invoke_block( }); insns.append(&mut get_args_from_obj_arr( &ref_data.method.proto.get_parameters(), - arg_arr as u8, // TODO + arg_arr, reg_inf.first_arg + 1, reg_inf, )); @@ -598,14 +1048,28 @@ pub fn get_scalar_to_obj_method(scalar_ty: &IdType) -> Result { /// types consecutive registers starting at `first_arg_reg`. /// `first_arg_reg` sould be `reg_inf.first_arg` or `reg_inf.first_arg+1` depending on if this /// is for a static or virtual call. -pub fn get_args_from_obj_arr( +fn get_args_from_obj_arr( params: &[IdType], - array_reg: u8, + 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, @@ -654,6 +1118,7 @@ pub fn get_args_from_obj_arr( reg_count += 1; } } + insns.append(&mut restore_array); insns } @@ -662,7 +1127,7 @@ pub fn get_args_from_obj_arr( /// - `method_obj_reg`: the register containing the `java.lang.reflect.Method` /// - `id_method`: the expected [`IdMethod`]. /// - `abort_label`: the label where to jump if the method does not match `id_method`. -pub fn test_method( +fn test_method( method_obj_reg: u16, id_method: IdMethod, abort_label: String, @@ -818,14 +1283,25 @@ fn get_cnstr_new_inst_block( ); insns.append(&mut get_args_from_obj_arr( &ref_data.constructor.proto.get_parameters(), - arg_arr as u8, // TODO + arg_arr, reg_inf.first_arg + 1, reg_inf, )); - insns.push(Instruction::NewInstance { - reg: reg_inf.first_arg as u8, - lit: ref_data.constructor.class_.clone(), - }); + 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(), @@ -848,7 +1324,7 @@ fn get_cnstr_new_inst_block( /// - `method_obj_reg`: the register containing the `java.lang.reflect.Method` /// - `id_method`: the expected [`IdMethod`]. /// - `abort_label`: the label where to jump if the method does not match `id_method`. -pub fn test_cnstr( +fn test_cnstr( cnst_reg: u16, id_method: IdMethod, abort_label: String,