This commit is contained in:
Jean-Marie 'Histausse' Mineau 2025-01-30 16:51:09 +01:00
parent 92f8e7092d
commit 2e0794c3e3
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
5 changed files with 1533 additions and 101 deletions

1310
patcher/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,4 +6,9 @@ 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", version = "0.1.0" } androscalpel = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git" }
apk_frauder = { git = "ssh://git@git.mineau.eu/histausse/androscalpel.git" }
anyhow = "1.0.95"
clap = { version = "4.5.27", features = ["derive"] }
env_logger = "0.11.6"
reqwest = { version = "0.12.12", default-features = false, features = ["blocking", "rustls-tls"] }

89
patcher/src/get_apk.rs Normal file
View file

@ -0,0 +1,89 @@
use androscalpel::Apk;
use clap::Args;
use std::fs::read_to_string;
use std::path::PathBuf;
use std::time::Duration;
#[derive(Clone, Args, Debug)]
pub struct ApkLocation {
#[arg(short, long, conflicts_with = "sha256")]
pub path: Option<PathBuf>,
#[arg(long, conflicts_with = "path", requires = "androzoo_key")]
pub sha256: Option<String>,
#[command(flatten)]
pub androzoo_key: Option<AndrozooKey>,
}
impl ApkLocation {
pub fn get_id(&self) -> String {
match self {
ApkLocation {
path: Some(path), ..
} => path.as_path().file_name().unwrap().to_str().unwrap().into(),
ApkLocation {
sha256: Some(sha256),
..
} => sha256.clone(),
_ => panic!("Invalid ApkLocation"),
}
}
}
#[derive(Clone, Args, Debug)]
pub struct AndrozooKey {
#[arg(long, group = "androzoo_key", conflicts_with = "api_key")]
api_key_path: Option<PathBuf>,
#[arg(long, group = "androzoo_key", conflicts_with = "api_key_path")]
api_key: Option<String>,
}
impl AndrozooKey {
fn get_key(&self) -> String {
match self {
AndrozooKey {
api_key_path: Some(path),
..
} => read_to_string(path)
.expect("Failed to read key from file")
.trim()
.to_string(),
AndrozooKey {
api_key: Some(key), ..
} => key.trim().to_string(),
_ => panic!("No key here"),
}
}
}
pub fn get_apk(location: &ApkLocation) -> Apk {
match location {
ApkLocation {
androzoo_key: Some(key),
sha256: Some(sha256),
..
} => {
let key = key.get_key();
let url = reqwest::Url::parse_with_params(
"https://androzoo.uni.lu/api/download",
&[("apikey", key), ("sha256", sha256.clone())],
)
.expect("Failed to parse url");
let res = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(300))
.build()
.expect("Failed to build client")
.get(url)
.send()
.expect("Failed to download apk");
match res.status() {
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()
}
ApkLocation {
path: Some(path), ..
} => Apk::load_apk(path.into(), false, false).unwrap(),
_ => panic!("Don't know what to do with:\n{:#?}", location),
}
}

148
patcher/src/lib.rs Normal file
View file

