put all the dex swapping & resigning shenanigans in a function

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2024-02-01 17:09:12 +01:00
parent 86a028f0bd
commit cb34f76063
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
5 changed files with 160 additions and 73 deletions

View file

@ -2,3 +2,4 @@
*.zip
*.dex
*.idsig
*.jks

View file

@ -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"

View file

@ -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<Path>,
dst: impl AsRef<Path>,
dexfiles: &mut [impl Read + Seek],
keystore: impl AsRef<Path>, // 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<impl AsRef<Path>>,
apksigner: Option<impl AsRef<Path>>,
) {
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::<u128>()));
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");
}

View file

@ -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();
}