diff --git a/apk-parser/src/signing_block.rs b/apk-parser/src/signing_block.rs index a312bc0..88b54ca 100644 --- a/apk-parser/src/signing_block.rs +++ b/apk-parser/src/signing_block.rs @@ -35,20 +35,20 @@ impl ApkSigningBlock { for (k, v) in &self.data { match *k { V2_SIG_BLOCK_ID => { - let v2 = get_length_prefixed_u32_sequence(v)?; + let v2_block = remove_prefix_layers(v)?; + let v2_block = get_lv_sequence(v2_block)?; ensure!( - v2.len() == 1, - "Expected 1 element in signing block got {}", - v2.len() + v2_block.len() == 3, + "Expected 3 elements in signing block got {}", + v2_block.len() ); - - let v2 = get_length_prefixed_u32_sequence(v2[0])?; - let signed_data = get_sequence(v2[0])?; + let signed_data = get_lv_sequence(v2_block[0])?; let digests = get_sequence_kv(signed_data[0])?; - let certificates = get_sequence(signed_data[1])?; + let certificates = get_lv_sequence(signed_data[1])?; let attributes = get_sequence_kv(signed_data[2])?; - let signatures = get_sequence_kv(v2[1])?; - let public_key = v2[2]; + + let signatures = get_sequence_kv(v2_block[1])?; + let public_key = v2_block[2]; let digests: HashMap = HashMap::from_iter(digests); sigs.push(ApkSignatureBlock::V2 { attributes: HashMap::from_iter( @@ -60,23 +60,20 @@ impl ApkSigningBlock { }); } V3_SIG_BLOCK_ID => { - let v3 = get_length_prefixed_u32_sequence(v)?; - ensure!( - v3.len() == 1, - "Expected 1 element in signing block got {}", - v3.len() - ); + let mut v = &v[4..]; + let mut v3_block = take_lv_u32(&mut v)?; - let v3 = get_length_prefixed_u32_sequence(v3[0])?; - let signed_data = get_sequence(v3[0])?; - let digests = get_sequence_kv(signed_data[0])?; - let certificates = get_sequence(signed_data[1])?; - let min_sdk_signed = u32::from_le_bytes(signed_data[2].try_into()?); - let max_sdk_signed = u32::from_le_bytes(signed_data[3].try_into()?); - let attributes = get_sequence_kv(signed_data[4])?; + let mut signed_data = take_lv_u32(&mut v3_block)?; + let digests = get_sequence_kv(take_lv_u32(&mut signed_data)?)?; + let certificates = get_lv_sequence(take_lv_u32(&mut signed_data)?)?; + let min_sdk_signed = u32::from_le_bytes(signed_data[..4].try_into()?); + let max_sdk_signed = u32::from_le_bytes(signed_data[4..8].try_into()?); + signed_data = &signed_data[8..]; + let attributes = get_sequence_kv(take_lv_u32(&mut signed_data)?)?; - let min_sdk = u32::from_le_bytes(v3[1].try_into()?); - let max_sdk = u32::from_le_bytes(v3[2].try_into()?); + let min_sdk = u32::from_le_bytes(v3_block[..4].try_into()?); + let max_sdk = u32::from_le_bytes(v3_block[4..8].try_into()?); + v3_block = &v3_block[8..]; ensure!( min_sdk_signed == min_sdk, @@ -91,8 +88,8 @@ impl ApkSigningBlock { max_sdk ); - let signatures = get_sequence_kv(v3[3])?; - let public_key = v3[4]; + let signatures = get_sequence_kv(take_lv_u32(&mut v3_block)?)?; + let public_key = take_lv_u32(&mut v3_block)?; let digests: HashMap = HashMap::from_iter(digests); sigs.push(ApkSignatureBlock::V3 { @@ -257,7 +254,7 @@ where let mut magic_buf = [0u8; 16]; loop { let magic_pos = zip.seek(SeekFrom::Current(-17))?; - if magic_pos <= 4 { + if magic_pos <= 16 { bail!("Failed to find signing block"); } @@ -276,19 +273,19 @@ where size2 ); - let mut data_bytes = size1 - 8 - 16; - let mut sigs = Vec::new(); + let mut data_bytes = size2 - 16 - 8; + let mut blocks = Vec::new(); loop { let (k, v) = read_u64_length_prefixed_kv(zip)?; data_bytes -= (v.len() + 4 + 8) as u64; - sigs.push((k, v)); + blocks.push((k, v)); if data_bytes == 0 { break; } } zip.seek(SeekFrom::Start(0))?; - return Ok(ApkSigningBlock { data: sigs }); + return Ok(ApkSigningBlock { data: blocks }); } } } @@ -302,41 +299,70 @@ where let k = file.read_u32::()?; let v_len = kv_len as usize - 4; let mut v = vec![0; v_len]; - file.read_exact(v.as_mut_slice())?; + file.read_exact(&mut v)?; Ok((k, v)) } #[inline] -fn get_u64_length_prefixed_kv(slice: &[u8]) -> Result<(u32, &[u8])> { - let kv_len = u64::from_le_bytes(slice[..8].try_into()?); - let k = u32::from_le_bytes(slice[8..12].try_into()?); - Ok((k, &slice[12..(kv_len as usize - 12)])) +fn get_lv_u32_kv(slice: &[u8]) -> Result<(u32, &[u8])> { + let data = get_lv_u32(slice)?; + let k = u32::from_le_bytes(data[0..4].try_into()?); + Ok((k, &data[4..])) } #[inline] -fn get_u32_length_prefixed_kv(slice: &[u8]) -> Result<(u32, &[u8])> { - let kv_len = u32::from_le_bytes(slice[..4].try_into()?); - let k = u32::from_le_bytes(slice[4..8].try_into()?); - Ok((k, &slice[8..(kv_len as usize - 8)])) -} - -#[inline] -fn get_length_prefixed_u32(slice: &[u8]) -> Result<&[u8]> { - let len = u32::from_le_bytes(slice[..4].try_into()?); +fn get_lv_u32(slice: &[u8]) -> Result<&[u8]> { + let len = u32::from_le_bytes(slice[0..4].try_into()?); + ensure!( + len <= (slice.len() - 4) as u32, + "Invalid LV sequence {} > {}", + len, + slice.len(), + ); Ok(&slice[4..4 + len as usize]) } #[inline] -fn get_length_prefixed_u32_sequence(slice: &[u8]) -> Result> { - let sequence_len = u32::from_le_bytes(slice[..4].try_into()?); - get_sequence(&slice[4..4 + sequence_len as usize]) +fn take_lv_u32<'a>(slice: &mut &'a[u8]) -> Result<&'a [u8]> { + let len = u32::from_le_bytes(slice[..4].try_into()?); + ensure!( + len <= (slice.len() - 4) as u32, + "Invalid LV sequence {} > {}", + len, + slice.len(), + ); + let (a, b) = slice[4..].split_at(len as usize); + *slice = b; + Ok(a) } +/// Remove 1 or more prefix layers +/// IDK why android needs 3 layers of prefixed lengths, maybe its just how it was written in Java, +/// but it makes no logical sense: +/// ```yaml +/// L: (1000) +/// L: (996) +/// L: V (740) - SEQ +/// L: V (252) - SEQ +/// ``` #[inline] -fn get_sequence(mut slice: &[u8]) -> Result> { +fn remove_prefix_layers(mut slice: &[u8]) -> Result<&[u8]> { + let l1 = u32::from_le_bytes(slice[..4].try_into()?); + loop { + slice = &slice[4..]; + let l2 = u32::from_le_bytes(slice[..4].try_into()?); + if l1 != l2 + 4 { + return Ok(slice); + } + } +} + +/// Read 0 or more length prefixed values until the slice is empty +#[inline] +fn get_lv_sequence(mut slice: &[u8]) -> Result> { let mut ret = Vec::new(); while slice.len() >= 4 { - let data = get_length_prefixed_u32(slice)?; + let data = get_lv_u32(&slice[..])?; let r_len = data.len() + 4; slice = &slice[r_len..]; ret.push(data); @@ -346,7 +372,7 @@ fn get_sequence(mut slice: &[u8]) -> Result> { #[inline] fn get_sequence_kv(slice: &[u8]) -> Result> { - let seq = get_sequence(slice)?; + let seq = get_lv_sequence(slice)?; Ok(seq .into_iter() .map(|s| { diff --git a/src/repo/mod.rs b/src/repo/mod.rs index f30d6af..0c647b3 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -71,26 +71,26 @@ impl TryInto for RepoArtifact { match self.metadata { ArtifactMetadata::APK { manifest, - signatures, + signature_blocks: signatures, } => { for signature in signatures { match signature { ApkSignatureBlock::Unknown { .. } => { warn!("No signature found in metadata"); } - ApkSignatureBlock::V2 { signatures, .. } => { - for signature in signatures { + ApkSignatureBlock::V2 { certificates, .. } => { + for certificate in certificates { b = b.tag(Tag::parse([ "apk_signature_hash", - &hex::encode(signature.digest), + &hex::encode(Sha256::digest(certificate)), ])?); } } - ApkSignatureBlock::V3 { signatures, .. } => { - for signature in signatures { + ApkSignatureBlock::V3 { certificates, .. } => { + for certificate in certificates { b = b.tag(Tag::parse([ "apk_signature_hash", - &hex::encode(signature.digest), + &hex::encode(Sha256::digest(certificate)), ])?); } } @@ -124,7 +124,7 @@ impl TryInto for RepoArtifact { pub enum ArtifactMetadata { APK { manifest: AndroidManifest, - signatures: Vec, + signature_blocks: Vec, }, } @@ -133,7 +133,7 @@ impl Display for ArtifactMetadata { match self { ArtifactMetadata::APK { manifest, - signatures, + signature_blocks: signatures, } => { write!( f, @@ -173,6 +173,7 @@ impl Display for Platform { Architecture::ARM64 => "arm64-v8a", Architecture::X86 => "x86", Architecture::X86_64 => "x86_64", + Architecture::Universal => "universal", } ), Platform::IOS { arch } => write!( @@ -220,6 +221,7 @@ impl Display for Platform { #[derive(Debug, Clone)] pub enum Architecture { + Universal, ARMv7, ARM64, X86, @@ -233,6 +235,7 @@ impl Display for Architecture { Architecture::ARM64 => write!(f, "arm64-v8a"), Architecture::X86 => write!(f, "x86"), Architecture::X86_64 => write!(f, "x86_64"), + Architecture::Universal => write!(f, "universal"), } } } @@ -396,8 +399,6 @@ fn load_apk_artifact(path: &Path) -> Result { }) .collect(); - ensure!(lib_arch.len() == 1, "Unknown library architecture"); - Ok(RepoArtifact { name: path.file_name().unwrap().to_str().unwrap().to_string(), size: path.metadata()?.len(), @@ -405,17 +406,21 @@ fn load_apk_artifact(path: &Path) -> Result { hash: hash_file(path)?, content_type: "application/vnd.android.package-archive".to_string(), platform: Platform::Android { - arch: match lib_arch.iter().next().unwrap().as_str() { - "arm64-v8a" => Architecture::ARM64, - "armeabi-v7a" => Architecture::ARMv7, - "x86_64" => Architecture::X86_64, - "x86" => Architecture::X86, - v => bail!("unknown architecture: {v}"), + arch: if lib_arch.is_empty() { + Architecture::Universal + } else { + match lib_arch.iter().next().unwrap().as_str() { + "arm64-v8a" => Architecture::ARM64, + "armeabi-v7a" => Architecture::ARMv7, + "x86_64" => Architecture::X86_64, + "x86" => Architecture::X86, + v => bail!("unknown architecture: {v}"), + } }, }, metadata: ArtifactMetadata::APK { manifest, - signatures: sig_block.get_signatures()?, + signature_blocks: sig_block.get_signatures()?, }, }) } @@ -423,7 +428,7 @@ fn load_apk_artifact(path: &Path) -> Result { fn hash_file(path: &Path) -> Result> { let mut file = File::open(path)?; let mut hash = Sha256::default(); - let mut buf = Vec::with_capacity(4096); + let mut buf = vec![0; 4096]; while let Ok(r) = file.read(&mut buf) { if r == 0 { break; @@ -471,7 +476,11 @@ mod tests { let path = "/home/kieran/Downloads/snort-arm64-v8a-v0.3.0.apk"; let apk = load_apk_artifact(&PathBuf::from(path))?; - eprint!("{}", apk); - Ok(()) + + eprintln!("{:?}", apk); + if let ArtifactMetadata::APK { .. } = apk.metadata { + return Ok(()); + } + bail!("missing apk metadata"); } }