start automagication

This commit is contained in:
Jean-Marie Mineau 2025-02-05 15:52:47 +01:00
parent 5fdeb25682
commit 81c85763fd
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
5 changed files with 263 additions and 76 deletions

8
patcher/Cargo.lock generated
View file

@ -35,7 +35,7 @@ dependencies = [
[[package]] [[package]]
name = "androscalpel" name = "androscalpel"
version = "0.1.0" 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 = [ dependencies = [
"adler", "adler",
"androscalpel_serializer", "androscalpel_serializer",
@ -51,7 +51,7 @@ dependencies = [
[[package]] [[package]]
name = "androscalpel_serializer" name = "androscalpel_serializer"
version = "0.1.0" 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 = [ dependencies = [
"androscalpel_serializer_derive", "androscalpel_serializer_derive",
"log", "log",
@ -60,7 +60,7 @@ dependencies = [
[[package]] [[package]]
name = "androscalpel_serializer_derive" name = "androscalpel_serializer_derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -129,7 +129,7 @@ dependencies = [
[[package]] [[package]]
name = "apk_frauder" name = "apk_frauder"
version = "0.1.0" 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 = [ dependencies = [
"androscalpel_serializer", "androscalpel_serializer",
"flate2", "flate2",

View file

@ -6,8 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
androscalpel = { 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 = "095ce2ce9340a7050aceb11ba626a1a9a966436a"} apk_frauder = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git", rev = "5d687081fb4a5cbf69dcc976dcb5ffae4d85fef7"}
anyhow = "1.0.95" anyhow = "1.0.95"
clap = { version = "4.5.27", features = ["derive"] } clap = { version = "4.5.27", features = ["derive"] }
env_logger = "0.11.6" env_logger = "0.11.6"

View file

@ -1,9 +1,12 @@
use androscalpel::Apk; use androscalpel::Apk;
use clap::Args; use clap::Args;
use std::fs::{read_to_string, File}; use std::fs::{read_to_string, File};
use std::io::Cursor;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
use crate::labeling;
#[derive(Clone, Args, Debug)] #[derive(Clone, Args, Debug)]
pub struct ApkLocation { pub struct ApkLocation {
#[arg(short, long, conflicts_with = "sha256")] #[arg(short, long, conflicts_with = "sha256")]
@ -79,11 +82,16 @@ pub fn get_apk(location: &ApkLocation) -> Apk {
reqwest::StatusCode::OK => (), reqwest::StatusCode::OK => (),
s => panic!("Failed to download apk: {:?}", s), 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 { ApkLocation {
path: Some(path), .. 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), _ => panic!("Don't know what to do with:\n{:#?}", location),
} }
} }

View file

@ -1,6 +1,7 @@
use androscalpel::SmaliName; use androscalpel::SmaliName;
use androscalpel::{IdMethod, Instruction, Method}; use androscalpel::{IdMethod, Instruction, Method};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use std::collections::{HashMap, HashSet};
use std::sync::LazyLock; use std::sync::LazyLock;
pub mod get_apk; 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 // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/reflection.cc;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=698
// does. // does.
/// Structure storing the runtime information of a reflection call. #[derive(Clone, PartialEq, Debug)]
pub struct ReflectionData {
pub invoke_data: Vec<ReflectionInvokeData>,
pub class_new_inst_data: Vec<ReflectionClassNewInstData>,
pub cnstr_new_inst_data: Vec<ReflectionCnstrNewInstData>,
}
impl ReflectionData {
/// List all the methods that made reflection calls.
pub fn get_method_referenced(&self) -> HashSet<IdMethod> {
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<String, Vec<ReflectionInvokeData>> {
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<String, Vec<ReflectionClassNewInstData>> {
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<String, Vec<ReflectionCnstrNewInstData>> {
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 { pub struct ReflectionInvokeData {
/// The method called by `java.lang.reflect.Method.invoke()`
pub method: IdMethod, 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: variable number of args?
// TODO: type of invoke? // 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 { pub struct ReflectionClassNewInstData {
/// The constructor called by `java.lang.Class.newInstance()`
pub constructor: IdMethod, 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 { pub struct ReflectionCnstrNewInstData {
/// The constructor calleb by `java.lang.reflect.Constructor.newInstance()`
pub constructor: IdMethod, 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 { pub struct RegistersInfo {
@ -84,15 +190,29 @@ static CNSTR_GET_DEC_CLS: LazyLock<IdMethod> = LazyLock::new(|| {
.unwrap() .unwrap()
}); });
/// Function passed to [`androscalpel::Apk::load_apk`] to label the instructions of interest.
fn labeling(_mth: &IdMethod, ins: &Instruction, addr: usize) -> Option<String> {
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 // 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 // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/method_verifier.cc;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=5328
pub fn transform_method( pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result<()> {
meth: &mut Method,
ref_invoke_data: &ReflectionInvokeData,
ref_class_new_inst_data: &ReflectionClassNewInstData,
ref_cnstr_new_inst_data: &ReflectionCnstrNewInstData,
) -> Result<()> {
// checking meth.annotations might be usefull at some point // 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 let code = meth
.code .code
.as_mut() .as_mut()
@ -111,13 +231,16 @@ pub fn transform_method(
}; };
let mut new_insns = vec![]; let mut new_insns = vec![];
let mut iter = code.insns.iter(); let mut iter = code.insns.iter();
let mut current_addr_label: Option<String> = None;
while let Some(ins) = iter.next() { while let Some(ins) = iter.next() {
match ins { match ins {
Instruction::InvokeVirtual { method, args } Instruction::InvokeVirtual { method, args }
if method == &*MTH_INVOKE if (method == &*MTH_INVOKE
|| method == &*CLASS_NEW_INST || 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()); let (pseudo_insns, move_ret) = get_move_result(iter.clone());
if move_ret.is_some() { if move_ret.is_some() {
while move_ret.as_ref() != iter.next() {} while move_ret.as_ref() != iter.next() {}
@ -130,36 +253,45 @@ pub fn transform_method(
panic!("Should not happen!") panic!("Should not happen!")
}; };
// TODO: recover from failure // TODO: recover from failure
let ins_block = if method == &*MTH_INVOKE { if method == &*MTH_INVOKE {
get_invoke_block( for ref_data in invoke_data.get(addr_label).unwrap_or(&vec![]) {
ref_invoke_data, for ins in get_invoke_block(
args.as_slice(), ref_data,
&mut register_info, args.as_slice(),
&end_label, &mut register_info,
move_ret.clone(), &end_label,
)? move_ret.clone(),
)? {
new_insns.push(ins);
}
}
} else if method == &*CLASS_NEW_INST { } else if method == &*CLASS_NEW_INST {
get_class_new_inst_block( for ref_data in class_new_inst_data.get(addr_label).unwrap_or(&vec![]) {
ref_class_new_inst_data, for ins in get_class_new_inst_block(
args.as_slice(), ref_data,
&mut register_info, args.as_slice(),
&end_label, &mut register_info,
move_ret.clone(), &end_label,
)? move_ret.clone(),
)? {
new_insns.push(ins);
}
}
} else if method == &*CNSTR_NEW_INST { } else if method == &*CNSTR_NEW_INST {
get_cnstr_new_inst_block( for ref_data in cnstr_new_inst_data.get(addr_label).unwrap_or(&vec![]) {
ref_cnstr_new_inst_data, for ins in get_cnstr_new_inst_block(
args.as_slice(), ref_data,
&mut register_info, args.as_slice(),
&end_label, &mut register_info,
move_ret.clone(), &end_label,
)? move_ret.clone(),
)? {
new_insns.push(ins);
}
}
} else { } else {
panic!("Should not happen!") panic!("Should not happen!")
}; };
for ins in ins_block.into_iter() {
new_insns.push(ins);
}
new_insns.push(ins.clone()); new_insns.push(ins.clone());
if let Some(move_ret) = move_ret { if let Some(move_ret) = move_ret {
for ins in pseudo_insns.into_iter() { for ins in pseudo_insns.into_iter() {
@ -169,8 +301,16 @@ pub fn transform_method(
} }
let end_label = Instruction::Label { name: end_label }; let end_label = Instruction::Label { name: end_label };
new_insns.push(end_label.clone()); 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 => { ins => {
if !ins.is_pseudo_ins() {
current_addr_label = None;
}
new_insns.push(ins.clone()); new_insns.push(ins.clone());
} }
} }

View file

@ -2,11 +2,12 @@ use std::collections::HashMap;
use std::io::Cursor; use std::io::Cursor;
use std::path::PathBuf; use std::path::PathBuf;
use androscalpel::{IdMethod, IdType}; use androscalpel::IdMethod;
use patcher::get_apk::{get_apk, ApkLocation}; use patcher::get_apk::{get_apk, ApkLocation};
use patcher::{ use patcher::{
transform_method, ReflectionClassNewInstData, ReflectionCnstrNewInstData, ReflectionInvokeData, transform_method, ReflectionClassNewInstData, ReflectionCnstrNewInstData, ReflectionData,
ReflectionInvokeData,
}; };
use clap::Parser; use clap::Parser;
@ -31,44 +32,82 @@ fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let mut apk = get_apk(&cli.apk); let mut apk = get_apk(&cli.apk);
//println!("{:#?}", apk.list_classes()); //println!("{:#?}", apk.list_classes());
let class = apk let reflection_data = ReflectionData {
.get_class_mut( invoke_data: vec![
&IdType::new("Lcom/example/theseus/reflection/MainActivity;".into()).unwrap(), ReflectionInvokeData {
) method: IdMethod::from_smali(
.unwrap(); "Lcom/example/theseus/reflection/Reflectee;\
//println!("{:#?}", class.direct_methods.keys()); ->transfer\
//println!("{:#?}", class.virtual_methods.keys()); (Ljava/lang/String;)Ljava/lang/String;",
for m in [ )
"Lcom/example/theseus/reflection/MainActivity;->callVirtualMethodReflectCall()V", .unwrap(),
"Lcom/example/theseus/reflection/MainActivity;->callConstructorVirtualMethodReflectConstr()V", caller_method: IdMethod::from_smali(
"Lcom/example/theseus/reflection/MainActivity;->callVirtualMethodReflectOldConst()V", "Lcom/example/theseus/reflection/MainActivity;\
] { ->callVirtualMethodReflectCall()V",
let method = class )
.virtual_methods .unwrap(),
.get_mut(&IdMethod::from_smali(m).unwrap()) addr: 0x2B,
.unwrap(); },
transform_method( ReflectionInvokeData {
method, method: IdMethod::from_smali(
&ReflectionInvokeData { "Lcom/example/theseus/reflection/Reflectee;\
method: IdMethod::from_smali( ->transfer(Ljava/lang/String;)Ljava/lang/String;",
"Lcom/example/theseus/reflection/Reflectee;->transfer(Ljava/lang/String;)Ljava/lang/String;", )
) .unwrap(),
.unwrap(), caller_method: IdMethod::from_smali(
}, "Lcom/example/theseus/reflection/MainActivity;\
&ReflectionClassNewInstData { ->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( constructor: IdMethod::from_smali(
"Lcom/example/theseus/reflection/Reflectee;-><init>()V", "Lcom/example/theseus/reflection/Reflectee;\
-><init>()V",
) )
.unwrap(), .unwrap(),
}, caller_method: IdMethod::from_smali(
&ReflectionCnstrNewInstData{ "Lcom/example/theseus/reflection/MainActivity;\
->callVirtualMethodReflectOldConst()V",
)
.unwrap(),
addr: 0x12,
}],
cnstr_new_inst_data: vec![ReflectionCnstrNewInstData {
constructor: IdMethod::from_smali( constructor: IdMethod::from_smali(
"Lcom/example/theseus/reflection/Reflectee;-><init>(Ljava/lang/String;)V", "Lcom/example/theseus/reflection/Reflectee;\
-><init>(Ljava/lang/String;)V",
) )
.unwrap(), .unwrap(),
}, caller_method: IdMethod::from_smali(
) "Lcom/example/theseus/reflection/MainActivity;\
.unwrap(); ->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 dex_files = vec![];
let mut files = apk.gen_raw_dex().unwrap(); let mut files = apk.gen_raw_dex().unwrap();