diff --git a/androscalpel/examples/count_ins_and_genapk.rs b/androscalpel/examples/count_ins_and_genapk.rs index 67c6595..a2e9818 100644 --- a/androscalpel/examples/count_ins_and_genapk.rs +++ b/androscalpel/examples/count_ins_and_genapk.rs @@ -1,5 +1,9 @@ use androscalpel::{Apk, Instruction, Result, VisitableMut, VisitorMut}; +use apk_frauder::replace_dex_unsigned; + +use std::collections::HashMap; use std::env; +use std::io::Cursor; #[derive(Default)] struct InsCounter { @@ -16,7 +20,7 @@ impl VisitorMut for InsCounter { fn main() { let args: Vec<_> = env::args().collect(); - assert!(args.len() > 1, "Need one argument"); + assert!(args.len() > 2, "usage: {} input.apk output.apk", args[0]); let mut cnt = InsCounter::default(); let apk = cnt @@ -24,16 +28,29 @@ fn main() { .unwrap(); println!("Nb INS: {}", cnt.n); + // This should be streamlined + 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( - (&args[1]).into(), - (&args[1] + ".pathed").into(), + replace_dex_unsigned( + &args[1], + &args[2], &mut dex_files, - cli.keystore, - cli.zipalign, - cli.apksigner, - cli.keypassword.as_deref(), None::>>>, - )*/ + ) + .unwrap(); } diff --git a/apk_frauder/src/lib.rs b/apk_frauder/src/lib.rs index 4dbea99..011ca2e 100644 --- a/apk_frauder/src/lib.rs +++ b/apk_frauder/src/lib.rs @@ -143,7 +143,74 @@ impl FileInfo { } } -/// Replace the dex files a an apk an resigned the apk. +/// Replace the dex files in an apk and strip the old signature. +#[allow(clippy::too_many_arguments)] +pub fn replace_dex_unsigned( + apk: impl AsRef, + dst: impl AsRef, + dexfiles: &mut [impl Read + Seek], + additionnal_files: Option>>, +) -> Result<()> { + let file = File::open(&apk).with_context(|| { + format!( + "failed to open file {}", + apk.as_ref().to_str().unwrap_or("") + ) + })?; + let mut apk = ZipFileReader::new(file)?; + let file = File::create(&dst).context("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() + .context("No dex file found in apk")?) + .clone(); + + apk.unlink_signature_files(); + apk.unlink_bytecode_files(); + + if let Some(additionnal_files) = &additionnal_files { + apk.files + .retain(|file| additionnal_files.get(&file.get_name()).is_none()); + } + + 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()), + file_info_ref.data_descriptor.clone(), + ); + } + + if let Some(mut additionnal_files) = additionnal_files { + for (name, data) in &mut additionnal_files { + file_info_ref.set_name(name); + if let Some(data) = data.as_mut() { + apk_out.insert_file( + data, + file_info_ref.header.clone(), + Some(file_info_ref.local_header.clone()), + file_info_ref.data_descriptor.clone(), + ); + } + } + } + apk_out.write_central_directory(); + Ok(()) +} + +/// Replace the dex files in an apk an resigned the apk. /// /// # Warning /// @@ -177,58 +244,8 @@ pub fn replace_dex( let aligned_path = tmp_dir.join("aligned.apk"); fs::create_dir_all(&tmp_dir).context("Failed to create temporary directory")?; - let file = File::open(&apk).with_context(|| { - format!( - "failed to open file {}", - apk.as_ref().to_str().unwrap_or("") - ) - })?; - let mut apk = ZipFileReader::new(file)?; - let file = File::create(&unaligned_path).context("failed to create file")?; - let mut apk_out = ZipFileWriter::new(file, apk.zip64_end_of_central_directory.clone()); + replace_dex_unsigned(apk, &unaligned_path, dexfiles, additionnal_files)?; - let mut file_info_ref = (*apk - .get_classes_file_info() - .first() - .context("No dex file found in apk")?) - .clone(); - - apk.unlink_signature_files(); - apk.unlink_bytecode_files(); - if let Some(additionnal_files) = &additionnal_files { - apk.files - .retain(|file| additionnal_files.get(&file.get_name()).is_none()); - } - 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()), - file_info_ref.data_descriptor.clone(), - ); - } - if let Some(mut additionnal_files) = additionnal_files { - for (name, data) in &mut additionnal_files { - file_info_ref.set_name(name); - if let Some(data) = data.as_mut() { - apk_out.insert_file( - data, - file_info_ref.header.clone(), - Some(file_info_ref.local_header.clone()), - file_info_ref.data_descriptor.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") diff --git a/apk_frauder/src/zip_writer.rs b/apk_frauder/src/zip_writer.rs index f5dd532..7569b08 100644 --- a/apk_frauder/src/zip_writer.rs +++ b/apk_frauder/src/zip_writer.rs @@ -2,11 +2,13 @@ use std::io; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use crate::compression::CompressionMethod; -use crate::data_descriptor::DataDescriptor; +use crate::data_descriptor::{DataDescriptor, DataDescriptor32, DataDescriptor64}; use crate::end_of_central_directory::{ EndCentralDirectory, Zip64EndCentralDirectory, Zip64EndCentralDirectoryLocator, }; -use crate::{FileHeader, FileInfo, LocalFileHeader, ZipFileReader, general_purpose_flags}; +use crate::{ + general_purpose_flags, ExtraField, FileHeader, FileInfo, LocalFileHeader, ZipFileReader, +}; use androscalpel_serializer::Serializable; use flate2::write::DeflateEncoder; use flate2::{Compression, CrcWriter}; @@ -67,9 +69,13 @@ impl ZipFileWriter { data_descriptor: Option, ) { assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0); - assert!( - header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0 - ); // TODO + //assert!( + // header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0, + // "Writing file with data_descriptor is not yet implemented" + //); // TODO + let mut use_data_descriptor = data_descriptor.is_some() + || ((header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR) + != 0); assert!(header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0); assert!( header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR == 0 @@ -88,9 +94,12 @@ impl ZipFileWriter { ); let mut local_header = if let Some(header) = local_header { assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0); - assert!( - header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0 - ); // TODO + //assert!( + // header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0, + // "Writing file with data_descriptor is not yet implemented" + //); // TODO + use_data_descriptor |= + header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR != 0; assert!( header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0 ); @@ -110,15 +119,20 @@ impl ZipFileWriter { header.compression_method == CompressionMethod::Deflated || header.compression_method == CompressionMethod::Stored ); + let mut general_purpose_flags = header.general_purpose_flags; + // We only support options for Deflate parameter and data descriptor + if header.compression_method == CompressionMethod::Deflated { + general_purpose_flags &= general_purpose_flags::MASK_COMPRESS_OPTION_1 + | general_purpose_flags::MASK_COMPRESS_OPTION_2; + } else { + general_purpose_flags = 0 + } + if use_data_descriptor { + general_purpose_flags |= general_purpose_flags::MASK_USE_DATA_DESCRIPTOR; + } LocalFileHeader { version_needed_to_extract: header.version_needed_to_extract, - general_purpose_flags: if header.compression_method == CompressionMethod::Deflated { - header.general_purpose_flags - & (general_purpose_flags::MASK_COMPRESS_OPTION_1 - | general_purpose_flags::MASK_COMPRESS_OPTION_2) - } else { - 0 - }, + general_purpose_flags, compression_method: header.compression_method, last_mod_file_time: header.last_mod_file_time, last_mod_file_data: header.last_mod_file_data, @@ -139,6 +153,10 @@ impl ZipFileWriter { | general_purpose_flags::MASK_COMPRESS_OPTION_2) } else { 0 + } | if use_data_descriptor { + general_purpose_flags::MASK_USE_DATA_DESCRIPTOR + } else { + 0 }, compression_method: header.compression_method, last_mod_file_time: header.last_mod_file_time, @@ -198,9 +216,74 @@ impl ZipFileWriter { header.set_uncompressed_size(local_header.get_uncompressed_size()); header.set_offset_local_header(header_offset); - if data_descriptor.is_some() { - panic!("Writing file with data_descriptor is not yet implemented"); + let use_z64 = header + .extra_field + .iter() + .any(|f| matches!(f, ExtraField::Zip64(_))); + + let data_descriptor = if use_data_descriptor { + match data_descriptor { + None if use_z64 => Some(DataDescriptor::Zip64(DataDescriptor64 { + crc_32: local_header.crc_32, + compressed_size: local_header.get_compressed_size(), + uncompressed_size: local_header.get_uncompressed_size(), + use_signature: true, + })), + None => Some(DataDescriptor::Zip32(DataDescriptor32 { + crc_32: local_header.crc_32, + compressed_size: local_header.get_compressed_size() as u32, + uncompressed_size: local_header.get_uncompressed_size() as u32, + use_signature: true, + })), + Some(DataDescriptor::Zip32(DataDescriptor32 { use_signature, .. })) if use_z64 => { + Some(DataDescriptor::Zip64(DataDescriptor64 { + crc_32: local_header.crc_32, + compressed_size: local_header.get_compressed_size(), + uncompressed_size: local_header.get_uncompressed_size(), + use_signature, + })) + } + Some(DataDescriptor::Zip64(DataDescriptor64 { use_signature, .. })) if !use_z64 => { + Some(DataDescriptor::Zip32(DataDescriptor32 { + crc_32: local_header.crc_32, + compressed_size: local_header.get_compressed_size() as u32, + uncompressed_size: local_header.get_uncompressed_size() as u32, + use_signature, + })) + } + Some(DataDescriptor::Zip64(DataDescriptor64 { use_signature, .. })) => { + Some(DataDescriptor::Zip64(DataDescriptor64 { + crc_32: local_header.crc_32, + compressed_size: local_header.get_compressed_size(), + uncompressed_size: local_header.get_uncompressed_size(), + use_signature, + })) + } + Some(DataDescriptor::Zip32(DataDescriptor32 { use_signature, .. })) => { + Some(DataDescriptor::Zip32(DataDescriptor32 { + crc_32: local_header.crc_32, + compressed_size: local_header.get_compressed_size() as u32, + uncompressed_size: local_header.get_uncompressed_size() as u32, + use_signature, + })) + } + } + } else { + None + }; + + match &data_descriptor { + Some(DataDescriptor::Zip32(data_descriptor)) => { + data_descriptor.serialize(&mut self.data).unwrap(); + self.current_offset += data_descriptor.size() as u64; + } + Some(DataDescriptor::Zip64(data_descriptor)) => { + data_descriptor.serialize(&mut self.data).unwrap(); + self.current_offset += data_descriptor.size() as u64; + } + _ => {} } + let file_info = FileInfo { local_header, header,