diff --git a/patcher/Cargo.lock b/patcher/Cargo.lock index d4c23d0..e91b5f5 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=095ce2ce9340a7050aceb11ba626a1a9a966436a#095ce2ce9340a7050aceb11ba626a1a9a966436a" +source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7#5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7" 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=095ce2ce9340a7050aceb11ba626a1a9a966436a#095ce2ce9340a7050aceb11ba626a1a9a966436a" +source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7#5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7" 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=095ce2ce9340a7050aceb11ba626a1a9a966436a#095ce2ce9340a7050aceb11ba626a1a9a966436a" +source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7#5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7" 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=095ce2ce9340a7050aceb11ba626a1a9a966436a#095ce2ce9340a7050aceb11ba626a1a9a966436a" +source = "git+ssh://git@git.mineau.eu/histausse/androscalpel.git?rev=5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7#5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7" dependencies = [ "androscalpel_serializer", "flate2", diff --git a/patcher/Cargo.toml b/patcher/Cargo.toml index 2c384ba..aff93d0 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 = "095ce2ce9340a7050aceb11ba626a1a9a966436a" } -apk_frauder = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git", rev = "095ce2ce9340a7050aceb11ba626a1a9a966436a"} +androscalpel = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git", rev = "5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7" } +apk_frauder = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git", rev = "5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7"} anyhow = "1.0.95" clap = { version = "4.5.27", features = ["derive"] } env_logger = "0.11.6" diff --git a/patcher/src/get_apk.rs b/patcher/src/get_apk.rs index 25ba690..9ec84c1 100644 --- a/patcher/src/get_apk.rs +++ b/patcher/src/get_apk.rs @@ -1,9 +1,12 @@ use androscalpel::Apk; use clap::Args; use std::fs::{read_to_string, File}; +use std::io::Cursor; use std::path::PathBuf; use std::time::Duration; +use crate::labeling; + #[derive(Clone, Args, Debug)] pub struct ApkLocation { #[arg(short, long, conflicts_with = "sha256")] @@ -79,11 +82,16 @@ pub fn get_apk(location: &ApkLocation) -> Apk { reqwest::StatusCode::OK => (), s => panic!("Failed to download apk: {:?}", s), } - Apk::load_apk_bin(&res.bytes().expect("Failed to get APK bytes"), false, false).unwrap() + Apk::load_apk( + &mut Cursor::new(res.bytes().expect("Failed to get APK bytes")), + labeling, + false, + ) + .unwrap() } ApkLocation { path: Some(path), .. - } => Apk::load_apk(File::open(path).unwrap(), |_, _, _| None, false).unwrap(), + } => Apk::load_apk(File::open(path).unwrap(), labeling, false).unwrap(), _ => panic!("Don't know what to do with:\n{:#?}", location), } } diff --git a/patcher/src/lib.rs b/patcher/src/lib.rs index 161882d..c29770e 100644 --- a/patcher/src/lib.rs +++ b/patcher/src/lib.rs @@ -1,6 +1,7 @@ use androscalpel::SmaliName; use androscalpel::{IdMethod, Instruction, Method}; use anyhow::{bail, Context, Result}; +use std::collections::{HashMap, HashSet}; use std::sync::LazyLock; pub mod get_apk; @@ -10,19 +11,124 @@ pub mod get_apk; // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/reflection.cc;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=698 // does. -/// Structure storing the runtime information of a reflection call. +#[derive(Clone, PartialEq, Debug)] +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)] 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, // TODO: variable number of args? // TODO: type of invoke? } +/// Structure storing the runtime information of a reflection instanciation using +/// `java.lang.Class.newInstance()`. +#[derive(Clone, PartialEq, Debug)] 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)] 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, } pub struct RegistersInfo { @@ -84,15 +190,29 @@ static CNSTR_GET_DEC_CLS: LazyLock = LazyLock::new(|| { .unwrap() }); +/// Function passed to [`androscalpel::Apk::load_apk`] to label the instructions of interest. +fn labeling(_mth: &IdMethod, ins: &Instruction, addr: usize) -> Option { + match ins { + Instruction::InvokeVirtual { method, .. } + if method == &*MTH_INVOKE + || method == &*CLASS_NEW_INST + || method == &*CNSTR_NEW_INST => + { + Some(format!("THESEUS_ADDR_{addr:08X}")) + } + _ => 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_invoke_data: &ReflectionInvokeData, - ref_class_new_inst_data: &ReflectionClassNewInstData, - ref_cnstr_new_inst_data: &ReflectionCnstrNewInstData, -) -> Result<()> { +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_mut() @@ -111,13 +231,16 @@ pub fn transform_method( }; 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 + if (method == &*MTH_INVOKE || method == &*CLASS_NEW_INST - || method == &*CNSTR_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() {} @@ -130,36 +253,45 @@ pub fn transform_method( panic!("Should not happen!") }; // TODO: recover from failure - let ins_block = if method == &*MTH_INVOKE { - get_invoke_block( - ref_invoke_data, - args.as_slice(), - &mut register_info, - &end_label, - move_ret.clone(), - )? + 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 { - get_class_new_inst_block( - ref_class_new_inst_data, - args.as_slice(), - &mut register_info, - &end_label, - move_ret.clone(), - )? + 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 { - get_cnstr_new_inst_block( - ref_cnstr_new_inst_data, - args.as_slice(), - &mut register_info, - &end_label, - move_ret.clone(), - )? + 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!") }; - for ins in ins_block.into_iter() { - new_insns.push(ins); - } new_insns.push(ins.clone()); if let Some(move_ret) = move_ret { for ins in pseudo_insns.into_iter() { @@ -169,8 +301,16 @@ pub fn transform_method( } let end_label = Instruction::Label { name: end_label }; new_insns.push(end_label.clone()); + 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()); } } diff --git a/patcher/src/main.rs b/patcher/src/main.rs index 9216d2c..07110dd 100644 --- a/patcher/src/main.rs +++ b/patcher/src/main.rs @@ -2,11 +2,12 @@ use std::collections::HashMap; use std::io::Cursor; use std::path::PathBuf; -use androscalpel::{IdMethod, IdType}; +use androscalpel::IdMethod; use patcher::get_apk::{get_apk, ApkLocation}; use patcher::{ - transform_method, ReflectionClassNewInstData, ReflectionCnstrNewInstData, ReflectionInvokeData, + transform_method, ReflectionClassNewInstData, ReflectionCnstrNewInstData, ReflectionData, + ReflectionInvokeData, }; use clap::Parser; @@ -31,44 +32,82 @@ fn main() { let cli = Cli::parse(); let mut apk = get_apk(&cli.apk); //println!("{:#?}", apk.list_classes()); - let class = apk - .get_class_mut( - &IdType::new("Lcom/example/theseus/reflection/MainActivity;".into()).unwrap(), - ) - .unwrap(); - //println!("{:#?}", class.direct_methods.keys()); - //println!("{:#?}", class.virtual_methods.keys()); - for m in [ - "Lcom/example/theseus/reflection/MainActivity;->callVirtualMethodReflectCall()V", - "Lcom/example/theseus/reflection/MainActivity;->callConstructorVirtualMethodReflectConstr()V", - "Lcom/example/theseus/reflection/MainActivity;->callVirtualMethodReflectOldConst()V", - ] { - let method = class - .virtual_methods - .get_mut(&IdMethod::from_smali(m).unwrap()) - .unwrap(); - transform_method( - method, - &ReflectionInvokeData { - method: IdMethod::from_smali( - "Lcom/example/theseus/reflection/Reflectee;->transfer(Ljava/lang/String;)Ljava/lang/String;", - ) - .unwrap(), - }, - &ReflectionClassNewInstData { + let reflection_data = ReflectionData { + invoke_data: vec![ + ReflectionInvokeData { + method: IdMethod::from_smali( + "Lcom/example/theseus/reflection/Reflectee;\ + ->transfer\ + (Ljava/lang/String;)Ljava/lang/String;", + ) + .unwrap(), + caller_method: IdMethod::from_smali( + "Lcom/example/theseus/reflection/MainActivity;\ + ->callVirtualMethodReflectCall()V", + ) + .unwrap(), + addr: 0x2B, + }, + ReflectionInvokeData { + method: IdMethod::from_smali( + "Lcom/example/theseus/reflection/Reflectee;\ + ->transfer(Ljava/lang/String;)Ljava/lang/String;", + ) + .unwrap(), + caller_method: IdMethod::from_smali( + "Lcom/example/theseus/reflection/MainActivity;\ + ->callConstructorVirtualMethodReflectConstr()V", + ) + .unwrap(), + addr: 0x38, + }, + ReflectionInvokeData { + method: IdMethod::from_smali( + "Lcom/example/theseus/reflection/Reflectee;\ + ->transfer(Ljava/lang/String;)Ljava/lang/String;", + ) + .unwrap(), + caller_method: IdMethod::from_smali( + "Lcom/example/theseus/reflection/MainActivity;\ + ->callVirtualMethodReflectOldConst()V", + ) + .unwrap(), + addr: 0x28, + }, + ], + class_new_inst_data: vec![ReflectionClassNewInstData { constructor: IdMethod::from_smali( - "Lcom/example/theseus/reflection/Reflectee;->()V", + "Lcom/example/theseus/reflection/Reflectee;\ + ->()V", ) .unwrap(), - }, - &ReflectionCnstrNewInstData{ + caller_method: IdMethod::from_smali( + "Lcom/example/theseus/reflection/MainActivity;\ + ->callVirtualMethodReflectOldConst()V", + ) + .unwrap(), + addr: 0x12, + }], + cnstr_new_inst_data: vec![ReflectionCnstrNewInstData { constructor: IdMethod::from_smali( - "Lcom/example/theseus/reflection/Reflectee;->(Ljava/lang/String;)V", + "Lcom/example/theseus/reflection/Reflectee;\ + ->(Ljava/lang/String;)V", ) .unwrap(), - }, - ) - .unwrap(); + caller_method: IdMethod::from_smali( + "Lcom/example/theseus/reflection/MainActivity;\ + ->callConstructorVirtualMethodReflectConstr()V", + ) + .unwrap(), + addr: 0x22, + }], + }; + for method in reflection_data.get_method_referenced().iter() { + let class = apk.get_class_mut(&method.class_).unwrap(); + //println!("{:#?}", class.direct_methods.keys()); + //println!("{:#?}", class.virtual_methods.keys()); + let method = class.virtual_methods.get_mut(method).unwrap(); + transform_method(method, &reflection_data).unwrap(); } let mut dex_files = vec![]; let mut files = apk.gen_raw_dex().unwrap();