use std::io::{Read, Seek, SeekFrom}; use androscalpel_serializer::Serializable; pub mod apk_signing_block; mod cp437; pub mod end_of_central_directory; pub mod extra_fields; pub mod file_header; use apk_signing_block::*; use end_of_central_directory::*; use file_header::FileHeader; #[derive(Debug, Clone, PartialEq, Eq, Serializable, Default)] pub struct Signature(pub u32); pub struct ZipFile { pub end_of_central_directory: EndCentralDirectory, pub zip64_end_of_central_directory: Option, pub files: Vec, pub apk_sign_block: Option, pub data: T, } impl ZipFile { pub fn new(mut reader: T) -> Self { let end_of_central_directory_off = Self::get_end_of_central_directory_offset(&mut reader).unwrap(); reader .seek(SeekFrom::Start(end_of_central_directory_off)) .unwrap(); let end_of_central_directory = EndCentralDirectory::deserialize(&mut reader).unwrap(); println!("{end_of_central_directory:#?}"); reader .seek(SeekFrom::Start( end_of_central_directory_off - Zip64EndCentralDirectoryLocator::SIZE as u64, )) .unwrap(); let zip64_ecd_locator = Zip64EndCentralDirectoryLocator::deserialize(&mut reader).ok(); let zip64_end_of_central_directory = if let Some(zip64_ecd_locator) = zip64_ecd_locator { assert_eq!( zip64_ecd_locator.disk_number_of_zip64_end_central_directory_start, 0 ); assert!(zip64_ecd_locator.total_number_of_disks <= 1); println!("Zip64 ECD Locator {:#?}", zip64_ecd_locator); let zip64_edc_record_off = zip64_ecd_locator.offset_zip64_end_of_central_directory_record; reader.seek(SeekFrom::Start(zip64_edc_record_off)).unwrap(); let zip64_edc_reccord = Zip64EndCentralDirectory::deserialize(&mut reader).ok(); println!("{zip64_edc_reccord:#?}"); zip64_edc_reccord } else { None }; // At this point python's ziplib recompute the location of the central directory from the // location of the end of central directory in case the zip was concanated after a file. // We probably don't need that for now. let mut zip_file = Self { end_of_central_directory, zip64_end_of_central_directory, data: reader, files: vec![], apk_sign_block: None, }; zip_file .data .seek(SeekFrom::Start(zip_file.get_ed_offset())) .unwrap(); let mut size_read = 0; let cd_size = zip_file.get_ed_size(); while size_read < cd_size { let file_header = FileHeader::deserialize(&mut zip_file.data).unwrap(); println!("{file_header:#?}"); size_read += file_header.size() as u64; zip_file.files.push(file_header); } assert_eq!(size_read, cd_size); if zip_file.get_ed_offset() > 16 { zip_file .data .seek(SeekFrom::Start(zip_file.get_ed_offset() - 16)) .unwrap(); let magic = Magic::deserialize(&mut zip_file.data).unwrap(); if magic == ApkSigningBlock::MAGIC { zip_file .data .seek(SeekFrom::Start(zip_file.get_ed_offset() - 16 - 8)) .unwrap(); let block_size = u64::deserialize(&mut zip_file.data).unwrap(); zip_file .data .seek(SeekFrom::Start(zip_file.get_ed_offset() - block_size - 8)) .unwrap(); zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok(); } } //println!("{:?}", zip_file.apk_sign_block); zip_file } pub fn is_zip64(&self) -> bool { self.zip64_end_of_central_directory.is_some() } pub fn get_disk_num(&self) -> u32 { if let Some(zip64_end_of_central_directory) = &self.zip64_end_of_central_directory { zip64_end_of_central_directory.number_of_this_disk } else { self.end_of_central_directory.disk_number as u32 } } pub fn get_disk_ed_start(&self) -> u32 { if let Some(zip64_end_of_central_directory) = &self.zip64_end_of_central_directory { zip64_end_of_central_directory.disk_number_of_central_directory_start } else { self.end_of_central_directory .disk_number_of_central_directory_start as u32 } } pub fn get_number_entries_on_disk(&self) -> u64 { if let Some(zip64_end_of_central_directory) = &self.zip64_end_of_central_directory { zip64_end_of_central_directory.number_entry_in_central_directory_on_this_disk } else { self.end_of_central_directory .number_of_entries_in_central_directory_on_disk as u64 } } pub fn get_number_entries(&self) -> u64 { if let Some(zip64_end_of_central_directory) = &self.zip64_end_of_central_directory { zip64_end_of_central_directory.number_entry_in_central_directory } else { self.end_of_central_directory .number_of_entries_in_central_directory as u64 } } pub fn get_ed_size(&self) -> u64 { if let Some(zip64_end_of_central_directory) = &self.zip64_end_of_central_directory { zip64_end_of_central_directory.size_of_central_directory } else { self.end_of_central_directory.size_central_directory as u64 } } pub fn get_ed_offset(&self) -> u64 { if let Some(zip64_end_of_central_directory) = &self.zip64_end_of_central_directory { zip64_end_of_central_directory.offset_central_directory } else { self.end_of_central_directory.offset_central_directory as u64 } } pub fn get_end_of_central_directory_offset(reader: &mut T) -> Option { let file_size = reader.seek(SeekFrom::End(0)).unwrap(); let mut sig = Signature::default(); let mut comment_size = 0; while sig != EndCentralDirectory::SIGNATURE { reader .seek(SeekFrom::End( -(EndCentralDirectory::MIN_SIZE as i64) - comment_size, )) .unwrap(); sig = Signature::deserialize(reader).unwrap(); comment_size += 1; if comment_size > 65536 || comment_size as usize + EndCentralDirectory::MIN_SIZE > file_size as usize { return None; } } comment_size -= 1; Some(file_size - comment_size as u64 - EndCentralDirectory::MIN_SIZE as u64) } pub fn get_file_names(&self) -> Vec { self.files.iter().map(|f| f.get_name()).collect() } /// Test if the zipfile contains files used to signe jar file (apk signature v1): /// META-INF/MANIFEST.MF /// META-INF/*.SF /// META-INF/*.DSA /// META-INF/*.RSA /// META-INF/SIG-* pub fn get_jar_sig_files(&self) -> Vec<&FileHeader> { 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-") }) .collect() } /// Test if the zipfile contains files used to signe jar file (apk signature v1): /// META-INF/MANIFEST.MF /// META-INF/*.SF /// META-INF/*.DSA /// META-INF/*.RSA /// META-INF/SIG-* /// /// TODO: there is a field `X-Android-APK-Signed` in .SF that indicate the use of v2 and v3 /// (and v4?) signature. pub fn is_signed_v1(&self) -> bool { !self.get_jar_sig_files().is_empty() } /// Test if the zipfile as apk signature block. pub fn is_signed_v2(&self) -> bool { self.apk_sign_block.is_some() } }