diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index defec05..fd863da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,18 @@ jobs: fail-fast: false matrix: include: + - api-level: 21 + target: default + arch: x86_64 + - api-level: 21 + target: default + arch: x86 + - api-level: 22 + target: default + arch: x86_64 + - api-level: 22 + target: default + arch: x86 - api-level: 23 target: default arch: x86_64 diff --git a/README.md b/README.md index 46ab793..60cb19e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # LSPlant ![](https://img.shields.io/badge/license-LGPL--3.0-orange.svg) -![](https://img.shields.io/badge/Android-6.0%20--%2013-blue.svg) +![](https://img.shields.io/badge/Android-5.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.svg) @@ -12,7 +12,7 @@ This project is part of LSPosed framework under GNU Lesser General Public Licens ## Features -+ Support Android 6.0 - 13 (API level 23 - 33) ++ Support Android 5.0 - 13 (API level 21 - 33) + Support armeabi-v7a, arm64-v8a, x86, x86-64 + Support customized inline hook framework and ART symbol resolver diff --git a/build.gradle.kts b/build.gradle.kts index c0a5728..80127da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ val androidTargetSdkVersion by extra(32) -val androidMinSdkVersion by extra(23) +val androidMinSdkVersion by extra(21) val androidBuildToolsVersion by extra("32.0.0") val androidCompileSdkVersion by extra(32) val androidNdkVersion by extra("23.1.7779620") diff --git a/lsplant/src/main/jni/.clang-format b/lsplant/src/main/jni/.clang-format index e2c8b95..b11b28d 100644 --- a/lsplant/src/main/jni/.clang-format +++ b/lsplant/src/main/jni/.clang-format @@ -13,6 +13,7 @@ Cpp11BracedListStyle: true Standard: Latest # IndentAccessModifiers: false IndentCaseLabels: false +BreakStringLiterals: false IndentExternBlock: false AccessModifierOffset: -4 # EmptyLineBeforeAccessModifier: true diff --git a/lsplant/src/main/jni/art/instrumentation.hpp b/lsplant/src/main/jni/art/instrumentation.hpp index a582ab9..2b0cb82 100644 --- a/lsplant/src/main/jni/art/instrumentation.hpp +++ b/lsplant/src/main/jni/art/instrumentation.hpp @@ -35,10 +35,25 @@ class Instrumentation { backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code); } }); + CREATE_MEM_HOOK_STUB_ENTRY( + "_ZN3art15instrumentation15Instrumentation17UpdateMethodsCodeEPNS_6mirror9ArtMethodEPKvS6_b", + void, UpdateMethodsCodeWithProtableCode, + (Instrumentation * thiz, ArtMethod *art_method, const void *quick_code, + const void *portable_code, bool have_portable_code), + { + backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code, portable_code, + have_portable_code); + }); public: static bool Init(const HookHandler &handler) { int sdk_int = GetAndroidApiLevel(); + if (sdk_int < __ANDROID_API_M__) [[unlikely]] { + if (!HookSyms(handler, UpdateMethodsCodeWithProtableCode)) { + return false; + } + return true; + } if (!HookSyms(handler, UpdateMethodsCode)) { return false; } diff --git a/lsplant/src/main/jni/art/runtime/art_method.hpp b/lsplant/src/main/jni/art/runtime/art_method.hpp index 9494eff..1000236 100644 --- a/lsplant/src/main/jni/art/runtime/art_method.hpp +++ b/lsplant/src/main/jni/art/runtime/art_method.hpp @@ -127,27 +127,35 @@ public: } static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) { - return reinterpret_cast(JNI_GetLongField(env, method, art_method_field)); + if (art_method_field) [[likely]] { + return reinterpret_cast( + JNI_GetLongField(env, method, art_method_field)); + } else { + return reinterpret_cast(env->FromReflectedMethod(method)); + } } static bool Init(JNIEnv *env, const HookHandler handler) { auto sdk_int = GetAndroidApiLevel(); - jclass executable = nullptr; + ScopedLocalRef executable{env, nullptr}; if (sdk_int >= __ANDROID_API_O__) { - executable = JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/reflect/Executable")); + executable = JNI_FindClass(env, "java/lang/reflect/Executable"); + } else if (sdk_int >= __ANDROID_API_M__) { + executable = JNI_FindClass(env, "java/lang/reflect/AbstractMethod"); } else { - executable = - JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/reflect/AbstractMethod")); + executable = JNI_FindClass(env, "java/lang/reflect/ArtMethod"); } if (!executable) { - LOGE("Failed to found Executable/AbstractMethod"); + LOGE("Failed to found Executable/AbstractMethod/ArtMethod"); return false; } - if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J"); - !art_method_field) { - LOGE("Failed to find artMethod field"); - return false; + if (sdk_int >= __ANDROID_API_M__) [[likely]] { + if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J"); + !art_method_field) { + LOGE("Failed to find artMethod field"); + return false; + } } auto throwable = JNI_FindClass(env, "java/lang/Throwable"); @@ -172,32 +180,59 @@ public: art_method_size = reinterpret_cast(second) - reinterpret_cast(first); LOGD("ArtMethod size: %zu", art_method_size); - if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) { - LOGW("ArtMethod size exceeds maximum assume. There may be something wrong."); + if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) [[unlikely]] { + if (sdk_int >= __ANDROID_API_M__) { + LOGW("ArtMethod size exceeds maximum assume. There may be something wrong."); + } } entry_point_offset = art_method_size - kPointerSize; - LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset); - data_offset = entry_point_offset - kPointerSize; - LOGD("ArtMethod::data offset: %zu", data_offset); - if (auto access_flags_field = JNI_GetFieldID(env, executable, "accessFlags", "I"); - access_flags_field) { - uint32_t real_flags = JNI_GetIntField(env, first_ctor, access_flags_field); - for (size_t i = 0; i < art_method_size; i += sizeof(uint32_t)) { - if (*reinterpret_cast(reinterpret_cast(first) + i) == - real_flags) { - access_flags_offset = i; - LOGD("ArtMethod::access_flags offset: %zu", access_flags_offset); - break; + if (sdk_int >= __ANDROID_API_M__) [[likely]] { + if (auto access_flags_field = JNI_GetFieldID(env, executable, "accessFlags", "I"); + access_flags_field) { + uint32_t real_flags = JNI_GetIntField(env, first_ctor, access_flags_field); + for (size_t i = 0; i < art_method_size; i += sizeof(uint32_t)) { + if (*reinterpret_cast(reinterpret_cast(first) + i) == + real_flags) { + access_flags_offset = i; + break; + } } } + if (access_flags_offset == 0) { + LOGW("Failed to find accessFlags field. Fallback to 4."); + access_flags_offset = 4U; + } + } else { + auto art_field = JNI_FindClass(env, "java/lang/reflect/ArtField"); + auto field = JNI_FindClass(env, "java/lang/reflect/Field"); + auto art_field_field = + JNI_GetFieldID(env, field, "artField", "Ljava/lang/reflect/ArtField;"); + auto field_offset = JNI_GetFieldID(env, art_field, "offset", "I"); + auto get_offset_from_art_method = [&](const char *name, const char *sig) { + return JNI_GetIntField( + env, + JNI_GetObjectField( + env, + env->ToReflectedField(executable, + JNI_GetFieldID(env, executable, name, sig), false), + art_field_field), + field_offset); + }; + access_flags_offset = get_offset_from_art_method("accessFlags", "I"); + if (sdk_int == __ANDROID_API_L__) { + entry_point_offset = + get_offset_from_art_method("entryPointFromQuickCompiledCode", "J"); + interpreter_entry_point_offset = + get_offset_from_art_method("entryPointFromInterpreter", "J"); + data_offset = get_offset_from_art_method("entryPointFromJni", "J"); + } } - if (access_flags_offset == 0) { - LOGW("Failed to find accessFlags field. Fallback to 4."); - access_flags_offset = 4U; - } + LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset); + LOGD("ArtMethod::data offset: %zu", data_offset); + LOGD("ArtMethod::access_flags offset: %zu", access_flags_offset); if (sdk_int < __ANDROID_API_R__) { kAccPreCompiled = 0; @@ -207,14 +242,16 @@ public: if (sdk_int < __ANDROID_API_Q__) kAccFastInterpreterToInterpreterInvoke = 0; if (!RETRIEVE_FUNC_SYMBOL(GetMethodShorty, - "_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID")) { + "_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID") && + !RETRIEVE_FUNC_SYMBOL(GetMethodShorty, + "_ZN3art15GetMethodShortyEP7_JNIEnvP10_jmethodID")) { LOGE("Failed to find GetMethodShorty"); return false; } - if (!RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art9ArtMethod12PrettyMethodEPS0_b")) { - RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art12PrettyMethodEPNS_9ArtMethodEb"); - } + !RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art9ArtMethod12PrettyMethodEPS0_b") && + !RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art12PrettyMethodEPNS_9ArtMethodEb") && + !RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art12PrettyMethodEPNS_6mirror9ArtMethodEb"); if (sdk_int <= __ANDROID_API_O__) [[unlikely]] { auto abstract_method_error = JNI_FindClass(env, "java/lang/AbstractMethodError"); @@ -246,12 +283,14 @@ public: if (sdk_int <= __ANDROID_API_N__) { kAccCompileDontBother = 0; } - if (sdk_int == __ANDROID_API_M__) [[unlikely]] { + if (sdk_int <= __ANDROID_API_M__) [[unlikely]] { if (!RETRIEVE_FUNC_SYMBOL(art_interpreter_to_compiled_code_bridge, "artInterpreterToCompiledCodeBridge")) { return false; } - interpreter_entry_point_offset = entry_point_offset - 2 * kPointerSize; + if (sdk_int >= __ANDROID_API_L_MR1__) { + interpreter_entry_point_offset = entry_point_offset - 2 * kPointerSize; + } } return true; diff --git a/lsplant/src/main/jni/art/runtime/dex_file.hpp b/lsplant/src/main/jni/art/runtime/dex_file.hpp index 0659931..e2ddd23 100644 --- a/lsplant/src/main/jni/art/runtime/dex_file.hpp +++ b/lsplant/src/main/jni/art/runtime/dex_file.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "common.hpp" @@ -18,26 +19,74 @@ class DexFile { return OpenMemorySym(dex_file, size, location, location_checksum, mem_map, oat_dex_file, error_msg); } + if (error_msg) *error_msg = "null sym"; return nullptr; } -public: - static std::unique_ptr OpenMemory(const void* dex_file, size_t size, - std::string location, std::string* error_msg) { - return OpenMemory(reinterpret_cast(dex_file), size, location, - reinterpret_cast(dex_file)->checksum_, nullptr, nullptr, - error_msg); + CREATE_FUNC_SYMBOL_ENTRY(const DexFile*, OpenMemoryRaw, 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 (OpenMemoryRawSym) [[likely]] { + return OpenMemoryRawSym(dex_file, size, location, location_checksum, mem_map, + oat_dex_file, error_msg); + } + if (error_msg) *error_msg = "null sym"; + return nullptr; } - jobject ToJavaDexFile(JNIEnv* env) { + CREATE_FUNC_SYMBOL_ENTRY(const DexFile*, OpenMemoryWithoutOdex, const uint8_t* dex_file, + size_t size, const std::string& location, uint32_t location_checksum, + void* mem_map, std::string* error_msg) { + if (OpenMemoryWithoutOdexSym) [[likely]] { + return OpenMemoryWithoutOdexSym(dex_file, size, location, location_checksum, mem_map, + error_msg); + } + if (error_msg) *error_msg = "null sym"; + return nullptr; + } + + CREATE_MEM_FUNC_SYMBOL_ENTRY(void, push_back, std::vector* thiz, + const DexFile** dex_file) { + push_backSym(thiz, dex_file); + } + +public: + static const DexFile* OpenMemory(const void* dex_file, size_t size, std::string location, + std::string* error_msg) { + if (OpenMemorySym) [[likely]] { + return OpenMemory(reinterpret_cast(dex_file), size, location, + reinterpret_cast(dex_file)->checksum_, nullptr, + nullptr, error_msg) + .release(); + } else if (OpenMemoryRawSym) { + return OpenMemoryRaw(reinterpret_cast(dex_file), size, location, + reinterpret_cast(dex_file)->checksum_, nullptr, + nullptr, error_msg); + } else if (OpenMemoryWithoutOdexSym) { + return OpenMemoryWithoutOdex(reinterpret_cast(dex_file), size, location, + reinterpret_cast(dex_file)->checksum_, + nullptr, error_msg); + } else { + if (error_msg) *error_msg = "no sym"; + return nullptr; + } + } + + jobject ToJavaDexFile(JNIEnv* env) const { 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(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); + if (dex_file_start_index != size_t(-1)) [[likely]] { + cookie[oat_file_index] = 0; + cookie[dex_file_start_index] = reinterpret_cast(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); + } + } else { + JNI_SetLongField( + env, java_dex_file, cookie_field, + static_cast(reinterpret_cast(new std::vector{this}))); } JNI_SetObjectField(env, java_dex_file, file_name_field, JNI_NewStringUTF(env, "")); return java_dex_file; @@ -53,15 +102,32 @@ public: LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_" "traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_", "_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_" - "traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_"))) - [[unlikely]] { + "traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_")) && + !RETRIEVE_FUNC_SYMBOL( + OpenMemoryRaw, + LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_" + "traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_", + "_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_" + "traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_")) && + !RETRIEVE_FUNC_SYMBOL( + OpenMemoryWithoutOdex, + LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_" + "traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_", + "_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_" + "traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_"))) [[unlikely]] { + LOGE("Failed to find OpenMemory"); 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 (sdk_int >= __ANDROID_API_M__) { + cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "Ljava/lang/Object;"); + } else { + cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "J"); + dex_file_start_index = -1; + } if (!cookie_field) { return false; } diff --git a/lsplant/src/main/jni/lsplant.cc b/lsplant/src/main/jni/lsplant.cc index 796b383..32a98fd 100644 --- a/lsplant/src/main/jni/lsplant.cc +++ b/lsplant/src/main/jni/lsplant.cc @@ -228,23 +228,23 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) { return false; } if (!Class::Init(env, handler)) { - LOGE("failed to init mirror class"); + LOGE("Failed to init mirror class"); return false; } if (!Instrumentation::Init(handler)) { - LOGE("failed to init instrumentation"); + LOGE("Failed to init instrumentation"); return false; } if (!ScopedSuspendAll::Init(handler)) { - LOGE("failed to init scoped suspend all"); + LOGE("Failed to init scoped suspend all"); return false; } if (!ScopedGCCriticalSection::Init(handler)) { - LOGE("failed to init scoped gc critical section"); + LOGE("Failed to init scoped gc critical section"); return false; } if (!JitCodeCache::Init(handler)) { - LOGE("failed to init jit code cache"); + LOGE("Failed to init jit code cache"); return false; } if (!DexFile::Init(env, handler)) { @@ -365,12 +365,14 @@ std::tuple BuildDex(JNIEnv *env, jobject 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) { + const auto *dex = DexFile::OpenMemory( + target, image.size(), generated_source_name.empty() ? "lsplant" : generated_source_name, + &err_msg); + if (!dex) { + LOGE("Failed to open memory dex: %s", err_msg.data()); + } + auto java_dex_file = WrapScope(env, dex ? dex->ToJavaDexFile(env) : jobject{nullptr}); + if (dex && java_dex_file) { auto p = JNI_NewObject(env, path_class_loader, path_class_loader_init, JNI_NewStringUTF(env, ""), class_loader); target_class = JNI_Cast(JNI_CallObjectMethod(