From cb34f76063b0590e20c88eb9250c54b01e46b6aa Mon Sep 17 00:00:00 2001 From: Jean-Marie 'Histausse' Mineau Date: Thu, 1 Feb 2024 17:09:12 +0100 Subject: [PATCH] put all the dex swapping & resigning shenanigans in a function --- Cargo.lock | 54 +++++++++++++++++++++++++ apk_frauder/.gitignore | 1 + apk_frauder/Cargo.toml | 1 + apk_frauder/src/lib.rs | 90 +++++++++++++++++++++++++++++++++++++++++ apk_frauder/src/main.rs | 87 +++++++-------------------------------- 5 files changed, 160 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4dde742..dba09e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,7 @@ version = "0.1.0" dependencies = [ "androscalpel_serializer", "flate2", + "rand", ] [[package]] @@ -182,6 +183,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" @@ -324,6 +336,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.78" @@ -436,6 +454,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -561,6 +609,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" diff --git a/apk_frauder/.gitignore b/apk_frauder/.gitignore index cfc8291..8721a39 100644 --- a/apk_frauder/.gitignore +++ b/apk_frauder/.gitignore @@ -2,3 +2,4 @@ *.zip *.dex *.idsig +*.jks diff --git a/apk_frauder/Cargo.toml b/apk_frauder/Cargo.toml index 375242c..5c02076 100644 --- a/apk_frauder/Cargo.toml +++ b/apk_frauder/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" } flate2 = { version = "1.0.28", features = ["rust_backend"] } +rand = "0.8.5" diff --git a/apk_frauder/src/lib.rs b/apk_frauder/src/lib.rs index 31627ec..f23fedb 100644 --- a/apk_frauder/src/lib.rs +++ b/apk_frauder/src/lib.rs @@ -1,4 +1,10 @@ use androscalpel_serializer::Serializable; +use std::env; +use std::fs; +use std::fs::File; +use std::io::{Read, Seek}; +use std::path::Path; +use std::process::Command; pub mod apk_signing_block; pub mod compression; @@ -132,3 +138,87 @@ impl FileInfo { } } } + +/// Replace the dex files a an apk an resigned the apk. +/// +/// # Warning +/// +/// For now, only jks keystore are allowed. +/// +/// The `zipalign` and `apksigner` args allow to use a specific version of the +/// tools instead of the one in the PATH (if it even exist) +pub fn replace_dex( + apk: impl AsRef, + dst: impl AsRef, + dexfiles: &mut [impl Read + Seek], + keystore: impl AsRef, // TODO enum for handling p11 and generating a random cert + // `keytool -genkey -v -keystore KEYSTORE.jks -keyalg RSA -keysize + // 2048 -validity 10000 -alias ALIAS` + zipalign: Option>, + apksigner: Option>, +) { + let zipalign = if let Some(path) = &zipalign { + path.as_ref().as_os_str() + } else { + "zipalign".as_ref() + }; + let apksigner = if let Some(path) = &apksigner { + path.as_ref().as_os_str() + } else { + "apksigner".as_ref() + }; + let tmp_dir = env::temp_dir().join(format!("apk_frauder_{:x}", rand::random::())); + let unaligned_path = tmp_dir.join("stripped.apk"); + let aligned_path = tmp_dir.join("aligned.apk"); + fs::create_dir_all(&tmp_dir).expect("Failed to create temporary directory"); + + let file = File::open(apk).expect("failed to open file"); + let mut apk = ZipFileReader::new(file); + let file = File::create(&unaligned_path).expect("failed to create file"); + let mut apk_out = ZipFileWriter::new(file, apk.zip64_end_of_central_directory.clone()); + + let mut file_info_ref = (*apk + .get_classes_file_info() + .first() + .expect("No dex file found in apk")) + .clone(); + + apk.unlink_signature_files(); + apk.unlink_bytecode_files(); + for f in apk.files.clone() { + apk_out.insert_file_from_zip(f, &mut apk); + } + for (i, mut dex) in dexfiles.iter_mut().enumerate() { + if i == 0 { + file_info_ref.set_name("classes.dex"); + } else { + file_info_ref.set_name(&format!("classes{}.dex", i + 1)); + } + apk_out.insert_file( + &mut dex, + file_info_ref.header.clone(), + Some(file_info_ref.local_header.clone()), + ); + } + apk_out.write_central_directory(); + // TODO: we can probably do that ourself an spare ourself the trouble of finding zipalign + Command::new(zipalign) + .arg("-v") + .arg("-p") + .arg("4") + .arg(unaligned_path.as_os_str()) + .arg(aligned_path.as_os_str()) + .status() + .unwrap(); + + Command::new(apksigner) + .arg("sign") + .arg("--ks") + .arg(keystore.as_ref().as_os_str()) + .arg("--out") + .arg(dst.as_ref().as_os_str()) + .arg(aligned_path.as_os_str()) + .status() + .unwrap(); + fs::remove_dir_all(tmp_dir).expect("Failled to remove tmp dir"); +} diff --git a/apk_frauder/src/main.rs b/apk_frauder/src/main.rs index 54fad26..0418064 100644 --- a/apk_frauder/src/main.rs +++ b/apk_frauder/src/main.rs @@ -1,78 +1,19 @@ -use apk_frauder::ZipFileReader; -use apk_frauder::ZipFileWriter; +use std::env; use std::fs::File; -use std::process::Command; - fn main() { - // Remove previous generation - println!("Remove files from previous run"); - Command::new("rm").arg("app-striped.apk").status().unwrap(); - Command::new("rm") - .arg("app-instrumented.apk") - .status() - .unwrap(); - Command::new("rm") - .arg("app-instrumented-signed.apk") - .status() - .unwrap(); - Command::new("rm") - .arg("app-instrumented-aligned.apk") - .status() - .unwrap(); - - println!("Read the headers of the existing dex file and strip the apk signature and dex files"); - let file = File::open("app-release.apk").expect("failed to open file"); - let mut apk = ZipFileReader::new(file); - - let mut file_info_ref = (*apk.get_classes_file_info().first().unwrap()).clone(); - - apk.unlink_signature_files(); - apk.unlink_bytecode_files(); - - let out_file = File::create("app-striped.apk").expect("failed to create file"); - let mut apk_out = ZipFileWriter::new(out_file, apk.zip64_end_of_central_directory.clone()); - for f in apk.files.clone() { - apk_out.insert_file_from_zip(f, &mut apk); - } - apk_out.write_central_directory(); - - println!("Insert the dex file to the stripped apk with the header of the original dex file"); - let file = File::open("app-striped.apk").expect("failed to open file"); - let mut bytecodes = File::open("classes.dex").expect("failed to open file"); - let mut apk = ZipFileReader::new(file); - let out_file = File::create("app-instrumented.apk").expect("failed to create file"); - let mut apk_out = ZipFileWriter::new(out_file, apk.zip64_end_of_central_directory.clone()); - for f in apk.files.clone() { - apk_out.insert_file_from_zip(f, &mut apk); - } - file_info_ref.set_name("classes.dex"); - apk_out.insert_file( - &mut bytecodes, - file_info_ref.header, - Some(file_info_ref.local_header), + apk_frauder::replace_dex( + "app-release.apk", + "app-instrumented.apk", + &mut [File::open("classes.dex").expect("failed to open file")], + "my-release-key.jks", + Some(&format!( + "{}/Android/Sdk/build-tools/34.0.0/zipalign", + env::var("HOME").expect("$HOME not set") + )), + Some(&format!( + "{}/Android/Sdk/build-tools/34.0.0/apksigner", + env::var("HOME").expect("$HOME not set") + )), ); - apk_out.write_central_directory(); - - println!("Align the apk"); - // This could probably be performed by ourself - Command::new("/home/histausse/Android/Sdk/build-tools/34.0.0/zipalign") - .arg("-v") - .arg("-p") - .arg("4") - .arg("app-instrumented.apk") - .arg("app-instrumented-aligned.apk") - .status() - .unwrap(); - - println!("Sign the apk"); - Command::new("/home/histausse/Android/Sdk/build-tools/34.0.0/apksigner") - .arg("sign") - .arg("--ks") - .arg("/home/histausse/AndroidStudioProjects/TestApplication/my-release-key.jks") - .arg("--out") - .arg("app-instrumented-signed.apk") - .arg("app-instrumented-aligned.apk") - .status() - .unwrap(); }