You've already forked TrickyStore
mirror of
https://github.com/5ec1cff/TrickyStore.git
synced 2025-09-06 06:37:07 +00:00
works now
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
298
module/src/main/cpp/inject/main.cpp
Normal file
298
module/src/main/cpp/inject/main.cpp
Normal 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, ®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<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]);
|
||||||
|
}
|
||||||
696
module/src/main/cpp/inject/utils.cpp
Normal file
696
module/src/main/cpp/inject/utils.cpp
Normal 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 ®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<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 ®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<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 ®s, 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 ®s, 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 ®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<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 ®s, 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, ®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<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;
|
||||||
|
}
|
||||||
166
module/src/main/cpp/inject/utils.hpp
Normal file
166
module/src/main/cpp/inject/utils.hpp
Normal 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 ®s);
|
||||||
|
|
||||||
|
bool set_regs(int pid, struct user_regs_struct ®s);
|
||||||
|
|
||||||
|
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 ®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<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 ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||||
|
std::vector<uintptr_t> &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);
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 *
|
||||||
|
|||||||
@@ -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
1
service/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
32
service/build.gradle.kts
Normal file
32
service/build.gradle.kts
Normal 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
21
service/proguard-rules.pro
vendored
Normal 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
|
||||||
2
service/src/main/AndroidManifest.xml
Normal file
2
service/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest />
|
||||||
224
service/src/main/java/io/github/a13e300/tricky_store/Main.kt
Normal file
224
service/src/main/java/io/github/a13e300/tricky_store/Main.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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-----
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,3 +18,5 @@ rootProject.name = "TrickyStore"
|
|||||||
include(
|
include(
|
||||||
":module"
|
":module"
|
||||||
)
|
)
|
||||||
|
include(":service")
|
||||||
|
include(":stub")
|
||||||
|
|||||||
1
stub/.gitignore
vendored
Normal file
1
stub/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
24
stub/build.gradle.kts
Normal file
24
stub/build.gradle.kts
Normal 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
0
stub/consumer-rules.pro
Normal file
21
stub/proguard-rules.pro
vendored
Normal file
21
stub/proguard-rules.pro
vendored
Normal 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
|
||||||
2
stub/src/main/AndroidManifest.xml
Normal file
2
stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest/>
|
||||||
13
stub/src/main/java/android/content/pm/IPackageManager.java
Normal file
13
stub/src/main/java/android/content/pm/IPackageManager.java
Normal 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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
stub/src/main/java/android/os/ServiceManager.java
Normal file
19
stub/src/main/java/android/os/ServiceManager.java
Normal 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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package android.system.keystore2;
|
||||||
|
|
||||||
|
public interface IKeystoreService {
|
||||||
|
String DESCRIPTOR = "android.system.keystore2.IKeystoreService";
|
||||||
|
|
||||||
|
class Stub {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
41
stub/src/main/java/android/system/keystore2/KeyMetadata.java
Normal file
41
stub/src/main/java/android/system/keystore2/KeyMetadata.java
Normal 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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user