From d55dc13e878e8219b93afa9aff9866f4f0c290cb Mon Sep 17 00:00:00 2001 From: 5ec1cff Date: Thu, 11 Jul 2024 14:53:13 +0800 Subject: [PATCH] works now --- build.gradle.kts | 30 +- gradle/libs.versions.toml | 9 + module/build.gradle.kts | 6 +- module/src/main/cpp/CMakeLists.txt | 3 + module/src/main/cpp/binder_interceptor.cpp | 3 +- module/src/main/cpp/inject/main.cpp | 298 ++++++++ module/src/main/cpp/inject/utils.cpp | 696 ++++++++++++++++++ module/src/main/cpp/inject/utils.hpp | 166 +++++ module/template/customize.sh | 29 +- module/template/sepolicy.rule | 4 + module/template/service.sh | 11 + service/.gitignore | 1 + service/build.gradle.kts | 32 + service/proguard-rules.pro | 21 + service/src/main/AndroidManifest.xml | 2 + .../io/github/a13e300/tricky_store/Main.kt | 224 ++++++ .../a13e300/tricky_store/fwpatch/Android.java | 188 +++++ .../a13e300/tricky_store/fwpatch/Keybox.java | 107 +++ .../a13e300/tricky_store/fwpatch/Utils.java | 69 ++ settings.gradle.kts | 2 + stub/.gitignore | 1 + stub/build.gradle.kts | 24 + stub/consumer-rules.pro | 0 stub/proguard-rules.pro | 21 + stub/src/main/AndroidManifest.xml | 2 + .../android/content/pm/IPackageManager.java | 13 + .../main/java/android/os/ServiceManager.java | 19 + .../system/keystore2/IKeystoreService.java | 9 + .../system/keystore2/KeyDescriptor.java | 39 + .../system/keystore2/KeyEntryResponse.java | 37 + .../android/system/keystore2/KeyMetadata.java | 41 ++ 31 files changed, 2084 insertions(+), 23 deletions(-) create mode 100644 module/src/main/cpp/inject/main.cpp create mode 100644 module/src/main/cpp/inject/utils.cpp create mode 100644 module/src/main/cpp/inject/utils.hpp create mode 100644 service/.gitignore create mode 100644 service/build.gradle.kts create mode 100644 service/proguard-rules.pro create mode 100644 service/src/main/AndroidManifest.xml create mode 100644 service/src/main/java/io/github/a13e300/tricky_store/Main.kt create mode 100644 service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Android.java create mode 100644 service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Keybox.java create mode 100644 service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Utils.java create mode 100644 stub/.gitignore create mode 100644 stub/build.gradle.kts create mode 100644 stub/consumer-rules.pro create mode 100644 stub/proguard-rules.pro create mode 100644 stub/src/main/AndroidManifest.xml create mode 100644 stub/src/main/java/android/content/pm/IPackageManager.java create mode 100644 stub/src/main/java/android/os/ServiceManager.java create mode 100644 stub/src/main/java/android/system/keystore2/IKeystoreService.java create mode 100644 stub/src/main/java/android/system/keystore2/KeyDescriptor.java create mode 100644 stub/src/main/java/android/system/keystore2/KeyEntryResponse.java create mode 100644 stub/src/main/java/android/system/keystore2/KeyMetadata.java diff --git a/build.gradle.kts b/build.gradle.kts index 570c9c5..29d908f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,11 @@ import com.android.build.gradle.AppExtension +import com.android.build.gradle.LibraryExtension import java.io.ByteArrayOutputStream plugins { alias(libs.plugins.agp.app) apply false + alias(libs.plugins.jetbrains.kotlin.android) apply false + alias(libs.plugins.android.library) apply false } fun String.execute(currentWorkingDir: File = file("./")): String { @@ -24,7 +27,7 @@ val moduleName by extra("Tricky Store") val verName by extra("v1") val verCode by extra(gitCommitCount) val commitHash by extra(gitCommitHash) -val abiList by extra(listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64")) +val abiList by extra(listOf("arm64-v8a", "x86_64")) val androidMinSdkVersion by extra(26) val androidTargetSdkVersion by extra(34) @@ -47,6 +50,9 @@ fun Project.configureBaseExtension() { defaultConfig { minSdk = androidMinSdkVersion + targetSdk = androidCompileSdkVersion + versionCode = verCode + versionName = verName } compileOptions { @@ -55,12 +61,34 @@ fun Project.configureBaseExtension() { } } + extensions.findByType(LibraryExtension::class)?.run { + namespace = "io.github.a13e300.tricky_store" + compileSdk = androidCompileSdkVersion + ndkVersion = androidCompileNdkVersion + buildToolsVersion = androidBuildToolsVersion + + defaultConfig { + minSdk = androidMinSdkVersion + } + + lint { + abortOnError = true + } + + compileOptions { + sourceCompatibility = androidSourceCompatibility + targetCompatibility = androidTargetCompatibility + } + } } subprojects { plugins.withId("com.android.application") { configureBaseExtension() } + plugins.withId("com.android.library") { + configureBaseExtension() + } plugins.withType(JavaPlugin::class.java) { extensions.configure(JavaPluginExtension::class.java) { sourceCompatibility = androidSourceCompatibility diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6a3a176..c042ed5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,14 @@ [versions] agp = "8.5.0" +kotlin = "2.0.0" +annotation = "1.8.0" +bcpkix-jdk15on = "1.58.0.0" + +[libraries] +annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } +bcpkix-jdk15on = { module = "com.madgag.spongycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" } [plugins] agp-app = { id = "com.android.application", version.ref = "agp" } +jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +android-library = { id = "com.android.library", version.ref = "agp" } diff --git a/module/build.gradle.kts b/module/build.gradle.kts index d8d0614..e40d1be 100644 --- a/module/build.gradle.kts +++ b/module/build.gradle.kts @@ -57,7 +57,7 @@ androidComponents.onVariants { variant -> val prepareModuleFilesTask = task("prepareModuleFiles$variantCapped") { group = "module" - dependsOn("assemble$variantCapped") + dependsOn("assemble$variantCapped", ":service:assemble$variantCapped") into(moduleDir) from(rootProject.layout.projectDirectory.file("README.md")) from(layout.projectDirectory.file("template")) { @@ -83,7 +83,11 @@ androidComponents.onVariants { variant -> filter("tokens" to tokens) filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) } + from(project(":service").layout.buildDirectory.file("outputs/apk/$variantLowered/service-$variantLowered.apk")) { + rename { "service.apk" } + } from(layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/strip${variantCapped}DebugSymbols/out/lib")) { + exclude("**/libbinder.so", "**/libutils.so") into("lib") } diff --git a/module/src/main/cpp/CMakeLists.txt b/module/src/main/cpp/CMakeLists.txt index 96940cc..ee13a33 100644 --- a/module/src/main/cpp/CMakeLists.txt +++ b/module/src/main/cpp/CMakeLists.txt @@ -35,5 +35,8 @@ add_library(binder SHARED binder/stub_binder.cpp) target_include_directories(binder PUBLIC binder/include) target_link_libraries(binder utils) +add_executable(libinject.so inject/main.cpp inject/utils.cpp) +target_link_libraries(libinject.so lspmparser my_logging) + add_library(${MODULE_NAME} SHARED binder_interceptor.cpp) target_link_libraries(${MODULE_NAME} log binder utils dobby elf_util my_logging) diff --git a/module/src/main/cpp/binder_interceptor.cpp b/module/src/main/cpp/binder_interceptor.cpp index 29bcedd..ddfc7f3 100644 --- a/module/src/main/cpp/binder_interceptor.cpp +++ b/module/src/main/cpp/binder_interceptor.cpp @@ -62,7 +62,7 @@ CREATE_MEM_HOOK_STUB_ENTRY( { LOGD("transact: binder=%p code=%d", thiz, code); if (IPCThreadState::self()->getCallingUid() == 0 && reply != nullptr && - thiz != gBinderInterceptor) { + thiz != gBinderInterceptor) [[unlikely]] { if (code == 0xdeadbeef) { LOGD("request binder interceptor"); reply->writeStrongBinder(gBinderInterceptor); @@ -128,6 +128,7 @@ BinderInterceptor::onTransact(uint32_t code, const android::Parcel &data, androi items.erase(it); return OK; } + return BAD_VALUE; } } return UNKNOWN_TRANSACTION; diff --git a/module/src/main/cpp/inject/main.cpp b/module/src/main/cpp/inject/main.cpp new file mode 100644 index 0000000..0fe6c7d --- /dev/null +++ b/module/src/main/cpp/inject/main.cpp @@ -0,0 +1,298 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lsplt.hpp" + +#include "logging.hpp" +#include "utils.hpp" + +using namespace std::string_literals; + +// zygote inject + +bool inject_library(int pid, const char *lib_path, const char* entry_name) { + LOGI("injecting %s and calling %s in %d", lib_path, entry_name, pid); + struct user_regs_struct regs{}, backup{}; + std::vector map; + + if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) { + PLOGE("attach"); + } + int status; + { + wait_for_trace(pid, &status, __WALL); + } + if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) { + if (!get_regs(pid, regs)) return false; + // The linker has been initialized now, we can do dlopen + LOGD("stopped at entry"); + // backup registers + memcpy(&backup, ®s, sizeof(regs)); + { + map = lsplt::MapInfo::Scan(std::to_string(pid)); + } + auto local_map = lsplt::MapInfo::Scan(); + auto libc_return_addr = find_module_return_addr(map, "libc.so"); + LOGD("libc return addr %p", libc_return_addr); + + std::vector args; + uintptr_t str, remote_handle, injector_entry; + auto close_addr = find_func_addr(local_map, map, "libc.so", "close"); + if (!close_addr) return false; + + int lib_fd = -1; + + // prepare fd + { + set_sockcreate_con("u:object_r:system_file:s0"); + UniqueFd local_socket = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (local_socket == -1) { + PLOGE("create local socket"); + return false; + } + if (setfilecon(lib_path, "u:object_r:system_file:s0") == -1) { + PLOGE("set context of lib"); + } + UniqueFd local_lib_fd = open(lib_path, O_RDONLY | O_CLOEXEC); + if (local_lib_fd == -1) { + PLOGE("open lib"); + return false; + } + auto socket_addr = find_func_addr(local_map, map, "libc.so", "socket"); + auto bind_addr = find_func_addr(local_map, map, "libc.so", "bind"); + auto recvmsg_addr = find_func_addr(local_map, map, "libc.so", "recvmsg"); + auto errno_addr = find_func_addr(local_map, map, "libc.so", "__errno"); + auto get_remote_errno = [&]() -> int { + if (!errno_addr) { + LOGE("could not get errno!"); + return 0; + } + auto addr = remote_call(pid, regs, (uintptr_t) errno_addr, 0, args); + int err = 0; + if (!read_proc(pid, addr, &err, sizeof(err))) return 0; + return err; + }; + if (!socket_addr || !bind_addr || !recvmsg_addr) return false; + args.clear(); + args.push_back(AF_UNIX); + args.push_back(SOCK_DGRAM | SOCK_CLOEXEC); + args.push_back(0); + int remote_fd = (int) remote_call(pid, regs, (uintptr_t) socket_addr, 0, args); + if (remote_fd == -1) { + errno = get_remote_errno(); + PLOGE("remote socket"); + return false; + } + + auto close_remote = [&](int fd) -> void { + args.clear(); + args.push_back(fd); + if (remote_call(pid, regs, (uintptr_t) close_addr, 0, args) != 0) { + LOGE("remote not closed: %d", fd); + } + }; + + auto magic = generateMagic(16); + struct sockaddr_un addr{ + .sun_family = AF_UNIX, + .sun_path = {0} + }; + LOGD("socket name %s", magic.c_str()); + memcpy(addr.sun_path + 1, magic.c_str(), magic.size()); + socklen_t len = sizeof(addr.sun_family) + 1 + magic.size(); + auto remote_addr = push_memory(pid, regs, &addr, sizeof(addr)); + args.clear(); + args.push_back(remote_fd); + args.push_back(remote_addr); + args.push_back(len); + auto bind_result = remote_call(pid, regs, (uintptr_t) bind_addr, 0, args); + if (bind_result == (uintptr_t) -1) { + errno = get_remote_errno(); + PLOGE("remote bind"); + close_remote(remote_fd); + return false; + } + + char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0}; + auto remote_cmsgbuf = push_memory(pid, regs, &cmsgbuf, sizeof(cmsgbuf)); + + struct msghdr hdr{}; + hdr.msg_control = (void*) remote_cmsgbuf; + hdr.msg_controllen = sizeof(cmsgbuf); + + auto remote_hdr = push_memory(pid, regs, &hdr, sizeof(hdr)); + + args.clear(); + args.push_back(remote_fd); + args.push_back(remote_hdr); + args.push_back(MSG_WAITALL); + if (!remote_pre_call(pid, regs, (uintptr_t) recvmsg_addr, 0, args)) { + // we can't do anything more + LOGE("pre call remote recvmsg"); + return false; + } + + hdr.msg_control = &cmsgbuf; + hdr.msg_name = &addr; + hdr.msg_namelen = len; + { + cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = AF_UNIX; + *(int *) CMSG_DATA(cmsg) = local_lib_fd; + } + + if (sendmsg(local_socket, &hdr, 0) == -1) { + PLOGE("send to remote"); + close_remote(remote_fd); + return false; + } + + auto recvmsg_result = (ssize_t) remote_post_call(pid, regs, 0); + if (recvmsg_result == -1) { + errno = get_remote_errno(); + PLOGE("post call recvmsg"); + close_remote(remote_fd); + return false; + } + + if (read_proc(pid, remote_cmsgbuf, &cmsgbuf, sizeof(cmsgbuf)) != sizeof(cmsgbuf)) { + LOGE("failed to read proc"); + close_remote(remote_fd); + return false; + } + + cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr); + if (cmsg == nullptr || cmsg->cmsg_len != CMSG_LEN(sizeof(int)) || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { + LOGE("remote recv fd failed!"); + close_remote(remote_fd); + return false; + } + + lib_fd = *(int*) CMSG_DATA(cmsg); + LOGD("remote lib fd: %d", lib_fd); + close_remote(remote_fd); + } + + // call dlopen + { + auto dlopen_addr = find_func_addr(local_map, map, "libdl.so", "android_dlopen_ext"); + if (dlopen_addr == nullptr) return false; + android_dlextinfo info{}; + info.flags = ANDROID_DLEXT_USE_LIBRARY_FD; + info.library_fd = lib_fd; + uintptr_t remote_info = push_memory(pid, regs, &info, sizeof(info)); + str = push_string(pid, regs, lib_path); + args.clear(); + args.push_back((long) str); + args.push_back((long) RTLD_NOW); + args.push_back(remote_info); + remote_handle = remote_call(pid, regs, (uintptr_t) dlopen_addr, + (uintptr_t) libc_return_addr, args); + LOGD("remote handle %p", (void *) remote_handle); + if (remote_handle == 0) { + LOGE("handle is null"); + // call dlerror + auto dlerror_addr = find_func_addr(local_map, map, "libdl.so", "dlerror"); + if (dlerror_addr == nullptr) { + LOGE("find dlerror"); + return false; + } + args.clear(); + auto dlerror_str_addr = remote_call(pid, regs, (uintptr_t) dlerror_addr, + (uintptr_t) libc_return_addr, args); + LOGD("dlerror str %p", (void *) dlerror_str_addr); + if (dlerror_str_addr == 0) return false; + auto strlen_addr = find_func_addr(local_map, map, "libc.so", "strlen"); + if (strlen_addr == nullptr) { + LOGE("find strlen"); + return false; + } + args.clear(); + args.push_back(dlerror_str_addr); + auto dlerror_len = remote_call(pid, regs, (uintptr_t) strlen_addr, + (uintptr_t) libc_return_addr, args); + if (dlerror_len <= 0) { + LOGE("dlerror len <= 0"); + return false; + } + std::string err; + err.resize(dlerror_len + 1, 0); + read_proc(pid, (uintptr_t) dlerror_str_addr, err.data(), dlerror_len); + LOGE("dlerror info %s", err.c_str()); + return false; + } + + args.clear(); + args.push_back(lib_fd); + if (remote_call(pid, regs, (uintptr_t) close_addr, 0, args) != 0) { + LOGE("remote lib not closed: %d", lib_fd); + return false; + } + } + + // call dlsym(handle, "entry") + { + auto dlsym_addr = find_func_addr(local_map, map, "libdl.so", "dlsym"); + if (dlsym_addr == nullptr) return false; + args.clear(); + str = push_string(pid, regs, "entry"); + args.push_back(remote_handle); + args.push_back((long) str); + injector_entry = remote_call(pid, regs, (uintptr_t) dlsym_addr, + (uintptr_t) libc_return_addr, args); + LOGD("injector entry %p", (void *) injector_entry); + if (injector_entry == 0) { + LOGE("injector entry is null"); + return false; + } + } + + // call injector entry(handle) + { + args.clear(); + args.push_back(remote_handle); + remote_call(pid, regs, injector_entry, (uintptr_t) libc_return_addr, args); + } + + LOGD("restore context"); + // restore registers + if (!set_regs(pid, backup)) return false; + if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) { + PLOGE("failed to detach"); + return false; + } + return true; + } else { + LOGE("stopped by other reason: %s", parse_status(status).c_str()); + } + return false; +} + +int main(int argc, char **argv) { + logging::setPrintEnabled(true); + auto pid = strtol(argv[1], nullptr, 0); + char buf[4096]; + realpath(argv[2], buf); + return !inject_library(pid, buf, argv[3]); +} diff --git a/module/src/main/cpp/inject/utils.cpp b/module/src/main/cpp/inject/utils.cpp new file mode 100644 index 0000000..7ab3fc3 --- /dev/null +++ b/module/src/main/cpp/inject/utils.cpp @@ -0,0 +1,696 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.hpp" +#include "logging.hpp" +#include +#include + +bool switch_mnt_ns(int pid, int *fd) { + int nsfd, old_nsfd = -1; + std::string path; + if (pid == 0) { + if (fd != nullptr) { + nsfd = *fd; + *fd = -1; + } else return false; + path = "/proc/self/fd/"; + path += std::to_string(nsfd); + } else { + if (fd != nullptr) { + old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (old_nsfd == -1) { + PLOGE("get old nsfd"); + return false; + } + *fd = old_nsfd; + } + path = std::string("/proc/") + std::to_string(pid) + "/ns/mnt"; + nsfd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + if (nsfd == -1) { + PLOGE("open nsfd %s", path.c_str()); + close(old_nsfd); + return false; + } + } + if (setns(nsfd, CLONE_NEWNS) == -1) { + PLOGE("set ns to %s", path.c_str()); + close(nsfd); + close(old_nsfd); + return false; + } + close(nsfd); + return true; +} + +ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem) { + LOGV("write to %d addr %" PRIxPTR " size %zu use_proc_mem=%d", pid, remote_addr, len, use_proc_mem); + if (use_proc_mem) { + char path[64]; + snprintf(path, sizeof(path), "/proc/%d/mem", pid); + auto fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd == -1) { + PLOGE("open proc mem"); + return -1; + } + auto l = pwrite(fd, buf, len, remote_addr); + if (l == -1) { + PLOGE("pwrite"); + } else if (static_cast(l) != len) { + LOGW("not fully written: %zu, excepted %zu", l, len); + } + return l; + } else { + struct iovec local{ + .iov_base = (void *) buf, + .iov_len = len + }; + struct iovec remote{ + .iov_base = (void *) remote_addr, + .iov_len = len + }; + auto l = process_vm_writev(pid, &local, 1, &remote, 1, 0); + if (l == -1) { + PLOGE("process_vm_writev"); + } else if (static_cast(l) != len) { + LOGW("not fully written: %zu, excepted %zu", l, len); + } + return l; + } +} + +ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len) { + struct iovec local{ + .iov_base = (void *) buf, + .iov_len = len + }; + struct iovec remote{ + .iov_base = (void *) remote_addr, + .iov_len = len + }; + auto l = process_vm_readv(pid, &local, 1, &remote, 1, 0); + if (l == -1) { + PLOGE("process_vm_readv"); + } else if (static_cast(l) != len) { + LOGW("not fully read: %zu, excepted %zu", l, len); + } + return l; +} + +bool get_regs(int pid, struct user_regs_struct ®s) { +#if defined(__x86_64__) || defined(__i386__) + if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) { + PLOGE("getregs"); + return false; + } +#elif defined(__aarch64__) || defined(__arm__) + struct iovec iov = { + .iov_base = ®s, + .iov_len = sizeof(struct user_regs_struct), + }; + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) { + PLOGE("getregs"); + return false; + } +#endif + return true; +} + +bool set_regs(int pid, struct user_regs_struct ®s) { +#if defined(__x86_64__) || defined(__i386__) + if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) { + PLOGE("setregs"); + return false; + } +#elif defined(__aarch64__) || defined(__arm__) + struct iovec iov = { + .iov_base = ®s, + .iov_len = sizeof(struct user_regs_struct), + }; + if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) { + PLOGE("setregs"); + return false; + } +#endif + return true; +} + +std::string get_addr_mem_region(std::vector &info, uintptr_t addr) { + for (auto &map: info) { + if (map.start <= addr && map.end > addr) { + auto s = std::string(map.path); + s += ' '; + s += map.perms & PROT_READ ? 'r' : '-'; + s += map.perms & PROT_WRITE ? 'w' : '-'; + s += map.perms & PROT_EXEC ? 'x' : '-'; + return s; + } + } + return ""; +} + + +void *find_module_return_addr(std::vector &info, std::string_view suffix) { + for (auto &map: info) { + if ((map.perms & PROT_EXEC) == 0 && map.path.ends_with(suffix)) { + return (void *) map.start; + } + } + return nullptr; +} + +void *find_module_base(std::vector &info, std::string_view suffix) { + for (auto &map: info) { + if (map.offset == 0 && map.path.ends_with(suffix)) { + return (void *) map.start; + } + } + return nullptr; +} + +void *find_func_addr( + std::vector &local_info, + std::vector &remote_info, + std::string_view module, + std::string_view func) { + auto lib = dlopen(module.data(), RTLD_NOW); + if (lib == nullptr) { + LOGE("failed to open lib %s: %s", module.data(), dlerror()); + return nullptr; + } + auto sym = reinterpret_cast(dlsym(lib, func.data())); + if (sym == nullptr) { + LOGE("failed to find sym %s in %s: %s", func.data(), module.data(), dlerror()); + dlclose(lib); + return nullptr; + } + LOGD("sym %s: %p", func.data(), sym); + dlclose(lib); + auto local_base = reinterpret_cast(find_module_base(local_info, module)); + if (local_base == nullptr) { + LOGE("failed to find local base for module %s", module.data()); + return nullptr; + } + auto remote_base = reinterpret_cast(find_module_base(remote_info, module)); + if (remote_base == nullptr) { + LOGE("failed to find remote base for module %s", module.data()); + return nullptr; + } + LOGD("found local base %p remote base %p", local_base, remote_base); + auto addr = (sym - local_base) + remote_base; + LOGD("addr %p", addr); + return addr; +} + +void align_stack(struct user_regs_struct ®s, uintptr_t preserve) { + regs.REG_SP = (regs.REG_SP - preserve) & ~0xf; +} + +uintptr_t push_memory(int pid, struct user_regs_struct ®s, void* local_addr, size_t len) { + regs.REG_SP -= len; + align_stack(regs); + auto addr = static_cast(regs.REG_SP); + if (!write_proc(pid, addr, local_addr, len)) { + LOGE("failed to write mem %p+%zu", local_addr, len); + } + LOGD("pushed mem %" PRIxPTR, addr); + return addr; +} + +uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str) { + auto len = strlen(str) + 1; + regs.REG_SP -= len; + align_stack(regs); + auto addr = static_cast(regs.REG_SP); + if (!write_proc(pid, addr, str, len)) { + LOGE("failed to write string %s", str); + } + LOGD("pushed string %" PRIxPTR, addr); + return addr; +} + +bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, + std::vector &args) { + align_stack(regs); + LOGV("calling remote function %" PRIxPTR " args %zu", func_addr, args.size()); + for (auto &a: args) { + LOGV("arg %p", (void *) a); + } +#if defined(__x86_64__) + if (args.size() >= 1) { + regs.rdi = args[0]; + } + if (args.size() >= 2) { + regs.rsi = args[1]; + } + if (args.size() >= 3) { + regs.rdx = args[2]; + } + if (args.size() >= 4) { + regs.rcx = args[3]; + } + if (args.size() >= 5) { + regs.r8 = args[4]; + } + if (args.size() >= 6) { + regs.r9 = args[5]; + } + if (args.size() > 6) { + auto remain = (args.size() - 6) * sizeof(uintptr_t); + align_stack(regs, remain); + if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) { + LOGE("failed to push arguments"); + } + } + regs.REG_SP -= sizeof(uintptr_t); + if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) { + LOGE("failed to write return addr"); + } + regs.REG_IP = func_addr; +#elif defined(__i386__) + if (args.size() > 0) { + auto remain = (args.size()) * sizeof(uintptr_t); + align_stack(regs, remain); + if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) { + LOGE("failed to push arguments"); + } + } + regs.REG_SP -= sizeof(uintptr_t); + if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) { + LOGE("failed to write return addr"); + } + regs.REG_IP = func_addr; +#elif defined(__aarch64__) + for (size_t i = 0; i < args.size() && i < 8; i++) { + regs.regs[i] = args[i]; + } + if (args.size() > 8) { + auto remain = (args.size() - 8) * sizeof(uintptr_t); + align_stack(regs, remain); + write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain); + } + regs.regs[30] = return_addr; + regs.REG_IP = func_addr; +#elif defined(__arm__) + for (size_t i = 0; i < args.size() && i < 4; i++) { + regs.uregs[i] = args[i]; + } + if (args.size() > 4) { + auto remain = (args.size() - 4) * sizeof(uintptr_t); + align_stack(regs, remain); + write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain); + } + regs.uregs[14] = return_addr; + regs.REG_IP = func_addr; + constexpr auto CPSR_T_MASK = 1lu << 5; + if ((regs.REG_IP & 1) != 0) { + regs.REG_IP = regs.REG_IP & ~1; + regs.uregs[16] = regs.uregs[16] | CPSR_T_MASK; + } else { + regs.uregs[16] = regs.uregs[16] & ~CPSR_T_MASK; + } +#endif + if (!set_regs(pid, regs)) { + LOGE("failed to set regs"); + return false; + } + return ptrace(PTRACE_CONT, pid, 0, 0) == 0; +} + +uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, uintptr_t return_addr) { + int status; + wait_for_trace(pid, &status, __WALL); + if (!get_regs(pid, regs)) { + LOGE("failed to get regs after call"); + return 0; + } + if (WSTOPSIG(status) == SIGSEGV) { + if (static_cast(regs.REG_IP) != return_addr) { + LOGE("wrong return addr %p", (void *) regs.REG_IP); + siginfo_t siginfo; + if (ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo) == -1) { + PLOGE("failed to get siginfo"); + } else { + LOGE("si_code=%d si_addr=%p", siginfo.si_code, siginfo.si_addr); + } + return 0; + } + return regs.REG_RET; + } else { + LOGE("stopped by other reason %s at addr %p", parse_status(status).c_str(), (void*) regs.REG_IP); + } + return 0; +} + +uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, + std::vector &args) { + if (!remote_pre_call(pid, regs, func_addr, return_addr, args)) return 0; + return remote_post_call(pid, regs, return_addr); +} + +int fork_dont_care() { + auto pid = fork(); + if (pid < 0) { + PLOGE("fork 1"); + } else if (pid == 0) { + pid = fork(); + if (pid < 0) { + PLOGE("fork 2"); + } else if (pid > 0) { + exit(0); + } + } else { + int status; + waitpid(pid, &status, __WALL); + } + return pid; +} + +bool wait_for_trace(int pid, int* status, int flags) { + while (true) { + auto result = waitpid(pid, status, flags); + if (result == -1) { + if (errno == EINTR) { + continue; + } else { + PLOGE("wait %d failed", pid); + return false; + } + } + if (!WIFSTOPPED(*status)) { + LOGE("process %d not stopped for trace: %s, exit", pid, parse_status(*status).c_str()); + return false; + } + return true; + } +} + +std::string parse_status(int status) { + char buf[64]; + if (WIFEXITED(status)) { + snprintf(buf, sizeof(buf), "0x%x exited with %d", status, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + snprintf(buf, sizeof(buf), "0x%x signaled with %s(%d)", status, sigabbrev_np(WTERMSIG(status)), WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + auto stop_sig = WSTOPSIG(status); + snprintf(buf, sizeof(buf), "0x%x stopped by signal=%s(%d),event=%s", status, sigabbrev_np(stop_sig), stop_sig, parse_ptrace_event(status)); + } else { + snprintf(buf, sizeof(buf), "0x%x unknown", status); + } + return buf; +} + +std::string get_program(int pid) { + std::string path = "/proc/"; + path += std::to_string(pid); + path += "/exe"; + constexpr const auto SIZE = 256; + char buf[SIZE + 1]; + auto sz = readlink(path.c_str(), buf, SIZE); + if (sz == -1) { + PLOGE("readlink /proc/%d/exe", pid); + return ""; + } + buf[sz] = 0; + return buf; +} + +std::string parse_exec(int pid) { + struct user_regs_struct regs; + if (!get_regs(pid, regs)) return ""; + auto nr = regs.REG_NR; + if (nr != SYS_execve) { + // LOGI("syscall %ld != %d", nr, SYS_execve); + return ""; + } + auto file_name_ptr = regs.REG_SYS_ARG0; + char buf[128]; + auto sz = read_proc(pid, file_name_ptr, buf, sizeof(buf)); + if (sz < 0) return ""; + for (auto i = 0; i < sz; i++) { + if (buf[i] == 0) { + LOGD("exec len %d prog %s", i, buf); + return buf; + } + } + // too long + return ""; +} + +bool skip_syscall(int pid) { + struct user_regs_struct regs; + if (!get_regs(pid, regs)) return false; +#if defined(__aarch64__) + // https://stackoverflow.com/questions/63620203/ptrace-change-syscall-number-arm64 + int syscallno = 0xffff; + struct iovec iov = { + .iov_base = &syscallno, + .iov_len = sizeof (int), + }; + if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_SYSTEM_CALL, &iov) == -1) { + PLOGE("failed to set syscall"); + } +#elif defined(__arm__) + if (ptrace(PTRACE_SET_SYSCALL, pid, 0, 0xffff) == -1) { + PLOGE("failed to set syscall"); + } +#else + auto orig_nr = regs.REG_NR; + regs.REG_NR = 0xffff; + if (!set_regs(pid, regs)) return false; + regs.REG_NR = orig_nr; +#endif + if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) { + PLOGE("failed to singlestep"); + return false; + } + int status; + waitpid(pid, &status, __WALL); +#if defined(__x86_64__) + regs.REG_IP -= 2; + regs.rax = orig_nr; +#elif defined(__i386__) + regs.REG_IP -= 2; + regs.eax = orig_nr; +#elif defined(__aarch64__) + regs.REG_IP -= 4; +#elif defined(__arm__) + regs.REG_IP -= (regs.REG_IP % 2) ? 2 : 4; +#endif + return set_regs(pid, regs); +} + +void wait_for_syscall(int pid) { + int status; + for (;;) { + if (!wait_for_trace(pid, &status, __WALL)) { + LOGE("could not wait for trace"); + exit(1); + } + if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) { + LOGV("!! stopped at syscall"); + break; + } + LOGV("! not syscall: %s", parse_status(status).c_str()); + if (ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status)) == -1) { + PLOGE("failed to cont"); + exit(1); + } + } +} + +bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5) { +#if defined(__i386__) + LOGE("do_syscall is unsupported on i386!"); + return false; +#else + struct user_regs_struct regs, backup_regs; + if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) { + PLOGE("failed to singlestep"); + return false; + } + wait_for_syscall(pid); + if (!get_regs(pid, regs)) return false; + memcpy(&backup_regs, ®s, sizeof(struct user_regs_struct)); + // set syscall nr and args +#if defined(__aarch64__) + // https://stackoverflow.com/questions/63620203/ptrace-change-syscall-number-arm64 + int syscallno = nr; + struct iovec iov = { + .iov_base = &syscallno, + .iov_len = sizeof (int), + }; + if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_SYSTEM_CALL, &iov) == -1) { + PLOGE("failed to set syscall"); + return false; + } + regs.regs[0] = arg0; + regs.regs[1] = arg1; + regs.regs[2] = arg2; + regs.regs[3] = arg3; + regs.regs[4] = arg4; + regs.regs[5] = arg5; +#elif defined(__arm__) + if (ptrace(PTRACE_SET_SYSCALL, pid, 0, nr) == -1) { + PLOGE("failed to set syscall"); + return false; + } + regs.uregs[0] = arg0; + regs.uregs[1] = arg1; + regs.uregs[2] = arg2; + regs.uregs[3] = arg3; + regs.uregs[4] = arg4; + regs.uregs[5] = arg5; +#elif defined(__x86_64__) + auto orig_nr = regs.REG_NR; + regs.REG_NR = nr; + regs.rdi = arg0; + regs.rsi = arg1; + regs.rdx = arg2; + regs.r10 = arg3; + regs.r8 = arg4; + regs.r9 = arg5; +#endif + if (!set_regs(pid, regs)) return false; + if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) { + PLOGE("failed to singlestep"); + return false; + } + wait_for_syscall(pid); + if (!get_regs(pid, regs)) return false; + // go back to last instruction + // on x86, we should fill the nr register with orig_nr +#if defined(__x86_64__) + ret = regs.rax; + backup_regs.REG_IP -= 2; + backup_regs.rax = orig_nr; +#elif defined(__aarch64__) + ret = regs.regs[0]; + backup_regs.REG_IP -= 4; +#elif defined(__arm__) + ret = regs.uregs[0]; + backup_regs.REG_IP -= (regs.REG_IP % 2) ? 2 : 4; +#endif + return set_regs(pid, backup_regs); +#endif // i386 +} + +uintptr_t remote_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0) { + if (!do_syscall(pid, ret, nr, arg0, arg1, arg2, arg3, arg4, arg5)) { + LOGE("do remote syscall %d failed", nr); + return -1; + } + if (SYSCALL_IS_ERR(ret)) { + LOGE("do remote syscall %d return error %d %s", nr, SYSCALL_ERR(ret), strerror(SYSCALL_ERR(ret))); + return -1; + } + return ret; +} + +uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset) { + uintptr_t result; + LOGD("remote mmap %" PRIxPTR " size %zu fd %d off %lu", addr, size, fd, offset); + if (!remote_syscall(pid, result, SYS_mmap, addr, size, prot, flags, fd, offset)) { + LOGE("do remote mmap failed"); + return -1; + } + LOGD("remote mmap get %" PRIxPTR, result); + return result; +} + +bool remote_munmap(int pid, uintptr_t addr, size_t size) { + uintptr_t result = 0; + if (remote_syscall(pid, result, SYS_munmap, addr, size)) { + LOGE("do remote munmap failed"); + return false; + } + return true; +} + +int remote_open(int pid, uintptr_t path_addr, int flags) { + uintptr_t result; + if (!remote_syscall(pid, result, SYS_openat, AT_FDCWD, path_addr, flags)) { + LOGE("remote open failed"); + return -1; + } + return result; +} + +bool remote_close(int pid, int fd) { + uintptr_t result = 0; + if (remote_syscall(pid, result, SYS_close, fd)) { + LOGE("remote close failed"); + return false; + } + return true; +} + +int wait_for_child(int pid) { + int status; + for (;;) { + if (waitpid(pid, &status, 0) == -1) { + if (errno == EINTR) continue; + PLOGE("waitpid %d", pid); + return -1; + } else { + return status; + } + } +} + +std::string generateMagic(size_t len) { + constexpr const char chrs[] = "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::mt19937 rg{std::random_device{}()}; + std::uniform_int_distribution pick(0, sizeof(chrs) - 2); + + std::string s; + s.reserve(len); + while(len--) s += chrs[pick(rg)]; + + return s; +} + +int setfilecon(const char* path, const char* con) { + return syscall(__NR_setxattr, path, XATTR_NAME_SELINUX, con, strlen(con) + 1, 0); +} + +bool set_sockcreate_con(const char* con) { + auto sz = static_cast(strlen(con) + 1); + UniqueFd fd = open("/proc/thread-self/attr/sockcreate", O_WRONLY | O_CLOEXEC); + if (fd == -1 || write(fd, con, sz) != sz) { + PLOGE("set socket con"); + char buf[128]; + snprintf(buf, sizeof(buf), "/proc/%d/attr/sockcreate", gettid()); + fd = open(buf, O_WRONLY | O_CLOEXEC); + if (fd == -1 || write(fd, con, sz) != sz) { + PLOGE("set socket con fallback"); + return false; + } + } + return true; +} diff --git a/module/src/main/cpp/inject/utils.hpp b/module/src/main/cpp/inject/utils.hpp new file mode 100644 index 0000000..e684632 --- /dev/null +++ b/module/src/main/cpp/inject/utils.hpp @@ -0,0 +1,166 @@ +#pragma once +#include +#include +#include +#include "lsplt.hpp" + +#define LOG_TAG "TrickyStore" + +#define SYSCALL_IS_ERR(e) (((unsigned long) e) > -4096UL) +#define SYSCALL_ERR(e) (-(int)(e)) + +#if defined(__x86_64__) +#define REG_SP rsp +#define REG_IP rip +#define REG_RET rax +#define REG_NR orig_rax +#define REG_SYS_ARG0 rdi +#elif defined(__i386__) +#define REG_SP esp +#define REG_IP eip +#define REG_RET eax +#define REG_NR orig_eax +#define REG_SYS_ARG0 ebx +#elif defined(__aarch64__) +#define REG_SP sp +#define REG_IP pc +#define REG_RET regs[0] +#define REG_NR regs[8] +#define REG_SYS_ARG0 regs[0] +#elif defined(__arm__) +#define REG_SP uregs[13] +#define REG_IP uregs[15] +#define REG_RET uregs[0] +#define REG_NR uregs[7] +#define REG_SYS_ARG0 uregs[0] +#define user_regs_struct user_regs +#define SYS_mmap SYS_mmap2 +#endif + +ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem = false); + +ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len); + +bool get_regs(int pid, struct user_regs_struct ®s); + +bool set_regs(int pid, struct user_regs_struct ®s); + +std::string get_addr_mem_region(std::vector &info, uintptr_t addr); + +void *find_module_base(std::vector &info, std::string_view suffix); + +void *find_func_addr( + std::vector &local_info, + std::vector &remote_info, + std::string_view module, + std::string_view func); + +void align_stack(struct user_regs_struct ®s, uintptr_t preserve = 0); + +uintptr_t push_memory(int pid, struct user_regs_struct ®s, void* local_addr, size_t len); + +uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str); + +uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, + std::vector &args); + +int fork_dont_care(); + +bool wait_for_trace(int pid, int* status, int flags); + +std::string parse_status(int status); + +#define WPTEVENT(x) (x >> 16) + +#define CASE_CONST_RETURN(x) case x: return #x; + +inline const char* parse_ptrace_event(int status) { + status = status >> 16; + switch (status) { + CASE_CONST_RETURN(PTRACE_EVENT_FORK) + CASE_CONST_RETURN(PTRACE_EVENT_VFORK) + CASE_CONST_RETURN(PTRACE_EVENT_CLONE) + CASE_CONST_RETURN(PTRACE_EVENT_EXEC) + CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE) + CASE_CONST_RETURN(PTRACE_EVENT_EXIT) + CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP) + CASE_CONST_RETURN(PTRACE_EVENT_STOP) + default: + return "(no event)"; + } +} + +inline const char* sigabbrev_np(int sig) { + if (sig > 0 && sig < NSIG) return sys_signame[sig]; + return "(unknown)"; +} + +std::string get_program(int pid); +void *find_module_return_addr(std::vector &info, std::string_view suffix); + +// pid = 0, fd != nullptr -> set to fd +// pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd +bool switch_mnt_ns(int pid, int *fd); + +std::vector get_cmdline(int pid); + +std::string parse_exec(int pid); + +bool skip_syscall(int pid); +bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0); + +uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset); +bool remote_munmap(int pid, uintptr_t addr, size_t size); + +int remote_open(int pid, uintptr_t path_addr, int flags); +bool remote_close(int pid, int fd); + +int wait_for_child(int pid); +int get_elf_class(std::string_view path); + +bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, + std::vector &args); + +uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, uintptr_t return_addr); + + +// magic.h +constexpr const auto kMainMagicLength = 16; +std::string generateMagic(size_t len); + +// files.hpp + +int setfilecon(const char* path, const char* con); + +// utils.hpp +class UniqueFd { + using Fd = int; +public: + UniqueFd() = default; + + UniqueFd(Fd fd) : fd_(fd) {} + + ~UniqueFd() { if (fd_ >= 0) close(fd_); } + + // Disallow copy + UniqueFd(const UniqueFd&) = delete; + + UniqueFd& operator=(const UniqueFd&) = delete; + + // Allow move + UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); } + + UniqueFd& operator=(UniqueFd&& other) { + std::swap(fd_, other.fd_); + return *this; + } + + // Implict cast to Fd + operator const Fd&() const { return fd_; } + +private: + Fd fd_ = -1; +}; + +// socket_utils.hpp +bool set_sockcreate_con(const char* con); diff --git a/module/template/customize.sh b/module/template/customize.sh index 5dfb6a4..dd15d82 100644 --- a/module/template/customize.sh +++ b/module/template/customize.sh @@ -57,29 +57,18 @@ ui_print "- Extracting module files" extract "$ZIPFILE" 'module.prop' "$MODPATH" extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" extract "$ZIPFILE" 'service.sh' "$MODPATH" +extract "$ZIPFILE" 'service.apk' "$MODPATH" mv "$TMPDIR/sepolicy.rule" "$MODPATH" -HAS32BIT=false && [ $(getprop ro.product.cpu.abilist32) ] && HAS32BIT=true - -mkdir "$MODPATH/zygisk" - -if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then - if [ "$HAS32BIT" = true ]; then - ui_print "- Extracting x86 libraries" - extract "$ZIPFILE" "lib/x86/lib$SONAME.so" "$MODPATH/zygisk/" true - mv "$MODPATH/zygisk/lib$SONAME.so" "$MODPATH/zygisk/x86.so" - fi - +if [ "$ARCH" = "x64" ]; then ui_print "- Extracting x64 libraries" - extract "$ZIPFILE" "lib/x86_64/lib$SONAME.so" "$MODPATH/zygisk" true - mv "$MODPATH/zygisk/lib$SONAME.so" "$MODPATH/zygisk/x86_64.so" + extract "$ZIPFILE" "lib/x86_64/lib$SONAME.so" "$MODPATH" true + extract "$ZIPFILE" "lib/x86_64/libinject.so" "$MODPATH" true else - if [ "$HAS32BIT" = true ]; then - extract "$ZIPFILE" "lib/armeabi-v7a/lib$SONAME.so" "$MODPATH/zygisk" true - mv "$MODPATH/zygisk/lib$SONAME.so" "$MODPATH/zygisk/armeabi-v7a.so" - fi - ui_print "- Extracting arm64 libraries" - extract "$ZIPFILE" "lib/arm64-v8a/lib$SONAME.so" "$MODPATH/zygisk" true - mv "$MODPATH/zygisk/lib$SONAME.so" "$MODPATH/zygisk/arm64-v8a.so" + extract "$ZIPFILE" "lib/arm64-v8a/lib$SONAME.so" "$MODPATH" true + extract "$ZIPFILE" "lib/arm64-v8a/libinject.so" "$MODPATH" true fi + +mv "$MODPATH/libinject.so" "$MODPATH/inject" +chmod 755 "$MODPATH/inject" diff --git a/module/template/sepolicy.rule b/module/template/sepolicy.rule index e69de29..d4c04fc 100644 --- a/module/template/sepolicy.rule +++ b/module/template/sepolicy.rule @@ -0,0 +1,4 @@ +allow keystore keystore process execmem +allow keystore system_file unix_dgram_socket * +allow system_file keystore unix_dgram_socket * +allow keystore system_file file * diff --git a/module/template/service.sh b/module/template/service.sh index 528d685..17ba60f 100644 --- a/module/template/service.sh +++ b/module/template/service.sh @@ -1,3 +1,14 @@ DEBUG=@DEBUG@ MODDIR=${0%/*} + +cd $MODDIR + +( +while [ true ]; do + /system/bin/app_process -Djava.class.path=./service.apk / --nice-name=TrickyStore io.github.a13e300.tricky_store.MainKt + if [ $? -ne 0 ]; then + exit 1 + fi +done +) & diff --git a/service/.gitignore b/service/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/service/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/service/build.gradle.kts b/service/build.gradle.kts new file mode 100644 index 0000000..9a8e041 --- /dev/null +++ b/service/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.agp.app) +} + +android { + namespace = "io.github.a13e300.tricky_store" + compileSdk = 34 + + defaultConfig { + applicationId = "io.github.a13e300.tricky_store" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + compileOnly(project(":stub")) + compileOnly(libs.annotation) + implementation(libs.bcpkix.jdk15on) +} \ No newline at end of file diff --git a/service/proguard-rules.pro b/service/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/service/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/service/src/main/AndroidManifest.xml b/service/src/main/AndroidManifest.xml new file mode 100644 index 0000000..568741e --- /dev/null +++ b/service/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/service/src/main/java/io/github/a13e300/tricky_store/Main.kt b/service/src/main/java/io/github/a13e300/tricky_store/Main.kt new file mode 100644 index 0000000..c65fe5c --- /dev/null +++ b/service/src/main/java/io/github/a13e300/tricky_store/Main.kt @@ -0,0 +1,224 @@ +package io.github.a13e300.tricky_store + +import android.annotation.SuppressLint +import android.content.pm.IPackageManager +import android.os.Binder +import android.os.IBinder +import android.os.Parcel +import android.os.ServiceManager +import android.system.keystore2.IKeystoreService +import android.system.keystore2.KeyEntryResponse +import android.util.Log +import io.github.a13e300.tricky_store.fwpatch.Android +import io.github.a13e300.tricky_store.fwpatch.Utils +import kotlin.system.exitProcess + +open class BinderInterceptor : Binder() { + sealed class Result + data object Skip : Result() + data object Continue : Result() + data class OverrideData(val data: Parcel) : Result() + data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result() + + open fun onPreTransact(target: IBinder, code: Int, flags: Int, callingUid: Int, callingPid: Int, data: Parcel): Result = Skip + open fun onPostTransact(target: IBinder, code: Int, flags: Int, callingUid: Int, callingPid: Int, data: Parcel, reply: Parcel?, resultCode: Int): Result = Skip + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { + val result = when (code) { + 1 -> { // PRE_TRANSACT + val target = data.readStrongBinder() + val theCode = data.readInt() + val theFlags = data.readInt() + val callingUid = data.readInt() + val callingPid = data.readInt() + val sz = data.readLong() + val theData = Parcel.obtain() + try { + theData.appendFrom(data, data.dataPosition(), sz.toInt()) + theData.setDataPosition(0) + onPreTransact(target, theCode, theFlags, callingUid, callingPid, theData) + } finally { + theData.recycle() + } + } + 2 -> { // POST_TRANSACT + val target = data.readStrongBinder() + val theCode = data.readInt() + val theFlags = data.readInt() + val callingUid = data.readInt() + val callingPid = data.readInt() + val resultCode = data.readInt() + val theData = Parcel.obtain() + val theReply = Parcel.obtain() + try { + val sz = data.readLong().toInt() + theData.appendFrom(data, data.dataPosition(), sz) + theData.setDataPosition(0) + data.setDataPosition(data.dataPosition() + sz) + val sz2 = data.readLong().toInt() + if (sz2 != 0) { + theReply.appendFrom(data, data.dataPosition(), sz2) + theReply.setDataPosition(0) + } + onPostTransact(target, theCode, theFlags, callingUid, callingPid, theData, if (sz2 == 0) null else theReply, resultCode) + } finally { + theData.recycle() + theReply.recycle() + } + } + else -> return super.onTransact(code, data, reply, flags) + } + when (result) { + Skip -> reply!!.writeInt(1) + Continue -> reply!!.writeInt(2) + is OverrideReply -> { + reply!!.writeInt(3) + reply.writeInt(result.code) + reply.writeLong(result.reply.dataSize().toLong()) + println("override reply code=${result.code} size=${result.reply.dataSize()}") + reply.appendFrom(result.reply, 0, result.reply.dataSize()) + result.reply.recycle() + } + is OverrideData -> { + reply!!.writeInt(4) + reply.writeLong(result.data.dataSize().toLong()) + reply.appendFrom(result.data, 0, result.data.dataSize()) + result.data.recycle() + } + else -> {} + } + return true + } +} + +fun getBinderBackdoor(b: IBinder): IBinder? { + val data = Parcel.obtain() + val reply = Parcel.obtain() + try { + b.transact(0xdeadbeef.toInt(), data, reply, 0) + return reply.readStrongBinder() + } catch (ignored: Throwable) { + return null + } finally { + data.recycle() + reply.recycle() + } +} + +fun registerBinderInterceptor(backdoor: IBinder, target: IBinder, interceptor: BinderInterceptor) { + val data = Parcel.obtain() + val reply = Parcel.obtain() + data.writeStrongBinder(target) + data.writeStrongBinder(interceptor) + backdoor.transact(1, data, reply, 0) +} + +val targetPackages = arrayOf("com.google.android.gms", "icu.nullptr.nativetest", "io.github.vvb2060.mahoshojo", "io.github.vvb2060.keyattestation") + +const val TAG = "TrickyStore" + +fun logD(msg: String) { + Log.d(TAG, msg) +} + +fun logE(msg: String, t: Throwable? = null) { + if (t == null) { + Log.e(TAG, msg) + } else { + Log.e(TAG, msg, t) + } +} + +var iPm: IPackageManager? = null + +fun getPm(): IPackageManager? { + if (iPm == null) { + iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")) + } + return iPm +} + +@SuppressLint("BlockedPrivateApi") +fun tryRunKeystoreInterceptor(): Boolean { + val b = ServiceManager.getService("android.system.keystore2.IKeystoreService/default") ?: return false + b.linkToDeath({ + logD("keystore exit, daemon exit") + exitProcess(0) + }, 0) + val bd = getBinderBackdoor(b) ?: return true + val targetTransaction = IKeystoreService.Stub::class.java.getDeclaredField("TRANSACTION_getKeyEntry").apply { isAccessible = true }.getInt(null) // 2 + val interceptor = object : BinderInterceptor() { + override fun onPreTransact( + target: IBinder, + code: Int, + flags: Int, + callingUid: Int, + callingPid: Int, + data: Parcel + ): Result { + if (code == targetTransaction) { + logD("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}") + kotlin.runCatching { + val ps = getPm()?.getPackagesForUid(callingUid) + if (ps?.any { it in targetPackages } == true) return Continue + }.onFailure { logE("failed to get packages", it) } + } + return Skip + } + + override fun onPostTransact( + target: IBinder, + code: Int, + flags: Int, + callingUid: Int, + callingPid: Int, + data: Parcel, + reply: Parcel?, + resultCode: Int + ): Result { + if (code != targetTransaction || reply == null) return Skip + val p = Parcel.obtain() + logD("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}") + try { + reply.readException() + val response = reply.readTypedObject(KeyEntryResponse.CREATOR) + val chain = Utils.getCertificateChain(response) + val newChain = Android.engineGetCertificateChain(chain) + Utils.putCertificateChain(response, newChain) + p.writeNoException() + p.writeTypedObject(response, 0) + return OverrideReply(0, p) + } catch (t: Throwable) { + logE("failed to hack certificate chain!", t) + p.recycle() + } + return Skip + } + } + registerBinderInterceptor(bd, b, interceptor) + while (true) { + Thread.sleep(1000000) + } +} + +fun main() { + while (true) { + Thread.sleep(1000) + // true -> can inject, false -> service not found, loop -> running + if (!tryRunKeystoreInterceptor()) continue + // no binder hook, try inject + val p = Runtime.getRuntime().exec( + arrayOf( + "/system/bin/sh", + "-c", + "exec ./inject `pidof keystore2` libtricky_store.so entry" + ) + ) + // logD(p.inputStream.readBytes().decodeToString()) + // logD(p.errorStream.readBytes().decodeToString()) + if (p.waitFor() != 0) { + logE("failed to inject! daemon exit") + exitProcess(1) + } + } +} diff --git a/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Android.java b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Android.java new file mode 100644 index 0000000..40c24ca --- /dev/null +++ b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Android.java @@ -0,0 +1,188 @@ +package io.github.a13e300.tricky_store.fwpatch; + +import android.os.Build; +import android.security.keystore.KeyProperties; +import android.util.Log; + +import org.spongycastle.asn1.ASN1Boolean; +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Enumerated; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.ASN1TaggedObject; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.DERTaggedObject; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.X509v3CertificateBuilder; +import org.spongycastle.cert.jcajce.JcaX509CertificateConverter; +import org.spongycastle.openssl.PEMKeyPair; +import org.spongycastle.openssl.PEMParser; +import org.spongycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.jcajce.JcaContentSignerBuilder; +import org.spongycastle.util.io.pem.PemReader; + +import java.io.ByteArrayInputStream; +import java.io.StringReader; +import java.lang.reflect.Field; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public final class Android { + private static final String TAG = "chiteroman"; + private static final PEMKeyPair EC, RSA; + private static final ASN1ObjectIdentifier OID = new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17"); + private static final List EC_CERTS = new ArrayList<>(); + private static final List RSA_CERTS = new ArrayList<>(); + private static final Map map = new HashMap<>(); + private static final CertificateFactory certificateFactory; + + static { + map.put("MANUFACTURER", "Google"); + map.put("MODEL", "Pixel"); + map.put("FINGERPRINT", "google/sailfish/sailfish:8.1.0/OPM1.171019.011/4448085:user/release-keys"); + map.put("BRAND", "google"); + map.put("PRODUCT", "sailfish"); + map.put("DEVICE", "sailfish"); + map.put("RELEASE", "8.1.0"); + map.put("ID", "OPM1.171019.011"); + map.put("INCREMENTAL", "4448085"); + map.put("SECURITY_PATCH", "2017-12-05"); + map.put("TYPE", "user"); + map.put("TAGS", "release-keys"); + try { + certificateFactory = CertificateFactory.getInstance("X.509"); + + EC = parseKeyPair(Keybox.EC.PRIVATE_KEY); + EC_CERTS.add(parseCert(Keybox.EC.CERTIFICATE_1)); + EC_CERTS.add(parseCert(Keybox.EC.CERTIFICATE_2)); + + RSA = parseKeyPair(Keybox.RSA.PRIVATE_KEY); + RSA_CERTS.add(parseCert(Keybox.RSA.CERTIFICATE_1)); + RSA_CERTS.add(parseCert(Keybox.RSA.CERTIFICATE_2)); + } catch (Throwable t) { + Log.e(TAG, t.toString()); + throw new RuntimeException(t); + } + } + + private static PEMKeyPair parseKeyPair(String key) throws Throwable { + try (PEMParser parser = new PEMParser(new StringReader(key))) { + return (PEMKeyPair) parser.readObject(); + } + } + + private static Certificate parseCert(String cert) throws Throwable { + try (PemReader reader = new PemReader(new StringReader(cert))) { + return certificateFactory.generateCertificate(new ByteArrayInputStream(reader.readPemObject().getContent())); + } + } + + private static Field getField(String fieldName) { + Field field = null; + try { + field = Build.class.getDeclaredField(fieldName); + } catch (Throwable ignored) { + try { + field = Build.VERSION.class.getDeclaredField(fieldName); + } catch (Throwable t) { + Log.e(TAG, "Couldn't find field " + fieldName); + } + } + return field; + } + + public static Certificate[] engineGetCertificateChain(Certificate[] caList) { + if (caList == null) throw new UnsupportedOperationException(); + try { + X509Certificate leaf = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(caList[0].getEncoded())); + + byte[] bytes = leaf.getExtensionValue(OID.getId()); + + if (bytes == null) return caList; + + X509CertificateHolder holder = new X509CertificateHolder(leaf.getEncoded()); + + Extension ext = holder.getExtension(OID); + + ASN1Sequence sequence = ASN1Sequence.getInstance(ext.getExtnValue().getOctets()); + + ASN1Encodable[] encodables = sequence.toArray(); + + ASN1Sequence teeEnforced = (ASN1Sequence) encodables[7]; + + ASN1EncodableVector vector = new ASN1EncodableVector(); + + for (ASN1Encodable asn1Encodable : teeEnforced) { + ASN1TaggedObject taggedObject = (ASN1TaggedObject) asn1Encodable; + if (taggedObject.getTagNo() == 704) continue; + vector.add(taggedObject); + } + + LinkedList certificates; + + X509v3CertificateBuilder builder; + ContentSigner signer; + + if (KeyProperties.KEY_ALGORITHM_EC.equals(leaf.getPublicKey().getAlgorithm())) { + certificates = new LinkedList<>(EC_CERTS); + builder = new X509v3CertificateBuilder(new X509CertificateHolder(EC_CERTS.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), EC.getPublicKeyInfo()); + signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(EC.getPrivateKeyInfo())); + } else { + certificates = new LinkedList<>(RSA_CERTS); + builder = new X509v3CertificateBuilder(new X509CertificateHolder(RSA_CERTS.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), RSA.getPublicKeyInfo()); + signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(RSA.getPrivateKeyInfo())); + } + + byte[] verifiedBootKey = new byte[32]; + byte[] verifiedBootHash = new byte[32]; + + ThreadLocalRandom.current().nextBytes(verifiedBootKey); + ThreadLocalRandom.current().nextBytes(verifiedBootHash); + + ASN1Encodable[] rootOfTrustEnc = {new DEROctetString(verifiedBootKey), ASN1Boolean.TRUE, new ASN1Enumerated(0), new DEROctetString(verifiedBootHash)}; + + ASN1Sequence rootOfTrustSeq = new DERSequence(rootOfTrustEnc); + + ASN1TaggedObject rootOfTrustTagObj = new DERTaggedObject(704, rootOfTrustSeq); + + vector.add(rootOfTrustTagObj); + + ASN1Sequence hackEnforced = new DERSequence(vector); + + encodables[7] = hackEnforced; + + ASN1Sequence hackedSeq = new DERSequence(encodables); + + ASN1OctetString hackedSeqOctets = new DEROctetString(hackedSeq); + + Extension hackedExt = new Extension(OID, false, hackedSeqOctets); + + builder.addExtension(hackedExt); + + for (ASN1ObjectIdentifier extensionOID : holder.getExtensions().getExtensionOIDs()) { + if (OID.getId().equals(extensionOID.getId())) continue; + builder.addExtension(holder.getExtension(extensionOID)); + } + + certificates.addFirst(new JcaX509CertificateConverter().getCertificate(builder.build(signer))); + + return certificates.toArray(new Certificate[0]); + + } catch (Throwable t) { + Log.e(TAG, t.toString()); + } + return caList; + } +} diff --git a/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Keybox.java b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Keybox.java new file mode 100644 index 0000000..43751df --- /dev/null +++ b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Keybox.java @@ -0,0 +1,107 @@ +package io.github.a13e300.tricky_store.fwpatch; + +public final class Keybox { + public static final class EC { + public static final String PRIVATE_KEY = """ + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49 + AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l + uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA== + -----END EC PRIVATE KEY----- + """; + public static final String CERTIFICATE_1 = """ + -----BEGIN CERTIFICATE----- + MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw + EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD + VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu + ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx + MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE + CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB + bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz + dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue + efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8 + U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R + qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG + AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8 + wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9 + Xvsiu+f+uXc/WT/7 + -----END CERTIFICATE----- + """; + public static final String CERTIFICATE_2 = """ + -----BEGIN CERTIFICATE----- + MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG + EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll + dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD + VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw + HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx + EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT + BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq + QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH + KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59 + dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O + BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W + EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG + SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN + C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw== + -----END CERTIFICATE----- + """; + } + + public static final class RSA { + public static final String PRIVATE_KEY = """ + -----BEGIN RSA PRIVATE KEY----- + MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb + KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI + O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB + AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3 + wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C + woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs + NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU + 0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy + DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY + ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3 + TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y + qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x + fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l + -----END RSA PRIVATE KEY----- + """; + public static final String CERTIFICATE_1 = """ + -----BEGIN CERTIFICATE----- + MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx + EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT + BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx + MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD + YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv + aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf + MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1 + qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX + 4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd + XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ + k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw + BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn + M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4 + Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f + 9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8= + -----END CERTIFICATE----- + """; + public static final String CERTIFICATE_2 = """ + -----BEGIN CERTIFICATE----- + MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV + BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW + aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN + MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE + CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB + AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs + Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL + t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID + AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw + FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B + Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j + HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc + DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op + Lvtc9mL1J1IXvmM= + -----END CERTIFICATE----- + """; + } +} diff --git a/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Utils.java b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Utils.java new file mode 100644 index 0000000..47b761e --- /dev/null +++ b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Utils.java @@ -0,0 +1,69 @@ +package io.github.a13e300.tricky_store.fwpatch; + +import android.system.keystore2.KeyEntryResponse; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +public class Utils { + private final static String TAG = "Utils"; + static X509Certificate toCertificate(byte[] bytes) { + try { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certFactory.generateCertificate( + new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + Log.w(TAG, "Couldn't parse certificate in keystore", e); + return null; + } + } + + @SuppressWarnings("unchecked") + private static Collection toCertificates(byte[] bytes) { + try { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (Collection) certFactory.generateCertificates( + new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + Log.w(TAG, "Couldn't parse certificates in keystore", e); + return new ArrayList<>(); + } + } + + public static Certificate[] getCertificateChain(KeyEntryResponse response) { + if (response == null || response.metadata.certificate == null) return null; + var leaf = toCertificate(response.metadata.certificate); + Certificate[] chain; + if (response.metadata.certificateChain != null) { + var certs = toCertificates(response.metadata.certificateChain); + chain = new Certificate[certs.size() + 1]; + final Iterator it = certs.iterator(); + int i = 1; + while (it.hasNext()) { + chain[i++] = it.next(); + } + } else { + chain = new Certificate[1]; + } + chain[0] = leaf; + return chain; + } + + public static void putCertificateChain(KeyEntryResponse response, Certificate[] chain) throws Throwable { + if (chain == null || chain.length == 0) return; + response.metadata.certificate = chain[0].getEncoded(); + var output = new ByteArrayOutputStream(); + for (int i = 1; i < chain.length; i++) { + output.write(chain[i].getEncoded()); + } + response.metadata.certificateChain = output.toByteArray(); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 85d1b3b..8042952 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,3 +18,5 @@ rootProject.name = "TrickyStore" include( ":module" ) +include(":service") +include(":stub") diff --git a/stub/.gitignore b/stub/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/stub/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/stub/build.gradle.kts b/stub/build.gradle.kts new file mode 100644 index 0000000..1a5e464 --- /dev/null +++ b/stub/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "io.github.a13e300.stub" + defaultConfig { + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } +} + +dependencies { + compileOnly(libs.annotation) +} \ No newline at end of file diff --git a/stub/consumer-rules.pro b/stub/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/stub/proguard-rules.pro b/stub/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/stub/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/stub/src/main/AndroidManifest.xml b/stub/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1d26c87 --- /dev/null +++ b/stub/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/stub/src/main/java/android/content/pm/IPackageManager.java b/stub/src/main/java/android/content/pm/IPackageManager.java new file mode 100644 index 0000000..1102c27 --- /dev/null +++ b/stub/src/main/java/android/content/pm/IPackageManager.java @@ -0,0 +1,13 @@ +package android.content.pm; + +import android.os.IBinder; + +public interface IPackageManager { + String[] getPackagesForUid(int uid); + + class Stub { + public static IPackageManager asInterface(IBinder binder) { + throw new RuntimeException(""); + } + } +} diff --git a/stub/src/main/java/android/os/ServiceManager.java b/stub/src/main/java/android/os/ServiceManager.java new file mode 100644 index 0000000..382995e --- /dev/null +++ b/stub/src/main/java/android/os/ServiceManager.java @@ -0,0 +1,19 @@ +package android.os; + +public class ServiceManager { + public static IBinder getService(String name) { + throw new UnsupportedOperationException("STUB!"); + } + + public static void addService(String name, IBinder binder) { + throw new UnsupportedOperationException("STUB!"); + } + + public static IBinder checkService(String name) { + throw new UnsupportedOperationException("STUB!"); + } + + public static String[] listServices() { + throw new UnsupportedOperationException("STUB!"); + } +} diff --git a/stub/src/main/java/android/system/keystore2/IKeystoreService.java b/stub/src/main/java/android/system/keystore2/IKeystoreService.java new file mode 100644 index 0000000..b6ad034 --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/IKeystoreService.java @@ -0,0 +1,9 @@ +package android.system.keystore2; + +public interface IKeystoreService { + String DESCRIPTOR = "android.system.keystore2.IKeystoreService"; + + class Stub { + + } +} diff --git a/stub/src/main/java/android/system/keystore2/KeyDescriptor.java b/stub/src/main/java/android/system/keystore2/KeyDescriptor.java new file mode 100644 index 0000000..0432425 --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/KeyDescriptor.java @@ -0,0 +1,39 @@ +package android.system.keystore2; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +public class KeyDescriptor implements Parcelable { + public String alias; + public byte[] blob; + public int domain = 0; + public long nspace = 0; + + protected KeyDescriptor(Parcel in) { + throw new RuntimeException(""); + } + + public static final Creator CREATOR = new Creator() { + @Override + public KeyDescriptor createFromParcel(Parcel in) { + return new KeyDescriptor(in); + } + + @Override + public KeyDescriptor[] newArray(int size) { + return new KeyDescriptor[size]; + } + }; + + @Override + public int describeContents() { + throw new RuntimeException(""); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int i) { + throw new RuntimeException(""); + } +} diff --git a/stub/src/main/java/android/system/keystore2/KeyEntryResponse.java b/stub/src/main/java/android/system/keystore2/KeyEntryResponse.java new file mode 100644 index 0000000..2bfd347 --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/KeyEntryResponse.java @@ -0,0 +1,37 @@ +package android.system.keystore2; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +public class KeyEntryResponse implements Parcelable { + // public IKeystoreSecurityLevel iSecurityLevel; + public KeyMetadata metadata; + + protected KeyEntryResponse(Parcel in) { + throw new RuntimeException(""); + } + + public static final Creator CREATOR = new Creator() { + @Override + public KeyEntryResponse createFromParcel(Parcel in) { + throw new RuntimeException(""); + } + + @Override + public KeyEntryResponse[] newArray(int size) { + throw new RuntimeException(""); + } + }; + + @Override + public int describeContents() { + throw new RuntimeException(""); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int i) { + throw new RuntimeException(""); + } +} diff --git a/stub/src/main/java/android/system/keystore2/KeyMetadata.java b/stub/src/main/java/android/system/keystore2/KeyMetadata.java new file mode 100644 index 0000000..762649d --- /dev/null +++ b/stub/src/main/java/android/system/keystore2/KeyMetadata.java @@ -0,0 +1,41 @@ +package android.system.keystore2; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +public class KeyMetadata implements Parcelable { + // public Authorization[] authorizations; + public byte[] certificate; + public byte[] certificateChain; + public KeyDescriptor key; + public int keySecurityLevel = 0; + public long modificationTimeMs = 0; + + protected KeyMetadata(Parcel in) { + throw new RuntimeException(""); + } + + public static final Creator CREATOR = new Creator() { + @Override + public KeyMetadata createFromParcel(Parcel in) { + throw new RuntimeException(""); + } + + @Override + public KeyMetadata[] newArray(int size) { + throw new RuntimeException(""); + } + }; + + @Override + public int describeContents() { + throw new RuntimeException(""); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int i) { + throw new RuntimeException(""); + } +}