transfert file from zip to another (wip)

This commit is contained in:
Jean-Marie Mineau 2024-01-17 13:57:01 +01:00
parent 0d305fbe62
commit 0fdf619360
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
10 changed files with 512 additions and 273 deletions

20
Cargo.lock generated
View file

@ -62,6 +62,7 @@ name = "apk_frauder"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"androscalpel_serializer", "androscalpel_serializer",
"flate2",
] ]
[[package]] [[package]]
@ -130,6 +131,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -150,6 +160,16 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"

View file

@ -7,3 +7,4 @@ edition = "2021"
[dependencies] [dependencies]
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" } androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
flate2 = { version = "1.0.28", features = ["rust_backend"] }

View file

@ -1 +0,0 @@
flate2/rust_backend

View file

@ -0,0 +1,64 @@
use androscalpel_serializer::Serializable;
#[derive(Serializable, Clone, Copy, PartialEq, Eq, Debug)]
#[prefix_type(u16)]
pub enum CompressionMethod {
#[prefix(0)]
Stored,
#[prefix(1)]
Shrunk,
#[prefix(2)]
ReducedF1,
#[prefix(3)]
ReducedF2,
#[prefix(4)]
ReducedF3,
#[prefix(5)]
ReducedF4,
#[prefix(6)]
Imploded,
#[prefix(7)]
ReservedTokenizing,
#[prefix(8)]
Deflated,
#[prefix(9)]
Deflate64,
#[prefix(10)]
PKWAREImploding,
#[prefix(11)]
Reserved1,
#[prefix(12)]
BZIP2,
#[prefix(13)]
Reserved2,
#[prefix(14)]
LZMA,
#[prefix(15)]
Reserved3,
#[prefix(16)]
CMPSC,
#[prefix(17)]
Reserved4,
#[prefix(18)]
TERSE,
#[prefix(19)]
LZ77,
#[prefix(20)]
Zstandard93,
#[prefix(93)]
Zstandard,
#[prefix(94)]
MP3,
#[prefix(95)]
XZ,
#[prefix(96)]
JPEG,
#[prefix(97)]
WavPack,
#[prefix(98)]
PPMdI1,
#[prefix(99)]
AEx,
#[default_variant]
Unknown(u16),
}

View file

@ -1,5 +1,6 @@
use std::io::{SeekFrom, Write}; use std::io::{SeekFrom, Write};
use crate::compression::CompressionMethod;
use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField}; use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField};
use crate::{cp437, general_purpose_flags, Encoding, Signature}; use crate::{cp437, general_purpose_flags, Encoding, Signature};
use androscalpel_serializer::{ReadSeek, Result, Serializable}; use androscalpel_serializer::{ReadSeek, Result, Serializable};
@ -10,7 +11,7 @@ pub struct FileHeader {
pub version_made_by: u16, pub version_made_by: u16,
pub version_needed_to_extract: u16, pub version_needed_to_extract: u16,
pub general_purpose_flags: u16, pub general_purpose_flags: u16,
pub compression_method: u16, pub compression_method: CompressionMethod,
pub last_mod_file_time: u16, pub last_mod_file_time: u16,
pub last_mod_file_data: u16, pub last_mod_file_data: u16,
pub crc_32: u32, pub crc_32: u32,
@ -70,7 +71,7 @@ impl Serializable for FileHeader {
let version_made_by = u16::deserialize(input)?; let version_made_by = u16::deserialize(input)?;
let version_needed_to_extract = u16::deserialize(input)?; let version_needed_to_extract = u16::deserialize(input)?;
let general_purpose_flags = u16::deserialize(input)?; let general_purpose_flags = u16::deserialize(input)?;
let compression_method = u16::deserialize(input)?; let compression_method = CompressionMethod::deserialize(input)?;
let last_mod_file_time = u16::deserialize(input)?; let last_mod_file_time = u16::deserialize(input)?;
let last_mod_file_data = u16::deserialize(input)?; let last_mod_file_data = u16::deserialize(input)?;
let crc_32 = u32::deserialize(input)?; let crc_32 = u32::deserialize(input)?;
@ -127,7 +128,7 @@ impl Serializable for FileHeader {
} }
} }
if extra_size_read > extra_field_length as usize { if extra_size_read > extra_field_length as usize {
println!("Failed to parsed last extra field in {}", header.get_name()); println!("Failed to parse last extra field in {}", header.get_name());
let size = header.extra_field.pop().unwrap().size(); let size = header.extra_field.pop().unwrap().size();
input.seek(SeekFrom::Current(-(size as i64))).unwrap(); input.seek(SeekFrom::Current(-(size as i64))).unwrap();
} }

