transfert file from zip to another (wip)
This commit is contained in:
parent
0d305fbe62
commit
0fdf619360
10 changed files with 512 additions and 273 deletions
|
|
@ -1,18 +1,19 @@
|
|||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use androscalpel_serializer::Serializable;
|
||||
|
||||
pub mod apk_signing_block;
|
||||
pub mod compression;
|
||||
mod cp437;
|
||||
pub mod end_of_central_directory;
|
||||
pub mod extra_fields;
|
||||
pub mod file_header;
|
||||
pub mod local_file_header;
|
||||
pub mod zip_reader;
|
||||
pub mod zip_writer;
|
||||
|
||||
use apk_signing_block::*;
|
||||
use end_of_central_directory::*;
|
||||
use extra_fields::{ExtraField, Zip64ExtraField};
|
||||
use file_header::FileHeader;
|
||||
use local_file_header::LocalFileHeader;
|
||||
pub use zip_reader::ZipFileReader;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serializable, Default)]
|
||||
pub struct Signature(pub u32);
|
||||
|
|
@ -33,13 +34,13 @@ pub mod general_purpose_flags {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct File {
|
||||
pub struct FileInfo {
|
||||
pub local_header: LocalFileHeader,
|
||||
pub header: FileHeader,
|
||||
}
|
||||
// TODO: support data descriptor (MASK_USE_DATA_DESCRIPTOR)
|
||||
|
||||
impl File {
|
||||
impl FileInfo {
|
||||
pub fn get_name(&self) -> String {
|
||||
self.header.get_name()
|
||||
}
|
||||
|
|
@ -49,267 +50,47 @@ impl File {
|
|||
pub fn get_compressed_size(&self) -> u64 {
|
||||
self.header.get_compressed_size()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ZipFile<T: Read + Seek> {
|
||||
pub end_of_central_directory: EndCentralDirectory,
|
||||
pub zip64_end_of_central_directory: Option<Zip64EndCentralDirectory>,
|
||||
pub files: Vec<File>,
|
||||
pub apk_sign_block: Option<ApkSigningBlock>,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
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 {
|
||||
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();
|
||||
pub fn get_file_offset(&self) -> u64 {
|
||||
self.get_offset_local_header() + self.local_header.size() as u64
|
||||
}
|
||||
pub fn set_offset_local_header(&mut self, new_offset: u64) {
|
||||
let use_z64 = new_offset > u32::MAX as u64;
|
||||
for extra in &mut self.local_header.extra_field {
|
||||
if let ExtraField::Zip64(extra) = extra {
|
||||
if use_z64 {
|
||||
extra.offset_header = Some(new_offset);
|
||||
} else {
|
||||
extra.offset_header = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
let mut z64found = false;
|
||||
for extra in &mut self.header.extra_field {
|
||||
if let ExtraField::Zip64(extra) = extra {
|
||||
if use_z64 {
|
||||
extra.offset_header = Some(new_offset);
|
||||
} else {
|
||||
extra.offset_header = None;
|
||||
}
|
||||
z64found = true;
|
||||
}
|
||||
}
|
||||
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 use_z64 && !z64found {
|
||||
self.header
|
||||
.extra_field
|
||||
.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 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 use_z64 {
|
||||
self.header.offset_local_header = u32::MAX;
|
||||
} else {
|
||||
self.header.offset_local_header = new_offset as u32;
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue