start adding error handling to apk_frauder

This commit is contained in:
Jean-Marie Mineau 2025-04-17 18:01:27 +02:00
parent 615f7a8f52
commit 5940434694
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
6 changed files with 106 additions and 65 deletions

7
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -67,9 +67,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.75" version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -79,6 +79,7 @@ name = "apk_frauder"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"androscalpel_serializer", "androscalpel_serializer",
"anyhow",
"flate2", "flate2",
"log", "log",
"rand", "rand",

View file

@ -3012,7 +3012,7 @@ impl Apk {
+ Send + Send
+ Sync, + Sync,
{ {
let mut apk_z = ZipFileReader::new(apk); let mut apk_z = ZipFileReader::new(apk)?;
let mut apk = Self::default(); let mut apk = Self::default();
let dex_names = apk_z let dex_names = apk_z
.get_classes_file_info() .get_classes_file_info()
@ -3020,7 +3020,7 @@ impl Apk {
.map(|info| info.get_name()) .map(|info| info.get_name())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for name in dex_names { for name in dex_names {
let data = apk_z.read_file_as_vec(&name); let data = apk_z.read_file_as_vec(&name)?; // TODO recover?
apk.add_dex_file(&name, &data, label_ins.clone(), cache)?; apk.add_dex_file(&name, &data, label_ins.clone(), cache)?;
} }
Ok(apk) Ok(apk)

View file

@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" } androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
anyhow = "1.0.98"
flate2 = { version = "1.0.28", features = ["rust_backend"] } flate2 = { version = "1.0.28", features = ["rust_backend"] }
log = "0.4.25" log = "0.4.25"
rand = "0.8.5" rand = "0.8.5"

View file

@ -1,4 +1,5 @@
use androscalpel_serializer::Serializable; use androscalpel_serializer::Serializable;
use anyhow::{Context, Result};
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::fs; use std::fs;
@ -162,7 +163,7 @@ pub fn replace_dex(
apksigner: Option<impl AsRef<Path>>, apksigner: Option<impl AsRef<Path>>,
keypassword: Option<&str>, keypassword: Option<&str>,
additionnal_files: Option<HashMap<String, Option<impl Read + Seek>>>, additionnal_files: Option<HashMap<String, Option<impl Read + Seek>>>,
) { ) -> Result<()> {
let zipalign = if let Some(path) = &zipalign { let zipalign = if let Some(path) = &zipalign {
path.as_ref().as_os_str() path.as_ref().as_os_str()
} else { } else {
@ -176,17 +177,22 @@ pub fn replace_dex(
let tmp_dir = env::temp_dir().join(format!("apk_frauder_{:x}", rand::random::<u128>())); let tmp_dir = env::temp_dir().join(format!("apk_frauder_{:x}", rand::random::<u128>()));
let unaligned_path = tmp_dir.join("stripped.apk"); let unaligned_path = tmp_dir.join("stripped.apk");
let aligned_path = tmp_dir.join("aligned.apk"); let aligned_path = tmp_dir.join("aligned.apk");
fs::create_dir_all(&tmp_dir).expect("Failed to create temporary directory"); fs::create_dir_all(&tmp_dir).context("Failed to create temporary directory")?;
let file = File::open(apk).expect("failed to open file"); let file = File::open(&apk).with_context(|| {
let mut apk = ZipFileReader::new(file); format!(
let file = File::create(&unaligned_path).expect("failed to create file"); "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 apk_out = ZipFileWriter::new(file, apk.zip64_end_of_central_directory.clone());
let mut file_info_ref = (*apk let mut file_info_ref = (*apk
.get_classes_file_info() .get_classes_file_info()
.first() .first()
.expect("No dex file found in apk")) .context("No dex file found in apk")?)
.clone(); .clone();
apk.unlink_signature_files(); apk.unlink_signature_files();
@ -233,7 +239,7 @@ pub fn replace_dex(
.arg(unaligned_path.as_os_str()) .arg(unaligned_path.as_os_str())
.arg(aligned_path.as_os_str()) .arg(aligned_path.as_os_str())
.status() .status()
.unwrap(); .context("Failed to run zipalign")?;
let mut cmd = Command::new(apksigner); let mut cmd = Command::new(apksigner);
let cmd = cmd let cmd = cmd
@ -249,6 +255,7 @@ pub fn replace_dex(
} else { } else {
cmd cmd
}; };
cmd.status().unwrap(); cmd.status().context("Failled to run apksigne")?;
fs::remove_dir_all(tmp_dir).expect("Failled to remove tmp dir"); fs::remove_dir_all(tmp_dir).context("Failled to remove tmp dir")?;
Ok(())
} }

View file

@ -22,6 +22,6 @@ fn main() {
None::<HashMap<String, Option<Cursor<&[u8]>>>>, None::<HashMap<String, Option<Cursor<&[u8]>>>>,
);*/ );*/
let file = File::open("zagruski.apk").unwrap(); let file = File::open("zagruski.apk").unwrap();
let reader = ZipFileReader::new(file); let reader = ZipFileReader::new(file).unwrap();
println!("{:#?}", &reader.files[..2]); println!("{:#?}", &reader.files[..2]);
} }

View file

@ -1,7 +1,3 @@
use log::{info, warn};
use std::collections::HashMap;
use std::io::{Read, Seek, SeekFrom};
use crate::{ use crate::{
apk_signing_block::ApkSigningBlock, apk_signing_block::ApkSigningBlock,
apk_signing_block::Magic, apk_signing_block::Magic,
@ -13,7 +9,11 @@ use crate::{
general_purpose_flags, FileHeader, FileInfo, LocalFileHeader, Signature, general_purpose_flags, FileHeader, FileInfo, LocalFileHeader, Signature,
}; };
use androscalpel_serializer::Serializable; use androscalpel_serializer::Serializable;
use anyhow::{bail, Context, Result};
use flate2::read::DeflateDecoder; use flate2::read::DeflateDecoder;
use log::{info, warn};
use std::collections::HashMap;
use std::io::{Read, Seek, SeekFrom};
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ZipFileReader<T: Read + Seek> { pub struct ZipFileReader<T: Read + Seek> {
@ -25,20 +25,23 @@ pub struct ZipFileReader<T: Read + Seek> {
} }
impl<T: Read + Seek> ZipFileReader<T> { impl<T: Read + Seek> ZipFileReader<T> {
pub fn new(mut reader: T) -> Self { pub fn new(mut reader: T) -> Result<Self> {
let end_of_central_directory_off = let end_of_central_directory_off =
Self::get_end_of_central_directory_offset(&mut reader).unwrap(); Self::get_end_of_central_directory_offset(&mut reader)
.context("end of centrall directory not found, probably not a zip")?;
reader reader
.seek(SeekFrom::Start(end_of_central_directory_off)) .seek(SeekFrom::Start(end_of_central_directory_off))
.unwrap(); .context("Failed to seek to end of central directory")?;
let end_of_central_directory = EndCentralDirectory::deserialize(&mut reader).unwrap(); let end_of_central_directory = EndCentralDirectory::deserialize(&mut reader)
reader .context("Failed to deserialize end of central directory")?;
let zip64_end_of_central_directory = if reader
.seek(SeekFrom::Start( .seek(SeekFrom::Start(
end_of_central_directory_off - Zip64EndCentralDirectoryLocator::SIZE as u64, end_of_central_directory_off - Zip64EndCentralDirectoryLocator::SIZE as u64,
)) ))
.unwrap(); .is_ok()
{
let zip64_ecd_locator = Zip64EndCentralDirectoryLocator::deserialize(&mut reader).ok(); let zip64_ecd_locator = Zip64EndCentralDirectoryLocator::deserialize(&mut reader).ok();
let zip64_end_of_central_directory = if let Some(zip64_ecd_locator) = zip64_ecd_locator { if let Some(zip64_ecd_locator) = zip64_ecd_locator {
assert_eq!( assert_eq!(
zip64_ecd_locator.disk_number_of_zip64_end_central_directory_start, zip64_ecd_locator.disk_number_of_zip64_end_central_directory_start,
0 0
@ -46,10 +49,15 @@ impl<T: Read + Seek> ZipFileReader<T> {
assert!(zip64_ecd_locator.total_number_of_disks <= 1); assert!(zip64_ecd_locator.total_number_of_disks <= 1);
let zip64_edc_record_off = let zip64_edc_record_off =
zip64_ecd_locator.offset_zip64_end_of_central_directory_record; zip64_ecd_locator.offset_zip64_end_of_central_directory_record;
reader.seek(SeekFrom::Start(zip64_edc_record_off)).unwrap(); reader
.seek(SeekFrom::Start(zip64_edc_record_off))
.context("Failed to seek to end of zip64 central directory")?;
Zip64EndCentralDirectory::deserialize(&mut reader).ok() Zip64EndCentralDirectory::deserialize(&mut reader).ok()
} else { } else {
None None
}
} else {
None
}; };
//println!("{:#?}", end_of_central_directory); //println!("{:#?}", end_of_central_directory);
@ -66,24 +74,29 @@ impl<T: Read + Seek> ZipFileReader<T> {
zip_file zip_file
.data .data
.seek(SeekFrom::Start(zip_file.get_cd_offset())) .seek(SeekFrom::Start(zip_file.get_cd_offset()))
.unwrap(); .context("Failed to seek to central directory")?;
let mut size_read = 0; let mut size_read = 0;
let cd_size = zip_file.get_cd_size(); let cd_size = zip_file.get_cd_size();
while size_read < cd_size { while size_read < cd_size {
let header = FileHeader::deserialize(&mut zip_file.data).unwrap(); let header = FileHeader::deserialize(&mut zip_file.data)
.context("Failed to deserialize file header")?;
//println!("{:#?}", header); //println!("{:#?}", header);
size_read += header.size() as u64; size_read += header.size() as u64;
let pos_in_dir = zip_file.data.stream_position().unwrap(); let pos_in_dir = zip_file
.data
.stream_position()
.context("Failed to get stream position")?;
if header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR != 0 if header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR != 0
{ {
panic!("Central directory encryption not supported"); bail!("Central directory encryption not supported");
} }
zip_file zip_file
.data .data
.seek(SeekFrom::Start(header.get_offset_local_header())) .seek(SeekFrom::Start(header.get_offset_local_header()))
.unwrap(); .context("Failled to seek to local header")?;
let local_header = LocalFileHeader::deserialize(&mut zip_file.data).unwrap(); let local_header = LocalFileHeader::deserialize(&mut zip_file.data)
.context("Failed to deserialize local file header")?;
let data_descriptor = if (local_header.general_purpose_flags let data_descriptor = if (local_header.general_purpose_flags
& general_purpose_flags::MASK_USE_DATA_DESCRIPTOR & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
!= 0) != 0)
@ -94,20 +107,25 @@ impl<T: Read + Seek> ZipFileReader<T> {
zip_file zip_file
.data .data
.seek(SeekFrom::Current(header.compressed_size as i64)) .seek(SeekFrom::Current(header.compressed_size as i64))
.unwrap(); .context("failed to seek to after the file data")?;
if zip_file.zip64_end_of_central_directory.is_some() { if zip_file.zip64_end_of_central_directory.is_some() {
Some(DataDescriptor::Zip64( Some(DataDescriptor::Zip64(
DataDescriptor64::deserialize(&mut zip_file.data).unwrap(), DataDescriptor64::deserialize(&mut zip_file.data)
.context("Failed to deserialize data descriptor 64")?,
)) ))
} else { } else {
Some(DataDescriptor::Zip32( Some(DataDescriptor::Zip32(
DataDescriptor32::deserialize(&mut zip_file.data).unwrap(), DataDescriptor32::deserialize(&mut zip_file.data)
.context("Failed to deserialize data descriptor")?,
)) ))
} }
} else { } else {
None None
}; };
zip_file.data.seek(SeekFrom::Start(pos_in_dir)).unwrap(); zip_file
.data
.seek(SeekFrom::Start(pos_in_dir))
.context("Failed to seek to position in directory")?;
zip_file.files.push(FileInfo { zip_file.files.push(FileInfo {
local_header, local_header,
header, header,
@ -119,24 +137,26 @@ impl<T: Read + Seek> ZipFileReader<T> {
zip_file zip_file
.data .data
.seek(SeekFrom::Start(zip_file.get_cd_offset() - 16)) .seek(SeekFrom::Start(zip_file.get_cd_offset() - 16))
.unwrap(); .context("Failed to seek to central directory")?;
let magic = Magic::deserialize(&mut zip_file.data).unwrap(); let magic =
Magic::deserialize(&mut zip_file.data).context("Failed to deserialize Magic")?;
if magic == ApkSigningBlock::MAGIC { if magic == ApkSigningBlock::MAGIC {
zip_file zip_file
.data .data
.seek(SeekFrom::Start(zip_file.get_cd_offset() - 16 - 8)) .seek(SeekFrom::Start(zip_file.get_cd_offset() - 16 - 8))
.unwrap(); .context("Failed to seek to central directory")?;
let block_size = u64::deserialize(&mut zip_file.data).unwrap(); let block_size = u64::deserialize(&mut zip_file.data)
.context("Failed to deserialize block size")?;
zip_file zip_file
.data .data
.seek(SeekFrom::Start(zip_file.get_cd_offset() - block_size - 8)) .seek(SeekFrom::Start(zip_file.get_cd_offset() - block_size - 8))
.unwrap(); .context("Failed to seek to central directory")?;
zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok(); zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok();
} }
} }
zip_file Ok(zip_file)
} }
pub fn is_zip64(&self) -> bool { pub fn is_zip64(&self) -> bool {
@ -195,7 +215,7 @@ impl<T: Read + Seek> ZipFileReader<T> {
} }
pub fn get_end_of_central_directory_offset(reader: &mut T) -> Option<u64> { pub fn get_end_of_central_directory_offset(reader: &mut T) -> Option<u64> {
let file_size = reader.seek(SeekFrom::End(0)).unwrap(); let file_size = reader.seek(SeekFrom::End(0)).ok()?;
let mut sig = Signature::default(); let mut sig = Signature::default();
let mut comment_size = 0; let mut comment_size = 0;
while sig != EndCentralDirectory::SIGNATURE { while sig != EndCentralDirectory::SIGNATURE {
@ -203,8 +223,8 @@ impl<T: Read + Seek> ZipFileReader<T> {
.seek(SeekFrom::End( .seek(SeekFrom::End(
-(EndCentralDirectory::MIN_SIZE as i64) - comment_size, -(EndCentralDirectory::MIN_SIZE as i64) - comment_size,
)) ))
.unwrap(); .ok()?;
sig = Signature::deserialize(reader).unwrap(); sig = Signature::deserialize(reader).ok()?;
comment_size += 1; comment_size += 1;
if comment_size > 65536 if comment_size > 65536
|| comment_size as usize + EndCentralDirectory::MIN_SIZE > file_size as usize || comment_size as usize + EndCentralDirectory::MIN_SIZE > file_size as usize
@ -305,38 +325,50 @@ impl<T: Read + Seek> ZipFileReader<T> {
} }
} }
pub fn get_bin(&mut self, offset: u64, size: usize) -> Vec<u8> { pub fn get_bin(&mut self, offset: u64, size: usize) -> Result<Vec<u8>> {
self.data.seek(SeekFrom::Start(offset)).unwrap(); self.data
.seek(SeekFrom::Start(offset))
.context("Failed to seek to data")?;
let mut data = vec![0u8; size]; let mut data = vec![0u8; size];
self.data.read_exact(&mut data).unwrap(); self.data
.read_exact(&mut data)
.context("failed to read data")?;
/* /*
for _ in 0..size { for _ in 0..size {
data.push(u8::deserialize(&mut self.data).unwrap()); data.push(u8::deserialize(&mut self.data).unwrap());
} }
*/ */
data Ok(data)
} }
pub fn read_file_as_vec(&mut self, name: &str) -> Vec<u8> { pub fn read_file_as_vec(&mut self, name: &str) -> Result<Vec<u8>> {
let file = self.get_file_info(name).unwrap(); let file = self
.get_file_info(name)
.with_context(|| format!("Failed to get info for {name}"))?;
let offset = file.get_file_offset(); let offset = file.get_file_offset();
let size_c = file.header.compressed_size as usize; let size_c = file.header.compressed_size as usize;
let size = file.header.uncompressed_size as usize; let size = file.header.uncompressed_size as usize;
let compression_method = file.header.compression_method; let compression_method = file.header.compression_method;
let mut data = vec![0u8; size_c]; let mut data = vec![0u8; size_c];
self.data.seek(SeekFrom::Start(offset)).unwrap(); self.data
self.data.read_exact(&mut data).unwrap(); .seek(SeekFrom::Start(offset))
.with_context(|| format!("Failed to seek to start of file {name} (at 0x{offset:x})"))?;
self.data
.read_exact(&mut data)
.with_context(|| format!("Failed to read data for file {name}"))?;
match compression_method { match compression_method {
CompressionMethod::Stored => {} CompressionMethod::Stored => {}
CompressionMethod::Deflated => { CompressionMethod::Deflated => {
let mut decomp_data = vec![0u8; size]; let mut decomp_data = vec![0u8; size];
let mut deflater = DeflateDecoder::new(&data[..]); let mut deflater = DeflateDecoder::new(&data[..]);
deflater.read_exact(&mut decomp_data).unwrap(); deflater
.read_exact(&mut decomp_data)
.with_context(|| format!("Failed to decompress data for file {name}"))?;
data = decomp_data data = decomp_data
} }
_ => unimplemented!(), _ => unimplemented!(),
} }
data Ok(data)
} }
pub fn get_file_info(&self, name: &str) -> Option<&FileInfo> { pub fn get_file_info(&self, name: &str) -> Option<&FileInfo> {