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

@ -1,5 +1,9 @@
use androscalpel::{Apk, Instruction, Result, VisitableMut, VisitorMut}; use androscalpel::{Apk, Instruction, Result, VisitableMut, VisitorMut};
use apk_frauder::replace_dex_unsigned;
use std::collections::HashMap;
use std::env; use std::env;
use std::io::Cursor;
#[derive(Default)] #[derive(Default)]
struct InsCounter { struct InsCounter {
@ -16,7 +20,7 @@ impl VisitorMut for InsCounter {
fn main() { fn main() {
let args: Vec<_> = env::args().collect(); let args: Vec<_> = env::args().collect();
assert!(args.len() > 1, "Need one argument"); assert!(args.len() > 2, "usage: {} input.apk output.apk", args[0]);
let mut cnt = InsCounter::default(); let mut cnt = InsCounter::default();
let apk = cnt let apk = cnt
@ -24,16 +28,29 @@ fn main() {
.unwrap(); .unwrap();
println!("Nb INS: {}", cnt.n); println!("Nb INS: {}", cnt.n);
// This should be streamlined
let mut dex_files = vec![];
let mut files = apk.gen_raw_dex().unwrap(); let mut files = apk.gen_raw_dex().unwrap();
let mut i = 0;
/*apk_frauder::replace_dex( loop {
(&args[1]).into(), let name = if i == 0 {
(&args[1] + ".pathed").into(), "classes.dex".into()
&mut dex_files, } else {
cli.keystore, format!("classes{}.dex", i + 1)
cli.zipalign, };
cli.apksigner, if let Some(file) = files.remove(&name) {
cli.keypassword.as_deref(), dex_files.push(Cursor::new(file))
None::<HashMap<_, Option<Cursor<&[u8]>>>>, } else {
)*/ break;
}
i += 1;
}
replace_dex_unsigned(
&args[1],
&args[2],
&mut dex_files,
None::<HashMap<_, Option<Cursor<&[u8]>>>>,
)
.unwrap();
} }

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 /// # Warning
/// ///
@ -177,58 +244,8 @@ pub fn replace_dex(
let aligned_path = tmp_dir.join("aligned.apk"); let aligned_path = tmp_dir.join("aligned.apk");
fs::create_dir_all(&tmp_dir).context("Failed to create temporary directory")?; fs::create_dir_all(&tmp_dir).context("Failed to create temporary directory")?;
let file = File::open(&apk).with_context(|| { replace_dex_unsigned(apk, &unaligned_path, dexfiles, additionnal_files)?;
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());
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 // TODO: we can probably do that ourself an spare ourself the trouble of finding zipalign
Command::new(zipalign) Command::new(zipalign)
.arg("-v") .arg("-v")

View file

@ -2,11 +2,13 @@ use std::io;
use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use crate::compression::CompressionMethod; use crate::compression::CompressionMethod;
use crate::data_descriptor::DataDescriptor; use crate::data_descriptor::{DataDescriptor, DataDescriptor32, DataDescriptor64};
use crate::end_of_central_directory::{ use crate::end_of_central_directory::{
EndCentralDirectory, Zip64EndCentralDirectory, Zip64EndCentralDirectoryLocator, 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 androscalpel_serializer::Serializable;
use flate2::write::DeflateEncoder; use flate2::write::DeflateEncoder;
use flate2::{Compression, CrcWriter}; use flate2::{Compression, CrcWriter};
@ -67,9 +69,13 @@ impl<T: Write> ZipFileWriter<T> {
data_descriptor: Option<DataDescriptor>, data_descriptor: Option<DataDescriptor>,
) { ) {
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0); assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
assert!( //assert!(
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0 // header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0,
); // TODO // "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_STRONG_ENCRYPTION == 0);
assert!( assert!(
header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR == 0 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 { 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_ENCRYPTED == 0);
assert!( //assert!(
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0 // header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0,
); // TODO // "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!( assert!(
header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0 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::Deflated
|| header.compression_method == CompressionMethod::Stored || 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 { LocalFileHeader {
version_needed_to_extract: header.version_needed_to_extract, version_needed_to_extract: header.version_needed_to_extract,
general_purpose_flags: if header.compression_method == CompressionMethod::Deflated { general_purpose_flags,
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
} else {
0
},
compression_method: header.compression_method, compression_method: header.compression_method,
last_mod_file_time: header.last_mod_file_time, last_mod_file_time: header.last_mod_file_time,
last_mod_file_data: header.last_mod_file_data, 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) | general_purpose_flags::MASK_COMPRESS_OPTION_2)
} else { } else {
0 0
} | if use_data_descriptor {
general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
} else {
0
}, },
compression_method: header.compression_method, compression_method: header.compression_method,
last_mod_file_time: header.last_mod_file_time, 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_uncompressed_size(local_header.get_uncompressed_size());
header.set_offset_local_header(header_offset); header.set_offset_local_header(header_offset);
if data_descriptor.is_some() { let use_z64 = header
panic!("Writing file with data_descriptor is not yet implemented"); .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 { let file_info = FileInfo {
local_header, local_header,
header, header,