Support down to Android 6 (#3)

This commit is contained in:
LoveSy 2022-02-22 13:54:58 +08:00 committed by GitHub
parent 83cdee50eb
commit 936f18c242
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 375 additions and 115 deletions

View File

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

View File

@ -1,7 +1,7 @@
# LSPlant
![](https://img.shields.io/badge/license-LGPL--3.0-orange.svg)
![](https://img.shields.io/badge/Android-8.0%20--%2013-blue.svg)
![](https://img.shields.io/badge/Android-6.0%20--%2013-blue.svg)
![](https://img.shields.io/badge/arch-armeabi--v7a%20%7C%20arm64--v8a%20%7C%20x86%20%7C%20x86--64-brightgreen.svg)
![](https://github.com/LSPosed/LSPlant/actions/workflows/build.yml/badge.svg?branch=master&event=push)
![](https://img.shields.io/maven-central/v/org.lsposed.lsplant/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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

@ -1 +1 @@
Subproject commit eb05e8b97fff754e9b3e825f94ea72bc4deed711
Subproject commit 8e1672aa6da3bbc151e2fda37c04e528dffae5aa

View File

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

View File

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

View File

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

View File

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