View file

@ -1,18 +1,19 @@
use std::io::{Read, Seek, SeekFrom};
use androscalpel_serializer::Serializable; use androscalpel_serializer::Serializable;
pub mod apk_signing_block; pub mod apk_signing_block;
pub mod compression;
mod cp437; mod cp437;
pub mod end_of_central_directory; pub mod end_of_central_directory;
pub mod extra_fields; pub mod extra_fields;
pub mod file_header; pub mod file_header;
pub mod local_file_header; pub mod local_file_header;
pub mod zip_reader;
pub mod zip_writer;
use apk_signing_block::*; use extra_fields::{ExtraField, Zip64ExtraField};
use end_of_central_directory::*;
use file_header::FileHeader; use file_header::FileHeader;
use local_file_header::LocalFileHeader; use local_file_header::LocalFileHeader;
pub use zip_reader::ZipFileReader;
#[derive(Debug, Clone, PartialEq, Eq, Serializable, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serializable, Default)]
pub struct Signature(pub u32); pub struct Signature(pub u32);
@ -33,13 +34,13 @@ pub mod general_purpose_flags {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct File { pub struct FileInfo {
pub local_header: LocalFileHeader, pub local_header: LocalFileHeader,
pub header: FileHeader, pub header: FileHeader,
} }
// TODO: support data descriptor (MASK_USE_DATA_DESCRIPTOR) // TODO: support data descriptor (MASK_USE_DATA_DESCRIPTOR)
impl File { impl FileInfo {
pub fn get_name(&self) -> String { pub fn get_name(&self) -> String {
self.header.get_name() self.header.get_name()
} }
@ -49,267 +50,47 @@ impl File {
pub fn get_compressed_size(&self) -> u64 { pub fn get_compressed_size(&self) -> u64 {
self.header.get_compressed_size() self.header.get_compressed_size()
} }
} pub fn get_file_offset(&self) -> u64 {
self.get_offset_local_header() + self.local_header.size() as u64
pub struct ZipFile<T: Read + Seek> { }
pub end_of_central_directory: EndCentralDirectory, pub fn set_offset_local_header(&mut self, new_offset: u64) {
pub zip64_end_of_central_directory: Option<Zip64EndCentralDirectory>, let use_z64 = new_offset > u32::MAX as u64;
pub files: Vec<File>, for extra in &mut self.local_header.extra_field {
pub apk_sign_block: Option<ApkSigningBlock>, if let ExtraField::Zip64(extra) = extra {
pub data: T, if use_z64 {
} extra.offset_header = Some(new_offset);
impl<T: Read + Seek> ZipFile<T> {
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();
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);
let zip64_edc_record_off =
zip64_ecd_locator.offset_zip64_end_of_central_directory_record;
reader.seek(SeekFrom::Start(zip64_edc_record_off)).unwrap();
Zip64EndCentralDirectory::deserialize(&mut reader).ok()
} else { } else {
None extra.offset_header = 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_cd_offset()))
.unwrap();
let mut size_read = 0;
let cd_size = zip_file.get_cd_size();
while size_read < cd_size {
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(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_cd_offset() - 16 - 8))
.unwrap();
let block_size = u64::deserialize(&mut zip_file.data).unwrap();
zip_file
.data
.seek(SeekFrom::Start(zip_file.get_cd_offset() - block_size - 8))
.unwrap();
zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok();
} }
} }
zip_file
} }
let mut z64found = false;
pub fn is_zip64(&self) -> bool { for extra in &mut self.header.extra_field {
self.zip64_end_of_central_directory.is_some() if let ExtraField::Zip64(extra) = extra {
} if use_z64 {
extra.offset_header = Some(new_offset);
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 { } else {
self.end_of_central_directory.disk_number as u32 extra.offset_header = None;
}
z64found = true;
} }
} }
if use_z64 && !z64found {
pub fn get_disk_ed_start(&self) -> u32 { self.header
if let Some(zip64_end_of_central_directory) = &self.zip64_end_of_central_directory { .extra_field
zip64_end_of_central_directory.disk_number_of_central_directory_start .push(ExtraField::Zip64(Zip64ExtraField {
original_size: Some(self.header.uncompressed_size as u64),
compressed_size: Some(self.header.compressed_size as u64),
offset_header: Some(new_offset),
disk_number: None,
}));
self.header.uncompressed_size = u32::MAX;
self.header.compressed_size = u32::MAX;
}
if use_z64 {
self.header.offset_local_header = u32::MAX;
} else { } else {
self.end_of_central_directory self.header.offset_local_header = new_offset as u32;
.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_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 {
self.end_of_central_directory.size_central_directory as 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 {
self.end_of_central_directory.offset_central_directory as u64
}
}
pub fn get_end_of_central_directory_offset(reader: &mut T) -> Option<u64> {
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<String> {
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<&File> {
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()
}
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<u8> {
self.data.seek(SeekFrom::Start(offset)).unwrap();
let mut data = vec![];
for _ in 0..size {
data.push(u8::deserialize(&mut self.data).unwrap());
}
data
}
} }

View file

@ -1,5 +1,6 @@
use std::io::{SeekFrom, Write}; use std::io::{SeekFrom, Write};
use crate::compression::CompressionMethod;
use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField}; use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField};
use crate::{cp437, general_purpose_flags, Encoding, Signature}; use crate::{cp437, general_purpose_flags, Encoding, Signature};
use androscalpel_serializer::{ReadSeek, Result, Serializable}; use androscalpel_serializer::{ReadSeek, Result, Serializable};
@ -9,7 +10,7 @@ pub struct LocalFileHeader {
// signature: Signature(0x04034b50) // signature: Signature(0x04034b50)
pub version_needed_to_extract: u16, pub version_needed_to_extract: u16,
pub general_purpose_flags: u16, pub general_purpose_flags: u16,
pub compression_method: u16, pub compression_method: CompressionMethod,
pub last_mod_file_time: u16, pub last_mod_file_time: u16,
pub last_mod_file_data: u16, pub last_mod_file_data: u16,
pub crc_32: u32, pub crc_32: u32,
@ -57,7 +58,7 @@ impl Serializable for LocalFileHeader {
assert_eq!(signature, Self::SIGNATURE); // TODO assert_eq!(signature, Self::SIGNATURE); // TODO
let version_needed_to_extract = u16::deserialize(input)?; let version_needed_to_extract = u16::deserialize(input)?;
let general_purpose_flags = u16::deserialize(input)?; let general_purpose_flags = u16::deserialize(input)?;
let compression_method = u16::deserialize(input)?; let compression_method = CompressionMethod::deserialize(input)?;
let last_mod_file_time = u16::deserialize(input)?; let last_mod_file_time = u16::deserialize(input)?;
let last_mod_file_data = u16::deserialize(input)?; let last_mod_file_data = u16::deserialize(input)?;
let crc_32 = u32::deserialize(input)?; let crc_32 = u32::deserialize(input)?;
@ -103,8 +104,10 @@ impl Serializable for LocalFileHeader {
header.extra_field.push(field); header.extra_field.push(field);
} }
} }
let mut failed_last_extra_field = false;
if extra_size_read > extra_field_length as usize { if extra_size_read > extra_field_length as usize {
println!("Failed to parsed last extra field in {}", header.get_name()); //println!("Failed to parse last extra field in {}", header.get_name());
failed_last_extra_field = true;
let size = header.extra_field.pop().unwrap().size(); let size = header.extra_field.pop().unwrap().size();
input.seek(SeekFrom::Current(-(size as i64))).unwrap(); input.seek(SeekFrom::Current(-(size as i64))).unwrap();
} }
@ -113,6 +116,14 @@ impl Serializable for LocalFileHeader {
header.malformed_extra_field.push(u8::deserialize(input)?); header.malformed_extra_field.push(u8::deserialize(input)?);
extra_size_read += 1; extra_size_read += 1;
} }
// If it is not padding from zipalign
if failed_last_extra_field
&& header.malformed_extra_field != vec![0]
&& header.malformed_extra_field != vec![0, 0]
&& header.malformed_extra_field != vec![0, 0, 0]
{
println!("Failed to parse last extra field in {}", header.get_name());
}
//input.seek(SeekFrom::Start(end_of_extra_field)).unwrap(); //input.seek(SeekFrom::Start(end_of_extra_field)).unwrap();
for field in &mut header.extra_field { for field in &mut header.extra_field {
if let ExtraField::Generic(GenericExtraField { if let ExtraField::Generic(GenericExtraField {

View file

@ -1,18 +1,20 @@
use apk_frauder::ZipFile; use apk_frauder::ZipFileReader;
use std::fs::File; use std::fs::File;
fn main() { fn main() {
let file = File::open("app-release.apk").expect("failed to open file"); 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 file = File::open("tst_64.zip").expect("failed to open file");
let zip_file = ZipFile::new(file); let zip_file = ZipFileReader::new(file);
//println!("{}", zip_file.get_file_names().join("\n")); //println!("{}", zip_file.get_file_names().join("\n"));
/*
for file in &zip_file.files { for file in &zip_file.files {
println!("{}", file.get_name()); println!("{}", file.get_name());
println!("local: {:?}", file.local_header.malformed_extra_field); println!("local: {:?}", file.local_header.malformed_extra_field);
println!("central dir: {:?}", file.header.malformed_extra_field); println!("central dir: {:?}", file.header.malformed_extra_field);
println!(); println!();
} }
/*println!(
println!(
"uncompressed size: {}", "uncompressed size: {}",
zip_file.files[0].get_uncompressed_size() zip_file.files[0].get_uncompressed_size()
); );
@ -24,11 +26,13 @@ fn main() {
.map(|f| f.get_name()) .map(|f| f.get_name())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n") .join("\n")
);*/ );
if zip_file.is_signed_v2() { if zip_file.is_signed_v2() {
println!("Signed >= v2"); println!("Signed >= v2");
} else { } else {
println!("Not signed whith scheme >= v2"); println!("Not signed whith scheme >= v2");
} }
zip_file.check_holes(); zip_file.check_holes();
*/
println!("{:#?}", zip_file.get_file_info("classes.dex"));
} }

View file

@ -0,0 +1,285 @@
use std::io::{Read, Seek, SeekFrom};
use crate::{
apk_signing_block::ApkSigningBlock, apk_signing_block::Magic,
end_of_central_directory::EndCentralDirectory,
end_of_central_directory::Zip64EndCentralDirectory,
end_of_central_directory::Zip64EndCentralDirectoryLocator, general_purpose_flags, FileHeader,
FileInfo, LocalFileHeader, Signature,
};
use androscalpel_serializer::Serializable;
pub struct ZipFileReader<T: Read + Seek> {
pub end_of_central_directory: EndCentralDirectory,
pub zip64_end_of_central_directory: Option<Zip64EndCentralDirectory>,
pub files: Vec<FileInfo>,
pub apk_sign_block: Option<ApkSigningBlock>,
pub data: T,
}
impl<T: Read + Seek> ZipFileReader<T> {
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();
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);
let zip64_edc_record_off =
zip64_ecd_locator.offset_zip64_end_of_central_directory_record;
reader.seek(SeekFrom::Start(zip64_edc_record_off)).unwrap();
Zip64EndCentralDirectory::deserialize(&mut reader).ok()
} 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_cd_offset()))
.unwrap();
let mut size_read = 0;
let cd_size = zip_file.get_cd_size();
while size_read < cd_size {
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(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();
if (local_header.general_purpose_flags
& general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
!= 0)
|| (header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
!= 0)
{
panic!("Data Descriptor not yet suported");
}
zip_file.files.push(FileInfo {
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_cd_offset() - 16 - 8))
.unwrap();
let block_size = u64::deserialize(&mut zip_file.data).unwrap();
zip_file
.data
.seek(SeekFrom::Start(zip_file.get_cd_offset() - block_size - 8))
.unwrap();
zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok();
}
}
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_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 {
self.end_of_central_directory.size_central_directory as 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 {
self.end_of_central_directory.offset_central_directory as u64
}
}
pub fn get_end_of_central_directory_offset(reader: &mut T) -> Option<u64> {
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<String> {
self.files.iter().map(|f| f.get_name()).collect()
}
/// List files used to signe jar file (apk signature v1) found in the zip file:
/// META-INF/MANIFEST.MF
/// META-INF/*.SF
/// META-INF/*.DSA
/// META-INF/*.RSA
/// META-INF/SIG-*
pub fn get_jar_sig_files(&self) -> Vec<&FileInfo> {
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()
}
pub fn check_holes(&self) {
let mut files: Vec<&FileInfo> = 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<u8> {
self.data.seek(SeekFrom::Start(offset)).unwrap();
let mut data = vec![];
for _ in 0..size {
data.push(u8::deserialize(&mut self.data).unwrap());
}
data
}
pub fn get_file_info(&self, name: &str) -> Option<&FileInfo> {
self.files.iter().find(|&file| file.get_name() == name)
}
}

View file

@ -0,0 +1,73 @@
use std::io::{Read, Seek, SeekFrom, Write};
use crate::{
end_of_central_directory::EndCentralDirectory,
end_of_central_directory::Zip64EndCentralDirectory, FileInfo, ZipFileReader,
};
use androscalpel_serializer::Serializable;
pub struct ZipFileWriter<T: Write> {
files: Vec<FileInfo>,
zip64_end_of_central_directory: Option<Zip64EndCentralDirectory>,
current_offset: u64,
data: T,
}
impl<T: Write> ZipFileWriter<T> {
pub fn new(writer: T, zip64_info: Option<Zip64EndCentralDirectory>) -> Self {
Self {
current_offset: 0,
zip64_end_of_central_directory: zip64_info,
files: vec![],
data: writer,
}
}
/// Insert an file from an existing zip
pub fn insert_file_from_zip<U: Read + Seek>(
&mut self,
mut file_info: FileInfo,
zip_file: &mut ZipFileReader<U>,
) {
let file_off = file_info.get_file_offset();
let file_size = file_info.get_compressed_size();
file_info.set_offset_local_header(self.current_offset);
file_info.local_header.serialize(&mut self.data).unwrap();
zip_file.data.seek(SeekFrom::Start(file_off)).unwrap();
let mut remaining_size = file_size;
let mut buffer = [0u8; 4096];
while remaining_size != 0 {
let read = zip_file
.data
.read(&mut buffer[0..core::cmp::min(4096, remaining_size as usize)])
.unwrap();
self.data.write_all(&buffer[0..read]).unwrap();
remaining_size -= read as u64;
}
self.current_offset += file_info.local_header.size() as u64 + file_size;
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 {
file_info.header.serialize(&mut self.data).unwrap();
}
// TODO: zip64
EndCentralDirectory {
disk_number: 0,
disk_number_of_central_directory_start: 0,
number_of_entries_in_central_directory_on_disk: self.files.len() as u16,
number_of_entries_in_central_directory: self.files.len() as u16,
size_central_directory: self
.files
.iter()
.map(|file| file.local_header.size() as u32 + file.get_compressed_size() as u32)
.sum(),
offset_central_directory: self.current_offset as u32,
comment: vec![],
}
.serialize(&mut self.data)
.unwrap();
}
}