@ -0,0 +1,148 @@
use androscalpel::{IdMethod, Instruction, Method};
use anyhow::{bail, Context, Result};
pub mod get_apk;
// TODO:
// Check what
// 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.
pub struct ReflectionData {
pub method: IdMethod,
// TODO: variable number of args?
// TODO: type of invoke?
}
struct RegistersInfo {
pub array_index: u8,
//pub array: u8,
pub array_val: u8,
//pub original_array_index_reg: Option<u16>,
//pub original_array_reg: Option<u16>,
pub first_arg: u16,
pub nb_arg_reg: u16,
}
impl RegistersInfo {
const NB_U8_REG: u16 = 2;
fn get_nb_added_reg(&self) -> u16 {
2 + self.nb_arg_reg
}
}
const INVOKE: &str =
"Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;";
pub fn transform_method(meth: &mut Method, ref_data: &ReflectionData) -> Result<()> {
let invoke = IdMethod::from_smali(INVOKE)?;
// checking meth.annotations might be usefull at some point
let code = meth
.code
.as_mut()
.with_context(|| format!("Code not found in {}", meth.descriptor.__str__()))?;
println!(
"registers_size: {}\nins_size: {}\nouts_size: {}",
code.registers_size, code.ins_size, code.outs_size,
);
// TODO
if code.registers_size + RegistersInfo::NB_U8_REG > u8::MAX as u16 {
bail!("To many registers");
}
let mut register_info = RegistersInfo {
array_index: code.registers_size as u8,
array_val: (code.registers_size + 1) as u8,
//array: 0,
first_arg: code.registers_size + 2,
nb_arg_reg: 0,
};
let mut new_insns = vec![];
for ins in &code.insns {
match ins {
Instruction::InvokeVirtual { method, args } if method == &invoke => {
// TODO move ret ?
// TODO: rever from get_invoke_block failure
for ins in
get_invoke_block(ref_data, args.as_slice(), &mut register_info)?.into_iter()
{
println!(" \x1b[92m{}\x1b[0m", ins.__str__());
new_insns.push(ins);
}
//new_insns.push(ins.clone());
println!(" \x1b[91m{}\x1b[0m", ins.__str__());
}
ins => {
println!(" {}", ins.__str__());
new_insns.push(ins.clone());
}
}
}
// TODO: scalar type
code.insns = vec![];
// Start the method by moving the parameter to their registers pre-transformation.
for i in 0..code.ins_size {
code.insns.push(Instruction::MoveObject {
from: code.registers_size - code.ins_size + i + register_info.get_nb_added_reg(),
to: code.registers_size - code.ins_size + i,
});
}
// Add the new code
code.insns.append(&mut new_insns);
code.registers_size += register_info.get_nb_added_reg();
println!("\nnew code:\n");
for ins in &code.insns {
println!(" {}", ins.__str__());
}
Ok(())
}
fn get_invoke_block(
ref_data: &ReflectionData,
invoke_arg: &[u16],
reg_inf: &mut RegistersInfo,
) -> Result<Vec<Instruction>> {
let (_method_obj, obj_inst, arg_arr) = if let &[a, b, c] = invoke_arg {
(a, b, c)
} else {
bail!(
"Method;->invoke arg should have exactrly 3 arguments, found {}",
invoke_arg.len()
);
};
if arg_arr > u8::MAX as u16 {
// TODO
bail!("Cannot transform invoke calls to a method using 16 bits register for its argument");
}
let nb_args = ref_data.method.proto.get_parameters().len();
if reg_inf.nb_arg_reg < nb_args as u16 + 1 {
reg_inf.nb_arg_reg = nb_args as u16 + 1;
}
let mut insns = vec![];
insns.push(Instruction::MoveObject {
from: obj_inst,
to: reg_inf.first_arg,
});
for i in 0..nb_args {
insns.push(Instruction::Const {
reg: reg_inf.array_index,
lit: i as i32,
});
insns.push(Instruction::AGetObject {
dest: reg_inf.array_val,
arr: arg_arr as u8, // TODO
idx: reg_inf.array_index,
});
insns.push(Instruction::MoveObject {
from: reg_inf.array_val as u16,
to: reg_inf.first_arg + 1 + i as u16,
});
}
insns.push(Instruction::InvokeVirtual {
method: ref_data.method.clone(),
args: (reg_inf.first_arg..reg_inf.first_arg + 1 + nb_args as u16).collect(),
});
// We need a few u8 regs here. For now, we assumes we work with less than 256 reg.
Ok(insns)
}

View file

@ -1,3 +1,79 @@
fn main() { use std::collections::HashMap;
println!("Hello, world!"); use std::io::Cursor;
use std::path::PathBuf;
use androscalpel::{IdMethod, IdType};
use patcher::get_apk::{get_apk, ApkLocation};
use patcher::{transform_method, ReflectionData};
use clap::Parser;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None, arg_required_else_help = true)]
struct Cli {
#[clap(flatten)]
apk: ApkLocation,
#[arg(short, long)]
out: PathBuf,
#[arg(short, long)]
keystore: PathBuf,
}
fn main() {
env_logger::init();
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());
let method = class
.virtual_methods
.get_mut(
&IdMethod::from_smali(
"Lcom/example/theseus/reflection/MainActivity;->callVirtualMethodReflectCall()V",
)
.unwrap(),
)
.unwrap();
transform_method(
method,
&ReflectionData {
method: IdMethod::from_smali(
"Lcom/example/theseus/reflection/Reflectee;->transfer(Ljava/lang/String;)Ljava/lang/String;",
)
.unwrap(),
},
)
.unwrap();
let mut dex_files = vec![];
let mut files = apk.gen_raw_dex().unwrap();
let mut i = 0;
loop {
let name = if i == 0 {
"classes.dex".into()
} else {
format!("classes{}.dex", i + 1)
};
if let Some(file) = files.remove(&name) {
dex_files.push(Cursor::new(file))
} else {
break;
}
i += 1;
}
apk_frauder::replace_dex(
cli.apk.path.unwrap(),
cli.out,
&mut dex_files,
cli.keystore,
None::<PathBuf>,
None::<PathBuf>,
None::<HashMap<_, Option<Cursor<&[u8]>>>>,
);
} }