Co-authored-by: canyie <31466456+canyie@users.noreply.github.com>
This commit is contained in:
parent
a3ce1b2556
commit
630bd7ab03
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* This file is part of LSPosed.
|
||||||
|
*
|
||||||
|
* LSPosed is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LSPosed is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 LSPosed Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <jni_helper.h>
|
||||||
|
#include <base/object.h>
|
||||||
|
#include "jni/yahfa.h"
|
||||||
|
|
||||||
|
namespace art {
|
||||||
|
namespace jit {
|
||||||
|
namespace jit_code_cache {
|
||||||
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, MoveObsoleteMethod, void *thiz,
|
||||||
|
void *old_method, void *new_method) {
|
||||||
|
if (MoveObsoleteMethodSym)
|
||||||
|
[[likely]] MoveObsoleteMethodSym(thiz, old_method, new_method);
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE_MEM_HOOK_STUB_ENTRIES(
|
||||||
|
"_ZN3art3jit12JitCodeCache19GarbageCollectCacheEPNS_6ThreadE",
|
||||||
|
void, GarbageCollectCache, (void * thiz, void * self), {
|
||||||
|
LOGD("Before jit cache gc, moving hooked methods");
|
||||||
|
for (auto[target, backup] : lspd::getJitMovements()) {
|
||||||
|
MoveObsoleteMethod(thiz, target, backup);
|
||||||
|
}
|
||||||
|
backup(thiz, self);
|
||||||
|
});
|
||||||
|
|
||||||
|
inline void Setup(const SandHook::ElfImg &handle) {
|
||||||
|
RETRIEVE_MEM_FUNC_SYMBOL(MoveObsoleteMethod,
|
||||||
|
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_");
|
||||||
|
lspd::HookSyms(handle, GarbageCollectCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of LSPosed.
|
|
||||||
*
|
|
||||||
* LSPosed is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LSPosed is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <jni_helper.h>
|
|
||||||
#include <base/object.h>
|
|
||||||
#include <art/runtime/mirror/class.h>
|
|
||||||
#include "utils.h"
|
|
||||||
#include "jni/pending_hooks.h"
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
namespace profiling_info {
|
|
||||||
inline static size_t OFFSET_art_method = -1;
|
|
||||||
|
|
||||||
CREATE_MEM_HOOK_STUB_ENTRIES(
|
|
||||||
"_ZN3art13ProfilingInfo13AddInvokeInfoEjPNS_6mirror5ClassE",
|
|
||||||
void, AddInvokeInfo, (void * thiz, uint32_t dex_pc, void * clazz_ptr), {
|
|
||||||
void *method = *reinterpret_cast<void **>(
|
|
||||||
reinterpret_cast<uintptr_t>(thiz) + OFFSET_art_method);
|
|
||||||
if (lspd::isHooked(method)) [[unlikely]] return;
|
|
||||||
backup(thiz, dex_pc, clazz_ptr);
|
|
||||||
});
|
|
||||||
|
|
||||||
static void Setup(const SandHook::ElfImg &handle) {
|
|
||||||
int api_level = lspd::GetAndroidApiLevel();
|
|
||||||
switch (api_level) {
|
|
||||||
case __ANDROID_API_Q__:
|
|
||||||
OFFSET_art_method = 0;
|
|
||||||
break;
|
|
||||||
case __ANDROID_API_O_MR1__:
|
|
||||||
[[fallthrough]];
|
|
||||||
case __ANDROID_API_P__:
|
|
||||||
[[fallthrough]];
|
|
||||||
case __ANDROID_API_R__:
|
|
||||||
[[fallthrough]];
|
|
||||||
case __ANDROID_API_S__:
|
|
||||||
if constexpr(lspd::is64) {
|
|
||||||
OFFSET_art_method = 8;
|
|
||||||
} else {
|
|
||||||
OFFSET_art_method = 4;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (OFFSET_art_method != size_t(-1))
|
|
||||||
lspd::HookSyms(handle, AddInvokeInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -35,6 +35,9 @@ namespace lspd {
|
||||||
namespace {
|
namespace {
|
||||||
std::unordered_set<const void *> hooked_methods_;
|
std::unordered_set<const void *> hooked_methods_;
|
||||||
std::shared_mutex hooked_methods_lock_;
|
std::shared_mutex hooked_methods_lock_;
|
||||||
|
|
||||||
|
std::vector<std::pair<void *, void*>> jit_movements_;
|
||||||
|
std::shared_mutex jit_movements_lock_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isHooked(void *art_method) {
|
bool isHooked(void *art_method) {
|
||||||
|
|
@ -47,7 +50,18 @@ namespace lspd {
|
||||||
hooked_methods_.insert(art_method);
|
hooked_methods_.insert(art_method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void recordJitMovement(void *target, void* backup) {
|
||||||
|
std::unique_lock lk(jit_movements_lock_);
|
||||||
|
jit_movements_.emplace_back(target, backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<void*, void*>> getJitMovements() {
|
||||||
|
std::unique_lock lk(jit_movements_lock_);
|
||||||
|
return std::move(jit_movements_);
|
||||||
|
}
|
||||||
|
|
||||||
using namespace startop::dex;
|
using namespace startop::dex;
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(void, Yahfa, init, jint sdkVersion) {
|
LSP_DEF_NATIVE_METHOD(void, Yahfa, init, jint sdkVersion) {
|
||||||
yahfa::init(env, clazz, sdkVersion);
|
yahfa::init(env, clazz, sdkVersion);
|
||||||
}
|
}
|
||||||
|
|
@ -59,16 +73,20 @@ namespace lspd {
|
||||||
}
|
}
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, backupAndHookNative, jobject target,
|
LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, backupAndHookNative, jobject target,
|
||||||
jobject hook, jobject backup) {
|
jobject hook, jobject backup, jboolean is_proxy) {
|
||||||
art::gc::ScopedGCCriticalSection section(art::Thread::Current().Get(),
|
art::gc::ScopedGCCriticalSection section(art::Thread::Current().Get(),
|
||||||
art::gc::kGcCauseDebugger,
|
art::gc::kGcCauseDebugger,
|
||||||
art::gc::kCollectorTypeDebugger);
|
art::gc::kCollectorTypeDebugger);
|
||||||
art::thread_list::ScopedSuspendAll suspend("Yahfa Hook", false);
|
art::thread_list::ScopedSuspendAll suspend("Yahfa Hook", false);
|
||||||
return yahfa::backupAndHookNative(env, clazz, target, hook, backup);
|
if (yahfa::backupAndHookNative(env, clazz, target, hook, backup)) {
|
||||||
}
|
auto *target_method = yahfa::getArtMethod(env, target);
|
||||||
|
auto *backup_method = yahfa::getArtMethod(env, backup);
|
||||||
LSP_DEF_NATIVE_METHOD(void, Yahfa, recordHooked, jobject member) {
|
recordHooked(target_method);
|
||||||
lspd::recordHooked(yahfa::getArtMethod(env, member));
|
if (!is_proxy) [[likely]] recordJitMovement(target_method, backup_method);
|
||||||
|
return JNI_TRUE;
|
||||||
|
} else {
|
||||||
|
return JNI_FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, isHooked, jobject member) {
|
LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, isHooked, jobject member) {
|
||||||
|
|
@ -80,7 +98,7 @@ namespace lspd {
|
||||||
static auto *kInMemoryClassloader = JNI_NewGlobalRef(env, JNI_FindClass(env,
|
static auto *kInMemoryClassloader = JNI_NewGlobalRef(env, JNI_FindClass(env,
|
||||||
"dalvik/system/InMemoryDexClassLoader"));
|
"dalvik/system/InMemoryDexClassLoader"));
|
||||||
static jmethodID kInitMid = JNI_GetMethodID(env, kInMemoryClassloader, "<init>",
|
static jmethodID kInitMid = JNI_GetMethodID(env, kInMemoryClassloader, "<init>",
|
||||||
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
||||||
DexBuilder dex_file;
|
DexBuilder dex_file;
|
||||||
|
|
||||||
auto parameter_length = env->GetArrayLength(classes);
|
auto parameter_length = env->GetArrayLength(classes);
|
||||||
|
|
@ -116,10 +134,10 @@ namespace lspd {
|
||||||
hook_builder.BuildNewArray(hook_params_array, TypeDescriptor::Object, tmp);
|
hook_builder.BuildNewArray(hook_params_array, TypeDescriptor::Object, tmp);
|
||||||
for (size_t i = 0U, j = 0U; i < parameter_types.size(); ++i, ++j) {
|
for (size_t i = 0U, j = 0U; i < parameter_types.size(); ++i, ++j) {
|
||||||
hook_builder.BuildBoxIfPrimitive(Value::Parameter(j), parameter_types[i],
|
hook_builder.BuildBoxIfPrimitive(Value::Parameter(j), parameter_types[i],
|
||||||
Value::Parameter(j));
|
Value::Parameter(j));
|
||||||
hook_builder.BuildConst(tmp, i);
|
hook_builder.BuildConst(tmp, i);
|
||||||
hook_builder.BuildAput(Instruction::Op::kAputObject, hook_params_array,
|
hook_builder.BuildAput(Instruction::Op::kAputObject, hook_params_array,
|
||||||
Value::Parameter(j), tmp);
|
Value::Parameter(j), tmp);
|
||||||
if (parameter_types[i].is_wide()) ++j;
|
if (parameter_types[i].is_wide()) ++j;
|
||||||
}
|
}
|
||||||
auto handle_hook_method{dex_file.GetOrDeclareMethod(
|
auto handle_hook_method{dex_file.GetOrDeclareMethod(
|
||||||
|
|
@ -171,10 +189,10 @@ namespace lspd {
|
||||||
env->DeleteLocalRef(dex_buffer);
|
env->DeleteLocalRef(dex_buffer);
|
||||||
|
|
||||||
static jmethodID kMid = JNI_GetMethodID(env, kInMemoryClassloader, "loadClass",
|
static jmethodID kMid = JNI_GetMethodID(env, kInMemoryClassloader, "loadClass",
|
||||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||||
if (!kMid) {
|
if (!kMid) {
|
||||||
kMid = JNI_GetMethodID(env, kInMemoryClassloader, "findClass",
|
kMid = JNI_GetMethodID(env, kInMemoryClassloader, "findClass",
|
||||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||||
}
|
}
|
||||||
auto target = JNI_CallObjectMethod(env, my_cl, kMid, env->NewStringUTF("LspHooker_"));
|
auto target = JNI_CallObjectMethod(env, my_cl, kMid, env->NewStringUTF("LspHooker_"));
|
||||||
// LOGD("Created %zd", image.size());
|
// LOGD("Created %zd", image.size());
|
||||||
|
|
@ -189,8 +207,7 @@ namespace lspd {
|
||||||
LSP_NATIVE_METHOD(Yahfa, findMethodNative,
|
LSP_NATIVE_METHOD(Yahfa, findMethodNative,
|
||||||
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Executable;"),
|
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Executable;"),
|
||||||
LSP_NATIVE_METHOD(Yahfa, backupAndHookNative,
|
LSP_NATIVE_METHOD(Yahfa, backupAndHookNative,
|
||||||
"(Ljava/lang/reflect/Executable;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"),
|
"(Ljava/lang/reflect/Executable;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;Z)Z"),
|
||||||
LSP_NATIVE_METHOD(Yahfa, recordHooked, "(Ljava/lang/reflect/Executable;)V"),
|
|
||||||
LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Executable;)Z"),
|
LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Executable;)Z"),
|
||||||
LSP_NATIVE_METHOD(Yahfa, buildHooker,
|
LSP_NATIVE_METHOD(Yahfa, buildHooker,
|
||||||
"(Ljava/lang/ClassLoader;C[CLjava/lang/String;)Ljava/lang/Class;"),
|
"(Ljava/lang/ClassLoader;C[CLjava/lang/String;)Ljava/lang/Class;"),
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "jni.h"
|
#include "jni.h"
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
|
|
||||||
bool isHooked(void* art_method);
|
bool isHooked(void* art_method);
|
||||||
|
|
||||||
void recordHooked(void* art_method);
|
std::vector<std::pair<void*, void*>> getJitMovements();
|
||||||
|
|
||||||
void RegisterYahfa(JNIEnv *);
|
void RegisterYahfa(JNIEnv *);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
#include "art/runtime/instrumentation.h"
|
#include "art/runtime/instrumentation.h"
|
||||||
#include "art/runtime/thread_list.h"
|
#include "art/runtime/thread_list.h"
|
||||||
#include "art/runtime/gc/scoped_gc_critical_section.h"
|
#include "art/runtime/gc/scoped_gc_critical_section.h"
|
||||||
#include "art/runtime/jit/profiling_info.h"
|
#include "art/runtime/jit/jit_code_cache.h"
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
static std::atomic_bool installed = false;
|
static std::atomic_bool installed = false;
|
||||||
|
|
@ -61,7 +61,7 @@ namespace lspd {
|
||||||
art::instrumentation::DisableUpdateHookedMethodsCode(handle_libart);
|
art::instrumentation::DisableUpdateHookedMethodsCode(handle_libart);
|
||||||
art::thread_list::ScopedSuspendAll::Setup(handle_libart);
|
art::thread_list::ScopedSuspendAll::Setup(handle_libart);
|
||||||
art::gc::ScopedGCCriticalSection::Setup(handle_libart);
|
art::gc::ScopedGCCriticalSection::Setup(handle_libart);
|
||||||
art::profiling_info::Setup(handle_libart);
|
art::jit::jit_code_cache::Setup(handle_libart);
|
||||||
art_img.reset();
|
art_img.reset();
|
||||||
LOGD("Inline hooks installed");
|
LOGD("Inline hooks installed");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
@ -51,10 +52,8 @@ public class HookMain {
|
||||||
// backup is just a placeholder and the constraint could be less strict
|
// backup is just a placeholder and the constraint could be less strict
|
||||||
checkCompatibleMethods(target, backup, "Backup");
|
checkCompatibleMethods(target, backup, "Backup");
|
||||||
}
|
}
|
||||||
if(!Yahfa.backupAndHookNative(target, hook, backup)){
|
if (!Yahfa.backupAndHookNative(target, hook, backup, Proxy.isProxyClass(target.getDeclaringClass()))) {
|
||||||
throw new RuntimeException("Failed to hook " + target + " with " + hook);
|
throw new RuntimeException("Failed to hook " + target + " with " + hook);
|
||||||
} else {
|
|
||||||
Yahfa.recordHooked(target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,13 @@ import java.lang.reflect.Method;
|
||||||
|
|
||||||
public class Yahfa {
|
public class Yahfa {
|
||||||
|
|
||||||
public static native boolean backupAndHookNative(Executable target, Method hook, Method backup);
|
public static native boolean backupAndHookNative(Executable target, Method hook, Method backup, boolean isProxy);
|
||||||
|
|
||||||
// JNI.ToReflectedMethod() could return either Method or Constructor
|
// JNI.ToReflectedMethod() could return either Method or Constructor
|
||||||
public static native Executable findMethodNative(Class<?> targetClass, String methodName, String methodSig);
|
public static native Executable findMethodNative(Class<?> targetClass, String methodName, String methodSig);
|
||||||
|
|
||||||
public static native void init(int sdkVersion);
|
public static native void init(int sdkVersion);
|
||||||
|
|
||||||
public static native void recordHooked(Executable member);
|
|
||||||
|
|
||||||
public static native boolean isHooked(Executable member);
|
public static native boolean isHooked(Executable member);
|
||||||
|
|
||||||
public static native Class<?> buildHooker(ClassLoader appClassLoader, char returnType, char[] params, String methodName);
|
public static native Class<?> buildHooker(ClassLoader appClassLoader, char returnType, char[] params, String methodName);
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,6 @@ public class ConfigFileManager {
|
||||||
DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(Utils.getZoneId());
|
DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(Utils.getZoneId());
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private static FileLocker locker = null;
|
private static FileLocker locker = null;
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
|
||||||
private static AssetManager am = null;
|
|
||||||
private static Resources res = null;
|
private static Resources res = null;
|
||||||
private static ParcelFileDescriptor fd = null;
|
private static ParcelFileDescriptor fd = null;
|
||||||
|
|
||||||
|
|
@ -119,7 +117,7 @@ public class ConfigFileManager {
|
||||||
private static void loadRes() {
|
private static void loadRes() {
|
||||||
if (res != null) return;
|
if (res != null) return;
|
||||||
try {
|
try {
|
||||||
am = AssetManager.class.newInstance();
|
var am = AssetManager.class.newInstance();
|
||||||
//noinspection JavaReflectionMemberAccess DiscouragedPrivateApi
|
//noinspection JavaReflectionMemberAccess DiscouragedPrivateApi
|
||||||
Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
|
Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
|
||||||
addAssetPath.setAccessible(true);
|
addAssetPath.setAccessible(true);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue