Get rid of vtable hook

This commit is contained in:
LoveSy 2023-02-10 22:25:07 +08:00 committed by John Wu
parent 2c53356bfd
commit a1a87c9956
2 changed files with 55 additions and 104 deletions

@ -1 +1 @@
Subproject commit 204a1636884d397617101e2bae3a78dd3febb163 Subproject commit c202f29df9c0a925fca987f9539525163c590cc0

View File

@ -113,8 +113,8 @@ hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
// Current context // Current context
HookContext *g_ctx; HookContext *g_ctx;
const JNINativeInterface *old_functions; const JNINativeInterface *old_functions = nullptr;
JNINativeInterface *new_functions; JNINativeInterface *new_functions = nullptr;
} // namespace } // namespace
@ -144,15 +144,9 @@ if (methods[i].name == #method##sv) {
namespace { namespace {
jclass gClassRef;
jmethodID class_getName;
string get_class_name(JNIEnv *env, jclass clazz) { string get_class_name(JNIEnv *env, jclass clazz) {
if (!gClassRef) { static auto class_getName = env->GetMethodID(
jclass cls = env->FindClass("java/lang/Class"); env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;");
gClassRef = (jclass) env->NewGlobalRef(cls);
env->DeleteLocalRef(cls);
class_getName = env->GetMethodID(gClassRef, "getName", "()Ljava/lang/String;");
}
auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName); auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName);
const char *name = env->GetStringUTFChars(nameRef, nullptr); const char *name = env->GetStringUTFChars(nameRef, nullptr);
string className(name); string className(name);
@ -173,11 +167,45 @@ jint env_RegisterNatives(
return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods); return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods);
} }
DCL_HOOK_FUNC(int, jniRegisterNativeMethods, DCL_HOOK_FUNC(void, androidSetCreateThreadFunc, void *func) {
JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) { ZLOGD("androidSetCreateThreadFunc\n");
ZLOGV("jniRegisterNativeMethods [%s]\n", className); using method_sig = jint(*)(JavaVM **, jsize, jsize *);
auto newMethods = hookAndSaveJNIMethods(className, methods, numMethods); do {
return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods); auto get_created_vms = reinterpret_cast<method_sig>(
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));
if (!get_created_vms) {
for (auto &map: lsplt::MapInfo::Scan()) {
if (!map.path.ends_with("/libnativehelper.so")) continue;
void *h = dlopen(map.path.data(), RTLD_LAZY);
if (!h) {
LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror());
break;
}
get_created_vms = reinterpret_cast<method_sig>(dlsym(h, "JNI_GetCreatedJavaVMs"));
dlclose(h);
break;
}
if (!get_created_vms) {
LOGW("JNI_GetCreatedJavaVMs not found\n");
break;
}
}
JavaVM *vm = nullptr;
jsize num = 0;
jint res = get_created_vms(&vm, 1, &num);
if (res != JNI_OK || vm == nullptr) break;
JNIEnv *env = nullptr;
res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (res != JNI_OK || env == nullptr) break;
default_new(new_functions);
memcpy(new_functions, env->functions, sizeof(*new_functions));
new_functions->RegisterNatives = &env_RegisterNatives;
// Replace the function table in JNIEnv to hook RegisterNatives
old_functions = env->functions;
env->functions = new_functions;
} while (false);
old_androidSetCreateThreadFunc(func);
} }
// Skip actual fork and return cached result if applicable // Skip actual fork and return cached result if applicable
@ -231,67 +259,6 @@ DCL_HOOK_FUNC(int, selinux_android_setcontext,
return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname); return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname);
} }
// -----------------------------------------------------------------
// The original android::AppRuntime virtual table
void **gAppRuntimeVTable;
// This method is a trampoline for hooking JNIEnv->RegisterNatives
void onVmCreated(void *self, JNIEnv* env) {
ZLOGD("AppRuntime::onVmCreated\n");
// Restore virtual table
auto new_table = *reinterpret_cast<void***>(self);
*reinterpret_cast<void***>(self) = gAppRuntimeVTable;
delete[] new_table;
new_functions = new JNINativeInterface();
memcpy(new_functions, env->functions, sizeof(*new_functions));
new_functions->RegisterNatives = &env_RegisterNatives;
// Replace the function table in JNIEnv to hook RegisterNatives
old_functions = env->functions;
env->functions = new_functions;
}
template<int N>
void vtable_entry(void *self, JNIEnv* env) {
// The first invocation will be onVmCreated. It will also restore the vtable.
onVmCreated(self, env);
// Call original function
reinterpret_cast<decltype(&onVmCreated)>(gAppRuntimeVTable[N])(self, env);
}
// This method is a trampoline for swizzling android::AppRuntime vtable
bool swizzled = false;
DCL_HOOK_FUNC(void, setArgv0, void *self, const char *argv0, bool setProcName) {
if (swizzled) {
old_setArgv0(self, argv0, setProcName);
return;
}
ZLOGD("AndroidRuntime::setArgv0\n");
// We don't know which entry is onVmCreated, so overwrite every one
// We also don't know the size of the vtable, but 8 is more than enough
auto new_table = new void*[8];
new_table[0] = reinterpret_cast<void*>(&vtable_entry<0>);
new_table[1] = reinterpret_cast<void*>(&vtable_entry<1>);
new_table[2] = reinterpret_cast<void*>(&vtable_entry<2>);
new_table[3] = reinterpret_cast<void*>(&vtable_entry<3>);
new_table[4] = reinterpret_cast<void*>(&vtable_entry<4>);
new_table[5] = reinterpret_cast<void*>(&vtable_entry<5>);
new_table[6] = reinterpret_cast<void*>(&vtable_entry<6>);
new_table[7] = reinterpret_cast<void*>(&vtable_entry<7>);
// Swizzle C++ vtable to hook virtual function
gAppRuntimeVTable = *reinterpret_cast<void***>(self);
*reinterpret_cast<void***>(self) = new_table;
swizzled = true;
old_setArgv0(self, argv0, setProcName);
}
#undef DCL_HOOK_FUNC #undef DCL_HOOK_FUNC
// ----------------------------------------------------------------- // -----------------------------------------------------------------
@ -327,7 +294,8 @@ void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods
if (hooks.empty()) if (hooks.empty())
return; return;
old_jniRegisterNativeMethods(env, clz, hooks.data(), hooks.size()); old_functions->RegisterNatives(env, env->FindClass(clz), hooks.data(),
static_cast<int>(hooks.size()));
} }
ZygiskModule::ZygiskModule(int id, void *handle, void *entry) ZygiskModule::ZygiskModule(int id, void *handle, void *entry)
@ -525,11 +493,12 @@ void HookContext::sanitize_fds() {
if (exempted_fds.empty()) if (exempted_fds.empty())
return nullptr; return nullptr;
jintArray array = env->NewIntArray(off + exempted_fds.size()); jintArray array = env->NewIntArray(static_cast<int>(off + exempted_fds.size()));
if (array == nullptr) if (array == nullptr)
return nullptr; return nullptr;
env->SetIntArrayRegion(array, off, exempted_fds.size(), exempted_fds.data()); env->SetIntArrayRegion(array, off, static_cast<int>(exempted_fds.size()),
exempted_fds.data());
for (int fd : exempted_fds) { for (int fd : exempted_fds) {
if (fd >= 0 && fd < MAX_FD_SIZE) { if (fd >= 0 && fd < MAX_FD_SIZE) {
allowed_fds[fd] = true; allowed_fds[fd] = true;
@ -720,7 +689,7 @@ void HookContext::nativeForkSystemServer_pre() {
dynamic_bitset bits; dynamic_bitset bits;
for (const auto &m : modules) for (const auto &m : modules)
bits[m.getId()] = true; bits[m.getId()] = true;
write_int(fd, bits.slots()); write_int(fd, static_cast<int>(bits.slots()));
for (int i = 0; i < bits.slots(); ++i) { for (int i = 0; i < bits.slots(); ++i) {
auto l = bits.get_slot(i); auto l = bits.get_slot(i);
xwrite(fd, &l, sizeof(l)); xwrite(fd, &l, sizeof(l));
@ -810,30 +779,15 @@ void hook_functions() {
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, jniRegisterNativeMethods);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, selinux_android_setcontext); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, selinux_android_setcontext);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc);
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close); PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
hook_commit(); hook_commit();
// Remove unhooked methods // Remove unhooked methods
plt_hook_list->erase( plt_hook_list->erase(
std::remove_if(plt_hook_list->begin(), plt_hook_list->end(), std::remove_if(plt_hook_list->begin(), plt_hook_list->end(),
[](auto &t) { return *std::get<3>(t) == nullptr;}), [](auto &t) { return *std::get<3>(t) == nullptr;}),
plt_hook_list->end()); plt_hook_list->end());
if (old_jniRegisterNativeMethods == nullptr) {
ZLOGD("jniRegisterNativeMethods not hooked, using fallback\n");
struct stat self_stat{};
stat("/proc/self/exe", &self_stat);
// android::AndroidRuntime::setArgv0(const char*, bool)
PLT_HOOK_REGISTER_SYM(self_stat.st_dev, self_stat.st_ino, "_ZN7android14AndroidRuntime8setArgv0EPKcb", setArgv0);
hook_commit();
// We still need old_jniRegisterNativeMethods as other code uses it
// android::AndroidRuntime::registerNativeMethods(_JNIEnv*, const char*, const JNINativeMethod*, int)
constexpr char sig[] = "_ZN7android14AndroidRuntime21registerNativeMethodsEP7_JNIEnvPKcPK15JNINativeMethodi";
*(void **) &old_jniRegisterNativeMethods = dlsym(RTLD_DEFAULT, sig);
}
} }
static bool unhook_functions() { static bool unhook_functions() {
@ -842,17 +796,14 @@ static bool unhook_functions() {
// Restore JNIEnv // Restore JNIEnv
if (g_ctx->env->functions == new_functions) { if (g_ctx->env->functions == new_functions) {
g_ctx->env->functions = old_functions; g_ctx->env->functions = old_functions;
if (gClassRef) { delete new_functions;
g_ctx->env->DeleteGlobalRef(gClassRef);
gClassRef = nullptr;
class_getName = nullptr;
}
} }
// Unhook JNI methods // Unhook JNI methods
for (const auto &[clz, methods] : *jni_hook_list) { for (const auto &[clz, methods] : *jni_hook_list) {
if (!methods.empty() && old_jniRegisterNativeMethods( if (!methods.empty() && g_ctx->env->RegisterNatives(
g_ctx->env, clz.data(), methods.data(), methods.size()) != 0) { g_ctx->env->FindClass(clz.data()), methods.data(),
static_cast<int>(methods.size())) != 0) {
ZLOGE("Failed to restore JNI hook of class [%s]\n", clz.data()); ZLOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
success = false; success = false;
} }