diff --git a/apk_frauder/src/apk_signing_block.rs b/apk_frauder/src/apk_signing_block.rs index 0d9a4b4..f1d3e54 100644 --- a/apk_frauder/src/apk_signing_block.rs +++ b/apk_frauder/src/apk_signing_block.rs @@ -51,6 +51,6 @@ impl Serializable for ApkSigningBlock { } fn size(&self) -> usize { - self.data.len() + 16 + 4 + 4 + self.data.len() + 16 + 8 + 8 } } diff --git a/apk_frauder/src/file_header.rs b/apk_frauder/src/file_header.rs index 7e96a9c..d7460ca 100644 --- a/apk_frauder/src/file_header.rs +++ b/apk_frauder/src/file_header.rs @@ -1,7 +1,7 @@ use std::io::{SeekFrom, Write}; use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField}; -use crate::{cp437, Encoding, Signature}; +use crate::{cp437, general_purpose_flags, Encoding, Signature}; use androscalpel_serializer::{ReadSeek, Result, Serializable}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -9,7 +9,7 @@ pub struct FileHeader { // signature: Signature(0x02014b50) pub version_made_by: u16, pub version_needed_to_extract: u16, - pub general_purpose_flag: u16, + pub general_purpose_flags: u16, pub compression_method: u16, pub last_mod_file_time: u16, pub last_mod_file_data: u16, @@ -35,7 +35,7 @@ impl Serializable for FileHeader { Self::SIGNATURE.serialize(output)?; self.version_made_by.serialize(output)?; self.version_needed_to_extract.serialize(output)?; - self.general_purpose_flag.serialize(output)?; + self.general_purpose_flags.serialize(output)?; self.compression_method.serialize(output)?; self.last_mod_file_time.serialize(output)?; self.last_mod_file_data.serialize(output)?; @@ -69,7 +69,7 @@ impl Serializable for FileHeader { assert_eq!(signature, Self::SIGNATURE); // TODO let version_made_by = u16::deserialize(input)?; let version_needed_to_extract = u16::deserialize(input)?; - let general_purpose_flag = u16::deserialize(input)?; + let general_purpose_flags = u16::deserialize(input)?; let compression_method = u16::deserialize(input)?; let last_mod_file_time = u16::deserialize(input)?; let last_mod_file_data = u16::deserialize(input)?; @@ -90,7 +90,7 @@ impl Serializable for FileHeader { let mut header = Self { version_made_by, version_needed_to_extract, - general_purpose_flag, + general_purpose_flags, compression_method, last_mod_file_time, last_mod_file_data, @@ -181,10 +181,8 @@ impl FileHeader { const SIGNATURE: Signature = Signature(0x02014b50); const MIN_SIZE: usize = 4 + 6 * 2 + 4 * 3 + 5 * 2 + 4 * 2; - const MASK_UTF8_FILENAME: u16 = 1 << 11; - pub fn get_name_encoding(&self) -> Encoding { - if self.general_purpose_flag & Self::MASK_UTF8_FILENAME != 0 { + if self.general_purpose_flags & general_purpose_flags::MASK_UTF8_FILENAME != 0 { Encoding::UTF8 } else { Encoding::CP437 diff --git a/apk_frauder/src/lib.rs b/apk_frauder/src/lib.rs index 1674b82..3ccceed 100644 --- a/apk_frauder/src/lib.rs +++ b/apk_frauder/src/lib.rs @@ -12,6 +12,7 @@ pub mod local_file_header; use apk_signing_block::*; use end_of_central_directory::*; use file_header::FileHeader; +use local_file_header::LocalFileHeader; #[derive(Debug, Clone, PartialEq, Eq, Serializable, Default)] pub struct Signature(pub u32); @@ -21,10 +22,39 @@ pub enum Encoding { UTF8, } +pub mod general_purpose_flags { + pub const MASK_ENCRYPTED: u16 = 1 << 0; + pub const MASK_COMPRESS_OPTION_1: u16 = 1 << 1; + pub const MASK_COMPRESS_OPTION_2: u16 = 1 << 2; + pub const MASK_USE_DATA_DESCRIPTOR: u16 = 1 << 3; + pub const MASK_STRONG_ENCRYPTION: u16 = 1 << 6; + pub const MASK_UTF8_FILENAME: u16 = 1 << 11; + pub const MASK_ENCRYPTED_CENTRAL_DIR: u16 = 1 << 13; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct File { + pub local_header: LocalFileHeader, + pub header: FileHeader, +} +// TODO: support data descriptor (MASK_USE_DATA_DESCRIPTOR) + +impl File { + pub fn get_name(&self) -> String { + self.header.get_name() + } + pub fn get_offset_local_header(&self) -> u64 { + self.header.get_offset_local_header() + } + pub fn get_compressed_size(&self) -> u64 { + self.header.get_compressed_size() + } +} + pub struct ZipFile { pub end_of_central_directory: EndCentralDirectory, pub zip64_end_of_central_directory: Option, - pub files: Vec, + pub files: Vec, pub apk_sign_block: Option, pub data: T, } @@ -37,7 +67,6 @@ impl ZipFile { .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, @@ -50,13 +79,10 @@ impl ZipFile { 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 + Zip64EndCentralDirectory::deserialize(&mut reader).ok() } else { None }; @@ -73,39 +99,51 @@ impl ZipFile { }; zip_file .data - .seek(SeekFrom::Start(zip_file.get_ed_offset())) + .seek(SeekFrom::Start(zip_file.get_cd_offset())) .unwrap(); let mut size_read = 0; - let cd_size = zip_file.get_ed_size(); + let cd_size = zip_file.get_cd_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 { + let header = FileHeader::deserialize(&mut zip_file.data).unwrap(); + size_read += header.size() as u64; + let pos_in_dir = zip_file.data.stream_position().unwrap(); + if header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR != 0 + { + panic!("Central directory encryption not supported"); + } zip_file .data - .seek(SeekFrom::Start(zip_file.get_ed_offset() - 16)) + .seek(SeekFrom::Start(header.get_offset_local_header())) + .unwrap(); + let local_header = LocalFileHeader::deserialize(&mut zip_file.data).unwrap(); + zip_file.data.seek(SeekFrom::Start(pos_in_dir)).unwrap(); + zip_file.files.push(File { + local_header, + header, + }); + } + assert_eq!(size_read, cd_size); + if zip_file.get_cd_offset() > 16 { + zip_file + .data + .seek(SeekFrom::Start(zip_file.get_cd_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)) + .seek(SeekFrom::Start(zip_file.get_cd_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)) + .seek(SeekFrom::Start(zip_file.get_cd_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 } @@ -149,7 +187,7 @@ impl ZipFile { } } - pub fn get_ed_size(&self) -> u64 { + pub fn get_cd_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 { @@ -157,7 +195,7 @@ impl ZipFile { } } - pub fn get_ed_offset(&self) -> u64 { + pub fn get_cd_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 { @@ -197,7 +235,7 @@ impl ZipFile { /// META-INF/*.DSA /// META-INF/*.RSA /// META-INF/SIG-* - pub fn get_jar_sig_files(&self) -> Vec<&FileHeader> { + pub fn get_jar_sig_files(&self) -> Vec<&File> { self.files .iter() .filter(|file| { @@ -229,4 +267,49 @@ impl ZipFile { pub fn is_signed_v2(&self) -> bool { self.apk_sign_block.is_some() } + + pub fn check_holes(&self) { + let mut files: Vec<&File> = self.files.iter().collect(); + files.sort_by_key(|f| f.get_offset_local_header()); + let mut lst_offset = 0; + for file in files.iter() { + if file.get_offset_local_header() != lst_offset { + println!( + "Hole before {} between 0x{:x} and 0x{:x}", + file.get_name(), + lst_offset, + file.get_offset_local_header() + ); + } + lst_offset += file.local_header.size() as u64; + lst_offset += file.get_compressed_size(); + } + if let Some(apk_sign_block) = &self.apk_sign_block { + let apk_sb_off = self.get_cd_offset() - apk_sign_block.size() as u64; + if apk_sb_off != lst_offset { + println!( + "Hole before apk signing block, between 0x{:x} and 0x{:x}", + lst_offset, apk_sb_off + ); + } + + lst_offset += apk_sign_block.size() as u64; + } + if self.get_cd_offset() != lst_offset { + println!( + "Hole before central directory between 0x{:x} and 0x{:x}", + lst_offset, + self.get_cd_offset() + ); + } + } + + pub fn get_bin(&mut self, offset: u64, size: usize) -> Vec { + self.data.seek(SeekFrom::Start(offset)).unwrap(); + let mut data = vec![]; + for _ in 0..size { + data.push(u8::deserialize(&mut self.data).unwrap()); + } + data + } } diff --git a/apk_frauder/src/local_file_header.rs b/apk_frauder/src/local_file_header.rs index fc16353..de54f43 100644 --- a/apk_frauder/src/local_file_header.rs +++ b/apk_frauder/src/local_file_header.rs @@ -1,14 +1,14 @@ use std::io::{SeekFrom, Write}; use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField}; -use crate::{cp437, Encoding, Signature}; +use crate::{cp437, general_purpose_flags, Encoding, Signature}; use androscalpel_serializer::{ReadSeek, Result, Serializable}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct LocalFileHeader { // signature: Signature(0x04034b50) pub version_needed_to_extract: u16, - pub general_purpose_flag: u16, + pub general_purpose_flags: u16, pub compression_method: u16, pub last_mod_file_time: u16, pub last_mod_file_data: u16, @@ -21,13 +21,14 @@ pub struct LocalFileHeader { pub extra_field: Vec, /// Remaining bytes in the extra_fields that could not be parsed as ExtraField pub malformed_extra_field: Vec, + pub decryption_header: Option, } impl Serializable for LocalFileHeader { fn serialize(&self, output: &mut dyn Write) -> Result<()> { Self::SIGNATURE.serialize(output)?; self.version_needed_to_extract.serialize(output)?; - self.general_purpose_flag.serialize(output)?; + self.general_purpose_flags.serialize(output)?; self.compression_method.serialize(output)?; self.last_mod_file_time.serialize(output)?; self.last_mod_file_data.serialize(output)?; @@ -45,15 +46,17 @@ impl Serializable for LocalFileHeader { for c in &self.malformed_extra_field { c.serialize(output)?; } + if let Some(header) = &self.decryption_header { + header.serialize(output)?; + } Ok(()) } fn deserialize(input: &mut dyn ReadSeek) -> Result { let signature = Signature::deserialize(input)?; assert_eq!(signature, Self::SIGNATURE); // TODO - let version_made_by = u16::deserialize(input)?; let version_needed_to_extract = u16::deserialize(input)?; - let general_purpose_flag = u16::deserialize(input)?; + let general_purpose_flags = u16::deserialize(input)?; let compression_method = u16::deserialize(input)?; let last_mod_file_time = u16::deserialize(input)?; let last_mod_file_data = u16::deserialize(input)?; @@ -62,18 +65,13 @@ impl Serializable for LocalFileHeader { let uncompressed_size = u32::deserialize(input)?; let file_name_length = u16::deserialize(input)?; let extra_field_length = u16::deserialize(input)?; - let file_comment_length = u16::deserialize(input)?; - let disk_number_start = u16::deserialize(input)?; - let internal_file_attributes = u16::deserialize(input)?; - let external_file_attributes = u32::deserialize(input)?; - let offset_local_header = u32::deserialize(input)?; let mut file_name = vec![]; for _ in 0..file_name_length { file_name.push(u8::deserialize(input)?); } let mut header = Self { version_needed_to_extract, - general_purpose_flag, + general_purpose_flags, compression_method, last_mod_file_time, last_mod_file_data, @@ -83,6 +81,7 @@ impl Serializable for LocalFileHeader { file_name, extra_field: vec![], malformed_extra_field: vec![], + decryption_header: None, }; //let end_of_extra_field = input.stream_position().unwrap() + extra_field_length as u64; let extra_field_off = input.stream_position().unwrap(); @@ -123,8 +122,8 @@ impl Serializable for LocalFileHeader { { let original_size = uncompressed_size == u32::MAX; let compressed_size = compressed_size == u32::MAX; - let offset_header = offset_local_header == u32::MAX; - let disk_number = disk_number_start == u16::MAX; + let offset_header = false; + let disk_number = false; let zip64_filed = Zip64ExtraField::from_generic( &GenericExtraField { id: Zip64ExtraField::ID, @@ -139,6 +138,12 @@ impl Serializable for LocalFileHeader { *field = ExtraField::Zip64(zip64_filed); } } + if (header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED != 0) + && (header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION != 0) + { + header.decryption_header = Some(DecryptionHeader::deserialize(input)?); + } + Ok(header) } @@ -147,6 +152,11 @@ impl Serializable for LocalFileHeader { + self.file_name.len() + self.extra_field.iter().map(|f| f.size()).sum::() + self.malformed_extra_field.len() + + self + .decryption_header + .as_ref() + .map(|h| h.size()) + .unwrap_or(0) } } @@ -157,7 +167,7 @@ impl LocalFileHeader { const MASK_UTF8_FILENAME: u16 = 1 << 11; pub fn get_name_encoding(&self) -> Encoding { - if self.general_purpose_flag & Self::MASK_UTF8_FILENAME != 0 { + if self.general_purpose_flags & Self::MASK_UTF8_FILENAME != 0 { Encoding::UTF8 } else { Encoding::CP437 @@ -205,3 +215,47 @@ impl LocalFileHeader { } } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DecryptionHeader { + // pub ivsize: u16, + pub iv_data: Vec, + // pub remaining_size: u32, + // TODO: parse, not needed for now + pub other_data: Vec, +} + +impl Serializable for DecryptionHeader { + fn serialize(&self, output: &mut dyn Write) -> Result<()> { + (self.iv_data.len() as u16).serialize(output)?; + for c in &self.iv_data { + c.serialize(output)?; + } + (self.other_data.len() as u32).serialize(output)?; + for c in &self.other_data { + c.serialize(output)?; + } + Ok(()) + } + + fn deserialize(input: &mut dyn ReadSeek) -> Result { + let iv_size = u16::deserialize(input)?; + let mut iv_data = vec![]; + for _ in 0..iv_size { + iv_data.push(u8::deserialize(input)?); + } + let remaining_size = u32::deserialize(input)?; + let mut other_data = vec![]; + for _ in 0..remaining_size { + other_data.push(u8::deserialize(input)?); + } + Ok(Self { + iv_data, + other_data, + }) + } + + fn size(&self) -> usize { + 2 + self.iv_data.len() + 4 + self.other_data.len() + } +} diff --git a/apk_frauder/src/main.rs b/apk_frauder/src/main.rs index 56b3fd7..6d1b6bd 100644 --- a/apk_frauder/src/main.rs +++ b/apk_frauder/src/main.rs @@ -6,10 +6,16 @@ fn main() { //let file = File::open("tst_64.zip").expect("failed to open file"); let zip_file = ZipFile::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!(); + } /*println!( "uncompressed size: {}", zip_file.files[0].get_uncompressed_size() - );*/ + ); println!( "{}", zip_file @@ -18,10 +24,11 @@ fn main() { .map(|f| f.get_name()) .collect::>() .join("\n") - ); + );*/ if zip_file.is_signed_v2() { println!("Signed >= v2"); } else { println!("Not signed whith scheme >= v2"); } + zip_file.check_holes(); }