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
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -62,6 +62,7 @@ name = "apk_frauder"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"androscalpel_serializer",
|
||||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -130,6 +131,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
|
@ -150,6 +160,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
|
|
|
|||
|
|
@ -7,3 +7,4 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
|
||||
flate2 = { version = "1.0.28", features = ["rust_backend"] }
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
flate2/rust_backend
|
||||
64
apk_frauder/src/compression.rs
Normal file
64
apk_frauder/src/compression.rs
Normal 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),
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use std::io::{SeekFrom, Write};
|
||||
|
||||
use crate::compression::CompressionMethod;
|
||||
use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField};
|
||||
use crate::{cp437, general_purpose_flags, Encoding, Signature};
|
||||
use androscalpel_serializer::{ReadSeek, Result, Serializable};
|
||||
|
|
@ -10,7 +11,7 @@ pub struct FileHeader {
|
|||
pub version_made_by: u16,
|
||||
pub version_needed_to_extract: u16,
|
||||
pub general_purpose_flags: u16,
|
||||
pub compression_method: u16,
|
||||
pub compression_method: CompressionMethod,
|
||||
pub last_mod_file_time: u16,
|
||||
pub last_mod_file_data: u16,
|
||||
pub crc_32: u32,
|
||||
|
|
@ -70,7 +71,7 @@ impl Serializable for FileHeader {
|
|||
let version_made_by = u16::deserialize(input)?;
|
||||
let version_needed_to_extract = 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_data = u16::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 {
|
||||
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();
|
||||
input.seek(SeekFrom::Current(-(size as i64))).unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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 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()
|
||||
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 {
|
||||
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();
|
||||
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
|
||||
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 {
|
||||
self.end_of_central_directory.disk_number as u32
|
||||
extra.offset_header = None;
|
||||
}
|
||||
z64found = true;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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 use_z64 {
|
||||
self.header.offset_local_header = u32::MAX;
|
||||
} else {
|
||||
self.end_of_central_directory
|
||||
.disk_number_of_central_directory_start as u32
|
||||
self.header.offset_local_header = new_offset 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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::io::{SeekFrom, Write};
|
||||
|
||||
use crate::compression::CompressionMethod;
|
||||
use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField};
|
||||
use crate::{cp437, general_purpose_flags, Encoding, Signature};
|
||||
use androscalpel_serializer::{ReadSeek, Result, Serializable};
|
||||
|
|
@ -9,7 +10,7 @@ pub struct LocalFileHeader {
|
|||
// signature: Signature(0x04034b50)
|
||||
pub version_needed_to_extract: u16,
|
||||
pub general_purpose_flags: u16,
|
||||
pub compression_method: u16,
|
||||
pub compression_method: CompressionMethod,
|
||||
pub last_mod_file_time: u16,
|
||||
pub last_mod_file_data: u16,
|
||||
pub crc_32: u32,
|
||||
|
|
@ -57,7 +58,7 @@ impl Serializable for LocalFileHeader {
|
|||
assert_eq!(signature, Self::SIGNATURE); // TODO
|
||||
let version_needed_to_extract = 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_data = u16::deserialize(input)?;
|
||||
let crc_32 = u32::deserialize(input)?;
|
||||
|
|
@ -103,8 +104,10 @@ impl Serializable for LocalFileHeader {
|
|||
header.extra_field.push(field);
|
||||
}
|
||||
}
|
||||
let mut failed_last_extra_field = false;
|
||||
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();
|
||||
input.seek(SeekFrom::Current(-(size as i64))).unwrap();
|
||||
}
|
||||
|
|
@ -113,6 +116,14 @@ impl Serializable for LocalFileHeader {
|
|||
header.malformed_extra_field.push(u8::deserialize(input)?);
|
||||
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();
|
||||
for field in &mut header.extra_field {
|
||||
if let ExtraField::Generic(GenericExtraField {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
use apk_frauder::ZipFile;
|
||||
use apk_frauder::ZipFileReader;
|
||||
use std::fs::File;
|
||||
|
||||
fn main() {
|
||||
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 zip_file = ZipFile::new(file);
|
||||
let zip_file = ZipFileReader::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!(
|
||||
|
||||
println!(
|
||||
"uncompressed size: {}",
|
||||
zip_file.files[0].get_uncompressed_size()
|
||||
);
|
||||
|
|
@ -24,11 +26,13 @@ fn main() {
|
|||
.map(|f| f.get_name())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);*/
|
||||
);
|
||||
if zip_file.is_signed_v2() {
|
||||
println!("Signed >= v2");
|
||||
} else {
|
||||
println!("Not signed whith scheme >= v2");
|
||||
}
|
||||
zip_file.check_holes();
|
||||
*/
|
||||
println!("{:#?}", zip_file.get_file_info("classes.dex"));
|
||||
}
|
||||
|
|
|
|||
285
apk_frauder/src/zip_reader.rs
Normal file
285
apk_frauder/src/zip_reader.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
73
apk_frauder/src/zip_writer.rs
Normal file
73
apk_frauder/src/zip_writer.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue