From 0794aac01624c9b42b54d609b35db02fb3caf54b Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Mon, 15 Jan 2024 15:52:47 +0100 Subject: [PATCH] add struct for localheader --- apk_frauder/src/file_header.rs | 7 +- apk_frauder/src/lib.rs | 6 + apk_frauder/src/local_file_header.rs | 207 +++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 apk_frauder/src/local_file_header.rs diff --git a/apk_frauder/src/file_header.rs b/apk_frauder/src/file_header.rs index 144a524..7e96a9c 100644 --- a/apk_frauder/src/file_header.rs +++ b/apk_frauder/src/file_header.rs @@ -1,14 +1,9 @@ use std::io::{SeekFrom, Write}; use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField}; -use crate::{cp437, Signature}; +use crate::{cp437, Encoding, Signature}; use androscalpel_serializer::{ReadSeek, Result, Serializable}; -pub enum Encoding { - CP437, - UTF8, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct FileHeader { // signature: Signature(0x02014b50) diff --git a/apk_frauder/src/lib.rs b/apk_frauder/src/lib.rs index 0d11d58..1674b82 100644 --- a/apk_frauder/src/lib.rs +++ b/apk_frauder/src/lib.rs @@ -7,6 +7,7 @@ mod cp437; pub mod end_of_central_directory; pub mod extra_fields; pub mod file_header; +pub mod local_file_header; use apk_signing_block::*; use end_of_central_directory::*; @@ -15,6 +16,11 @@ use file_header::FileHeader; #[derive(Debug, Clone, PartialEq, Eq, Serializable, Default)] pub struct Signature(pub u32); +pub enum Encoding { + CP437, + UTF8, +} + pub struct ZipFile { pub end_of_central_directory: EndCentralDirectory, pub zip64_end_of_central_directory: Option, diff --git a/apk_frauder/src/local_file_header.rs b/apk_frauder/src/local_file_header.rs new file mode 100644 index 0000000..fc16353 --- /dev/null +++ b/apk_frauder/src/local_file_header.rs @@ -0,0 +1,207 @@ +use std::io::{SeekFrom, Write}; + +use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField}; +use crate::{cp437, 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 compression_method: u16, + pub last_mod_file_time: u16, + pub last_mod_file_data: u16, + pub crc_32: u32, + pub compressed_size: u32, + pub uncompressed_size: u32, + // file_name_length: u16, + // extra_field_length: u16, + pub file_name: Vec, + pub extra_field: Vec, + /// Remaining bytes in the extra_fields that could not be parsed as ExtraField + pub malformed_extra_field: Vec, +} + +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.compression_method.serialize(output)?; + self.last_mod_file_time.serialize(output)?; + self.last_mod_file_data.serialize(output)?; + self.crc_32.serialize(output)?; + self.compressed_size.serialize(output)?; + self.uncompressed_size.serialize(output)?; + (self.file_name.len() as u16).serialize(output)?; + (self.extra_field.len() as u16).serialize(output)?; + for c in &self.file_name { + c.serialize(output)?; + } + for c in &self.extra_field { + c.serialize(output)?; + } + for c in &self.malformed_extra_field { + c.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 compression_method = u16::deserialize(input)?; + let last_mod_file_time = u16::deserialize(input)?; + let last_mod_file_data = u16::deserialize(input)?; + let crc_32 = u32::deserialize(input)?; + let compressed_size = u32::deserialize(input)?; + 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, + compression_method, + last_mod_file_time, + last_mod_file_data, + crc_32, + compressed_size, + uncompressed_size, + file_name, + extra_field: vec![], + malformed_extra_field: vec![], + }; + //let end_of_extra_field = input.stream_position().unwrap() + extra_field_length as u64; + let extra_field_off = input.stream_position().unwrap(); + let mut extra_size_read = 0; + while extra_size_read < extra_field_length as usize { + let field_off = input.stream_position().unwrap(); + let field = ExtraField::deserialize(input); + + if let Err(err) = field { + println!( + "Failed to parsed extra field in {}: {err:?}", + header.get_name() + ); + input.seek(SeekFrom::Start(field_off)).unwrap(); + break; + } else { + let field = field.unwrap(); + extra_size_read += field.size(); + header.extra_field.push(field); + } + } + if extra_size_read > extra_field_length as usize { + println!("Failed to parsed last extra field in {}", header.get_name()); + let size = header.extra_field.pop().unwrap().size(); + input.seek(SeekFrom::Current(-(size as i64))).unwrap(); + } + let mut extra_size_read = input.stream_position().unwrap() - extra_field_off; + while extra_size_read < extra_field_length as u64 { + header.malformed_extra_field.push(u8::deserialize(input)?); + extra_size_read += 1; + } + //input.seek(SeekFrom::Start(end_of_extra_field)).unwrap(); + for field in &mut header.extra_field { + if let ExtraField::Generic(GenericExtraField { + id: Zip64ExtraField::ID, + data, + }) = field + { + 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 zip64_filed = Zip64ExtraField::from_generic( + &GenericExtraField { + id: Zip64ExtraField::ID, + data: data.clone(), + }, + original_size, + compressed_size, + offset_header, + disk_number, + ) + .unwrap(); + *field = ExtraField::Zip64(zip64_filed); + } + } + Ok(header) + } + + fn size(&self) -> usize { + Self::MIN_SIZE + + self.file_name.len() + + self.extra_field.iter().map(|f| f.size()).sum::() + + self.malformed_extra_field.len() + } +} + +impl LocalFileHeader { + const SIGNATURE: Signature = Signature(0x04034b50); + const MIN_SIZE: usize = 4 + 5 * 2 + 3 * 4 + 2 * 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 { + Encoding::UTF8 + } else { + Encoding::CP437 + } + } + + pub fn get_name(&self) -> String { + match self.get_name_encoding() { + Encoding::UTF8 => std::str::from_utf8(&self.file_name).unwrap().into(), + Encoding::CP437 => cp437::cp437_to_string(&self.file_name), + } + } + + pub fn get_uncompressed_size(&self) -> u64 { + if self.uncompressed_size != u32::MAX { + self.uncompressed_size as u64 + } else if let Some(ExtraField::Zip64(Zip64ExtraField { + original_size: Some(original_size), + .. + })) = self + .extra_field + .iter() + .find(|f| matches!(f, ExtraField::Zip64(_))) + { + *original_size + } else { + self.uncompressed_size as u64 + } + } + + pub fn get_compressed_size(&self) -> u64 { + if self.compressed_size != u32::MAX { + self.compressed_size as u64 + } else if let Some(ExtraField::Zip64(Zip64ExtraField { + compressed_size: Some(compressed_size), + .. + })) = self + .extra_field + .iter() + .find(|f| matches!(f, ExtraField::Zip64(_))) + { + *compressed_size + } else { + self.compressed_size as u64 + } + } +}