works now

This commit is contained in:
5ec1cff
2024-07-11 14:53:13 +08:00
parent f0eb0f2d92
commit d55dc13e87
31 changed files with 2084 additions and 23 deletions

View File

@@ -1,8 +1,11 @@
import com.android.build.gradle.AppExtension import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
plugins { plugins {
alias(libs.plugins.agp.app) apply false 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 { fun String.execute(currentWorkingDir: File = file("./")): String {
@@ -24,7 +27,7 @@ val moduleName by extra("Tricky Store")
val verName by extra("v1") val verName by extra("v1")
val verCode by extra(gitCommitCount) val verCode by extra(gitCommitCount)
val commitHash by extra(gitCommitHash) 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 androidMinSdkVersion by extra(26)
val androidTargetSdkVersion by extra(34) val androidTargetSdkVersion by extra(34)
@@ -47,6 +50,9 @@ fun Project.configureBaseExtension() {
defaultConfig { defaultConfig {
minSdk = androidMinSdkVersion minSdk = androidMinSdkVersion
targetSdk = androidCompileSdkVersion
versionCode = verCode
versionName = verName
} }
compileOptions { 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 { subprojects {
plugins.withId("com.android.application") { plugins.withId("com.android.application") {
configureBaseExtension() configureBaseExtension()
} }
plugins.withId("com.android.library") {
configureBaseExtension()
}
plugins.withType(JavaPlugin::class.java) { plugins.withType(JavaPlugin::class.java) {
extensions.configure(JavaPluginExtension::class.java) { extensions.configure(JavaPluginExtension::class.java) {
sourceCompatibility = androidSourceCompatibility sourceCompatibility = androidSourceCompatibility

View File

@@ -1,5 +1,14 @@
[versions] [versions]
agp = "8.5.0" 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] [plugins]
agp-app = { id = "com.android.application", version.ref = "agp" } 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" }

View File

@@ -57,7 +57,7 @@ androidComponents.onVariants { variant ->
val prepareModuleFilesTask = task<Sync>("prepareModuleFiles$variantCapped") { val prepareModuleFilesTask = task<Sync>("prepareModuleFiles$variantCapped") {
group = "module" group = "module"
dependsOn("assemble$variantCapped") dependsOn("assemble$variantCapped", ":service:assemble$variantCapped")
into(moduleDir) into(moduleDir)
from(rootProject.layout.projectDirectory.file("README.md")) from(rootProject.layout.projectDirectory.file("README.md"))
from(layout.projectDirectory.file("template")) { from(layout.projectDirectory.file("template")) {
@@ -83,7 +83,11 @@ androidComponents.onVariants { variant ->
filter<ReplaceTokens>("tokens" to tokens) filter<ReplaceTokens>("tokens" to tokens)
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf")) filter<FixCrLfFilter>("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")) { from(layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/strip${variantCapped}DebugSymbols/out/lib")) {
exclude("**/libbinder.so", "**/libutils.so")
into("lib") into("lib")
} }

View File

@@ -35,5 +35,8 @@ add_library(binder SHARED binder/stub_binder.cpp)
target_include_directories(binder PUBLIC binder/include) target_include_directories(binder PUBLIC binder/include)
target_link_libraries(binder utils) 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) add_library(${MODULE_NAME} SHARED binder_interceptor.cpp)
target_link_libraries(${MODULE_NAME} log binder utils dobby elf_util my_logging) target_link_libraries(${MODULE_NAME} log binder utils dobby elf_util my_logging)

View File

@@ -62,7 +62,7 @@ CREATE_MEM_HOOK_STUB_ENTRY(
{ {
LOGD("transact: binder=%p code=%d", thiz, code); LOGD("transact: binder=%p code=%d", thiz, code);
if (IPCThreadState::self()->getCallingUid() == 0 && reply != nullptr && if (IPCThreadState::self()->getCallingUid() == 0 && reply != nullptr &&
thiz != gBinderInterceptor) { thiz != gBinderInterceptor) [[unlikely]] {
if (code == 0xdeadbeef) { if (code == 0xdeadbeef) {
LOGD("request binder interceptor"); LOGD("request binder interceptor");
reply->writeStrongBinder(gBinderInterceptor); reply->writeStrongBinder(gBinderInterceptor);
@@ -128,6 +128,7 @@ BinderInterceptor::onTransact(uint32_t code, const android::Parcel &data, androi
items.erase(it); items.erase(it);
return OK; return OK;
} }
return BAD_VALUE;
} }
} }
return UNKNOWN_TRANSACTION; return UNKNOWN_TRANSACTION;

View File

@@ -0,0 +1,298 @@
#include <sys/ptrace.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/auxv.h>
#include <elf.h>
#include <link.h>
#include <vector>
#include <string>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdlib>
#include <cstdio>
#include <dlfcn.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <android/dlext.h>
#include <fcntl.h>
#include <csignal>
#include <sys/system_properties.h>
#include <string>
#include <cinttypes>
#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<lsplt::MapInfo> 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, &regs, 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<uintptr_t> 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]);
}

View File

@@ -0,0 +1,696 @@
#include <vector>
#include <sys/mman.h>
#include <sys/sysmacros.h>
#include <array>
#include <cinttypes>
#include <sys/ptrace.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/auxv.h>
#include <elf.h>
#include <link.h>
#include <vector>
#include <string>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdlib>
#include <cstdio>
#include <dlfcn.h>
#include <csignal>
#include <cstring>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <cinttypes>
#include <sys/xattr.h>
#include <random>
#include "utils.hpp"
#include "logging.hpp"
#include <sched.h>
#include <fcntl.h>
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<size_t>(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<size_t>(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<size_t>(l) != len) {
LOGW("not fully read: %zu, excepted %zu", l, len);
}
return l;
}
bool get_regs(int pid, struct user_regs_struct &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_GETREGS, pid, 0, &regs) == -1) {
PLOGE("getregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.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 &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_SETREGS, pid, 0, &regs) == -1) {
PLOGE("setregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.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<lsplt::MapInfo> &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 "<unknown>";
}
void *find_module_return_addr(std::vector<lsplt::MapInfo> &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<lsplt::MapInfo> &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<lsplt::MapInfo> &local_info,
std::vector<lsplt::MapInfo> &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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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 &regs, uintptr_t preserve) {
regs.REG_SP = (regs.REG_SP - preserve) & ~0xf;
}
uintptr_t push_memory(int pid, struct user_regs_struct &regs, void* local_addr, size_t len) {
regs.REG_SP -= len;
align_stack(regs);
auto addr = static_cast<uintptr_t>(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 &regs, const char *str) {
auto len = strlen(str) + 1;
regs.REG_SP -= len;
align_stack(regs);
auto addr = static_cast<uintptr_t>(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 &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &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 &regs, 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<uintptr_t>(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 &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &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, &regs, 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<std::string::size_type> 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<ssize_t>(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;
}

View File

@@ -0,0 +1,166 @@
#pragma once
#include <string>
#include <sys/ptrace.h>
#include <map>
#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 &regs);
bool set_regs(int pid, struct user_regs_struct &regs);
std::string get_addr_mem_region(std::vector<lsplt::MapInfo> &info, uintptr_t addr);
void *find_module_base(std::vector<lsplt::MapInfo> &info, std::string_view suffix);
void *find_func_addr(
std::vector<lsplt::MapInfo> &local_info,
std::vector<lsplt::MapInfo> &remote_info,
std::string_view module,
std::string_view func);
void align_stack(struct user_regs_struct &regs, uintptr_t preserve = 0);
uintptr_t push_memory(int pid, struct user_regs_struct &regs, void* local_addr, size_t len);
uintptr_t push_string(int pid, struct user_regs_struct &regs, const char *str);
uintptr_t remote_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &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<lsplt::MapInfo> &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<std::string> 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 &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &args);
uintptr_t remote_post_call(int pid, struct user_regs_struct &regs, 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);

View File

@@ -57,29 +57,18 @@ ui_print "- Extracting module files"
extract "$ZIPFILE" 'module.prop' "$MODPATH" extract "$ZIPFILE" 'module.prop' "$MODPATH"
extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
extract "$ZIPFILE" 'service.sh' "$MODPATH" extract "$ZIPFILE" 'service.sh' "$MODPATH"
extract "$ZIPFILE" 'service.apk' "$MODPATH"
mv "$TMPDIR/sepolicy.rule" "$MODPATH" mv "$TMPDIR/sepolicy.rule" "$MODPATH"
HAS32BIT=false && [ $(getprop ro.product.cpu.abilist32) ] && HAS32BIT=true if [ "$ARCH" = "x64" ]; then
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
ui_print "- Extracting x64 libraries" ui_print "- Extracting x64 libraries"
extract "$ZIPFILE" "lib/x86_64/lib$SONAME.so" "$MODPATH/zygisk" true extract "$ZIPFILE" "lib/x86_64/lib$SONAME.so" "$MODPATH" true
mv "$MODPATH/zygisk/lib$SONAME.so" "$MODPATH/zygisk/x86_64.so" extract "$ZIPFILE" "lib/x86_64/libinject.so" "$MODPATH" true
else 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" ui_print "- Extracting arm64 libraries"
extract "$ZIPFILE" "lib/arm64-v8a/lib$SONAME.so" "$MODPATH/zygisk" true extract "$ZIPFILE" "lib/arm64-v8a/lib$SONAME.so" "$MODPATH" true
mv "$MODPATH/zygisk/lib$SONAME.so" "$MODPATH/zygisk/arm64-v8a.so" extract "$ZIPFILE" "lib/arm64-v8a/libinject.so" "$MODPATH" true
fi fi
mv "$MODPATH/libinject.so" "$MODPATH/inject"
chmod 755 "$MODPATH/inject"

View File

@@ -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 *

View File

@@ -1,3 +1,14 @@
DEBUG=@DEBUG@ DEBUG=@DEBUG@
MODDIR=${0%/*} 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
) &

1
service/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

32
service/build.gradle.kts Normal file
View File

@@ -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)
}

21
service/proguard-rules.pro vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@@ -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)
}
}
}

View File

@@ -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<Certificate> EC_CERTS = new ArrayList<>();
private static final List<Certificate> RSA_CERTS = new ArrayList<>();
private static final Map<String, String> 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<Certificate> 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;
}
}

View File

@@ -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-----
""";
}
}

View File

@@ -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<X509Certificate> toCertificates(byte[] bytes) {
try {
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
return (Collection<X509Certificate>) 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<X509Certificate> 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();
}
}

View File

@@ -18,3 +18,5 @@ rootProject.name = "TrickyStore"
include( include(
":module" ":module"
) )
include(":service")
include(":stub")

1
stub/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

24
stub/build.gradle.kts Normal file
View File

@@ -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)
}

0
stub/consumer-rules.pro Normal file
View File

21
stub/proguard-rules.pro vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest/>

View File

@@ -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("");
}
}
}

View File

@@ -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!");
}
}

View File

@@ -0,0 +1,9 @@
package android.system.keystore2;
public interface IKeystoreService {
String DESCRIPTOR = "android.system.keystore2.IKeystoreService";
class Stub {
}
}

View File

@@ -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<KeyDescriptor> CREATOR = new Creator<KeyDescriptor>() {
@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("");
}
}

View File

@@ -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<KeyEntryResponse> CREATOR = new Creator<KeyEntryResponse>() {
@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("");
}
}

View File

@@ -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<KeyMetadata> CREATOR = new Creator<KeyMetadata>() {
@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("");
}
}