From fafbdb6537bcc9e0554b2639d784e4f945a015d4 Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Mon, 15 Jan 2024 14:42:26 +0100 Subject: [PATCH] detect apk signatur --- apk_frauder/src/apk_signing_block.rs | 56 +++++++++++++++++++++++ apk_frauder/src/lib.rs | 66 +++++++++++++++++++++++++++- apk_frauder/src/main.rs | 22 ++++++++-- 3 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 apk_frauder/src/apk_signing_block.rs diff --git a/apk_frauder/src/apk_signing_block.rs b/apk_frauder/src/apk_signing_block.rs new file mode 100644 index 0000000..0d9a4b4 --- /dev/null +++ b/apk_frauder/src/apk_signing_block.rs @@ -0,0 +1,56 @@ +use androscalpel_serializer::{Error, ReadSeek, Result, Serializable}; +use std::io::Write; + +#[derive(Debug, Clone, PartialEq, Eq, Serializable, Default)] +pub struct Magic([u8; 16]); + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ApkSigningBlock { + // TODO: parse the block: Length (u64) - id - value (size = length - 4) + pub data: Vec, +} + +impl ApkSigningBlock { + pub const MAGIC: Magic = Magic([ + b'A', b'P', b'K', b' ', b'S', b'i', b'g', b' ', b'B', b'l', b'o', b'c', b'k', b' ', b'4', + b'2', + ]); +} + +impl Serializable for ApkSigningBlock { + fn serialize(&self, output: &mut dyn Write) -> Result<()> { + let size: u64 = self.data.len() as u64 + 16 + 4; + size.serialize(output)?; + for c in &self.data { + c.serialize(output)?; + } + size.serialize(output)?; + Self::MAGIC.serialize(output)?; + Ok(()) + } + + fn deserialize(input: &mut dyn ReadSeek) -> Result { + let size = u64::deserialize(input)?; + let mut data = vec![]; + for _ in 0..(size - 8 - 16) { + data.push(u8::deserialize(input)?); + } + let size_b = u64::deserialize(input)?; + if size_b != size { + return Err(Error::DeserializationError( + "Not an apk signing block: the size fields do not match".into(), + ))?; + } + let magic = Magic::deserialize(input)?; + if magic != Self::MAGIC { + return Err(Error::DeserializationError( + "Not an apk signing block: invalid magic number".into(), + ))?; + } + Ok(Self { data }) + } + + fn size(&self) -> usize { + self.data.len() + 16 + 4 + 4 + } +} diff --git a/apk_frauder/src/lib.rs b/apk_frauder/src/lib.rs index cce34a1..cf38037 100644 --- a/apk_frauder/src/lib.rs +++ b/apk_frauder/src/lib.rs @@ -2,10 +2,12 @@ use std::io::{Read, Seek, SeekFrom}; use androscalpel_serializer::Serializable; +pub mod apk_signing_block; mod cp437; pub mod end_of_central_directory; pub mod file_header; +use apk_signing_block::*; use end_of_central_directory::*; use file_header::FileHeader; @@ -16,6 +18,7 @@ pub struct ZipFile { pub end_of_central_directory: EndCentralDirectory, pub zip64_end_of_central_directory: Option, pub files: Vec, + pub apk_sign_block: Option, pub data: T, } @@ -59,6 +62,7 @@ impl ZipFile { zip64_end_of_central_directory, data: reader, files: vec![], + apk_sign_block: None, }; zip_file .data @@ -74,9 +78,28 @@ impl ZipFile { zip_file.files.push(file_header); } assert_eq!(size_read, cd_size); - for f in &zip_file.files { - println!("{f:#?}"); + if zip_file.get_ed_offset() > 16 { + zip_file + .data + .seek(SeekFrom::Start(zip_file.get_ed_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_ed_offset() - 16 - 8)) + .unwrap(); + let block_size = u64::deserialize(&mut zip_file.data).unwrap(); + zip_file + .data + .seek(SeekFrom::Start(zip_file.get_ed_offset() - block_size - 8)) + .unwrap(); + + zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok(); + } } + //println!("{:?}", zip_file.apk_sign_block); + zip_file } @@ -160,4 +183,43 @@ impl ZipFile { pub fn get_file_names(&self) -> Vec { 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<&FileHeader> { + 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() + } } diff --git a/apk_frauder/src/main.rs b/apk_frauder/src/main.rs index 66fd308..56b3fd7 100644 --- a/apk_frauder/src/main.rs +++ b/apk_frauder/src/main.rs @@ -2,12 +2,26 @@ use apk_frauder::ZipFile; 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 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); - println!("{}", zip_file.get_file_names().join("\n")); - println!( + //println!("{}", zip_file.get_file_names().join("\n")); + /*println!( "uncompressed size: {}", zip_file.files[0].get_uncompressed_size() + );*/ + println!( + "{}", + zip_file + .get_jar_sig_files() + .iter() + .map(|f| f.get_name()) + .collect::>() + .join("\n") ); + if zip_file.is_signed_v2() { + println!("Signed >= v2"); + } else { + println!("Not signed whith scheme >= v2"); + } }