wip
This commit is contained in:
parent
92f8e7092d
commit
2e0794c3e3
5 changed files with 1533 additions and 101 deletions
1310
patcher/Cargo.lock
generated
1310
patcher/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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
89
patcher/src/get_apk.rs
Normal 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
148
patcher/src/lib.rs
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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]>>>>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue