Verify app signature

This commit is contained in:
vvb2060 2022-04-08 18:03:58 +08:00 committed by John Wu
parent 4cff0384f7
commit 05d6d2b51b
11 changed files with 205 additions and 30 deletions

View File

@ -41,6 +41,7 @@ object Config : PreferenceModel, DBConfig {
const val DENYLIST = "denylist" const val DENYLIST = "denylist"
const val SU_MANAGER = "requester" const val SU_MANAGER = "requester"
const val KEYSTORE = "keystore" const val KEYSTORE = "keystore"
const val CERTDIGEST = "cert_digest"
// prefs // prefs
const val SU_REQUEST_TIMEOUT = "su_request_timeout" const val SU_REQUEST_TIMEOUT = "su_request_timeout"
@ -152,6 +153,7 @@ object Config : PreferenceModel, DBConfig {
var denyList by DBBoolSettingsNoWrite(Key.DENYLIST, false) var denyList by DBBoolSettingsNoWrite(Key.DENYLIST, false)
var suManager by dbStrings(Key.SU_MANAGER, "", true) var suManager by dbStrings(Key.SU_MANAGER, "", true)
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true) var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
var certDigest by dbStrings(Key.CERTDIGEST, "", true)
private const val SU_FINGERPRINT = "su_fingerprint" private const val SU_FINGERPRINT = "su_fingerprint"

View File

@ -121,6 +121,7 @@ object HideAPK {
val repack = File(activity.cacheDir, "patched.apk") val repack = File(activity.cacheDir, "patched.apk")
val pkg = genPackageName() val pkg = genPackageName()
Config.keyStoreRaw = "" Config.keyStoreRaw = ""
Config.certDigest = ""
if (!patch(activity, stub, FileOutputStream(repack), pkg, label)) if (!patch(activity, stub, FileOutputStream(repack), pkg, label))
return false return false

View File

@ -12,6 +12,7 @@ import java.io.ByteArrayOutputStream
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.KeyStore import java.security.KeyStore
import java.security.MessageDigest
import java.security.PrivateKey import java.security.PrivateKey
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.* import java.util.*
@ -73,6 +74,9 @@ class Keygen : CertKeyProvider {
} }
Config.keyStoreRaw = bytes.toString("UTF-8") Config.keyStoreRaw = bytes.toString("UTF-8")
val digest = MessageDigest.getInstance("SHA-256").digest(cert.encoded)
Config.certDigest = digest.joinToString("") { "%02x".format(it) }
return ks return ks
} }
} }

View File

@ -13,7 +13,8 @@ LOCAL_STATIC_LIBRARIES := \
libnanopb \ libnanopb \
libsystemproperties \ libsystemproperties \
libphmap \ libphmap \
libxhook libxhook \
libmincrypt
LOCAL_SRC_FILES := \ LOCAL_SRC_FILES := \
core/applets.cpp \ core/applets.cpp \

View File

@ -20,6 +20,7 @@ using namespace std;
static bool safe_mode = false; static bool safe_mode = false;
static int stub_fd = -1; static int stub_fd = -1;
bool zygisk_enabled = false; bool zygisk_enabled = false;
string APKCERT;
/********* /*********
* Setup * * Setup *
@ -124,6 +125,7 @@ static bool magisk_env() {
LOGI("* Initializing Magisk environment\n"); LOGI("* Initializing Magisk environment\n");
string stub_path = MAGISKTMP + "/stub.apk"; string stub_path = MAGISKTMP + "/stub.apk";
APKCERT = read_certificate(stub_path);
stub_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC); stub_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC);
unlink(stub_path.data()); unlink(stub_path.data());

View File

@ -6,6 +6,7 @@
#include <db.hpp> #include <db.hpp>
#include <socket.hpp> #include <socket.hpp>
#include <utils.hpp> #include <utils.hpp>
#include <mincrypt/sha256.h>
#define DB_VERSION 12 #define DB_VERSION 12
@ -359,43 +360,89 @@ int get_db_strings(db_strings &str, int key) {
return 0; 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) { bool get_manager(int user_id, std::string *pkg, struct stat *st) {
db_strings str; db_strings str;
get_db_strings(str, SU_MANAGER); get_db_strings(str, SU_MANAGER);
get_db_strings(str, CERT_DIGEST);
char app_path[128]; char app_path[128];
if (APKCERT.empty())
LOGW("su: skip check app signature\n");
if (!str[SU_MANAGER].empty()) { if (!str[SU_MANAGER].empty()) {
// App is repackaged // App is repackaged
sprintf(app_path, "%s/%d/%s", APP_DATA_DIR, user_id, str[SU_MANAGER].data()); sprintf(app_path, "%s/%d/%s", APP_DATA_DIR, user_id, str[SU_MANAGER].data());
if (stat(app_path, st) == 0) { if (stat(app_path, st) == 0) {
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) if (pkg)
pkg->swap(str[SU_MANAGER]); pkg->swap(str[SU_MANAGER]);
return true; 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 // Check the official package name
sprintf(app_path, "%s/%d/" JAVA_PACKAGE_NAME, APP_DATA_DIR, user_id); sprintf(app_path, "%s/%d/" JAVA_PACKAGE_NAME, APP_DATA_DIR, user_id);
if (stat(app_path, st) == 0) { if (stat(app_path, st) == 0) {
string cert = read_certificate(find_apk_path(JAVA_PACKAGE_NAME));
if (APKCERT.empty())
cert.clear();
if (cert == APKCERT) {
if (pkg) if (pkg)
*pkg = JAVA_PACKAGE_NAME; *pkg = JAVA_PACKAGE_NAME;
return true; return true;
} else { } else {
LOGE("su: cannot find manager\n"); LOGW("su: app signature mismatch\n");
}
}
LOGE("su: cannot find trusted app\n");
memset(st, 0, sizeof(*st)); memset(st, 0, sizeof(*st));
if (pkg) if (pkg)
pkg->clear(); pkg->clear();
return false; return false;
} }
}
bool get_manager(string *pkg) { bool get_manager(string *pkg) {
struct stat st; struct stat st{};
return get_manager(0, pkg, &st); return get_manager(0, pkg, &st);
} }
int get_manager_app_id() { int get_manager_app_id() {
struct stat st; struct stat st{};
if (get_manager(0, nullptr, &st)) if (get_manager(0, nullptr, &st))
return to_app_id(st.st_uid); return to_app_id(st.st_uid);
return -1; return -1;

View File

@ -152,6 +152,7 @@ void exec_module_scripts(const char *stage, const vector<string_view> &modules)
constexpr char install_script[] = R"EOF( constexpr char install_script[] = R"EOF(
APK=%s APK=%s
log -t Magisk "apk_uninstall: $(pm uninstall %s 2>&1)"
log -t Magisk "apk_install: $APK" log -t Magisk "apk_install: $APK"
log -t Magisk "apk_install: $(pm install -r $APK 2>&1)" log -t Magisk "apk_install: $(pm install -r $APK 2>&1)"
rm -f $APK rm -f $APK
@ -163,7 +164,7 @@ void install_apk(const char *apk) {
.fork = fork_no_orphan .fork = fork_no_orphan
}; };
char cmds[sizeof(install_script) + 4096]; 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); exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
} }

View File

@ -84,11 +84,12 @@ protected:
* DB Strings * * DB Strings *
**************/ **************/
constexpr const char *DB_STRING_KEYS[] = { "requester" }; constexpr const char *DB_STRING_KEYS[] = { "requester", "cert_digest" };
// Strings keys indices // Strings keys indices
enum { enum {
SU_MANAGER = 0 SU_MANAGER = 0,
CERT_DIGEST
}; };
class db_strings : public db_dict<std::string, std::size(DB_STRING_KEYS)> { class db_strings : public db_dict<std::string, std::size(DB_STRING_KEYS)> {

View File

@ -35,6 +35,7 @@ constexpr const char *applet_names[] = { "su", "resetprop", nullptr };
extern int SDK_INT; extern int SDK_INT;
#define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user") #define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user")
extern std::string APKCERT;
// Multi-call entrypoints // Multi-call entrypoints
int magisk_main(int argc, char *argv[]); int magisk_main(int argc, char *argv[]);

View File

@ -57,17 +57,33 @@ static void post_order_walk(int dirfd, const Func &fn) {
} }
} }
enum visit_result {
CONTINUE, SKIP, TERMINATE
};
template <typename Func> template <typename Func>
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); auto dir = xopen_dir(dirfd);
if (!dir) return; if (!dir) {
close(dirfd);
return SKIP;
}
for (dirent *entry; (entry = xreaddir(dir.get()));) { for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (!fn(dirfd, entry)) switch (fn(dirfd, entry)) {
case CONTINUE:
break;
case SKIP:
continue; continue;
if (entry->d_type == DT_DIR) case TERMINATE:
pre_order_walk(xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC), fn); 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) { static void remove_at(int dirfd, struct dirent *entry) {
@ -400,20 +416,20 @@ void parse_mnt(const char *file, const function<bool(mntent*)> &fn) {
} }
void backup_folder(const char *dir, vector<raw_file> &files) { void backup_folder(const char *dir, vector<raw_file> &files) {
char path[4096]; char path[PATH_MAX];
xrealpath(dir, path); xrealpath(dir, path);
int len = strlen(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); int fd = xopenat(dfd, entry->d_name, O_RDONLY);
if (fd < 0) if (fd < 0)
return false; return SKIP;
run_finally f([&]{ close(fd); }); run_finally f([&]{ close(fd); });
if (fd_path(fd, path, sizeof(path)) < 0) if (fd_path(fd, path, sizeof(path)) < 0)
return false; return SKIP;
raw_file file; raw_file file;
file.path = path + len + 1; file.path = path + len + 1;
if (fgetattr(fd, &file.attr) < 0) if (fgetattr(fd, &file.attr) < 0)
return false; return SKIP;
if (entry->d_type == DT_REG) { if (entry->d_type == DT_REG) {
fd_full_read(fd, file.buf, file.sz); fd_full_read(fd, file.buf, file.sz);
} else if (entry->d_type == DT_LNK) { } else if (entry->d_type == DT_LNK) {
@ -423,7 +439,7 @@ void backup_folder(const char *dir, vector<raw_file> &files) {
memcpy(file.buf, path, file.sz); memcpy(file.buf, path, file.sz);
} }
files.emplace_back(std::move(file)); 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); close(fd);
buf = static_cast<uint8_t *>(b); buf = static_cast<uint8_t *>(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;
}

View File

@ -102,6 +102,9 @@ void parse_mnt(const char *file, const std::function<bool(mntent*)> &fn);
void backup_folder(const char *dir, std::vector<raw_file> &files); void backup_folder(const char *dir, std::vector<raw_file> &files);
void restore_folder(const char *dir, std::vector<raw_file> &files); void restore_folder(const char *dir, std::vector<raw_file> &files);
std::string find_apk_path(const char *pkg);
std::string read_certificate(std::string app_path);
template <typename T> template <typename T>
void full_read(const char *filename, T &buf, size_t &size) { void full_read(const char *filename, T &buf, size_t &size) {
static_assert(std::is_pointer<T>::value); static_assert(std::is_pointer<T>::value);