detect apk signatur
This commit is contained in:
parent
98f00c4066
commit
fafbdb6537
3 changed files with 138 additions and 6 deletions
56
apk_frauder/src/apk_signing_block.rs
Normal file
56
apk_frauder/src/apk_signing_block.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue