From 05d6d2b51b8457088faae361ce0fa11a675d9f70 Mon Sep 17 00:00:00 2001 From: vvb2060 Date: Fri, 8 Apr 2022 18:03:58 +0800 Subject: [PATCH] Verify app signature --- .../java/com/topjohnwu/magisk/core/Config.kt | 2 + .../topjohnwu/magisk/core/tasks/HideAPK.kt | 1 + .../com/topjohnwu/magisk/core/utils/Keygen.kt | 4 + native/jni/Android.mk | 3 +- native/jni/core/bootstages.cpp | 2 + native/jni/core/db.cpp | 75 ++++++++-- native/jni/core/scripting.cpp | 3 +- native/jni/include/db.hpp | 5 +- native/jni/include/magisk.hpp | 1 + native/jni/utils/files.cpp | 136 ++++++++++++++++-- native/jni/utils/files.hpp | 3 + 11 files changed, 205 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Config.kt b/app/src/main/java/com/topjohnwu/magisk/core/Config.kt index e5b283002..b6d90cb50 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Config.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Config.kt @@ -41,6 +41,7 @@ object Config : PreferenceModel, DBConfig { const val DENYLIST = "denylist" const val SU_MANAGER = "requester" const val KEYSTORE = "keystore" + const val CERTDIGEST = "cert_digest" // prefs const val SU_REQUEST_TIMEOUT = "su_request_timeout" @@ -152,6 +153,7 @@ object Config : PreferenceModel, DBConfig { var denyList by DBBoolSettingsNoWrite(Key.DENYLIST, false) var suManager by dbStrings(Key.SU_MANAGER, "", true) var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true) + var certDigest by dbStrings(Key.CERTDIGEST, "", true) private const val SU_FINGERPRINT = "su_fingerprint" diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt index 061e714be..a8a5dd12d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt @@ -121,6 +121,7 @@ object HideAPK { val repack = File(activity.cacheDir, "patched.apk") val pkg = genPackageName() Config.keyStoreRaw = "" + Config.certDigest = "" if (!patch(activity, stub, FileOutputStream(repack), pkg, label)) return false diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/Keygen.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/Keygen.kt index f4e834815..2fbd56f4b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/Keygen.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/Keygen.kt @@ -12,6 +12,7 @@ import java.io.ByteArrayOutputStream import java.math.BigInteger import java.security.KeyPairGenerator import java.security.KeyStore +import java.security.MessageDigest import java.security.PrivateKey import java.security.cert.X509Certificate import java.util.* @@ -73,6 +74,9 @@ class Keygen : CertKeyProvider { } Config.keyStoreRaw = bytes.toString("UTF-8") + val digest = MessageDigest.getInstance("SHA-256").digest(cert.encoded) + Config.certDigest = digest.joinToString("") { "%02x".format(it) } + return ks } } diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 643fc0bee..89fd80a85 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -13,7 +13,8 @@ LOCAL_STATIC_LIBRARIES := \ libnanopb \ libsystemproperties \ libphmap \ - libxhook + libxhook \ + libmincrypt LOCAL_SRC_FILES := \ core/applets.cpp \ diff --git a/native/jni/core/bootstages.cpp b/native/jni/core/bootstages.cpp index 3f1f646c1..47ee19e34 100644 --- a/native/jni/core/bootstages.cpp +++ b/native/jni/core/bootstages.cpp @@ -20,6 +20,7 @@ using namespace std; static bool safe_mode = false; static int stub_fd = -1; bool zygisk_enabled = false; +string APKCERT; /********* * Setup * @@ -124,6 +125,7 @@ static bool magisk_env() { LOGI("* Initializing Magisk environment\n"); string stub_path = MAGISKTMP + "/stub.apk"; + APKCERT = read_certificate(stub_path); stub_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC); unlink(stub_path.data()); diff --git a/native/jni/core/db.cpp b/native/jni/core/db.cpp index 6fadacdcc..bd5c08d8a 100644 --- a/native/jni/core/db.cpp +++ b/native/jni/core/db.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #define DB_VERSION 12 @@ -359,43 +360,89 @@ int get_db_strings(db_strings &str, int key) { return 0; } +static bool is_stub_trusted(const char *pkg, const char *trust_hash) { + // TODO: Remove when next stable released + if (trust_hash[0] == 0) { + LOGW("su: skip check stub.apk signature\n"); + return true; + } + + string cert = read_certificate(find_apk_path(pkg)); + if (cert.empty()) + return false; + uint8_t hash[SHA256_DIGEST_SIZE]; + SHA256_hash(cert.data(), cert.length(), hash); + char hash_hex[SHA256_DIGEST_SIZE * 2 + 1]; + char *ptr = &hash_hex[0]; + for (uint8_t i: hash) { + ptr += sprintf(ptr, "%02x", i); + } + return strcmp(trust_hash, hash_hex) == 0; +} + bool get_manager(int user_id, std::string *pkg, struct stat *st) { db_strings str; get_db_strings(str, SU_MANAGER); + get_db_strings(str, CERT_DIGEST); char app_path[128]; + if (APKCERT.empty()) + LOGW("su: skip check app signature\n"); + if (!str[SU_MANAGER].empty()) { // App is repackaged sprintf(app_path, "%s/%d/%s", APP_DATA_DIR, user_id, str[SU_MANAGER].data()); if (stat(app_path, st) == 0) { - if (pkg) - pkg->swap(str[SU_MANAGER]); - return true; + if (is_stub_trusted(str[SU_MANAGER].data(), str[CERT_DIGEST].data())) { + strcpy(app_path, "/dyn/current.apk"); + if (!APKCERT.empty() && access(app_path, F_OK) == 0) { + if (read_certificate(app_path) == APKCERT) { + if (pkg) + pkg->swap(str[SU_MANAGER]); + return true; + } else { + LOGW("su: current.apk signature mismatch\n"); + } + } else { + if (pkg) + pkg->swap(str[SU_MANAGER]); + return true; + } + } else { + LOGW("su: stub.apk signature mismatch\n"); + } } } // Check the official package name sprintf(app_path, "%s/%d/" JAVA_PACKAGE_NAME, APP_DATA_DIR, user_id); if (stat(app_path, st) == 0) { - if (pkg) - *pkg = JAVA_PACKAGE_NAME; - return true; - } else { - LOGE("su: cannot find manager\n"); - memset(st, 0, sizeof(*st)); - if (pkg) - pkg->clear(); - return false; + string cert = read_certificate(find_apk_path(JAVA_PACKAGE_NAME)); + if (APKCERT.empty()) + cert.clear(); + if (cert == APKCERT) { + if (pkg) + *pkg = JAVA_PACKAGE_NAME; + return true; + } else { + LOGW("su: app signature mismatch\n"); + } } + + LOGE("su: cannot find trusted app\n"); + memset(st, 0, sizeof(*st)); + if (pkg) + pkg->clear(); + return false; } bool get_manager(string *pkg) { - struct stat st; + struct stat st{}; return get_manager(0, pkg, &st); } int get_manager_app_id() { - struct stat st; + struct stat st{}; if (get_manager(0, nullptr, &st)) return to_app_id(st.st_uid); return -1; diff --git a/native/jni/core/scripting.cpp b/native/jni/core/scripting.cpp index b9c735193..bb6756b29 100644 --- a/native/jni/core/scripting.cpp +++ b/native/jni/core/scripting.cpp @@ -152,6 +152,7 @@ void exec_module_scripts(const char *stage, const vector &modules) constexpr char install_script[] = R"EOF( APK=%s +log -t Magisk "apk_uninstall: $(pm uninstall %s 2>&1)" log -t Magisk "apk_install: $APK" log -t Magisk "apk_install: $(pm install -r $APK 2>&1)" rm -f $APK @@ -163,7 +164,7 @@ void install_apk(const char *apk) { .fork = fork_no_orphan }; char cmds[sizeof(install_script) + 4096]; - sprintf(cmds, install_script, apk); + sprintf(cmds, install_script, apk, JAVA_PACKAGE_NAME); exec_command_sync(exec, "/system/bin/sh", "-c", cmds); } diff --git a/native/jni/include/db.hpp b/native/jni/include/db.hpp index 42d87ef79..6e14a783f 100644 --- a/native/jni/include/db.hpp +++ b/native/jni/include/db.hpp @@ -84,11 +84,12 @@ protected: * DB Strings * **************/ -constexpr const char *DB_STRING_KEYS[] = { "requester" }; +constexpr const char *DB_STRING_KEYS[] = { "requester", "cert_digest" }; // Strings keys indices enum { - SU_MANAGER = 0 + SU_MANAGER = 0, + CERT_DIGEST }; class db_strings : public db_dict { diff --git a/native/jni/include/magisk.hpp b/native/jni/include/magisk.hpp index 466e5f439..c738a64e8 100644 --- a/native/jni/include/magisk.hpp +++ b/native/jni/include/magisk.hpp @@ -35,6 +35,7 @@ constexpr const char *applet_names[] = { "su", "resetprop", nullptr }; extern int SDK_INT; #define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user") +extern std::string APKCERT; // Multi-call entrypoints int magisk_main(int argc, char *argv[]); diff --git a/native/jni/utils/files.cpp b/native/jni/utils/files.cpp index 60f615a16..0f4a9ea97 100644 --- a/native/jni/utils/files.cpp +++ b/native/jni/utils/files.cpp @@ -57,17 +57,33 @@ static void post_order_walk(int dirfd, const Func &fn) { } } +enum visit_result { + CONTINUE, SKIP, TERMINATE +}; + template -static void pre_order_walk(int dirfd, const Func &fn) { +static visit_result pre_order_walk(int dirfd, const Func &fn) { auto dir = xopen_dir(dirfd); - if (!dir) return; + if (!dir) { + close(dirfd); + return SKIP; + } for (dirent *entry; (entry = xreaddir(dir.get()));) { - if (!fn(dirfd, entry)) - continue; - if (entry->d_type == DT_DIR) - pre_order_walk(xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC), fn); + switch (fn(dirfd, entry)) { + case CONTINUE: + break; + case SKIP: + continue; + case TERMINATE: + return TERMINATE; + } + if (entry->d_type == DT_DIR) { + int fd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC); + if (pre_order_walk(fd, fn) == TERMINATE) return TERMINATE; + } } + return CONTINUE; } static void remove_at(int dirfd, struct dirent *entry) { @@ -400,20 +416,20 @@ void parse_mnt(const char *file, const function &fn) { } void backup_folder(const char *dir, vector &files) { - char path[4096]; + char path[PATH_MAX]; xrealpath(dir, path); int len = strlen(path); - pre_order_walk(xopen(dir, O_RDONLY), [&](int dfd, dirent *entry) -> bool { + pre_order_walk(xopen(dir, O_RDONLY), [&](int dfd, dirent *entry) -> visit_result { int fd = xopenat(dfd, entry->d_name, O_RDONLY); if (fd < 0) - return false; + return SKIP; run_finally f([&]{ close(fd); }); if (fd_path(fd, path, sizeof(path)) < 0) - return false; + return SKIP; raw_file file; file.path = path + len + 1; if (fgetattr(fd, &file.attr) < 0) - return false; + return SKIP; if (entry->d_type == DT_REG) { fd_full_read(fd, file.buf, file.sz); } else if (entry->d_type == DT_LNK) { @@ -423,7 +439,7 @@ void backup_folder(const char *dir, vector &files) { memcpy(file.buf, path, file.sz); } files.emplace_back(std::move(file)); - return true; + return CONTINUE; }); } @@ -507,3 +523,99 @@ mmap_data::mmap_data(const char *name, bool rw) { close(fd); buf = static_cast(b); } + +string find_apk_path(const char *pkg) { + char buf[PATH_MAX]; + pre_order_walk(xopen("/data/app", O_RDONLY), [&](int dfd, dirent *entry) -> visit_result { + if (entry->d_type != DT_DIR) + return SKIP; + size_t len = strlen(pkg); + if (strncmp(entry->d_name, pkg, len) == 0 && entry->d_name[len] == '-') { + fd_pathat(dfd, entry->d_name, buf, sizeof(buf)); + return TERMINATE; + } else if (strncmp(entry->d_name, "~~", 2) == 0) { + return CONTINUE; + } else return SKIP; + }); + string path(buf); + return path.append("/base.apk"); +} + +string read_certificate(string app_path) { + string certificate; + uint32_t size4; + uint64_t size8, size_of_block; + + int fd = xopen(app_path.data(), O_RDONLY); + if (fd < 0) { + return certificate; + } + run_finally f([&] { close(fd); }); + + for (int i = 0;; i++) { + unsigned short n; + lseek(fd, -i - 2, SEEK_END); + read(fd, &n, 2); + if (n == i) { + lseek(fd, -22, SEEK_CUR); + read(fd, &size4, 4); + if (size4 == 0x6054b50u) { // central directory end magic + break; + } + } + if (i == 0xffff) { + return certificate; + } + } + + lseek(fd, 12, SEEK_CUR); + + read(fd, &size4, 0x4); + lseek(fd, (off_t) (size4 - 0x18), SEEK_SET); + + read(fd, &size8, 0x8); + unsigned char buffer[0x10] = {0}; + read(fd, buffer, 0x10); + if (memcmp(buffer, "APK Sig Block 42", 0x10) != 0) { + return certificate; + } + + lseek(fd, (off_t) (size4 - (size8 + 0x8)), SEEK_SET); + read(fd, &size_of_block, 0x8); + if (size_of_block != size8) { + return certificate; + } + + for (;;) { + uint32_t id; + uint32_t offset; + read(fd, &size8, 0x8); // sequence length + if (size8 == size_of_block) { + break; + } + read(fd, &id, 0x4); // id + offset = 4; + + if (id == 0x7109871au) { + read(fd, &size4, 0x4); // signer-sequence length + read(fd, &size4, 0x4); // signer length + read(fd, &size4, 0x4); // signed data length + offset += 0x4 * 3; + + read(fd, &size4, 0x4); // digests-sequence length + lseek(fd, (off_t) (size4), SEEK_CUR);// skip digests + offset += 0x4 + size4; + + read(fd, &size4, 0x4); // certificates length + read(fd, &size4, 0x4); // certificate length + offset += 0x4 * 2; + + certificate.resize(size4); + read(fd, certificate.data(), size4); + + offset += size4; + } + lseek(fd, (off_t) (size8 - offset), SEEK_CUR); + } + return certificate; +} diff --git a/native/jni/utils/files.hpp b/native/jni/utils/files.hpp index b277424a6..11be8e30e 100644 --- a/native/jni/utils/files.hpp +++ b/native/jni/utils/files.hpp @@ -102,6 +102,9 @@ void parse_mnt(const char *file, const std::function &fn); void backup_folder(const char *dir, std::vector &files); void restore_folder(const char *dir, std::vector &files); +std::string find_apk_path(const char *pkg); +std::string read_certificate(std::string app_path); + template void full_read(const char *filename, T &buf, size_t &size) { static_assert(std::is_pointer::value);