From 3374e16cc055898f33fc7562a93c7bf17de8683a Mon Sep 17 00:00:00 2001 From: NJL <23+njl@noreply.git.churchofmalware.org> Date: Sun, 12 Jul 2026 19:37:19 +0000 Subject: [PATCH] Upload files to "/" --- Cargo.toml | 13 ++ axml.rs | 278 +++++++++++++++++++++++ bigint.rs | 208 +++++++++++++++++ cert.rs | 63 +++++ deflate.rs | 657 +++++++++++++++++++++++++++++++++++++++++++++++++++++ keydata.rs | 5 + main.rs | 85 +++++++ sha256.rs | 83 +++++++ sign_v2.rs | 198 ++++++++++++++++ zip.rs | 130 +++++++++++ 10 files changed, 1720 insertions(+) create mode 100644 Cargo.toml create mode 100644 axml.rs create mode 100644 bigint.rs create mode 100644 cert.rs create mode 100644 deflate.rs create mode 100644 keydata.rs create mode 100644 main.rs create mode 100644 sha256.rs create mode 100644 sign_v2.rs create mode 100644 zip.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e251180 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "apk" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "apk" +path = "src/main.rs" + +[profile.release] +opt-level = "z" +lto = true +panic = "abort" diff --git a/axml.rs b/axml.rs new file mode 100644 index 0000000..5b8d2d4 --- /dev/null +++ b/axml.rs @@ -0,0 +1,278 @@ +const RES_XML_TYPE: u16 = 0x0003; +const RES_STRING_POOL_TYPE: u16 = 0x0001; +const RES_XML_RESOURCE_MAP_TYPE: u16 = 0x0180; +const RES_XML_START_NAMESPACE_TYPE: u16 = 0x0100; +const RES_XML_END_NAMESPACE_TYPE: u16 = 0x0101; +const RES_XML_START_ELEMENT_TYPE: u16 = 0x0102; +const RES_XML_END_ELEMENT_TYPE: u16 = 0x0103; + +const NO_COMMENT: u32 = 0; + +const ATTR_RES_IDS: &[(&str, u32)] = &[ + ("versionCode", 0x0101021b), + ("versionName", 0x0101021c), + ("minSdkVersion", 0x0101020c), + ("targetSdkVersion", 0x01010270), + ("label", 0x01010001), + ("name", 0x01010003), + ("exported", 0x01010010), + ("hasCode", 0x0101000c), + ("configChanges", 0x0101001f), +]; + +pub const TYPE_STRING: u8 = 0x03; +pub const TYPE_INT_DEC: u8 = 0x10; +pub const TYPE_INT_BOOL: u8 = 0x12; + +struct StringPool { + strings: Vec, + index: std::collections::HashMap, +} + +impl StringPool { + fn new() -> Self { + StringPool { + strings: Vec::new(), + index: std::collections::HashMap::new(), + } + } + fn intern(&mut self, s: &str) -> u32 { + if let Some(&i) = self.index.get(s) { + return i; + } + let i = self.strings.len() as u32; + self.strings.push(s.to_string()); + self.index.insert(s.to_string(), i); + i + } + fn encode(&self) -> Vec { + const UTF8_FLAG: u32 = 1 << 8; + + let mut offsets = Vec::new(); + let mut string_data = Vec::new(); + for s in &self.strings { + offsets.push(string_data.len() as u32); + let utf16_units = s.encode_utf16().count(); + let bytes = s.as_bytes(); + push_utf8_len(&mut string_data, utf16_units); + push_utf8_len(&mut string_data, bytes.len()); + string_data.extend_from_slice(bytes); + string_data.push(0); + } + while string_data.len() % 4 != 0 { + string_data.push(0); + } + + let string_count = self.strings.len() as u32; + let header_size = 28u32; + let offsets_size = string_count * 4; + let strings_start = header_size + offsets_size; + let chunk_size = strings_start + string_data.len() as u32; + + let mut out = Vec::new(); + push_u16(&mut out, RES_STRING_POOL_TYPE); + push_u16(&mut out, 28); + push_u32(&mut out, chunk_size); + push_u32(&mut out, string_count); + push_u32(&mut out, 0); + push_u32(&mut out, UTF8_FLAG); + push_u32(&mut out, strings_start); + push_u32(&mut out, 0); + for o in offsets { + push_u32(&mut out, o); + } + out.extend_from_slice(&string_data); + out + } +} + +struct Attr { + ns: Option, + name: u32, + raw_value: i32, + typ: u8, + data: u32, +} + +pub struct AxmlBuilder { + pool: StringPool, + body: Vec, + android_ns_uri: u32, + android_ns_prefix: u32, + line: u32, +} + +impl AxmlBuilder { + pub fn new() -> Self { + AxmlBuilder { + pool: StringPool::new(), + body: Vec::new(), + android_ns_uri: 0xFFFF_FFFF, + android_ns_prefix: 0xFFFF_FFFF, + line: 1, + } + } + + fn ns_uri(&mut self) -> u32 { + if self.android_ns_uri == 0xFFFF_FFFF { + self.android_ns_uri = self.pool.intern("http://schemas.android.com/apk/res/android"); + } + self.android_ns_uri + } + + pub fn intern(&mut self, s: &str) -> u32 { + self.pool.intern(s) + } + + pub const EMIT_NAMESPACE_CHUNKS: bool = false; + + pub fn start(&mut self) { + if !Self::EMIT_NAMESPACE_CHUNKS { + return; + } + let mut c = Vec::new(); + push_u16(&mut c, RES_XML_START_NAMESPACE_TYPE); + push_u16(&mut c, 16); + push_u32(&mut c, 24); + push_u32(&mut c, self.line); + push_u32(&mut c, NO_COMMENT); + push_u32(&mut c, self.android_ns_prefix); + push_u32(&mut c, self.android_ns_uri); + self.body.extend_from_slice(&c); + } + + pub fn end(&mut self) { + if !Self::EMIT_NAMESPACE_CHUNKS { + return; + } + let mut c = Vec::new(); + push_u16(&mut c, RES_XML_END_NAMESPACE_TYPE); + push_u16(&mut c, 16); + push_u32(&mut c, 24); + push_u32(&mut c, self.line); + push_u32(&mut c, 0xFFFF_FFFF); + push_u32(&mut c, self.android_ns_prefix); + push_u32(&mut c, self.android_ns_uri); + self.body.extend_from_slice(&c); + } + + pub fn open(&mut self, tag: u32, attrs: &[(u32, u8, u32, i32)]) { + let with_ns: Vec<(u32, u8, u32, i32, bool)> = + attrs.iter().map(|&(n, t, d, r)| (n, t, d, r, true)).collect(); + self.open_ns(tag, &with_ns); + } + + pub fn open_ns(&mut self, tag: u32, attrs: &[(u32, u8, u32, i32, bool)]) { + let uri = if attrs.iter().any(|a| a.4) { Some(self.ns_uri()) } else { None }; + let attr_structs: Vec = attrs + .iter() + .map(|&(name, typ, data, raw, android_ns)| Attr { + ns: if android_ns { uri } else { None }, + name, + raw_value: raw, + typ, + data, + }) + .collect(); + + let mut c = Vec::new(); + let attr_count = attr_structs.len() as u32; + let chunk_size = 16 + 20 + attr_count * 20; + push_u16(&mut c, RES_XML_START_ELEMENT_TYPE); + push_u16(&mut c, 16); + push_u32(&mut c, chunk_size); + push_u32(&mut c, self.line); + push_u32(&mut c, NO_COMMENT); + push_u32(&mut c, 0); + push_u32(&mut c, tag); + push_u16(&mut c, 20); + push_u16(&mut c, 20); + push_u16(&mut c, attr_count as u16); + push_u16(&mut c, 0); + push_u16(&mut c, 0); + push_u16(&mut c, 0); + + for a in &attr_structs { + push_u32(&mut c, a.ns.unwrap_or(0xFFFF_FFFF)); + push_u32(&mut c, a.name); + push_u32(&mut c, a.raw_value as u32); + push_u16(&mut c, 8); + c.push(0); + c.push(a.typ); + push_u32(&mut c, a.data); + } + self.body.extend_from_slice(&c); + } + + pub fn close(&mut self, tag: u32) { + let mut c = Vec::new(); + push_u16(&mut c, RES_XML_END_ELEMENT_TYPE); + push_u16(&mut c, 16); + push_u32(&mut c, 24); + push_u32(&mut c, self.line); + push_u32(&mut c, NO_COMMENT); + push_u32(&mut c, 0); + push_u32(&mut c, tag); + self.body.extend_from_slice(&c); + } + + pub fn res_id_for(name: &str) -> Option { + ATTR_RES_IDS.iter().find(|(n, _)| *n == name).map(|(_, id)| *id) + } + + pub fn finish(self, attr_name_to_resid: &[(u32, u32)]) -> Vec { + let pool_chunk = self.pool.encode(); + + let max_attr_idx = attr_name_to_resid + .iter() + .map(|&(str_idx, _)| str_idx as usize) + .max() + .unwrap_or(0); + let mut resmap = vec![0u32; max_attr_idx + 1]; + for &(str_idx, resid) in attr_name_to_resid { + if (str_idx as usize) < resmap.len() { + resmap[str_idx as usize] = resid; + } + } + let mut resmap_chunk = Vec::new(); + let resmap_size = 8 + resmap.len() as u32 * 4; + push_u16(&mut resmap_chunk, RES_XML_RESOURCE_MAP_TYPE); + push_u16(&mut resmap_chunk, 8); + push_u32(&mut resmap_chunk, resmap_size); + for r in &resmap { + push_u32(&mut resmap_chunk, *r); + } + + let inner_len = pool_chunk.len() + resmap_chunk.len() + self.body.len(); + let total = 8 + inner_len; + let mut out = Vec::new(); + push_u16(&mut out, RES_XML_TYPE); + push_u16(&mut out, 8); + push_u32(&mut out, total as u32); + out.extend_from_slice(&pool_chunk); + out.extend_from_slice(&resmap_chunk); + out.extend_from_slice(&self.body); + out + } +} + +pub mod val { + #[allow(unused_imports)] + pub use super::{TYPE_INT_BOOL, TYPE_INT_DEC, TYPE_STRING}; +} + +fn push_u16(v: &mut Vec, x: u16) { + v.extend_from_slice(&x.to_le_bytes()); +} +fn push_u32(v: &mut Vec, x: u32) { + v.extend_from_slice(&x.to_le_bytes()); +} + +fn push_utf8_len(v: &mut Vec, len: usize) { + if len < 0x80 { + v.push(len as u8); + } else { + v.push(((len >> 8) & 0x7f) as u8 | 0x80); + v.push((len & 0xff) as u8); + } +} diff --git a/bigint.rs b/bigint.rs new file mode 100644 index 0000000..83b88af --- /dev/null +++ b/bigint.rs @@ -0,0 +1,208 @@ +#[derive(Clone, Debug)] +pub struct BigUint { + limbs: Vec, +} + +impl BigUint { + pub fn zero() -> Self { + BigUint { limbs: vec![] } + } + + pub fn from_u32(x: u32) -> Self { + if x == 0 { + BigUint::zero() + } else { + BigUint { limbs: vec![x] } + } + } + + pub fn from_be_bytes(bytes: &[u8]) -> Self { + let mut b = bytes.to_vec(); + while b.len() > 1 && b[0] == 0 { + b.remove(0); + } + let mut limbs = Vec::new(); + let mut i = b.len(); + while i > 0 { + let start = i.saturating_sub(4); + let mut limb = 0u32; + for (k, &byte) in b[start..i].iter().enumerate() { + let shift = (b[start..i].len() - 1 - k) * 8; + limb |= (byte as u32) << shift; + } + limbs.push(limb); + i = start; + } + let mut r = BigUint { limbs }; + r.normalize(); + r + } + + pub fn to_be_bytes(&self) -> Vec { + if self.limbs.is_empty() { + return vec![0]; + } + let mut out = Vec::new(); + for &limb in self.limbs.iter().rev() { + out.extend_from_slice(&limb.to_be_bytes()); + } + while out.len() > 1 && out[0] == 0 { + out.remove(0); + } + out + } + + pub fn to_be_bytes_padded(&self, len: usize) -> Vec { + let mut b = self.to_be_bytes(); + if b.len() > len { + b = b[b.len() - len..].to_vec(); + } + while b.len() < len { + b.insert(0, 0); + } + b + } + + fn normalize(&mut self) { + while let Some(&0) = self.limbs.last() { + self.limbs.pop(); + } + } + + pub fn is_zero(&self) -> bool { + self.limbs.is_empty() + } + + fn bit_len(&self) -> usize { + if self.limbs.is_empty() { + return 0; + } + let top = *self.limbs.last().unwrap(); + (self.limbs.len() - 1) * 32 + (32 - top.leading_zeros() as usize) + } + + fn bit(&self, i: usize) -> bool { + let limb = i / 32; + let off = i % 32; + if limb >= self.limbs.len() { + return false; + } + (self.limbs[limb] >> off) & 1 == 1 + } + + fn cmp(&self, other: &BigUint) -> std::cmp::Ordering { + use std::cmp::Ordering::*; + if self.limbs.len() != other.limbs.len() { + return self.limbs.len().cmp(&other.limbs.len()); + } + for i in (0..self.limbs.len()).rev() { + match self.limbs[i].cmp(&other.limbs[i]) { + Equal => continue, + ord => return ord, + } + } + Equal + } + + fn sub(&self, other: &BigUint) -> BigUint { + let mut out = Vec::new(); + let mut borrow = 0i64; + for i in 0..self.limbs.len() { + let a = self.limbs[i] as i64; + let b = *other.limbs.get(i).unwrap_or(&0) as i64; + let mut diff = a - b - borrow; + if diff < 0 { + diff += 1 << 32; + borrow = 1; + } else { + borrow = 0; + } + out.push(diff as u32); + } + let mut r = BigUint { limbs: out }; + r.normalize(); + r + } + + fn shl1(&self) -> BigUint { + let mut out = Vec::with_capacity(self.limbs.len() + 1); + let mut carry = 0u32; + for &limb in &self.limbs { + let new = (limb << 1) | carry; + carry = limb >> 31; + out.push(new); + } + if carry != 0 { + out.push(carry); + } + let mut r = BigUint { limbs: out }; + r.normalize(); + r + } + + fn mul(&self, other: &BigUint) -> BigUint { + if self.is_zero() || other.is_zero() { + return BigUint::zero(); + } + let mut out = vec![0u64; self.limbs.len() + other.limbs.len()]; + for (i, &a) in self.limbs.iter().enumerate() { + let mut carry = 0u64; + for (j, &b) in other.limbs.iter().enumerate() { + let cur = out[i + j] + (a as u64) * (b as u64) + carry; + out[i + j] = cur & 0xFFFF_FFFF; + carry = cur >> 32; + } + out[i + other.limbs.len()] += carry; + } + let limbs: Vec = out.iter().map(|&x| x as u32).collect(); + let mut r = BigUint { limbs }; + r.normalize(); + r + } + + fn rem(&self, m: &BigUint) -> BigUint { + use std::cmp::Ordering::*; + if m.is_zero() { + panic!("mod by zero"); + } + if self.cmp(m) == Less { + return self.clone(); + } + let mut rem = BigUint::zero(); + for i in (0..self.bit_len()).rev() { + rem = rem.shl1(); + if self.bit(i) { + if rem.limbs.is_empty() { + rem.limbs.push(1); + } else { + rem.limbs[0] |= 1; + } + } + if rem.cmp(m) != Less { + rem = rem.sub(m); + } + } + rem.normalize(); + rem + } + + fn mulmod(&self, other: &BigUint, m: &BigUint) -> BigUint { + self.mul(other).rem(m) + } + + pub fn modpow(&self, exp: &BigUint, m: &BigUint) -> BigUint { + if m.cmp(&BigUint::from_u32(1)) == std::cmp::Ordering::Equal { + return BigUint::zero(); + } + let mut result = BigUint::from_u32(1); + let mut base = self.rem(m); + let bits = exp.bit_len(); + for i in 0..bits { + if exp.bit(i) { + result = result.mulmod(&base, m); + } + base = base.mulmod(&base, m); + } + result + } +} diff --git a/cert.rs b/cert.rs new file mode 100644 index 0000000..9796709 --- /dev/null +++ b/cert.rs @@ -0,0 +1,63 @@ +use crate::keydata::SPKI_DER; + +pub fn build_min_cert() -> Vec { + let sig_alg = der_seq(&der_oid(&[0x2a])); + + let name = der_seq(&[]); + + let not_before = der_tlv(0x17, b"000101000000Z"); + let not_after = der_tlv(0x17, b"490101000000Z"); + let validity = der_seq(&[not_before, not_after].concat()); + + let cert_spki = SPKI_DER.to_vec(); + let tbs = der_seq( + &[ + der_int(&[1]), + sig_alg.clone(), + name.clone(), + validity, + name, + cert_spki, + ] + .concat(), + ); + + let sig_bitstring = der_tlv(0x03, &[0x00]); + + der_seq(&[tbs, sig_alg, sig_bitstring].concat()) +} + +fn der_len(len: usize) -> Vec { + if len < 0x80 { + vec![len as u8] + } else { + let mut b = Vec::new(); + let mut l = len; + while l > 0 { + b.insert(0, (l & 0xff) as u8); + l >>= 8; + } + let mut out = vec![0x80 | b.len() as u8]; + out.extend_from_slice(&b); + out + } +} +fn der_tlv(tag: u8, body: &[u8]) -> Vec { + let mut out = vec![tag]; + out.extend_from_slice(&der_len(body.len())); + out.extend_from_slice(body); + out +} +fn der_seq(body: &[u8]) -> Vec { + der_tlv(0x30, body) +} +fn der_oid(bytes: &[u8]) -> Vec { + der_tlv(0x06, bytes) +} +fn der_int(bytes: &[u8]) -> Vec { + let mut b = bytes.to_vec(); + if !b.is_empty() && b[0] & 0x80 != 0 { + b.insert(0, 0x00); + } + der_tlv(0x02, &b) +} diff --git a/deflate.rs b/deflate.rs new file mode 100644 index 0000000..446e2f2 --- /dev/null +++ b/deflate.rs @@ -0,0 +1,657 @@ +enum Token { + Lit(u8), + Match { len: u16, dist: u16 }, +} + +pub fn deflate(input: &[u8]) -> Vec { + let mut best: Option> = None; + let consider = |block: Vec, best: &mut Option>| { + if best.as_ref().map_or(true, |b| block.len() < b.len()) { + *best = Some(block); + } + }; + + let lazy = lz77(input); + consider(emit_dynamic_block(&lazy), &mut best); + consider(emit_fixed_block(&lazy), &mut best); + + let matches = build_match_table(input); + let default_parse = lz77_optimal(input, &matches, &default_costs()); + consider(emit_dynamic_block(&default_parse), &mut best); + consider(emit_fixed_block(&default_parse), &mut best); + + for seed in [&default_parse, &lazy] { + let mut last = lz77_optimal(input, &matches, &costs_from_tokens(seed)); + for _ in 0..30 { + consider(emit_dynamic_block(&last), &mut best); + consider(emit_fixed_block(&last), &mut best); + let next = lz77_optimal(input, &matches, &costs_from_tokens(&last)); + last = next; + } + } + + best.unwrap() +} + +struct Costs { + lit: [u32; 288], + dist: [u32; 30], +} + +fn default_costs() -> Costs { + let mut lit = [9u32; 288]; + for i in 0..144 { + lit[i] = 8; + } + for i in 256..288 { + lit[i] = 8; + } + Costs { lit, dist: [5u32; 30] } +} + +fn costs_from_tokens(tokens: &[Token]) -> Costs { + let (lit_freq, dist_freq) = tally(tokens); + let lit_lens = huffman_lengths(&lit_freq, 15); + let dist_lens = huffman_lengths(&dist_freq, 15); + let mut lit = [0u32; 288]; + for i in 0..288 { + lit[i] = if lit_lens[i] > 0 { lit_lens[i] as u32 } else { 15 }; + } + let mut dist = [0u32; 30]; + for i in 0..30 { + dist[i] = if dist_lens[i] > 0 { dist_lens[i] as u32 } else { 15 }; + } + Costs { lit, dist } +} + +fn build_match_table(input: &[u8]) -> (Vec, Vec) { + let n = input.len(); + let mut best_len = vec![0u16; n]; + let mut best_dist = vec![0u16; n]; + if n == 0 { + return (best_len, best_dist); + } + let mut head = vec![usize::MAX; 1 << 15]; + let mut prev = vec![usize::MAX; n]; + let hash = |a: u8, b: u8, c: u8| -> usize { + (((a as usize) << 10) ^ ((b as usize) << 5) ^ (c as usize)) & ((1 << 15) - 1) + }; + for i in 0..n { + if i + 3 <= n { + let h = hash(input[i], input[i + 1], input[i + 2]); + let mut j = head[h]; + let mut chain = 4096; + let max_len = (n - i).min(258); + let mut bl = 0; + let mut bd = 0; + while j != usize::MAX && chain > 0 { + if i - j > 32768 { + break; + } + let mut l = 0; + while l < max_len && input[j + l] == input[i + l] { + l += 1; + } + if l > bl { + bl = l; + bd = i - j; + if l >= max_len { + break; + } + } + j = prev[j]; + chain -= 1; + } + best_len[i] = bl as u16; + best_dist[i] = bd as u16; + prev[i] = head[h]; + head[h] = i; + } + } + (best_len, best_dist) +} + +fn lz77_optimal(input: &[u8], matches: &(Vec, Vec), costs: &Costs) -> Vec { + let n = input.len(); + if n == 0 { + return Vec::new(); + } + let (best_len, best_dist) = matches; + + let lit_cost = |b: u8| -> u32 { costs.lit[b as usize] }; + let match_cost = |len: u16, dist: u16| -> u32 { + let (lsym, _, lex) = length_code(len); + let (dsym, _, dex) = dist_code(dist); + costs.lit[lsym as usize] + lex + costs.dist[dsym as usize] + dex + }; + + let mut cost = vec![u32::MAX; n + 1]; + let mut choice_len = vec![0u16; n + 1]; + cost[n] = 0; + for i in (0..n).rev() { + let mut bc = lit_cost(input[i]).saturating_add(cost[i + 1]); + let mut bl = 0u16; + let ml = best_len[i]; + if ml >= 3 { + let d = best_dist[i]; + let mut l = 3u16; + while l <= ml { + let nxt = cost[i + l as usize]; + if nxt != u32::MAX { + let c = match_cost(l, d).saturating_add(nxt); + if c < bc { + bc = c; + bl = l; + } + } + l += 1; + } + } + cost[i] = bc; + choice_len[i] = bl; + } + + let mut tokens = Vec::new(); + let mut i = 0; + while i < n { + let l = choice_len[i]; + if l >= 3 { + tokens.push(Token::Match { len: l, dist: best_dist[i] }); + i += l as usize; + } else { + tokens.push(Token::Lit(input[i])); + i += 1; + } + } + tokens +} + +fn lz77(input: &[u8]) -> Vec { + let n = input.len(); + let mut tokens = Vec::new(); + let mut head = vec![usize::MAX; 1 << 15]; + let mut prev = vec![usize::MAX; n.max(1)]; + + let hash = |a: u8, b: u8, c: u8| -> usize { + (((a as usize) << 10) ^ ((b as usize) << 5) ^ (c as usize)) & ((1 << 15) - 1) + }; + + let find = |p: usize, head: &[usize], prev: &[usize]| -> (usize, usize) { + if p + 3 > n { + return (0, 0); + } + let h = hash(input[p], input[p + 1], input[p + 2]); + let mut j = head[h]; + let mut best_len = 0; + let mut best_dist = 0; + let mut chain = 4096; + let max_len = (n - p).min(258); + while j != usize::MAX && chain > 0 { + if p - j > 32768 { + break; + } + let mut l = 0; + while l < max_len && input[j + l] == input[p + l] { + l += 1; + } + if l > best_len { + best_len = l; + best_dist = p - j; + if l >= max_len { + break; + } + } + j = prev[j]; + chain -= 1; + } + (best_len, best_dist) + }; + let insert = |p: usize, head: &mut Vec, prev: &mut Vec| { + if p + 3 <= n { + let h = hash(input[p], input[p + 1], input[p + 2]); + prev[p] = head[h]; + head[h] = p; + } + }; + + let mut i = 0; + let mut inserted = 0usize; + macro_rules! insert_before { + ($before:expr) => {{ + let before = $before; + while inserted < before && inserted < n { + insert(inserted, &mut head, &mut prev); + inserted += 1; + } + }}; + } + while i < n { + insert_before!(i); + let (mlen, mdist) = find(i, &head, &prev); + + if mlen >= 3 { + if i + 1 < n { + insert_before!(i + 1); + let (next_len, _) = find(i + 1, &head, &prev); + if next_len > mlen { + tokens.push(Token::Lit(input[i])); + i += 1; + continue; + } + } + tokens.push(Token::Match { len: mlen as u16, dist: mdist as u16 }); + i += mlen; + } else { + tokens.push(Token::Lit(input[i])); + i += 1; + } + } + tokens +} + +fn length_code(len: u16) -> (u16, u32, u32) { + const BASE: [(u16, u16, u32); 29] = [ + (257, 3, 0), (258, 4, 0), (259, 5, 0), (260, 6, 0), (261, 7, 0), + (262, 8, 0), (263, 9, 0), (264, 10, 0), (265, 11, 1), (266, 13, 1), + (267, 15, 1), (268, 17, 1), (269, 19, 2), (270, 23, 2), (271, 27, 2), + (272, 31, 2), (273, 35, 3), (274, 43, 3), (275, 51, 3), (276, 59, 3), + (277, 67, 4), (278, 83, 4), (279, 99, 4), (280, 115, 4), (281, 131, 5), + (282, 163, 5), (283, 195, 5), (284, 227, 5), (285, 258, 0), + ]; + let mut chosen = BASE[0]; + for &e in BASE.iter() { + if e.1 <= len { + chosen = e; + } else { + break; + } + } + let (sym, base, ebits) = chosen; + (sym, (len - base) as u32, ebits) +} + +fn dist_code(dist: u16) -> (u16, u32, u32) { + const BASE: [(u16, u16, u32); 30] = [ + (0, 1, 0), (1, 2, 0), (2, 3, 0), (3, 4, 0), (4, 5, 1), (5, 7, 1), + (6, 9, 2), (7, 13, 2), (8, 17, 3), (9, 25, 3), (10, 33, 4), (11, 49, 4), + (12, 65, 5), (13, 97, 5), (14, 129, 6), (15, 193, 6), (16, 257, 7), + (17, 385, 7), (18, 513, 8), (19, 769, 8), (20, 1025, 9), (21, 1537, 9), + (22, 2049, 10), (23, 3073, 10), (24, 4097, 11), (25, 6145, 11), + (26, 8193, 12), (27, 12289, 12), (28, 16385, 13), (29, 24577, 13), + ]; + let mut chosen = BASE[0]; + for &e in BASE.iter() { + if e.1 <= dist { + chosen = e; + } else { + break; + } + } + let (sym, base, ebits) = chosen; + (sym, (dist - base) as u32, ebits) +} + +fn length_extra_bits(sym: u16) -> u32 { + length_code_extra(sym) +} +fn length_code_extra(sym: u16) -> u32 { + match sym { + 257..=264 => 0, + 265..=268 => 1, + 269..=272 => 2, + 273..=276 => 3, + 277..=280 => 4, + 281..=284 => 5, + 285 => 0, + _ => 0, + } +} +fn dist_extra_bits(sym: u16) -> u32 { + match sym { + 0..=3 => 0, + s => ((s / 2) - 1) as u32, + } +} + +fn huffman_lengths(freqs: &[u32], max_bits: usize) -> Vec { + let n = freqs.len(); + let mut lens = vec![0u8; n]; + + let used: Vec = (0..n).filter(|&i| freqs[i] > 0).collect(); + if used.is_empty() { + return lens; + } + if used.len() == 1 { + lens[used[0]] = 1; + return lens; + } + + #[derive(Clone)] + struct Node { + freq: u64, + sym: i32, + left: i32, + right: i32, + } + let mut nodes: Vec = Vec::new(); + let mut heap: Vec = Vec::new(); + for &s in &used { + nodes.push(Node { freq: freqs[s] as u64, sym: s as i32, left: -1, right: -1 }); + heap.push(nodes.len() - 1); + } + let pop_min = |heap: &mut Vec, nodes: &Vec| -> usize { + let mut mi = 0; + for k in 1..heap.len() { + if nodes[heap[k]].freq < nodes[heap[mi]].freq { + mi = k; + } + } + heap.swap_remove(mi) + }; + while heap.len() > 1 { + let a = pop_min(&mut heap, &nodes); + let b = pop_min(&mut heap, &nodes); + let f = nodes[a].freq + nodes[b].freq; + nodes.push(Node { freq: f, sym: -1, left: a as i32, right: b as i32 }); + heap.push(nodes.len() - 1); + } + let root = heap[0]; + + fn depth(nodes: &[Node], idx: i32, d: u8, lens: &mut Vec) { + let node = &nodes[idx as usize]; + if node.sym >= 0 { + lens[node.sym as usize] = d.max(1); + } else { + depth(nodes, node.left, d + 1, lens); + depth(nodes, node.right, d + 1, lens); + } + } + depth(&nodes, root as i32, 0, &mut lens); + + clamp_lengths(&mut lens, max_bits); + lens +} + +fn clamp_lengths(lens: &mut [u8], max_bits: usize) { + let maxb = max_bits as u8; + loop { + let mut over = false; + for l in lens.iter() { + if *l > maxb { + over = true; + break; + } + } + if !over { + break; + } + let mut long_idx = None; + for (i, l) in lens.iter().enumerate() { + if *l > maxb { + long_idx = Some(i); + break; + } + } + let li = long_idx.unwrap(); + let mut donor = None; + for (i, l) in lens.iter().enumerate() { + if *l > 0 && *l < maxb && i != li { + donor = Some(i); + break; + } + } + match donor { + Some(di) => { + lens[li] = maxb; + lens[di] += 1; + } + None => { + lens[li] = maxb; + } + } + } +} + +fn canonical_codes(lens: &[u8]) -> Vec<(u32, u32)> { + let max_len = *lens.iter().max().unwrap_or(&0) as usize; + let mut bl_count = vec![0u32; max_len + 1]; + for &l in lens { + if l > 0 { + bl_count[l as usize] += 1; + } + } + let mut next_code = vec![0u32; max_len + 2]; + let mut code = 0u32; + for bits in 1..=max_len { + code = (code + bl_count[bits - 1]) << 1; + next_code[bits] = code; + } + let mut codes = vec![(0u32, 0u32); lens.len()]; + for (i, &l) in lens.iter().enumerate() { + if l > 0 { + codes[i] = (next_code[l as usize], l as u32); + next_code[l as usize] += 1; + } + } + codes +} + +fn tally(tokens: &[Token]) -> ([u32; 288], [u32; 30]) { + let mut lit = [0u32; 288]; + let mut dst = [0u32; 30]; + for t in tokens { + match t { + Token::Lit(b) => lit[*b as usize] += 1, + Token::Match { len, dist } => { + let (lc, _, _) = length_code(*len); + lit[lc as usize] += 1; + let (dc, _, _) = dist_code(*dist); + dst[dc as usize] += 1; + } + } + } + lit[256] += 1; + (lit, dst) +} + +fn emit_dynamic_block(tokens: &[Token]) -> Vec { + let (lit_freq, dist_freq) = tally(tokens); + + let mut lit_lens = huffman_lengths(&lit_freq, 15); + let mut dist_lens = huffman_lengths(&dist_freq, 15); + + let mut hlit = 286; + while hlit > 257 && lit_lens[hlit - 1] == 0 { + hlit -= 1; + } + let mut hdist = 30; + while hdist > 1 && dist_lens[hdist - 1] == 0 { + hdist -= 1; + } + lit_lens.truncate(hlit); + dist_lens.truncate(hdist); + + let lit_codes = canonical_codes(&lit_lens); + let dist_codes = canonical_codes(&dist_lens); + + let mut cl_seq: Vec = Vec::new(); + cl_seq.extend_from_slice(&lit_lens); + cl_seq.extend_from_slice(&dist_lens); + let (cl_syms, cl_extra) = rle_code_lengths(&cl_seq); + + let mut cl_freq = [0u32; 19]; + for &s in &cl_syms { + cl_freq[s as usize] += 1; + } + let cl_lens_full = huffman_lengths(&cl_freq, 7); + let cl_codes = canonical_codes(&cl_lens_full); + + const ORDER: [usize; 19] = [ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15, + ]; + let mut hclen = 19; + while hclen > 4 && cl_lens_full[ORDER[hclen - 1]] == 0 { + hclen -= 1; + } + + let mut bw = BitWriter::new(); + bw.write_bits(1, 1); + bw.write_bits(0b10, 2); + bw.write_bits((hlit - 257) as u32, 5); + bw.write_bits((hdist - 1) as u32, 5); + bw.write_bits((hclen - 4) as u32, 4); + for k in 0..hclen { + bw.write_bits(cl_lens_full[ORDER[k]] as u32, 3); + } + for (idx, &s) in cl_syms.iter().enumerate() { + let (code, len) = cl_codes[s as usize]; + bw.write_bits_msb(code, len); + match s { + 16 => bw.write_bits(cl_extra[idx], 2), + 17 => bw.write_bits(cl_extra[idx], 3), + 18 => bw.write_bits(cl_extra[idx], 7), + _ => {} + } + } + + emit_tokens(&mut bw, tokens, &lit_codes, &dist_codes); + bw.finish() +} + +fn emit_fixed_block(tokens: &[Token]) -> Vec { + let mut bw = BitWriter::new(); + bw.write_bits(1, 1); + bw.write_bits(0b01, 2); + + let lit_codes: Vec<(u32, u32)> = (0..288).map(fixed_lit_code_u).collect(); + let dist_codes: Vec<(u32, u32)> = (0..30).map(|s| (s as u32, 5u32)).collect(); + emit_tokens(&mut bw, tokens, &lit_codes, &dist_codes); + bw.finish() +} + +fn fixed_lit_code_u(sym: usize) -> (u32, u32) { + match sym { + 0..=143 => (0x30 + sym as u32, 8), + 144..=255 => (0x190 + (sym as u32 - 144), 9), + 256..=279 => (sym as u32 - 256, 7), + 280..=287 => (0xc0 + (sym as u32 - 280), 8), + _ => unreachable!(), + } +} + +fn emit_tokens( + bw: &mut BitWriter, + tokens: &[Token], + lit_codes: &[(u32, u32)], + dist_codes: &[(u32, u32)], +) { + for t in tokens { + match t { + Token::Lit(b) => { + let (c, l) = lit_codes[*b as usize]; + bw.write_bits_msb(c, l); + } + Token::Match { len, dist } => { + let (lc, lextra, lebits) = length_code(*len); + let (c, l) = lit_codes[lc as usize]; + bw.write_bits_msb(c, l); + if lebits > 0 { + bw.write_bits(lextra, lebits); + } + let (dc, dextra, debits) = dist_code(*dist); + let (c2, l2) = dist_codes[dc as usize]; + bw.write_bits_msb(c2, l2); + if debits > 0 { + bw.write_bits(dextra, debits); + } + } + } + } + let (c, l) = lit_codes[256]; + bw.write_bits_msb(c, l); + let _ = (length_extra_bits, length_code_extra, dist_extra_bits); +} + +fn rle_code_lengths(lens: &[u8]) -> (Vec, Vec) { + let mut syms = Vec::new(); + let mut extra = Vec::new(); + let n = lens.len(); + let mut i = 0; + while i < n { + let cur = lens[i]; + let mut run = 1; + while i + run < n && lens[i + run] == cur { + run += 1; + } + if cur == 0 { + while run >= 11 { + let take = run.min(138); + syms.push(18); + extra.push((take - 11) as u32); + run -= take; + i += take; + } + while run >= 3 { + let take = run.min(10); + syms.push(17); + extra.push((take - 3) as u32); + run -= take; + i += take; + } + for _ in 0..run { + syms.push(0); + extra.push(0); + i += 1; + } + } else { + syms.push(cur); + extra.push(0); + i += 1; + let mut rem = run - 1; + while rem >= 3 { + let take = rem.min(6); + syms.push(16); + extra.push((take - 3) as u32); + rem -= take; + i += take; + } + for _ in 0..rem { + syms.push(cur); + extra.push(0); + i += 1; + } + } + } + (syms, extra) +} + +struct BitWriter { + out: Vec, + bitbuf: u32, + bitcnt: u32, +} +impl BitWriter { + fn new() -> Self { + BitWriter { out: Vec::new(), bitbuf: 0, bitcnt: 0 } + } + fn write_bits(&mut self, val: u32, n: u32) { + self.bitbuf |= (val & ((1u32 << n) - 1)) << self.bitcnt; + self.bitcnt += n; + while self.bitcnt >= 8 { + self.out.push((self.bitbuf & 0xff) as u8); + self.bitbuf >>= 8; + self.bitcnt -= 8; + } + } + fn write_bits_msb(&mut self, code: u32, n: u32) { + for k in (0..n).rev() { + self.write_bits((code >> k) & 1, 1); + } + } + fn finish(mut self) -> Vec { + if self.bitcnt > 0 { + self.out.push((self.bitbuf & 0xff) as u8); + } + self.out + } +} diff --git a/keydata.rs b/keydata.rs new file mode 100644 index 0000000..eff2d31 --- /dev/null +++ b/keydata.rs @@ -0,0 +1,5 @@ +pub const RSA_N: &[u8] = &[0xbe,0xfe,0x5e,0x63,0x8e,0x82,0x60,0xe5,0x16,0x75,0x36,0x8f,0x02,0xbd,0xaf,0x7e,0x8d,0x53,0xd6,0x9a,0xe4,0x25,0xa6,0xde,0xbd,0x4b,0xca,0x8a,0x0f,0x1a,0x11,0xca,0xc2,0x27,0xfa,0xc0,0x13,0x34,0x4d,0xbb,0x55,0x37,0x7d,0x99,0x2d,0xaf,0x58,0x9b,0x10,0xc7,0xfe,0x16,0x24,0xbe,0x23,0x06,0x0a,0xaf,0x45,0xa4,0x5c,0x80,0x7f,0xbd,]; + +pub const RSA_D: &[u8] = &[0x7f,0x54,0x3e,0xed,0x09,0xac,0x40,0x98,0xb9,0xa3,0x79,0xb4,0xac,0x7e,0x74,0xff,0x08,0xe2,0x8f,0x11,0xed,0x6e,0x6f,0x3f,0x28,0xdd,0x31,0xb1,0x5f,0x66,0xb6,0x86,0x03,0xe7,0xd0,0x29,0x0c,0x6a,0xe9,0x33,0x4c,0x74,0x11,0xfa,0xe1,0x7f,0xdd,0x53,0x9f,0xf5,0xc5,0x19,0x62,0x3b,0xd9,0x25,0x60,0xb4,0x92,0x9d,0xa8,0x2c,0xe8,0x93,]; + +pub const SPKI_DER: &[u8] = &[0x30,0x5a,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x01,0x05,0x00,0x03,0x49,0x00,0x30,0x46,0x02,0x41,0x00,0xbe,0xfe,0x5e,0x63,0x8e,0x82,0x60,0xe5,0x16,0x75,0x36,0x8f,0x02,0xbd,0xaf,0x7e,0x8d,0x53,0xd6,0x9a,0xe4,0x25,0xa6,0xde,0xbd,0x4b,0xca,0x8a,0x0f,0x1a,0x11,0xca,0xc2,0x27,0xfa,0xc0,0x13,0x34,0x4d,0xbb,0x55,0x37,0x7d,0x99,0x2d,0xaf,0x58,0x9b,0x10,0xc7,0xfe,0x16,0x24,0xbe,0x23,0x06,0x0a,0xaf,0x45,0xa4,0x5c,0x80,0x7f,0xbd,0x02,0x01,0x03,]; diff --git a/main.rs b/main.rs new file mode 100644 index 0000000..090da88 --- /dev/null +++ b/main.rs @@ -0,0 +1,85 @@ +mod axml; +mod bigint; +mod cert; +mod deflate; +mod keydata; +mod sha256; +mod sign_v2; +mod zip; + +use axml::{val, AxmlBuilder}; + +fn build_manifest() -> Vec { + let mut b = AxmlBuilder::new(); + + let a_name = b.intern("name"); + let a_hascode = b.intern("hasCode"); + let a_exported = b.intern("exported"); + let a_minsdk = b.intern("minSdkVersion"); + + let s_manifest = b.intern("manifest"); + let s_uses_sdk = b.intern("uses-sdk"); + let s_application = b.intern("application"); + let s_activity = b.intern("activity"); + let a_package = b.intern("package"); + + let v_package = b.intern("android.app"); + let v_activity = b.intern(".Activity"); + + let resmap = vec![ + (a_name, AxmlBuilder::res_id_for("name").unwrap()), + (a_minsdk, AxmlBuilder::res_id_for("minSdkVersion").unwrap()), + (a_hascode, AxmlBuilder::res_id_for("hasCode").unwrap()), + (a_exported, AxmlBuilder::res_id_for("exported").unwrap()), + ]; + + b.start(); + b.open_ns( + s_manifest, + &[(a_package, val::TYPE_STRING, v_package, v_package as i32, false)], + ); + b.open(s_uses_sdk, &[(a_minsdk, val::TYPE_INT_DEC, 34, -1)]); + b.close(s_uses_sdk); + b.open(s_application, &[(a_hascode, val::TYPE_INT_BOOL, 0, -1)]); + b.open( + s_activity, + &[ + (a_name, val::TYPE_STRING, v_activity, v_activity as i32), + (a_exported, val::TYPE_INT_BOOL, 0xFFFF_FFFF, -1), + ], + ); + let s_intent_filter = b.intern("intent-filter"); + let s_action = b.intern("action"); + let s_category = b.intern("category"); + let v_action_main = b.intern("android.intent.action.MAIN"); + let v_cat_launcher = b.intern("android.intent.category.LAUNCHER"); + b.open(s_intent_filter, &[]); + b.open(s_action, &[(a_name, val::TYPE_STRING, v_action_main, v_action_main as i32)]); + b.close(s_action); + b.open(s_category, &[(a_name, val::TYPE_STRING, v_cat_launcher, v_cat_launcher as i32)]); + b.close(s_category); + b.close(s_intent_filter); + b.close(s_activity); + b.close(s_application); + b.close(s_manifest); + b.end(); + b.finish(&resmap) +} + +fn main() { + let manifest = build_manifest(); + + let mut z = zip::Zip::new(); + z.add_deflated("AndroidManifest.xml", manifest); + let apk_unsigned = z.finish(); + + let apk = sign_v2::add_v2_signature(&apk_unsigned); + + std::fs::create_dir_all("out").expect("create out dir"); + let path = "out/app.apk"; + std::fs::write(path, &apk).expect("write apk"); + + println!("{} ({} bytes)", path, apk.len()); + println!("adb install -r {}", path); + println!("adb shell am start -n android.app/.Activity"); +} diff --git a/sha256.rs b/sha256.rs new file mode 100644 index 0000000..05afe3d --- /dev/null +++ b/sha256.rs @@ -0,0 +1,83 @@ +const K: [u32; 64] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +]; + +pub fn sha256(data: &[u8]) -> [u8; 32] { + let mut h: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + ]; + + let ml = (data.len() as u64) * 8; + let mut msg = data.to_vec(); + msg.push(0x80); + while msg.len() % 64 != 56 { + msg.push(0); + } + msg.extend_from_slice(&ml.to_be_bytes()); + + for chunk in msg.chunks(64) { + let mut w = [0u32; 64]; + for i in 0..16 { + w[i] = u32::from_be_bytes([ + chunk[i * 4], + chunk[i * 4 + 1], + chunk[i * 4 + 2], + chunk[i * 4 + 3], + ]); + } + for i in 16..64 { + let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3); + let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + .wrapping_add(s0) + .wrapping_add(w[i - 7]) + .wrapping_add(s1); + } + + let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh) = + (h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]); + + for i in 0..64 { + let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25); + let ch = (e & f) ^ ((!e) & g); + let t1 = hh + .wrapping_add(s1) + .wrapping_add(ch) + .wrapping_add(K[i]) + .wrapping_add(w[i]); + let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22); + let maj = (a & b) ^ (a & c) ^ (b & c); + let t2 = s0.wrapping_add(maj); + hh = g; + g = f; + f = e; + e = d.wrapping_add(t1); + d = c; + c = b; + b = a; + a = t1.wrapping_add(t2); + } + + h[0] = h[0].wrapping_add(a); + h[1] = h[1].wrapping_add(b); + h[2] = h[2].wrapping_add(c); + h[3] = h[3].wrapping_add(d); + h[4] = h[4].wrapping_add(e); + h[5] = h[5].wrapping_add(f); + h[6] = h[6].wrapping_add(g); + h[7] = h[7].wrapping_add(hh); + } + + let mut out = [0u8; 32]; + for i in 0..8 { + out[i * 4..i * 4 + 4].copy_from_slice(&h[i].to_be_bytes()); + } + out +} diff --git a/sign_v2.rs b/sign_v2.rs new file mode 100644 index 0000000..a862737 --- /dev/null +++ b/sign_v2.rs @@ -0,0 +1,198 @@ +use crate::bigint::BigUint; +use crate::cert::build_min_cert; +use crate::keydata::{RSA_D, RSA_N, SPKI_DER}; +use crate::sha256::sha256; + +const APK_SIG_BLOCK_MAGIC: &[u8; 16] = b"APK Sig Block 42"; +const V2_BLOCK_ID: u32 = 0x7109_871a; +const SIG_ALG_RSA_PKCS1_SHA256: u32 = 0x0103; + +const CHUNK_SIZE: usize = 1024 * 1024; + +fn v2_sig_len() -> usize { + RSA_N.len() +} + +pub fn add_v2_signature(zip: &[u8]) -> Vec { + let eocd_pos = find_eocd(zip).expect("EOCD not found"); + let cd_offset = read_u32(zip, eocd_pos + 16) as usize; + let cd_size = read_u32(zip, eocd_pos + 12) as usize; + + let section1 = &zip[0..cd_offset]; + let section2 = &zip[cd_offset..cd_offset + cd_size]; + let eocd = &zip[eocd_pos..]; + + let sig_len = v2_sig_len(); + let dummy_digest = [0u8; 32]; + let dummy_block = build_signing_block(&dummy_digest, &vec![0u8; sig_len]); + let block_len = dummy_block.len(); + + let mut eocd_for_digest = eocd.to_vec(); + write_u32(&mut eocd_for_digest, 16, cd_offset as u32); + + let mut eocd_final = eocd.to_vec(); + write_u32(&mut eocd_final, 16, (cd_offset + block_len) as u32); + + let digest = chunked_sha256(&[section1, section2, &eocd_for_digest]); + + let (real_block, _) = build_signed_block(&digest); + assert_eq!( + real_block.len(), + block_len, + "signing block length changed between passes" + ); + + let mut out = Vec::with_capacity(zip.len() + block_len); + out.extend_from_slice(section1); + out.extend_from_slice(&real_block); + out.extend_from_slice(section2); + out.extend_from_slice(&eocd_final); + out +} + +fn build_signed_block(content_digest: &[u8; 32]) -> (Vec, Vec) { + let signed_data = build_signed_data(content_digest); + + let signature = rsa_sign_sha256(&signed_data); + + let block = build_signing_block(content_digest, &signature); + (block, signature) +} + +fn build_signed_data(content_digest: &[u8]) -> Vec { + let mut digest_entry = Vec::new(); + write_u32_push(&mut digest_entry, SIG_ALG_RSA_PKCS1_SHA256); + push_len_prefixed(&mut digest_entry, content_digest); + let mut digests = Vec::new(); + push_len_prefixed(&mut digests, &digest_entry); + + let cert = build_min_cert(); + let mut certs = Vec::new(); + push_len_prefixed(&mut certs, &cert); + + let mut sd = Vec::new(); + push_len_prefixed(&mut sd, &digests); + push_len_prefixed(&mut sd, &certs); + let additional: Vec = Vec::new(); + push_len_prefixed(&mut sd, &additional); + sd +} + +fn build_signing_block(content_digest: &[u8; 32], signature: &[u8]) -> Vec { + let signed_data = build_signed_data(content_digest); + + let mut sig_entry = Vec::new(); + write_u32_push(&mut sig_entry, SIG_ALG_RSA_PKCS1_SHA256); + push_len_prefixed(&mut sig_entry, signature); + let mut signatures = Vec::new(); + push_len_prefixed(&mut signatures, &sig_entry); + + let mut signer = Vec::new(); + push_len_prefixed(&mut signer, &signed_data); + push_len_prefixed(&mut signer, &signatures); + push_len_prefixed(&mut signer, SPKI_DER); + + let mut signers = Vec::new(); + push_len_prefixed(&mut signers, &signer); + + let mut v2_value = Vec::new(); + push_len_prefixed(&mut v2_value, &signers); + + let pair_payload_len = 4 + v2_value.len(); + let mut pairs = Vec::new(); + write_u64_push(&mut pairs, pair_payload_len as u64); + write_u32_push(&mut pairs, V2_BLOCK_ID); + pairs.extend_from_slice(&v2_value); + + let size_of_block = (pairs.len() + 8 + 16) as u64; + let mut block = Vec::new(); + write_u64_push(&mut block, size_of_block); + block.extend_from_slice(&pairs); + write_u64_push(&mut block, size_of_block); + block.extend_from_slice(APK_SIG_BLOCK_MAGIC); + block +} + +fn chunked_sha256(sections: &[&[u8]]) -> [u8; 32] { + let mut chunk_digests: Vec<[u8; 32]> = Vec::new(); + for sec in sections { + let mut off = 0; + while off < sec.len() { + let end = (off + CHUNK_SIZE).min(sec.len()); + let chunk = &sec[off..end]; + let mut buf = Vec::with_capacity(1 + 4 + chunk.len()); + buf.push(0xa5); + buf.extend_from_slice(&(chunk.len() as u32).to_le_bytes()); + buf.extend_from_slice(chunk); + chunk_digests.push(sha256(&buf)); + off = end; + } + } + let mut top = Vec::with_capacity(1 + 4 + chunk_digests.len() * 32); + top.push(0x5a); + top.extend_from_slice(&(chunk_digests.len() as u32).to_le_bytes()); + for d in &chunk_digests { + top.extend_from_slice(d); + } + sha256(&top) +} + +fn rsa_sign_sha256(message: &[u8]) -> Vec { + let n = BigUint::from_be_bytes(RSA_N); + let d = BigUint::from_be_bytes(RSA_D); + let k = RSA_N.len(); + + let digest = sha256(message); + let sha256_prefix: &[u8] = &[ + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, 0x04, 0x20, + ]; + let mut t = Vec::new(); + t.extend_from_slice(sha256_prefix); + t.extend_from_slice(&digest); + + let ps_len = k - t.len() - 3; + let mut em = Vec::with_capacity(k); + em.push(0x00); + em.push(0x01); + em.extend(std::iter::repeat(0xFF).take(ps_len)); + em.push(0x00); + em.extend_from_slice(&t); + + let m = BigUint::from_be_bytes(&em); + let s = m.modpow(&d, &n); + s.to_be_bytes_padded(k) +} + +fn push_len_prefixed(out: &mut Vec, data: &[u8]) { + out.extend_from_slice(&(data.len() as u32).to_le_bytes()); + out.extend_from_slice(data); +} +fn write_u32_push(out: &mut Vec, x: u32) { + out.extend_from_slice(&x.to_le_bytes()); +} +fn write_u64_push(out: &mut Vec, x: u64) { + out.extend_from_slice(&x.to_le_bytes()); +} +fn read_u32(buf: &[u8], pos: usize) -> u32 { + u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]) +} +fn write_u32(buf: &mut [u8], pos: usize, x: u32) { + buf[pos..pos + 4].copy_from_slice(&x.to_le_bytes()); +} + +fn find_eocd(buf: &[u8]) -> Option { + if buf.len() < 22 { + return None; + } + let mut pos = buf.len() - 22; + loop { + if read_u32(buf, pos) == 0x0605_4b50 { + return Some(pos); + } + if pos == 0 { + return None; + } + pos -= 1; + } +} diff --git a/zip.rs b/zip.rs new file mode 100644 index 0000000..8c44a86 --- /dev/null +++ b/zip.rs @@ -0,0 +1,130 @@ +use crate::deflate::deflate; + +pub struct Entry { + pub name: String, + stored: Vec, + uncompressed_size: u32, + method: u16, + offset: u32, + crc: u32, +} + +pub struct Zip { + entries: Vec, +} + +impl Zip { + pub fn new() -> Self { + Zip { entries: Vec::new() } + } + + pub fn add(&mut self, name: &str, data: Vec) { + let crc = crc32(&data); + let size = data.len() as u32; + self.entries.push(Entry { + name: name.to_string(), + stored: data, + uncompressed_size: size, + method: 0, + offset: 0, + crc, + }); + } + + pub fn add_deflated(&mut self, name: &str, data: Vec) { + let crc = crc32(&data); + let uncompressed_size = data.len() as u32; + let compressed = deflate(&data); + if compressed.len() < data.len() { + self.entries.push(Entry { + name: name.to_string(), + stored: compressed, + uncompressed_size, + method: 8, + offset: 0, + crc, + }); + } else { + self.add(name, data); + } + } + + pub fn finish(mut self) -> Vec { + let mut out = Vec::new(); + + for e in self.entries.iter_mut() { + e.offset = out.len() as u32; + write_local_header(&mut out, e); + out.extend_from_slice(&e.stored); + } + + let cd_start = out.len() as u32; + for e in &self.entries { + write_central_header(&mut out, e); + } + let cd_size = out.len() as u32 - cd_start; + + write_eocd(&mut out, self.entries.len() as u16, cd_size, cd_start); + + out + } +} + +fn write_local_header(out: &mut Vec, e: &Entry) { + out.extend_from_slice(&0x0403_4b50u32.to_le_bytes()); + out.extend_from_slice(&20u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&e.method.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0x21u16.to_le_bytes()); + out.extend_from_slice(&e.crc.to_le_bytes()); + out.extend_from_slice(&(e.stored.len() as u32).to_le_bytes()); + out.extend_from_slice(&e.uncompressed_size.to_le_bytes()); + out.extend_from_slice(&(e.name.len() as u16).to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(e.name.as_bytes()); +} + +fn write_central_header(out: &mut Vec, e: &Entry) { + out.extend_from_slice(&0x0201_4b50u32.to_le_bytes()); + out.extend_from_slice(&20u16.to_le_bytes()); + out.extend_from_slice(&20u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&e.method.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0x21u16.to_le_bytes()); + out.extend_from_slice(&e.crc.to_le_bytes()); + out.extend_from_slice(&(e.stored.len() as u32).to_le_bytes()); + out.extend_from_slice(&e.uncompressed_size.to_le_bytes()); + out.extend_from_slice(&(e.name.len() as u16).to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0u32.to_le_bytes()); + out.extend_from_slice(&e.offset.to_le_bytes()); + out.extend_from_slice(e.name.as_bytes()); +} + +fn write_eocd(out: &mut Vec, count: u16, cd_size: u32, cd_offset: u32) { + out.extend_from_slice(&0x0605_4b50u32.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&count.to_le_bytes()); + out.extend_from_slice(&count.to_le_bytes()); + out.extend_from_slice(&cd_size.to_le_bytes()); + out.extend_from_slice(&cd_offset.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); +} + +pub fn crc32(data: &[u8]) -> u32 { + let mut crc: u32 = 0xFFFF_FFFF; + for &b in data { + crc ^= b as u32; + for _ in 0..8 { + let mask = (crc & 1).wrapping_neg(); + crc = (crc >> 1) ^ (0xEDB8_8320 & mask); + } + } + !crc +}