From d3e005fd681d9d7267b1db65f666bf82b1f18f2f Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Mon, 22 Jan 2024 16:50:26 +0100 Subject: [PATCH] poc repackaging --- apk_frauder/.gitignore | 2 + apk_frauder/src/main.rs | 130 +++++++++++++++++----------------- apk_frauder/src/zip_reader.rs | 63 +++++++++++++--- 3 files changed, 121 insertions(+), 74 deletions(-) diff --git a/apk_frauder/.gitignore b/apk_frauder/.gitignore index bcc6354..cfc8291 100644 --- a/apk_frauder/.gitignore +++ b/apk_frauder/.gitignore @@ -1,2 +1,4 @@ *.apk *.zip +*.dex +*.idsig diff --git a/apk_frauder/src/main.rs b/apk_frauder/src/main.rs index d3dc0a9..54fad26 100644 --- a/apk_frauder/src/main.rs +++ b/apk_frauder/src/main.rs @@ -1,76 +1,78 @@ -use apk_frauder::external_file_attributes; -use apk_frauder::file_header::FileHeader; use apk_frauder::ZipFileReader; use apk_frauder::ZipFileWriter; use std::fs::File; -use std::io::Cursor; + +use std::process::Command; fn main() { - //let file = File::open("app-release.apk").expect("failed to open file"); - //let file = File::open("tst_64.zip").expect("failed to open file"); - //let zip_file = ZipFileReader::new(file); - /* - //println!("{}", zip_file.get_file_names().join("\n")); - for file in &zip_file.files { - println!("{}", file.get_name()); - println!("local: {:?}", file.local_header.malformed_extra_field); - println!("central dir: {:?}", file.header.malformed_extra_field); - println!(); + // 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!( - "uncompressed size: {}", - zip_file.files[0].get_uncompressed_size() - ); - println!( - "{}", - zip_file - .get_jar_sig_files() - .iter() - .map(|f| f.get_name()) - .collect::>() - .join("\n") - ); - if zip_file.is_signed_v2() { - println!("Signed >= v2"); - } else { - println!("Not signed whith scheme >= v2"); + 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); } - zip_file.check_holes(); - */ - //println!("{:#?}", zip_file.get_file_info("classes.dex")); - - /* - let file = File::open("tst_64.zip").expect("failed to open file"); - let out_file = File::create("tst_64.out.zip").expect("failed to create file"); - let mut zip_file = ZipFileReader::new(file); - let mut out_file = - ZipFileWriter::new(out_file, zip_file.zip64_end_of_central_directory.clone()); - for f in zip_file.files.clone() { - out_file.insert_file_from_zip(f, &mut zip_file); - } - out_file.write_central_directory(); - */ - //println!("{:#?}", zip_file.zip64_end_of_central_directory); - let out_file = File::create("test_write.zip").expect("failed to create file"); - let mut out_file = ZipFileWriter::new(out_file, None); - out_file.insert_file( - &mut Cursor::new(b"plop\n"), - FileHeader::new_default("plop.txt"), - None, - ); - out_file.insert_file( - &mut Cursor::new(b"Hello World !!!\n"), - FileHeader::new_default("plip.txt"), - None, + file_info_ref.set_name("classes.dex"); + apk_out.insert_file( + &mut bytecodes, + file_info_ref.header, + Some(file_info_ref.local_header), ); + apk_out.write_central_directory(); - //let mut header_dir = FileHeader::new_default("dir"); - //header_dir.set_file_type(external_file_attributes::DIR_FILE); - //out_file.insert_file(&mut Cursor::new(b""), header_dir, None); - let mut header_link = FileHeader::new_default("dir/link"); - header_link.set_file_type(external_file_attributes::SYMBOLIC_LINK_FILE); - out_file.insert_file(&mut Cursor::new(b"../plop.txt"), header_link, None); + 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(); - out_file.write_central_directory(); + 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(); } diff --git a/apk_frauder/src/zip_reader.rs b/apk_frauder/src/zip_reader.rs index 7096c35..584530b 100644 --- a/apk_frauder/src/zip_reader.rs +++ b/apk_frauder/src/zip_reader.rs @@ -207,15 +207,7 @@ impl ZipFileReader { pub fn get_jar_sig_files(&self) -> Vec<&FileInfo> { self.files .iter() - .filter(|file| { - let name = file.get_name(); - let l = name.len(); - (name == "META-INF/MANIFEST.MF") - || (l >= 13 && &name[..9] == "META-INF/" && &name[l - 3..] == ".SF") - || (l >= 14 && &name[..9] == "META-INF/" && &name[l - 4..] == ".DSA") - || (l >= 14 && &name[..9] == "META-INF/" && &name[l - 4..] == ".RSA") - || (l >= 14 && &name[..13] == "META-INF/SIG-") - }) + .filter(|file| match_v1_signature_file(&file.get_name())) .collect() } @@ -237,6 +229,24 @@ impl ZipFileReader { self.apk_sign_block.is_some() } + /// Remove v1 signature files from the file index. + /// + /// This function does no modify the original file, only the index store + /// in the struct. + pub fn unlink_signature_files(&mut self) { + self.files + .retain(|file| !match_v1_signature_file(&file.get_name())); + } + + /// Unlink bytecode files. + /// + /// This function does no modify the original file, only the index store + /// in the struct. + pub fn unlink_bytecode_files(&mut self) { + self.files + .retain(|file| !match_dexfile_name(&file.get_name())); + } + pub fn check_holes(&self) { let mut files: Vec<&FileInfo> = self.files.iter().collect(); files.sort_by_key(|f| f.get_offset_local_header()); @@ -262,7 +272,7 @@ impl ZipFileReader { ); } - lst_offset += apk_sign_block.size() as u64; + lst_offset = self.get_cd_offset(); } if self.get_cd_offset() != lst_offset { println!( @@ -285,4 +295,37 @@ impl ZipFileReader { pub fn get_file_info(&self, name: &str) -> Option<&FileInfo> { self.files.iter().find(|&file| file.get_name() == name) } + + pub fn get_classes_file_info(&self) -> Vec<&FileInfo> { + self.files + .iter() + .filter(|&file| match_dexfile_name(&file.get_name())) + .collect() + } +} + +// Not worth a regex +/// Check if a file is a bytecode file (from its name) +fn match_dexfile_name(name: &str) -> bool { + if name.len() < 11 { + return false; + } + if &name[0..7] != "classes" { + return false; + } + if &name[name.len() - 4..name.len()] != ".dex" { + return false; + } + + name.len() == 11 || name[7..name.len() - 4].parse::().is_ok() +} + +/// Check if a file is used to sign the APK with signature scheme v1. +fn match_v1_signature_file(name: &str) -> bool { + let l = name.len(); + (name == "META-INF/MANIFEST.MF") + || (l >= 13 && &name[..9] == "META-INF/" && &name[l - 3..] == ".SF") + || (l >= 14 && &name[..9] == "META-INF/" && &name[l - 4..] == ".DSA") + || (l >= 14 && &name[..9] == "META-INF/" && &name[l - 4..] == ".RSA") + || (l >= 14 && &name[..13] == "META-INF/SIG-") }