From 3f521b57546033285b405b3a1aaf42fa534d225a Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Fri, 19 Jan 2024 11:46:36 +0100 Subject: [PATCH] add crc --- apk_frauder/src/file_header.rs | 76 ++++++++++++++ apk_frauder/src/local_file_header.rs | 50 +++++++++ apk_frauder/src/main.rs | 6 +- apk_frauder/src/zip_writer.rs | 150 ++++++++++++++++++++++++++- 4 files changed, 277 insertions(+), 5 deletions(-) diff --git a/apk_frauder/src/file_header.rs b/apk_frauder/src/file_header.rs index fb6e094..3b0b3e2 100644 --- a/apk_frauder/src/file_header.rs +++ b/apk_frauder/src/file_header.rs @@ -220,6 +220,31 @@ impl FileHeader { } } + pub fn set_uncompressed_size(&mut self, uncompressed_size: u64) { + if let Some(ExtraField::Zip64(Zip64ExtraField { + original_size: Some(original_size), + .. + })) = self + .extra_field + .iter_mut() + .find(|f| matches!(f, ExtraField::Zip64(_))) + { + *original_size = uncompressed_size; + self.uncompressed_size = u32::MAX; + } else if uncompressed_size > u32::MAX as u64 { + self.extra_field.push(ExtraField::Zip64(Zip64ExtraField { + original_size: Some(uncompressed_size), + compressed_size: Some(self.compressed_size as u64), + disk_number: None, + offset_header: None, + })); + self.uncompressed_size = u32::MAX; + self.compressed_size = u32::MAX; + } else { + self.uncompressed_size = uncompressed_size as u32; + } + } + pub fn get_compressed_size(&self) -> u64 { if self.compressed_size != u32::MAX { self.compressed_size as u64 @@ -237,6 +262,31 @@ impl FileHeader { } } + pub fn set_compressed_size(&mut self, compressed_size: u64) { + if let Some(ExtraField::Zip64(Zip64ExtraField { + compressed_size: Some(compressed_size_), + .. + })) = self + .extra_field + .iter_mut() + .find(|f| matches!(f, ExtraField::Zip64(_))) + { + *compressed_size_ = compressed_size; + self.compressed_size = u32::MAX; + } else if compressed_size > u32::MAX as u64 { + self.extra_field.push(ExtraField::Zip64(Zip64ExtraField { + original_size: Some(self.uncompressed_size as u64), + compressed_size: Some(compressed_size), + disk_number: None, + offset_header: None, + })); + self.uncompressed_size = u32::MAX; + self.compressed_size = u32::MAX; + } else { + self.compressed_size = compressed_size as u32; + } + } + pub fn get_offset_local_header(&self) -> u64 { if self.offset_local_header != u32::MAX { self.offset_local_header as u64 @@ -254,6 +304,32 @@ impl FileHeader { } } + pub fn set_offset_local_header(&mut self, offset: u64) { + if let Some(ExtraField::Zip64(Zip64ExtraField { + offset_header: Some(offset_header), + .. + })) = self + .extra_field + .iter_mut() + .find(|f| matches!(f, ExtraField::Zip64(_))) + { + *offset_header = offset; + self.offset_local_header = u32::MAX; + } else if offset > u32::MAX as u64 { + self.extra_field.push(ExtraField::Zip64(Zip64ExtraField { + original_size: Some(self.uncompressed_size as u64), + compressed_size: Some(self.compressed_size as u64), + disk_number: None, + offset_header: Some(offset), + })); + self.uncompressed_size = u32::MAX; + self.compressed_size = u32::MAX; + self.offset_local_header = u32::MAX; + } else { + self.offset_local_header = offset as u32; + } + } + pub fn get_disk_number_start(&self) -> u32 { if self.disk_number_start != u16::MAX { self.disk_number_start as u32 diff --git a/apk_frauder/src/local_file_header.rs b/apk_frauder/src/local_file_header.rs index 6600075..635b271 100644 --- a/apk_frauder/src/local_file_header.rs +++ b/apk_frauder/src/local_file_header.rs @@ -215,6 +215,31 @@ impl LocalFileHeader { } } + pub fn set_uncompressed_size(&mut self, uncompressed_size: u64) { + if let Some(ExtraField::Zip64(Zip64ExtraField { + original_size: Some(original_size), + .. + })) = self + .extra_field + .iter_mut() + .find(|f| matches!(f, ExtraField::Zip64(_))) + { + *original_size = uncompressed_size; + self.uncompressed_size = u32::MAX; + } else if uncompressed_size > u32::MAX as u64 { + self.extra_field.push(ExtraField::Zip64(Zip64ExtraField { + original_size: Some(uncompressed_size), + compressed_size: Some(self.compressed_size as u64), + disk_number: None, + offset_header: None, + })); + self.uncompressed_size = u32::MAX; + self.compressed_size = u32::MAX; + } else { + self.uncompressed_size = uncompressed_size as u32; + } + } + pub fn get_compressed_size(&self) -> u64 { if self.compressed_size != u32::MAX { self.compressed_size as u64 @@ -231,6 +256,31 @@ impl LocalFileHeader { self.compressed_size as u64 } } + + pub fn set_compressed_size(&mut self, compressed_size: u64) { + if let Some(ExtraField::Zip64(Zip64ExtraField { + compressed_size: Some(compressed_size_), + .. + })) = self + .extra_field + .iter_mut() + .find(|f| matches!(f, ExtraField::Zip64(_))) + { + *compressed_size_ = compressed_size; + self.compressed_size = u32::MAX; + } else if compressed_size > u32::MAX as u64 { + self.extra_field.push(ExtraField::Zip64(Zip64ExtraField { + original_size: Some(self.uncompressed_size as u64), + compressed_size: Some(compressed_size), + disk_number: None, + offset_header: None, + })); + self.uncompressed_size = u32::MAX; + self.compressed_size = u32::MAX; + } else { + self.compressed_size = compressed_size as u32; + } + } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/apk_frauder/src/main.rs b/apk_frauder/src/main.rs index 6c736c5..f308536 100644 --- a/apk_frauder/src/main.rs +++ b/apk_frauder/src/main.rs @@ -3,10 +3,10 @@ use apk_frauder::ZipFileWriter; use std::fs::File; 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()); @@ -34,9 +34,10 @@ fn main() { println!("Not signed whith scheme >= v2"); } zip_file.check_holes(); - println!("{:#?}", zip_file.get_file_info("classes.dex")); */ + 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); @@ -46,5 +47,6 @@ fn main() { out_file.insert_file_from_zip(f, &mut zip_file); } out_file.write_central_directory(); + */ //println!("{:#?}", zip_file.zip64_end_of_central_directory); } diff --git a/apk_frauder/src/zip_writer.rs b/apk_frauder/src/zip_writer.rs index 7d045c7..5b3205b 100644 --- a/apk_frauder/src/zip_writer.rs +++ b/apk_frauder/src/zip_writer.rs @@ -1,10 +1,14 @@ -use std::io::{Read, Seek, SeekFrom, Write}; +use std::io; +use std::io::{Cursor, Read, Seek, SeekFrom, Write}; +use crate::compression::CompressionMethod; use crate::end_of_central_directory::{ EndCentralDirectory, Zip64EndCentralDirectory, Zip64EndCentralDirectoryLocator, }; -use crate::{FileInfo, ZipFileReader}; +use crate::{general_purpose_flags, FileHeader, FileInfo, LocalFileHeader, ZipFileReader}; use androscalpel_serializer::Serializable; +use flate2::write::DeflateEncoder; +use flate2::{Compression, CrcWriter}; pub struct ZipFileWriter { files: Vec, @@ -25,7 +29,7 @@ impl ZipFileWriter { } } - /// Insert an file from an existing zip + /// Insert a file from an existing zip pub fn insert_file_from_zip( &mut self, mut file_info: FileInfo, @@ -50,6 +54,146 @@ impl ZipFileWriter { self.files.push(file_info); } + /// Insert a file + /// `header` provide for the values that are not computed from the file. + /// `local_header` can be provided is information in the local header differ + /// from the central directory header. + pub fn insert_file( + &mut self, + file: &mut U, + mut header: FileHeader, + local_header: 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_STRONG_ENCRYPTION == 0); + assert!( + header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR == 0 + ); + assert!( + header.general_purpose_flags + & (general_purpose_flags::MASK_COMPRESS_OPTION_1 + | general_purpose_flags::MASK_COMPRESS_OPTION_2 + | general_purpose_flags::MASK_UTF8_FILENAME) + == 0 + ); + assert!( + header.compression_method == CompressionMethod::Deflated + || header.compression_method == CompressionMethod::Stored + ); + 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_STRONG_ENCRYPTION == 0 + ); + assert!( + header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR + == 0 + ); + assert!( + header.general_purpose_flags + & (general_purpose_flags::MASK_COMPRESS_OPTION_1 + | general_purpose_flags::MASK_COMPRESS_OPTION_2 + | general_purpose_flags::MASK_UTF8_FILENAME) + == 0 + ); + assert!( + header.compression_method == CompressionMethod::Deflated + || header.compression_method == CompressionMethod::Stored + ); + 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 + }, + compression_method: header.compression_method, + last_mod_file_time: header.last_mod_file_time, + last_mod_file_data: header.last_mod_file_data, + crc_32: 0, + compressed_size: 0, + uncompressed_size: 0, + file_name: header.file_name, + extra_field: header.extra_field, + malformed_extra_field: header.malformed_extra_field.clone(), + decryption_header: None, + } + } else { + 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 + }, + compression_method: header.compression_method, + last_mod_file_time: header.last_mod_file_time, + last_mod_file_data: header.last_mod_file_data, + crc_32: 0, + compressed_size: 0, + uncompressed_size: 0, + file_name: header.file_name.clone(), + extra_field: header.extra_field.clone(), + malformed_extra_field: header.malformed_extra_field.clone(), + decryption_header: None, + } + }; + + let pos = file.stream_position().unwrap(); + let pos_end = file.seek(SeekFrom::End(0)).unwrap(); + file.seek(SeekFrom::Start(pos)).unwrap(); + let uncompressed_size = pos_end - pos; + let header_offset = self.current_offset; + local_header.set_uncompressed_size(uncompressed_size); + + let data = match local_header.compression_method { + CompressionMethod::Stored => { + let mut crc_writer = CrcWriter::new(Vec::new()); + io::copy(file, &mut crc_writer).unwrap(); + local_header.crc_32 = crc_writer.crc().sum(); + crc_writer.into_inner() + } + CompressionMethod::Deflated => { + // TODO: find a way to do this in place, the compressed data can be large, and storing it + // in memory is bad, but Deflate consume the Reader, so we cannot juste give it self.data + // TODO: Compression::default -> nop, use flags + let mut compressor = + DeflateEncoder::new(CrcWriter::new(Vec::new()), Compression::default()); + io::copy(file, &mut compressor).unwrap(); + let finished = compressor.finish().unwrap(); + local_header.crc_32 = finished.crc().sum(); + finished.into_inner() + } + _ => unimplemented!(), + }; + local_header.set_compressed_size(data.len() as u64); + local_header.serialize(&mut self.data).unwrap(); + self.current_offset += local_header.size() as u64; + io::copy(&mut Cursor::new(data), &mut self.data).unwrap(); + self.current_offset += local_header.get_compressed_size(); + + header.crc_32 = local_header.crc_32; + header.set_compressed_size(local_header.get_compressed_size()); + header.set_uncompressed_size(local_header.get_uncompressed_size()); + header.set_offset_local_header(header_offset); + + let file_info = FileInfo { + local_header, + header, + }; + self.files.push(file_info); + } + /// Finish the zip file by writing the central directory. pub fn write_central_directory(&mut self) { for file_info in &self.files {