add support for data descriptor
This commit is contained in:
parent
28f47eaba4
commit
047d2636c3
3 changed files with 195 additions and 78 deletions
|
|
@ -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;
|
||||||
|
loop {
|
||||||
|
let name = if i == 0 {
|
||||||
|
"classes.dex".into()
|
||||||
|
} else {
|
||||||
|
format!("classes{}.dex", i + 1)
|
||||||
|
};
|
||||||
|
if let Some(file) = files.remove(&name) {
|
||||||
|
dex_files.push(Cursor::new(file))
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
/*apk_frauder::replace_dex(
|
replace_dex_unsigned(
|
||||||
(&args[1]).into(),
|
&args[1],
|
||||||
(&args[1] + ".pathed").into(),
|
&args[2],
|
||||||
&mut dex_files,
|
&mut dex_files,
|
||||||
cli.keystore,
|
|
||||||
cli.zipalign,
|
|
||||||
cli.apksigner,
|
|
||||||
cli.keypassword.as_deref(),
|
|
||||||
None::<HashMap<_, Option<Cursor<&[u8]>>>>,
|
None::<HashMap<_, Option<Cursor<&[u8]>>>>,
|
||||||
)*/
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue