add support for data descriptor

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2025-10-06 23:20:24 +02:00
parent 28f47eaba4
commit 047d2636c3
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
3 changed files with 195 additions and 78 deletions

View file

@ -143,7 +143,74 @@ impl FileInfo {
}
}
/// Replace the dex files a an apk an resigned the apk.
/// Replace the dex files in an apk and strip the old signature.
#[allow(clippy::too_many_arguments)]
pub fn replace_dex_unsigned(
apk: impl AsRef<Path>,
dst: impl AsRef<Path>,
dexfiles: &mut [impl Read + Seek],
additionnal_files: Option<HashMap<String, Option<impl Read + Seek>>>,
) -> Result<()> {
let file = File::open(&apk).with_context(|| {
format!(
"failed to open file {}",
apk.as_ref().to_str().unwrap_or("")
)
})?;
let mut apk = ZipFileReader::new(file)?;
let file = File::create(&dst).context("failed to create file")?;
let mut apk_out = ZipFileWriter::new(file, apk.zip64_end_of_central_directory.clone());
let mut file_info_ref = (*apk
.get_classes_file_info()
.first()
.context("No dex file found in apk")?)
.clone();
apk.unlink_signature_files();
apk.unlink_bytecode_files();
if let Some(additionnal_files) = &additionnal_files {
apk.files
.retain(|file| additionnal_files.get(&file.get_name()).is_none());
}
for f in apk.files.clone() {
apk_out.insert_file_from_zip(f, &mut apk);
}
for (i, mut dex) in dexfiles.iter_mut().enumerate() {
if i == 0 {
file_info_ref.set_name("classes.dex");
} else {
file_info_ref.set_name(&format!("classes{}.dex", i + 1));
}
apk_out.insert_file(
&mut dex,
file_info_ref.header.clone(),
Some(file_info_ref.local_header.clone()),
file_info_ref.data_descriptor.clone(),
);
}
if let Some(mut additionnal_files) = additionnal_files {
for (name, data) in &mut additionnal_files {
file_info_ref.set_name(name);
if let Some(data) = data.as_mut() {
apk_out.insert_file(
data,
file_info_ref.header.clone(),
Some(file_info_ref.local_header.clone()),
file_info_ref.data_descriptor.clone(),
);
}
}
}
apk_out.write_central_directory();
Ok(())
}
/// Replace the dex files in an apk an resigned the apk.
///
/// # Warning
///
@ -177,58 +244,8 @@ pub fn replace_dex(
let aligned_path = tmp_dir.join("aligned.apk");
fs::create_dir_all(&tmp_dir).context("Failed to create temporary directory")?;
let file = File::open(&apk).with_context(|| {
format!(
"failed to open file {}",
apk.as_ref().to_str().unwrap_or("")
)
})?;
let mut apk = ZipFileReader::new(file)?;
let file = File::create(&unaligned_path).context("failed to create file")?;
let mut apk_out = ZipFileWriter::new(file, apk.zip64_end_of_central_directory.clone());
replace_dex_unsigned(apk, &unaligned_path, dexfiles, additionnal_files)?;
let mut file_info_ref = (*apk
.get_classes_file_info()
.first()
.context("No dex file found in apk")?)
.clone();
apk.unlink_signature_files();
apk.unlink_bytecode_files();
if let Some(additionnal_files) = &additionnal_files {
apk.files
.retain(|file| additionnal_files.get(&file.get_name()).is_none());
}
for f in apk.files.clone() {
apk_out.insert_file_from_zip(f, &mut apk);
}
for (i, mut dex) in dexfiles.iter_mut().enumerate() {
if i == 0 {
file_info_ref.set_name("classes.dex");
} else {
file_info_ref.set_name(&format!("classes{}.dex", i + 1));
}
apk_out.insert_file(
&mut dex,
file_info_ref.header.clone(),
Some(file_info_ref.local_header.clone()),
file_info_ref.data_descriptor.clone(),
);
}
if let Some(mut additionnal_files) = additionnal_files {
for (name, data) in &mut additionnal_files {
file_info_ref.set_name(name);
if let Some(data) = data.as_mut() {
apk_out.insert_file(
data,
file_info_ref.header.clone(),
Some(file_info_ref.local_header.clone()),
file_info_ref.data_descriptor.clone(),
);
}
}
}
apk_out.write_central_directory();
// TODO: we can probably do that ourself an spare ourself the trouble of finding zipalign
Command::new(zipalign)
.arg("-v")

View file

@ -2,11 +2,13 @@ use std::io;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use crate::compression::CompressionMethod;
use crate::data_descriptor::DataDescriptor;
use crate::data_descriptor::{DataDescriptor, DataDescriptor32, DataDescriptor64};
use crate::end_of_central_directory::{
EndCentralDirectory, Zip64EndCentralDirectory, Zip64EndCentralDirectoryLocator,
};
use crate::{FileHeader, FileInfo, LocalFileHeader, ZipFileReader, general_purpose_flags};
use crate::{
general_purpose_flags, ExtraField, FileHeader, FileInfo, LocalFileHeader, ZipFileReader,
};
use androscalpel_serializer::Serializable;
use flate2::write::DeflateEncoder;
use flate2::{Compression, CrcWriter};
@ -67,9 +69,13 @@ impl<T: Write> ZipFileWriter<T> {
data_descriptor: Option<DataDescriptor>,
) {
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0
); // TODO
//assert!(
// header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0,
// "Writing file with data_descriptor is not yet implemented"
//); // TODO
let mut use_data_descriptor = data_descriptor.is_some()
|| ((header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR)
!= 0);
assert!(header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR == 0
@ -88,9 +94,12 @@ impl<T: Write> ZipFileWriter<T> {
);
let mut local_header = if let Some(header) = local_header {
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0
); // TODO
//assert!(
// header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0,
// "Writing file with data_descriptor is not yet implemented"
//); // TODO
use_data_descriptor |=
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR != 0;
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0
);
@ -110,15 +119,20 @@ impl<T: Write> ZipFileWriter<T> {
header.compression_method == CompressionMethod::Deflated
|| header.compression_method == CompressionMethod::Stored
);
let mut general_purpose_flags = header.general_purpose_flags;
// We only support options for Deflate parameter and data descriptor
if header.compression_method == CompressionMethod::Deflated {
general_purpose_flags &= general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2;
} else {
general_purpose_flags = 0
}
if use_data_descriptor {
general_purpose_flags |= general_purpose_flags::MASK_USE_DATA_DESCRIPTOR;
}
LocalFileHeader {
version_needed_to_extract: header.version_needed_to_extract,
general_purpose_flags: if header.compression_method == CompressionMethod::Deflated {
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
} else {
0
},
general_purpose_flags,
compression_method: header.compression_method,
last_mod_file_time: header.last_mod_file_time,
last_mod_file_data: header.last_mod_file_data,
@ -139,6 +153,10 @@ impl<T: Write> ZipFileWriter<T> {
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
} else {
0
} | if use_data_descriptor {
general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
} else {
0
},
compression_method: header.compression_method,
last_mod_file_time: header.last_mod_file_time,
@ -198,9 +216,74 @@ impl<T: Write> ZipFileWriter<T> {
header.set_uncompressed_size(local_header.get_uncompressed_size());
header.set_offset_local_header(header_offset);
if data_descriptor.is_some() {
panic!("Writing file with data_descriptor is not yet implemented");
let use_z64 = header
.extra_field
.iter()
.any(|f| matches!(f, ExtraField::Zip64(_)));
let data_descriptor = if use_data_descriptor {
match data_descriptor {
None if use_z64 => Some(DataDescriptor::Zip64(DataDescriptor64 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size(),
uncompressed_size: local_header.get_uncompressed_size(),
use_signature: true,
})),
None => Some(DataDescriptor::Zip32(DataDescriptor32 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size() as u32,
uncompressed_size: local_header.get_uncompressed_size() as u32,
use_signature: true,
})),
Some(DataDescriptor::Zip32(DataDescriptor32 { use_signature, .. })) if use_z64 => {
Some(DataDescriptor::Zip64(DataDescriptor64 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size(),
uncompressed_size: local_header.get_uncompressed_size(),
use_signature,
}))
}
Some(DataDescriptor::Zip64(DataDescriptor64 { use_signature, .. })) if !use_z64 => {
Some(DataDescriptor::Zip32(DataDescriptor32 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size() as u32,
uncompressed_size: local_header.get_uncompressed_size() as u32,
use_signature,
}))
}
Some(DataDescriptor::Zip64(DataDescriptor64 { use_signature, .. })) => {
Some(DataDescriptor::Zip64(DataDescriptor64 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size(),
uncompressed_size: local_header.get_uncompressed_size(),
use_signature,
}))
}
Some(DataDescriptor::Zip32(DataDescriptor32 { use_signature, .. })) => {
Some(DataDescriptor::Zip32(DataDescriptor32 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size() as u32,
uncompressed_size: local_header.get_uncompressed_size() as u32,
use_signature,
}))
}
}
} else {
None
};
match &data_descriptor {
Some(DataDescriptor::Zip32(data_descriptor)) => {
data_descriptor.serialize(&mut self.data).unwrap();
self.current_offset += data_descriptor.size() as u64;
}
Some(DataDescriptor::Zip64(data_descriptor)) => {
data_descriptor.serialize(&mut self.data).unwrap();
self.current_offset += data_descriptor.size() as u64;
}
_ => {}
}
let file_info = FileInfo {
local_header,
header,