detect apk signatur

This commit is contained in:
Jean-Marie Mineau 2024-01-15 14:42:26 +01:00
parent 98f00c4066
commit fafbdb6537
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
3 changed files with 138 additions and 6 deletions

View file

@ -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<u8>,
}
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<Self> {
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
}
}

View file

@ -2,10 +2,12 @@ use std::io::{Read, Seek, SeekFrom};
use androscalpel_serializer::Serializable; use androscalpel_serializer::Serializable;
pub mod apk_signing_block;
mod cp437; mod cp437;
pub mod end_of_central_directory; pub mod end_of_central_directory;
pub mod file_header; pub mod file_header;
use apk_signing_block::*;
use end_of_central_directory::*; use end_of_central_directory::*;
use file_header::FileHeader; use file_header::FileHeader;
@ -16,6 +18,7 @@ pub struct ZipFile<T: Read + Seek> {
pub end_of_central_directory: EndCentralDirectory, pub end_of_central_directory: EndCentralDirectory,
pub zip64_end_of_central_directory: Option<Zip64EndCentralDirectory>, pub zip64_end_of_central_directory: Option<Zip64EndCentralDirectory>,
pub files: Vec<FileHeader>, pub files: Vec<FileHeader>,
pub apk_sign_block: Option<ApkSigningBlock>,
pub data: T, pub data: T,
} }
@ -59,6 +62,7 @@ impl<T: Read + Seek> ZipFile<T> {
zip64_end_of_central_directory, zip64_end_of_central_directory,
data: reader, data: reader,
files: vec![], files: vec![],
apk_sign_block: None,
}; };
zip_file zip_file
.data .data
@ -74,9 +78,28 @@ impl<T: Read + Seek> ZipFile<T> {
zip_file.files.push(file_header); zip_file.files.push(file_header);
} }
assert_eq!(size_read, cd_size); assert_eq!(size_read, cd_size);
for f in &zip_file.files { if zip_file.get_ed_offset() > 16 {
println!("{f:#?}"); 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 zip_file
} }
@ -160,4 +183,43 @@ impl<T: Read + Seek> ZipFile<T> {
pub fn get_file_names(&self) -> Vec<String> { pub fn get_file_names(&self) -> Vec<String> {
self.files.iter().map(|f| f.get_name()).collect() 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()
}
} }

View file

@ -2,12 +2,26 @@ use apk_frauder::ZipFile;
use std::fs::File; use std::fs::File;
fn main() { fn main() {
//let file = File::open("app-release.apk").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 file = File::open("tst_64.zip").expect("failed to open file");
let zip_file = ZipFile::new(file); let zip_file = ZipFile::new(file);
println!("{}", zip_file.get_file_names().join("\n")); //println!("{}", zip_file.get_file_names().join("\n"));
println!( /*println!(
"uncompressed size: {}", "uncompressed size: {}",
zip_file.files[0].get_uncompressed_size() zip_file.files[0].get_uncompressed_size()
);*/
println!(
"{}",
zip_file
.get_jar_sig_files()
.iter()
.map(|f| f.get_name())
.collect::<Vec<_>>()
.join("\n")
); );
if zip_file.is_signed_v2() {
println!("Signed >= v2");
} else {
println!("Not signed whith scheme >= v2");
}
} }