mirror of
https://github.com/LSPosed/LSPlant.git
synced 2025-05-04 20:42:02 +08:00
Support down to Android 6 (#3)
This commit is contained in:
parent
83cdee50eb
commit
936f18c242
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@ -44,6 +44,24 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- api-level: 23
|
||||
target: default
|
||||
arch: x86_64
|
||||
- api-level: 23
|
||||
target: default
|
||||
arch: x86
|
||||
- api-level: 24
|
||||
target: default
|
||||
arch: x86_64
|
||||
- api-level: 24
|
||||
target: default
|
||||
arch: x86
|
||||
- api-level: 25
|
||||
target: default
|
||||
arch: x86_64
|
||||
- api-level: 25
|
||||
target: default
|
||||
arch: x86
|
||||
- api-level: 26
|
||||
target: default
|
||||
arch: x86_64
|
||||
|
@ -1,7 +1,7 @@
|
||||
# LSPlant
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
@ -12,7 +12,7 @@ This project is part of LSPosed framework under GNU Lesser General Public Licens
|
||||
|
||||
## Features
|
||||
|
||||
+ Support Android 8.0 - 13 (API level 26 - 34)
|
||||
+ Support Android 6.0 - 13 (API level 23 - 34)
|
||||
+ Support armeabi-v7a, arm64-v8a, x86, x86-64
|
||||
+ Support customized inline hook framework and ART symbol resolver
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
val androidTargetSdkVersion by extra(32)
|
||||
val androidMinSdkVersion by extra(26)
|
||||
val androidMinSdkVersion by extra(23)
|
||||
val androidBuildToolsVersion by extra("32.0.0")
|
||||
val androidCompileSdkVersion by extra(32)
|
||||
val androidNdkVersion by extra("23.1.7779620")
|
||||
|
@ -7,25 +7,38 @@ namespace lsplant::art {
|
||||
|
||||
class Instrumentation {
|
||||
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||
"_ZN3art15instrumentation15Instrumentation21UpdateMethodsCodeImplEPNS_9ArtMethodEPKv",
|
||||
void, UpdateMethodsCode,
|
||||
(Instrumentation * thiz, ArtMethod * art_method, const void *quick_code), {
|
||||
if (IsHooked(art_method)) [[unlikely]] {
|
||||
LOGD("Skip update method code for hooked method %s",
|
||||
art_method->PrettyMethod().c_str());
|
||||
return;
|
||||
} else {
|
||||
backup(thiz, art_method, quick_code);
|
||||
}
|
||||
});
|
||||
"_ZN3art15instrumentation15Instrumentation21UpdateMethodsCodeImplEPNS_9ArtMethodEPKv", void,
|
||||
UpdateMethodsCodeImpl,
|
||||
(Instrumentation * thiz, ArtMethod *art_method, const void *quick_code), {
|
||||
if (IsHooked(art_method)) [[unlikely]] {
|
||||
LOGD("Skip update method code for hooked method %s",
|
||||
art_method->PrettyMethod().c_str());
|
||||
return;
|
||||
} else {
|
||||
backup(thiz, art_method, quick_code);
|
||||
}
|
||||
});
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||
"_ZN3art15instrumentation15Instrumentation17UpdateMethodsCodeEPNS_9ArtMethodEPKv", void,
|
||||
UpdateMethodsCode, (Instrumentation * thiz, ArtMethod *art_method, const void *quick_code),
|
||||
{
|
||||
if (IsHooked(art_method)) [[unlikely]] {
|
||||
LOGD("Skip update method code for hooked method %s",
|
||||
art_method->PrettyMethod().c_str());
|
||||
return;
|
||||
} else {
|
||||
backup(thiz, art_method, quick_code);
|
||||
}
|
||||
});
|
||||
|
||||
public:
|
||||
static bool Init(const HookHandler &handler) {
|
||||
if (!HookSyms(handler, UpdateMethodsCode)) {
|
||||
if (!HookSyms(handler, UpdateMethodsCodeImpl, UpdateMethodsCode)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace lsplant::art
|
||||
|
@ -82,6 +82,7 @@ public:
|
||||
bool IsFinal() { return GetAccessFlags() & kAccFinal; }
|
||||
bool IsStatic() { return GetAccessFlags() & kAccStatic; }
|
||||
bool IsNative() { return GetAccessFlags() & kAccNative; }
|
||||
bool IsConstructor() { return GetAccessFlags() & kAccConstructor; }
|
||||
|
||||
void CopyFrom(const ArtMethod *other) { memcpy(this, other, art_method_size); }
|
||||
|
||||
@ -98,6 +99,10 @@ public:
|
||||
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + data_offset);
|
||||
}
|
||||
|
||||
void SetData(void *data) {
|
||||
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + data_offset) = data;
|
||||
}
|
||||
|
||||
uint32_t GetAccessFlags() {
|
||||
return (reinterpret_cast<const std::atomic<uint32_t> *>(reinterpret_cast<uintptr_t>(this) +
|
||||
access_flags_offset))
|
||||
@ -119,9 +124,16 @@ public:
|
||||
}
|
||||
|
||||
static bool Init(JNIEnv *env, const HookHandler handler) {
|
||||
auto executable = JNI_FindClass(env, "java/lang/reflect/Executable");
|
||||
auto sdk_int = GetAndroidApiLevel();
|
||||
jclass executable = nullptr;
|
||||
if (sdk_int >= __ANDROID_API_O__) {
|
||||
executable = JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/reflect/Executable"));
|
||||
} else {
|
||||
executable =
|
||||
JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/reflect/AbstractMethod"));
|
||||
}
|
||||
if (!executable) {
|
||||
LOGE("Failed to found Executable");
|
||||
LOGE("Failed to found Executable/AbstractMethod");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -153,7 +165,7 @@ public:
|
||||
art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
|
||||
LOGD("ArtMethod size: %zu", art_method_size);
|
||||
|
||||
if (RoundUpTo(4 * 4 + 2 * 2, kPointerSize) + kPointerSize * 3 < art_method_size) {
|
||||
if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) {
|
||||
LOGW("ArtMethod size exceeds maximum assume. There may be something wrong.");
|
||||
}
|
||||
|
||||
@ -179,10 +191,12 @@ public:
|
||||
LOGW("Failed to find accessFlags field. Fallback to 4.");
|
||||
access_flags_offset = 4U;
|
||||
}
|
||||
auto sdk_int = GetAndroidApiLevel();
|
||||
|
||||
if (sdk_int < __ANDROID_API_R__) kAccPreCompiled = 0;
|
||||
else if (sdk_int >= __ANDROID_API_S__) kAccPreCompiled = 0x00800000;
|
||||
if (sdk_int < __ANDROID_API_R__) {
|
||||
kAccPreCompiled = 0;
|
||||
} else if (sdk_int >= __ANDROID_API_S__) {
|
||||
kAccPreCompiled = 0x00800000;
|
||||
}
|
||||
if (sdk_int < __ANDROID_API_Q__) kAccFastInterpreterToInterpreterInvoke = 0;
|
||||
|
||||
if (!RETRIEVE_FUNC_SYMBOL(GetMethodShorty,
|
||||
@ -191,30 +205,36 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sdk_int == __ANDROID_API_O__) [[unlikely]] {
|
||||
if (sdk_int <= __ANDROID_API_O__) [[unlikely]] {
|
||||
auto abstract_method_error = JNI_FindClass(env, "java/lang/AbstractMethodError");
|
||||
if (!abstract_method_error) {
|
||||
LOGE("Failed to find AbstractMethodError");
|
||||
return false;
|
||||
}
|
||||
auto executable_get_name =
|
||||
JNI_GetMethodID(env, executable, "getName", "()Ljava/lang/String;");
|
||||
if (!executable_get_name) {
|
||||
LOGE("Failed to find Executable.getName");
|
||||
return false;
|
||||
if (sdk_int == __ANDROID_API_O__) [[unlikely]] {
|
||||
auto executable_get_name =
|
||||
JNI_GetMethodID(env, executable, "getName", "()Ljava/lang/String;");
|
||||
if (!executable_get_name) {
|
||||
LOGE("Failed to find Executable.getName");
|
||||
return false;
|
||||
}
|
||||
auto abstract_method = FromReflectedMethod(
|
||||
env, JNI_ToReflectedMethod(env, executable, executable_get_name, false));
|
||||
uint32_t access_flags = abstract_method->GetAccessFlags();
|
||||
abstract_method->SetAccessFlags(access_flags | kAccDefaultConflict);
|
||||
abstract_method->ThrowInvocationTimeError();
|
||||
abstract_method->SetAccessFlags(access_flags);
|
||||
}
|
||||
auto abstract_method = FromReflectedMethod(
|
||||
env, JNI_ToReflectedMethod(env, executable, executable_get_name, false));
|
||||
uint32_t access_flags = abstract_method->GetAccessFlags();
|
||||
abstract_method->SetAccessFlags(access_flags | kAccDefaultConflict);
|
||||
abstract_method->ThrowInvocationTimeError();
|
||||
abstract_method->SetAccessFlags(access_flags);
|
||||
if (auto exception = env->ExceptionOccurred();
|
||||
env->ExceptionClear(),
|
||||
(!exception || JNI_IsInstanceOf(env, exception, abstract_method_error))) {
|
||||
(!exception || JNI_IsInstanceOf(env, exception, abstract_method_error)))
|
||||
[[likely]] {
|
||||
kAccCompileDontBother = kAccDefaultConflict;
|
||||
}
|
||||
}
|
||||
if (sdk_int <= __ANDROID_API_N__) {
|
||||
kAccCompileDontBother = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -227,6 +247,7 @@ public:
|
||||
constexpr static uint32_t kAccStatic = 0x0008; // field, method, ic
|
||||
constexpr static uint32_t kAccNative = 0x0100; // method
|
||||
constexpr static uint32_t kAccFinal = 0x0010; // class, field, method, ic
|
||||
constexpr static uint32_t kAccConstructor = 0x00010000;
|
||||
|
||||
private:
|
||||
inline static jfieldID art_method_field = nullptr;
|
||||
|
@ -31,6 +31,13 @@ private:
|
||||
MaybeDelayHook(clazz);
|
||||
});
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6mirror5ClassE",
|
||||
void, FixupStaticTrampolinesRaw,
|
||||
(ClassLinker * thiz, mirror::Class *clazz), {
|
||||
backup(thiz, clazz);
|
||||
MaybeDelayHook(clazz);
|
||||
});
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
|
||||
void, FixupStaticTrampolinesWithThread,
|
||||
@ -72,7 +79,7 @@ private:
|
||||
|
||||
public:
|
||||
static bool Init(const HookHandler &handler) {
|
||||
int api_level = GetAndroidApiLevel();
|
||||
int sdk_int = GetAndroidApiLevel();
|
||||
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(
|
||||
SetEntryPointsToInterpreter,
|
||||
@ -80,11 +87,14 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HookSyms(handler, ShouldUseInterpreterEntrypoint, ShouldStayInSwitchInterpreter)) {
|
||||
return false;
|
||||
if (sdk_int >= __ANDROID_API_N__) {
|
||||
if (!HookSyms(handler, ShouldUseInterpreterEntrypoint, ShouldStayInSwitchInterpreter))
|
||||
[[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (api_level >= __ANDROID_API_R__) {
|
||||
if (sdk_int >= __ANDROID_API_R__) {
|
||||
// In android R, FixupStaticTrampolines won't be called unless it's marking it as
|
||||
// visiblyInitialized.
|
||||
// So we miss some calls between initialized and visiblyInitialized.
|
||||
@ -95,7 +105,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!HookSyms(handler, FixupStaticTrampolines)) {
|
||||
if (!HookSyms(handler, FixupStaticTrampolines, FixupStaticTrampolinesRaw)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -119,6 +129,11 @@ public:
|
||||
}
|
||||
|
||||
[[gnu::always_inline]] static bool SetEntryPointsToInterpreter(ArtMethod *art_method) {
|
||||
if (SetEntryPointsToInterpreterSym) [[likely]] {
|
||||
SetEntryPointsToInterpreter(nullptr, art_method);
|
||||
return true;
|
||||
}
|
||||
// Android 13
|
||||
if (art_quick_to_interpreter_bridgeSym && art_quick_generic_jni_trampolineSym) [[likely]] {
|
||||
if (art_method->GetAccessFlags() & ArtMethod::kAccNative) [[unlikely]] {
|
||||
art_method->SetEntryPoint(
|
||||
@ -129,10 +144,6 @@ public:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (SetEntryPointsToInterpreterSym) [[likely]] {
|
||||
SetEntryPointsToInterpreter(nullptr, art_method);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
91
lsplant/src/main/jni/art/runtime/dex_file.hpp
Normal file
91
lsplant/src/main/jni/art/runtime/dex_file.hpp
Normal file
@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
namespace lsplant::art {
|
||||
class DexFile {
|
||||
struct Header {
|
||||
uint8_t magic_[8];
|
||||
uint32_t checksum_; // See also location_checksum_
|
||||
};
|
||||
CREATE_FUNC_SYMBOL_ENTRY(std::unique_ptr<DexFile>, OpenMemory, const uint8_t* dex_file,
|
||||
size_t size, const std::string& location, uint32_t location_checksum,
|
||||
void* mem_map, const void* oat_dex_file, std::string* error_msg) {
|
||||
if (OpenMemorySym) [[likely]] {
|
||||
return OpenMemorySym(dex_file, size, location, location_checksum, mem_map, oat_dex_file,
|
||||
error_msg);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
static std::unique_ptr<DexFile> OpenMemory(const void* dex_file, size_t size,
|
||||
std::string location, std::string* error_msg) {
|
||||
return OpenMemory(reinterpret_cast<const uint8_t*>(dex_file), size, location,
|
||||
reinterpret_cast<const Header*>(dex_file)->checksum_, nullptr, nullptr,
|
||||
error_msg);
|
||||
}
|
||||
|
||||
jobject ToJavaDexFile(JNIEnv* env) {
|
||||
auto java_dex_file = env->AllocObject(dex_file_class);
|
||||
auto cookie = JNI_NewLongArray(env, dex_file_start_index + 1);
|
||||
cookie[oat_file_index] = 0;
|
||||
cookie[dex_file_start_index] = reinterpret_cast<jlong>(this);
|
||||
cookie.commit();
|
||||
JNI_SetObjectField(env, java_dex_file, cookie_field, cookie);
|
||||
if (internal_cookie_field) {
|
||||
JNI_SetObjectField(env, java_dex_file, internal_cookie_field, cookie);
|
||||
}
|
||||
JNI_SetObjectField(env, java_dex_file, file_name_field, JNI_NewStringUTF(env, ""));
|
||||
return java_dex_file;
|
||||
}
|
||||
|
||||
static bool Init(JNIEnv* env, const HookHandler& handler) {
|
||||
auto sdk_int = GetAndroidApiLevel();
|
||||
if (sdk_int >= __ANDROID_API_O__) [[likely]] {
|
||||
return true;
|
||||
}
|
||||
if (!RETRIEVE_FUNC_SYMBOL(
|
||||
OpenMemory,
|
||||
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
|
||||
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_",
|
||||
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
|
||||
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_")))
|
||||
[[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
dex_file_class = JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/DexFile"));
|
||||
if (!dex_file_class) {
|
||||
return false;
|
||||
}
|
||||
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "Ljava/lang/Object;");
|
||||
if (!cookie_field) {
|
||||
return false;
|
||||
}
|
||||
file_name_field = JNI_GetFieldID(env, dex_file_class, "mFileName", "Ljava/lang/String;");
|
||||
if (!file_name_field) {
|
||||
return false;
|
||||
}
|
||||
if (sdk_int >= __ANDROID_API_N__) {
|
||||
internal_cookie_field =
|
||||
JNI_GetFieldID(env, dex_file_class, "mInternalCookie", "Ljava/lang/Object;");
|
||||
if (!internal_cookie_field) {
|
||||
return false;
|
||||
}
|
||||
dex_file_start_index = 1u;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
inline static jclass dex_file_class = nullptr;
|
||||
inline static jfieldID cookie_field = nullptr;
|
||||
inline static jfieldID file_name_field = nullptr;
|
||||
inline static jfieldID internal_cookie_field = nullptr;
|
||||
inline static size_t oat_file_index = 0u;
|
||||
inline static size_t dex_file_start_index = 0u;
|
||||
};
|
||||
} // namespace lsplant::art
|
@ -37,13 +37,18 @@ public:
|
||||
~ScopedGCCriticalSection() { destructor(this); }
|
||||
|
||||
static bool Init(const HookHandler &handler) {
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor,
|
||||
"_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_"
|
||||
"7GcCauseENS0_13CollectorTypeE")) {
|
||||
return false;
|
||||
}
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art2gc23ScopedGCCriticalSectionD2Ev")) {
|
||||
return false;
|
||||
// for Android M, it's safe to not found since we have suspendVM & resumeVM
|
||||
auto sdk_int = GetAndroidApiLevel();
|
||||
if (sdk_int >= __ANDROID_API_N__) {
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor,
|
||||
"_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_"
|
||||
"7GcCauseENS0_13CollectorTypeE")) [[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art2gc23ScopedGCCriticalSectionD2Ev"))
|
||||
[[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -6,8 +6,13 @@ namespace lsplant::art::jit {
|
||||
class JitCodeCache {
|
||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, MoveObsoleteMethod, JitCodeCache *thiz,
|
||||
ArtMethod *old_method, ArtMethod *new_method) {
|
||||
if (MoveObsoleteMethodSym) [[likely]]
|
||||
if (MoveObsoleteMethodSym) [[likely]] {
|
||||
MoveObsoleteMethodSym(thiz, old_method, new_method);
|
||||
} else {
|
||||
// fallback to set data
|
||||
new_method->SetData(old_method->GetData());
|
||||
old_method->SetData(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art3jit12JitCodeCache19GarbageCollectCacheEPNS_6ThreadE", void,
|
||||
@ -21,13 +26,19 @@ class JitCodeCache {
|
||||
|
||||
public:
|
||||
static bool Init(const HookHandler &handler) {
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(
|
||||
MoveObsoleteMethod,
|
||||
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_")) {
|
||||
return false;
|
||||
auto sdk_int = GetAndroidApiLevel();
|
||||
if (sdk_int >= __ANDROID_API_O__) [[likely]] {
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(
|
||||
MoveObsoleteMethod,
|
||||
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_"))
|
||||
[[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!HookSyms(handler, GarbageCollectCache)) {
|
||||
return false;
|
||||
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
|
||||
if (!HookSyms(handler, GarbageCollectCache)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -7,15 +7,36 @@ namespace lsplant::art::thread_list {
|
||||
class ScopedSuspendAll {
|
||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, ScopedSuspendAll *thiz, const char *cause,
|
||||
bool long_suspend) {
|
||||
if (thiz == nullptr) [[unlikely]] return;
|
||||
if (constructorSym) [[likely]]
|
||||
if (thiz == nullptr) [[unlikely]]
|
||||
return;
|
||||
if (constructorSym) [[likely]] {
|
||||
return constructorSym(thiz, cause, long_suspend);
|
||||
} else {
|
||||
SuspendVM();
|
||||
}
|
||||
}
|
||||
|
||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedSuspendAll *thiz) {
|
||||
if (thiz == nullptr) [[unlikely]] return;
|
||||
if (destructorSym) [[likely]]
|
||||
if (thiz == nullptr) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
if (destructorSym) [[likely]] {
|
||||
return destructorSym(thiz);
|
||||
} else {
|
||||
ResumeVM();
|
||||
}
|
||||
}
|
||||
|
||||
CREATE_FUNC_SYMBOL_ENTRY(void, SuspendVM) {
|
||||
if (SuspendVMSym) [[likely]] {
|
||||
SuspendVMSym();
|
||||
}
|
||||
}
|
||||
|
||||
CREATE_FUNC_SYMBOL_ENTRY(void, ResumeVM) {
|
||||
if (ResumeVMSym) [[likely]] {
|
||||
ResumeVMSym();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
@ -23,20 +44,21 @@ public:
|
||||
constructor(this, cause, long_suspend);
|
||||
}
|
||||
|
||||
~ScopedSuspendAll() {
|
||||
destructor(this);
|
||||
}
|
||||
~ScopedSuspendAll() { destructor(this); }
|
||||
|
||||
static bool Init(const HookHandler &handler) {
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art16ScopedSuspendAllC2EPKcb")) {
|
||||
return false;
|
||||
if (!RETRIEVE_FUNC_SYMBOL(SuspendVM, "_ZN3art3Dbg9SuspendVMEv")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art16ScopedSuspendAllD2Ev")) {
|
||||
return false;
|
||||
if (!RETRIEVE_FUNC_SYMBOL(ResumeVM, "_ZN3art3Dbg8ResumeVMEv")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
} // namespace lsplant::art::thread_list
|
||||
|
2
lsplant/src/main/jni/external/dex_builder
vendored
2
lsplant/src/main/jni/external/dex_builder
vendored
@ -1 +1 @@
|
||||
Subproject commit eb05e8b97fff754e9b3e825f94ea72bc4deed711
|
||||
Subproject commit 8e1672aa6da3bbc151e2fda37c04e528dffae5aa
|
@ -5,6 +5,12 @@
|
||||
#include "lsplant.hpp"
|
||||
#include "jni_helper.hpp"
|
||||
|
||||
#if defined(__LP64__)
|
||||
# define LP_SELECT(lp32, lp64) lp64
|
||||
#else
|
||||
# define LP_SELECT(lp32, lp64) lp32
|
||||
#endif
|
||||
|
||||
#define CONCATENATE(a, b) a##b
|
||||
|
||||
#define CREATE_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
|
||||
|
@ -311,9 +311,9 @@ template <ScopeOrObject Object>
|
||||
|
||||
// setters
|
||||
|
||||
template <ScopeOrObject Object>
|
||||
template <ScopeOrObject Object, ScopeOrObject Value>
|
||||
[[maybe_unused]] inline auto JNI_SetObjectField(JNIEnv *env, Object &&obj, jfieldID fieldId,
|
||||
const Object &value) {
|
||||
const Value &value) {
|
||||
return JNI_SafeInvoke(env, &JNIEnv::SetObjectField, std::forward<Object>(obj), fieldId, value);
|
||||
}
|
||||
|
||||
@ -973,7 +973,10 @@ public:
|
||||
|
||||
const JArrayUnderlyingType<T> &operator[](size_t index) const { return elements_[index]; }
|
||||
|
||||
void commit() { ReleaseElements(JNI_COMMIT); }
|
||||
void commit() {
|
||||
ReleaseElements(JNI_COMMIT);
|
||||
modified_ = false;
|
||||
}
|
||||
|
||||
// We do not expose an empty constructor as it can easily lead to errors
|
||||
// using common idioms, e.g.:
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "art/instrumentation.hpp"
|
||||
#include "art/runtime/art_method.hpp"
|
||||
#include "art/runtime/class_linker.hpp"
|
||||
#include "art/runtime/dex_file.hpp"
|
||||
#include "art/runtime/gc/scoped_gc_critical_section.hpp"
|
||||
#include "art/runtime/jit/jit_code_cache.hpp"
|
||||
#include "art/thread.hpp"
|
||||
@ -27,6 +28,7 @@ namespace lsplant {
|
||||
|
||||
using art::ArtMethod;
|
||||
using art::ClassLinker;
|
||||
using art::DexFile;
|
||||
using art::Instrumentation;
|
||||
using art::Thread;
|
||||
using art::gc::ScopedGCCriticalSection;
|
||||
@ -78,6 +80,10 @@ jmethodID load_class = nullptr;
|
||||
jmethodID set_accessible = nullptr;
|
||||
jclass executable = nullptr;
|
||||
|
||||
// for old platform
|
||||
jclass path_class_loader = nullptr;
|
||||
jmethodID path_class_loader_init = nullptr;
|
||||
|
||||
std::string generated_class_name;
|
||||
std::string generated_source_name;
|
||||
std::string generated_field_name;
|
||||
@ -104,9 +110,14 @@ bool InitConfig(const InitInfo &info) {
|
||||
}
|
||||
|
||||
bool InitJNI(JNIEnv *env) {
|
||||
executable = JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/reflect/Executable"));
|
||||
int sdk_int = GetAndroidApiLevel();
|
||||
if (sdk_int >= __ANDROID_API_O__) {
|
||||
executable = JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/reflect/Executable"));
|
||||
} else {
|
||||
executable = JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/reflect/AbstractMethod"));
|
||||
}
|
||||
if (!executable) {
|
||||
LOGE("Failed to found Executable");
|
||||
LOGE("Failed to found Executable/AbstractMethod");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -151,17 +162,34 @@ bool InitJNI(JNIEnv *env) {
|
||||
LOGE("Failed to find Class.accessFlags");
|
||||
return false;
|
||||
}
|
||||
|
||||
in_memory_class_loader =
|
||||
JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/InMemoryDexClassLoader"));
|
||||
in_memory_class_loader_init = JNI_GetMethodID(
|
||||
env, in_memory_class_loader, "<init>", "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
||||
|
||||
load_class = JNI_GetMethodID(env, in_memory_class_loader, "loadClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
if (!load_class) {
|
||||
load_class = JNI_GetMethodID(env, in_memory_class_loader, "findClass",
|
||||
if (sdk_int >= __ANDROID_API_O__ &&
|
||||
(in_memory_class_loader = JNI_NewGlobalRef(
|
||||
env, JNI_FindClass(env, "dalvik/system/InMemoryDexClassLoader")))) [[likely]] {
|
||||
in_memory_class_loader_init =
|
||||
JNI_GetMethodID(env, in_memory_class_loader, "<init>",
|
||||
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
||||
load_class = JNI_GetMethodID(env, in_memory_class_loader, "loadClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
if (!load_class) {
|
||||
load_class = JNI_GetMethodID(env, in_memory_class_loader, "findClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
}
|
||||
} else if (auto dex_file = JNI_FindClass(env, "dalvik/system/DexFile");
|
||||
dex_file && (path_class_loader = JNI_NewGlobalRef(
|
||||
env, JNI_FindClass(env, "dalvik/system/PathClassLoader")))) {
|
||||
path_class_loader_init = JNI_GetMethodID(env, path_class_loader, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
|
||||
if (!path_class_loader_init) {
|
||||
LOGE("Failed to find PathClassLoader.<init>");
|
||||
return false;
|
||||
}
|
||||
load_class =
|
||||
JNI_GetMethodID(env, dex_file, "loadClass",
|
||||
"(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;");
|
||||
}
|
||||
if (!load_class) {
|
||||
LOGE("Failed to find a suitable way to load class");
|
||||
return false;
|
||||
}
|
||||
auto accessible_object = JNI_FindClass(env, "java/lang/reflect/AccessibleObject");
|
||||
if (!accessible_object) {
|
||||
@ -219,6 +247,10 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
|
||||
LOGE("failed to init jit code cache");
|
||||
return false;
|
||||
}
|
||||
if (!DexFile::Init(env, handler)) {
|
||||
LOGE("Failed to init dex file");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -314,27 +346,50 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
|
||||
|
||||
slicer::MemView image{dex_file.CreateImage()};
|
||||
|
||||
auto dex_buffer = JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()),
|
||||
static_cast<jlong>(image.size()));
|
||||
auto my_cl = JNI_NewObject(env, in_memory_class_loader, in_memory_class_loader_init, dex_buffer,
|
||||
class_loader);
|
||||
if (my_cl) {
|
||||
auto *target_class =
|
||||
JNI_Cast<jclass>(
|
||||
JNI_CallObjectMethod(env, my_cl, load_class,
|
||||
JNI_NewStringUTF(env, generated_class_name.data())))
|
||||
.release();
|
||||
if (target_class) {
|
||||
return {
|
||||
target_class,
|
||||
JNI_GetStaticFieldID(env, target_class, hooker_field->decl->name->c_str(),
|
||||
hooker_field->decl->type->descriptor->c_str()),
|
||||
JNI_GetStaticMethodID(env, target_class, hook_method->decl->name->c_str(),
|
||||
hook_method->decl->prototype->Signature().data()),
|
||||
JNI_GetStaticMethodID(env, target_class, backup_method->decl->name->c_str(),
|
||||
backup_method->decl->prototype->Signature().data()),
|
||||
};
|
||||
jclass target_class = nullptr;
|
||||
|
||||
if (in_memory_class_loader_init) [[unlikely]] {
|
||||
auto dex_buffer = JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()),
|
||||
static_cast<jlong>(image.size()));
|
||||
auto my_cl = JNI_NewObject(env, in_memory_class_loader, in_memory_class_loader_init,
|
||||
dex_buffer, class_loader);
|
||||
if (my_cl) {
|
||||
target_class = JNI_Cast<jclass>(JNI_CallObjectMethod(
|
||||
env, my_cl, load_class,
|
||||
JNI_NewStringUTF(env, generated_class_name.data())))
|
||||
.release();
|
||||
}
|
||||
} else {
|
||||
void *target =
|
||||
mmap(nullptr, image.size(), PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
memcpy(target, image.ptr(), image.size());
|
||||
mprotect(target, image.size(), PROT_READ);
|
||||
std::string err_msg;
|
||||
auto *dex = DexFile::OpenMemory(
|
||||
target, image.size(),
|
||||
generated_source_name.empty() ? "lsplant" : generated_source_name, &err_msg)
|
||||
.release();
|
||||
auto java_dex_file = WrapScope(env, dex->ToJavaDexFile(env));
|
||||
if (java_dex_file) {
|
||||
auto p = JNI_NewObject(env, path_class_loader, path_class_loader_init,
|
||||
JNI_NewStringUTF(env, ""), class_loader);
|
||||
target_class = JNI_Cast<jclass>(JNI_CallObjectMethod(
|
||||
env, java_dex_file, load_class,
|
||||
env->NewStringUTF(generated_class_name.data()), p))
|
||||
.release();
|
||||
}
|
||||
}
|
||||
|
||||
if (target_class) {
|
||||
return {
|
||||
target_class,
|
||||
JNI_GetStaticFieldID(env, target_class, hooker_field->decl->name->c_str(),
|
||||
hooker_field->decl->type->descriptor->c_str()),
|
||||
JNI_GetStaticMethodID(env, target_class, hook_method->decl->name->c_str(),
|
||||
hook_method->decl->prototype->Signature().data()),
|
||||
JNI_GetStaticMethodID(env, target_class, backup_method->decl->name->c_str(),
|
||||
backup_method->decl->prototype->Signature().data()),
|
||||
};
|
||||
}
|
||||
return {nullptr, nullptr, nullptr, nullptr};
|
||||
}
|
||||
@ -496,7 +551,10 @@ using ::lsplant::IsHooked;
|
||||
{
|
||||
auto callback_name =
|
||||
JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_method, method_get_name));
|
||||
JUTFString method_name(callback_name);
|
||||
JUTFString callback_method_name(callback_name);
|
||||
auto target_name =
|
||||
JNI_Cast<jstring>(JNI_CallObjectMethod(env, target_method, method_get_name));
|
||||
JUTFString target_method_name(target_name);
|
||||
auto callback_class = JNI_Cast<jclass>(
|
||||
JNI_CallObjectMethod(env, callback_method, method_get_declaring_class));
|
||||
auto callback_class_loader =
|
||||
@ -508,10 +566,11 @@ using ::lsplant::IsHooked;
|
||||
LOGE("callback_method is not a method of hooker_object");
|
||||
return nullptr;
|
||||
}
|
||||
std::tie(built_class, hooker_field, hook_method, backup_method) =
|
||||
WrapScope(env, BuildDex(env, callback_class_loader,
|
||||
ArtMethod::GetMethodShorty(env, target_method), is_static,
|
||||
method_name.get(), class_name.get(), method_name.get()));
|
||||
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
|
||||
env,
|
||||
BuildDex(env, callback_class_loader, ArtMethod::GetMethodShorty(env, target_method),
|
||||
is_static, target->IsConstructor() ? "constructor" : target_method_name.get(),
|
||||
class_name.get(), callback_method_name.get()));
|
||||
if (!built_class || !hooker_field || !hook_method || !backup_method) {
|
||||
LOGE("Failed to generate hooker");
|
||||
return nullptr;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.lsposed.lsplant;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class Hooker {
|
||||
@ -18,16 +18,16 @@ public class Hooker {
|
||||
|
||||
public Method backup;
|
||||
|
||||
private Executable target;
|
||||
private Member target;
|
||||
private Method replacement;
|
||||
private Object owner = null;
|
||||
|
||||
private Hooker() {
|
||||
}
|
||||
|
||||
private native Method doHook(Executable original, Method callback);
|
||||
private native Method doHook(Member original, Method callback);
|
||||
|
||||
private native boolean doUnhook(Executable target);
|
||||
private native boolean doUnhook(Member target);
|
||||
|
||||
public Object callback(Object[] args) throws InvocationTargetException, IllegalAccessException {
|
||||
var methodCallback = new MethodCallback(backup, args);
|
||||
@ -38,7 +38,7 @@ public class Hooker {
|
||||
return doUnhook(target);
|
||||
}
|
||||
|
||||
public static Hooker hook(Executable target, Method replacement, Object owner) {
|
||||
public static Hooker hook(Member target, Method replacement, Object owner) {
|
||||
Hooker hooker = new Hooker();
|
||||
try {
|
||||
var callbackMethod = Hooker.class.getDeclaredMethod("callback", Object[].class);
|
||||
|
Loading…
x
Reference in New Issue
Block a user