From a9c89cbbbbad10412e05d497c1fdccaa3010a554 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Wed, 14 Jun 2023 22:24:42 +0800 Subject: [PATCH] Read certificate in Rust Co-authored-by: topjohnwu --- native/src/Android.mk | 1 - native/src/base/misc.rs | 21 ++-- native/src/core/cert.cpp | 208 ------------------------------------ native/src/core/cert.rs | 144 +++++++++++++++++++++++++ native/src/core/core.hpp | 1 - native/src/core/lib.rs | 4 + native/src/core/package.cpp | 18 +++- 7 files changed, 173 insertions(+), 224 deletions(-) delete mode 100644 native/src/core/cert.cpp create mode 100644 native/src/core/cert.rs diff --git a/native/src/Android.mk b/native/src/Android.mk index 26f5ddd77..a8b31cf3c 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -25,7 +25,6 @@ LOCAL_SRC_FILES := \ core/socket.cpp \ core/db.cpp \ core/package.cpp \ - core/cert.cpp \ core/scripting.cpp \ core/restorecon.cpp \ core/module.cpp \ diff --git a/native/src/base/misc.rs b/native/src/base/misc.rs index 73cb3351b..e9cceb536 100644 --- a/native/src/base/misc.rs +++ b/native/src/base/misc.rs @@ -288,27 +288,30 @@ pub unsafe fn slice_from_ptr_mut<'a, T>(buf: *mut T, len: usize) -> &'a mut [T] } } -pub trait FlatData { - fn as_raw_bytes(&self) -> &[u8] - where - Self: Sized, - { +pub trait FlatData +where + Self: Sized, +{ + fn as_raw_bytes(&self) -> &[u8] { unsafe { let self_ptr = self as *const Self as *const u8; slice::from_raw_parts(self_ptr, std::mem::size_of::()) } } - fn as_raw_bytes_mut(&mut self) -> &mut [u8] - where - Self: Sized, - { + fn as_raw_bytes_mut(&mut self) -> &mut [u8] { unsafe { let self_ptr = self as *mut Self as *mut u8; slice::from_raw_parts_mut(self_ptr, std::mem::size_of::()) } } + + fn bytes_size(&self) -> usize { + std::mem::size_of::() + } } +impl FlatData for T {} + // Check libc return value and map errors to Result pub trait LibcReturn: Copy { fn is_error(&self) -> bool; diff --git a/native/src/core/cert.cpp b/native/src/core/cert.cpp deleted file mode 100644 index 08a0e0915..000000000 --- a/native/src/core/cert.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include - -using namespace std; - -#define APK_SIGNING_BLOCK_MAGIC "APK Sig Block 42" -#define SIGNATURE_SCHEME_V2_MAGIC 0x7109871a -#define EOCD_MAGIC 0x6054b50 - -// Top-level block container -struct signing_block { - uint64_t block_sz; - - struct id_value_pair { - uint64_t len; - struct /* v2_signature */ { - uint32_t id; - uint8_t value[0]; // size = (len - 4) - }; - } id_value_pair_sequence[0]; - - uint64_t block_sz_; // *MUST* be same as block_sz - char magic[16]; // "APK Sig Block 42" -}; - -struct len_prefixed { - uint32_t len; -}; - -// Generic length prefixed raw data -struct len_prefixed_value : public len_prefixed { - uint8_t value[0]; -}; - -// V2 Signature Block -struct v2_signature { - uint32_t id; // 0x7109871a - uint32_t signer_sequence_len; - struct signer : public len_prefixed { - struct signed_data : public len_prefixed { - uint32_t digest_sequence_len; - struct : public len_prefixed { - uint32_t algorithm; - len_prefixed_value digest; - } digest_sequence[0]; - - uint32_t certificate_sequence_len; - len_prefixed_value certificate_sequence[0]; - - uint32_t attribute_sequence_len; - struct attribute : public len_prefixed { - uint32_t id; - uint8_t value[0]; // size = (len - 4) - } attribute_sequence[0]; - } signed_data; - - uint32_t signature_sequence_len; - struct : public len_prefixed { - uint32_t id; - len_prefixed_value signature; - } signature_sequence[0]; - - len_prefixed_value public_key; - } signer_sequence[0]; -}; - -// End of central directory record -struct EOCD { - uint32_t magic; // 0x6054b50 - uint8_t pad[8]; // 8 bytes of irrelevant data - uint32_t central_dir_sz; // size of central directory - uint32_t central_dir_off; // offset of central directory - uint16_t comment_sz; // size of comment - char comment[0]; -} __attribute__((packed)); - -/* - * A v2/v3 signed APK has the format as following - * - * +---------------+ - * | zip content | - * +---------------+ - * | signing block | - * +---------------+ - * | central dir | - * +---------------+ - * | EOCD | - * +---------------+ - * - * Scan from end of file to find EOCD, and figure our way back to the - * offset of the signing block. Next, directly extract the certificate - * from the v2 signature block. - * - * All structures above are mostly just for documentation purpose. - * - * This method extracts the first certificate of the first signer - * within the APK v2 signature block. - */ -string read_certificate(int fd, int version) { - uint32_t u32; - uint64_t u64; - - // Find EOCD - for (int i = 0;; i++) { - // i is the absolute offset to end of file - uint16_t comment_sz = 0; - xlseek64(fd, -static_cast(sizeof(comment_sz)) - i, SEEK_END); - xxread(fd, &comment_sz, sizeof(comment_sz)); - if (comment_sz == i) { - // Double check if we actually found the structure - xlseek64(fd, -static_cast(sizeof(EOCD)), SEEK_CUR); - uint32_t magic = 0; - xxread(fd, &magic, sizeof(magic)); - if (magic == EOCD_MAGIC) { - break; - } - } - if (i == 0xffff) { - // Comments cannot be longer than 0xffff (overflow), abort - LOGE("cert: invalid APK format\n"); - return {}; - } - } - - // We are now at EOCD + sizeof(magic) - // Seek and read central_dir_off to find start of central directory - uint32_t central_dir_off = 0; - { - constexpr off64_t off = offsetof(EOCD, central_dir_off) - sizeof(EOCD::magic); - xlseek64(fd, off, SEEK_CUR); - } - xxread(fd, ¢ral_dir_off, sizeof(central_dir_off)); - - // Parse APK comment to get version code - if (version >= 0) { - xlseek64(fd, sizeof(EOCD::comment_sz), SEEK_CUR); - FILE *fp = fdopen(fd, "r"); // DO NOT close this file pointer - int apk_ver = -1; - parse_prop_file(fp, [&](string_view key, string_view value) -> bool { - if (key == "versionCode") { - apk_ver = parse_int(value); - return false; - } - return true; - }); - if (version > apk_ver) { - // Enforce the magisk app to always be newer than magiskd - LOGE("cert: APK version too low\n"); - return {}; - } - } - - // Next, find the start of the APK signing block - { - constexpr int off = sizeof(signing_block::block_sz_) + sizeof(signing_block::magic); - xlseek64(fd, (off64_t) (central_dir_off - off), SEEK_SET); - } - xxread(fd, &u64, sizeof(u64)); // u64 = block_sz_ - char magic[sizeof(signing_block::magic)] = {0}; - xxread(fd, magic, sizeof(magic)); - if (memcmp(magic, APK_SIGNING_BLOCK_MAGIC, sizeof(magic)) != 0) { - // Invalid signing block magic, abort - LOGE("cert: invalid signing block magic\n"); - return {}; - } - uint64_t signing_blk_sz = 0; - xlseek64(fd, -static_cast(u64 + sizeof(signing_blk_sz)), SEEK_CUR); - xxread(fd, &signing_blk_sz, sizeof(signing_blk_sz)); - if (signing_blk_sz != u64) { - // block_sz != block_sz_, invalid signing block format, abort - LOGE("cert: invalid signing block format\n"); - return {}; - } - - // Finally, we are now at the beginning of the id-value pair sequence - - for (;;) { - xxread(fd, &u64, sizeof(u64)); // id-value pair length - if (u64 == signing_blk_sz) { - // Outside of the id-value pair sequence; actually reading block_sz_ - break; - } - - uint32_t id; - xxread(fd, &id, sizeof(id)); - if (id == SIGNATURE_SCHEME_V2_MAGIC) { - // Skip [signer sequence length] + [1st signer length] + [signed data length] - xlseek64(fd, sizeof(uint32_t) * 3, SEEK_CUR); - - xxread(fd, &u32, sizeof(u32)); // digest sequence length - xlseek64(fd, u32, SEEK_CUR); // skip all digests - - xlseek64(fd, sizeof(uint32_t), SEEK_CUR); // cert sequence length - xxread(fd, &u32, sizeof(u32)); // 1st cert length - - string cert; - cert.resize(u32); - xxread(fd, cert.data(), u32); - - return cert; - } else { - // Skip this id-value pair - xlseek64(fd, u64 - sizeof(id), SEEK_CUR); - } - } - - LOGE("cert: cannot find certificate\n"); - return {}; -} diff --git a/native/src/core/cert.rs b/native/src/core/cert.rs new file mode 100644 index 000000000..eba0716ce --- /dev/null +++ b/native/src/core/cert.rs @@ -0,0 +1,144 @@ +use std::fs::File; +use std::io; +use std::io::{Read, Seek, SeekFrom}; +use std::os::fd::{FromRawFd, RawFd}; + +use base::*; + +const EOCD_MAGIC: u32 = 0x06054B50; +const APK_SIGNING_BLOCK_MAGIC: [u8; 16] = *b"APK Sig Block 42"; +const SIGNATURE_SCHEME_V2_MAGIC: u32 = 0x7109871A; + +macro_rules! bad_apk { + ($msg:literal) => { + io::Error::new(io::ErrorKind::InvalidData, concat!("cert: ", $msg)) + }; +} + +/* + * A v2/v3 signed APK has the format as following + * + * +---------------+ + * | zip content | + * +---------------+ + * | signing block | + * +---------------+ + * | central dir | + * +---------------+ + * | EOCD | + * +---------------+ + * + * Scan from end of file to find EOCD, and figure our way back to the + * offset of the signing block. Next, directly extract the certificate + * from the v2 signature block. + * + * All structures above are mostly just for documentation purpose. + * + * This method extracts the first certificate of the first signer + * within the APK v2 signature block. + */ +pub fn read_certificate(fd: RawFd, version: i32) -> Vec { + fn inner(apk: &mut File, version: i32) -> Result, io::Error> { + let mut u32_value = 0u32; + let mut u64_value = 0u64; + + // Find EOCD + for i in 0u16.. { + let mut comment_sz = 0u16; + apk.seek(SeekFrom::End(-(comment_sz.bytes_size() as i64) - i as i64))?; + apk.read_exact(comment_sz.as_raw_bytes_mut())?; + + if comment_sz == i { + apk.seek(SeekFrom::Current(-22))?; + let mut magic = 0u32; + apk.read_exact(magic.as_raw_bytes_mut())?; + if magic == EOCD_MAGIC { + break; + } + } + if i == 0xffff { + return Err(bad_apk!("invalid APK format")); + } + } + + // We are now at EOCD + sizeof(magic) + // Seek and read central_dir_off to find the start of the central directory + let mut central_dir_off = 0u32; + apk.seek(SeekFrom::Current(12))?; + + apk.read_exact(central_dir_off.as_raw_bytes_mut())?; + + // Code for parse APK comment to get version code + if version >= 0 { + let mut comment_sz = 0u16; + apk.read_exact(comment_sz.as_raw_bytes_mut())?; + let mut comment = vec![0u8; comment_sz as usize]; + apk.read_exact(&mut comment)?; + let mut comment = io::Cursor::new(&comment); + let mut apk_ver = 0; + comment.foreach_props(|k, v| { + if k == "versionCode" { + apk_ver = v.trim().parse::().unwrap_or(0); + true + } else { + false + } + }); + if version > apk_ver { + return Err(bad_apk!("APK version too low")); + } + } + + // Next, find the start of the APK signing block + apk.seek(SeekFrom::Start((central_dir_off - 24) as u64))?; + apk.read_exact(u64_value.as_raw_bytes_mut())?; // u64_value = block_sz_ + let mut magic = [0u8; 16]; + apk.read_exact(magic.as_raw_bytes_mut())?; + if magic != APK_SIGNING_BLOCK_MAGIC { + return Err(bad_apk!("invalid signing block magic")); + } + let mut signing_blk_sz = 0u64; + apk.seek(SeekFrom::Current( + -(u64_value as i64) - (signing_blk_sz.bytes_size() as i64), + ))?; + apk.read_exact(signing_blk_sz.as_raw_bytes_mut())?; + if signing_blk_sz != u64_value { + return Err(bad_apk!("invalid signing block size")); + } + + // Finally, we are now at the beginning of the id-value pair sequence + loop { + apk.read_exact(u64_value.as_raw_bytes_mut())?; // id-value pair length + if u64_value == signing_blk_sz { + break; + } + + let mut id = 0u32; + apk.read_exact(id.as_raw_bytes_mut())?; + if id == SIGNATURE_SCHEME_V2_MAGIC { + // Skip [signer sequence length] + [1st signer length] + [signed data length] + apk.seek(SeekFrom::Current((u32_value.bytes_size() * 3) as i64))?; + + apk.read_exact(u32_value.as_raw_bytes_mut())?; // digest sequence length + apk.seek(SeekFrom::Current(u32_value as i64))?; // skip all digests + + apk.seek(SeekFrom::Current(u32_value.bytes_size() as i64))?; // cert sequence length + apk.read_exact(u32_value.as_raw_bytes_mut())?; // 1st cert length + + let mut cert = vec![0; u32_value as usize]; + apk.read_exact(cert.as_mut())?; + return Ok(cert); + } else { + // Skip this id-value pair + apk.seek(SeekFrom::Current( + u64_value as i64 - (id.bytes_size() as i64), + ))?; + } + } + + Err(bad_apk!("cannot find certificate")) + } + inner(unsafe { &mut File::from_raw_fd(libc::dup(fd)) }, version) + .log() + .unwrap_or(vec![]) +} diff --git a/native/src/core/core.hpp b/native/src/core/core.hpp index 5ab3fb998..7f5148e5b 100644 --- a/native/src/core/core.hpp +++ b/native/src/core/core.hpp @@ -12,7 +12,6 @@ extern std::atomic pkg_xml_ino; std::string find_preinit_device(); void unlock_blocks(); void reboot(); -std::string read_certificate(int fd, int version = -1); // Module stuffs void handle_modules(); diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 757a033bc..9a80bd086 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -1,9 +1,12 @@ +#![feature(format_args_nl)] #![allow(clippy::missing_safety_doc)] use base::Utf8CStr; +use cert::*; use daemon::*; use logging::*; +mod cert; #[path = "../include/consts.rs"] mod consts; mod daemon; @@ -28,6 +31,7 @@ pub mod ffi { extern "Rust" { fn daemon_entry(); fn zygisk_entry(); + fn read_certificate(fd: i32, version: i32) -> Vec; type MagiskD; fn get_magiskd() -> &'static MagiskD; diff --git a/native/src/core/package.cpp b/native/src/core/package.cpp index 0dde74154..6cac8c8e7 100644 --- a/native/src/core/package.cpp +++ b/native/src/core/package.cpp @@ -25,6 +25,11 @@ static string *mgr_cert; static int stub_apk_fd = -1; static const string *default_cert; +static string read_certificate(int fd, int version) { + auto cert = rust::read_certificate(fd, version); + return string{cert.begin(), cert.end()}; +} + void check_pkg_refresh() { struct stat st{}; if (stat("/data/system/packages.xml", &st) == 0 && @@ -71,7 +76,9 @@ void preserve_stub_apk() { string stub_path = MAGISKTMP + "/stub.apk"; stub_apk_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC); unlink(stub_path.data()); - default_cert = new string(read_certificate(stub_apk_fd)); + auto cert = read_certificate(stub_apk_fd, -1); + if (!cert.empty()) + default_cert = new string(std::move(cert)); lseek(stub_apk_fd, 0, SEEK_SET); } @@ -107,7 +114,8 @@ int get_manager(int user_id, string *pkg, bool install) { LOGW("pkg: no dyn APK, ignore\n"); return false; } - bool mismatch = default_cert && read_certificate(dyn, MAGISK_VER_CODE) != *default_cert; + auto cert = read_certificate(dyn, MAGISK_VER_CODE); + bool mismatch = default_cert && cert != *default_cert; close(dyn); if (mismatch) { LOGE("pkg: dyn APK signature mismatch: %s\n", app_path); @@ -172,12 +180,12 @@ int get_manager(int user_id, string *pkg, bool install) { byte_array apk; find_apk_path(byte_view(str[SU_MANAGER]), apk); int fd = xopen((const char *) apk.buf(), O_RDONLY | O_CLOEXEC); - string cert = read_certificate(fd); + auto cert = read_certificate(fd, -1); close(fd); // Verify validity if (str[SU_MANAGER] == *mgr_pkg) { - if (app_id != mgr_app_id || cert != *mgr_cert) { + if (app_id != mgr_app_id || cert.empty() || cert != *mgr_cert) { // app ID or cert should never change LOGE("pkg: repackaged APK signature invalid: %s\n", apk.buf()); uninstall_pkg(mgr_pkg->data()); @@ -226,7 +234,7 @@ int get_manager(int user_id, string *pkg, bool install) { byte_array apk; find_apk_path(byte_view(JAVA_PACKAGE_NAME), apk); int fd = xopen((const char *) apk.buf(), O_RDONLY | O_CLOEXEC); - string cert = read_certificate(fd, MAGISK_VER_CODE); + auto cert = read_certificate(fd, MAGISK_VER_CODE); close(fd); if (default_cert && cert != *default_cert) { // Found APK with invalid signature, force replace with stub