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 +}