Refactor `core` to use LSPlant as the ART hook framework (#1756)
This commit is contained in:
parent
2a258e3323
commit
0bf0948e9a
|
|
@ -46,6 +46,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches/build-cache-*
|
~/.gradle/caches/build-cache-*
|
||||||
|
~/.gradle/buildOutputCleanup/cache.properties
|
||||||
key: gradle-builds-core-${{ github.sha }}
|
key: gradle-builds-core-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
gradle-builds
|
gradle-builds
|
||||||
|
|
@ -66,8 +67,6 @@ jobs:
|
||||||
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
|
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
|
||||||
ccache -zp
|
ccache -zp
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
env:
|
|
||||||
NDK_CCACHE: ccache
|
|
||||||
run: |
|
run: |
|
||||||
[ $(du -s ~/.gradle/wrapper | awk '{ print $1 }') -gt 250000 ] && rm -rf ~/.gradle/wrapper/* || true
|
[ $(du -s ~/.gradle/wrapper | awk '{ print $1 }') -gt 250000 ] && rm -rf ~/.gradle/wrapper/* || true
|
||||||
find ~/.gradle/caches -exec touch -d "2 days ago" {} + || true
|
find ~/.gradle/caches -exec touch -d "2 days ago" {} + || true
|
||||||
|
|
@ -117,6 +116,13 @@ jobs:
|
||||||
path: |
|
path: |
|
||||||
core/build/outputs/mapping
|
core/build/outputs/mapping
|
||||||
app/build/outputs/mapping
|
app/build/outputs/mapping
|
||||||
|
- name: Upload symbols
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: symbols
|
||||||
|
path: |
|
||||||
|
core/build/symbols
|
||||||
|
daemon/build/symbols
|
||||||
- name: Post to channel
|
- name: Post to channel
|
||||||
if: ${{ github.event_name != 'pull_request' && success() && github.ref == 'refs/heads/master' }}
|
if: ${{ github.event_name != 'pull_request' && success() && github.ref == 'refs/heads/master' }}
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
[submodule "service"]
|
[submodule "service"]
|
||||||
path = service
|
path = service
|
||||||
url = https://github.com/libxposed/XposedService.git
|
url = https://github.com/libxposed/XposedService.git
|
||||||
[submodule "core/src/main/cpp/external/DexBuilder"]
|
[submodule "external/cxx"]
|
||||||
path = core/src/main/cpp/external/DexBuilder
|
path = external/cxx
|
||||||
url = https://github.com/LSPosed/DexBuilder.git
|
url = https://github.com/LSPosed/libcxx.git
|
||||||
|
[submodule "external/lsplant"]
|
||||||
|
path = external/lsplant
|
||||||
|
url = https://github.com/LSPosed/LSPlant.git
|
||||||
|
[submodule "external/dobby"]
|
||||||
|
path = external/dobby
|
||||||
|
url = https://github.com/LSPosed/Dobby.git
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
/build
|
/build
|
||||||
/release
|
/release
|
||||||
/src/main/cpp/main/api/config.cpp
|
/src/main/cpp/api/config.cpp
|
||||||
|
/.cxx
|
||||||
|
|
|
||||||
|
|
@ -73,11 +73,32 @@ android {
|
||||||
multiDexEnabled = false
|
multiDexEnabled = false
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
cmake {
|
||||||
arguments += "INJECTED_AID=$injectedPackageUid"
|
arguments += "-DEXTERNAL_ROOT=${File(rootDir.absolutePath, "external")}"
|
||||||
arguments += "VERSION_CODE=$verCode"
|
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||||
arguments += "VERSION_NAME=$verName"
|
val flags = arrayOf(
|
||||||
arguments += "-j${Runtime.getRuntime().availableProcessors()}"
|
"-Wall",
|
||||||
|
"-Qunused-arguments",
|
||||||
|
"-Wno-gnu-string-literal-operator-template",
|
||||||
|
"-fno-rtti",
|
||||||
|
"-fvisibility=hidden",
|
||||||
|
"-fvisibility-inlines-hidden",
|
||||||
|
"-fno-exceptions",
|
||||||
|
"-fno-stack-protector",
|
||||||
|
"-fomit-frame-pointer",
|
||||||
|
"-Wno-builtin-macro-redefined",
|
||||||
|
"-Wno-unused-value",
|
||||||
|
"-D__FILE__=__FILE_NAME__",
|
||||||
|
"-DINJECTED_AID=$injectedPackageUid",
|
||||||
|
)
|
||||||
|
cppFlags("-std=c++20", *flags)
|
||||||
|
cFlags("-std=c18", *flags)
|
||||||
|
arguments(
|
||||||
|
"-DANDROID_STL=none",
|
||||||
|
"-DVERSION_CODE=$verCode",
|
||||||
|
"-DVERSION_NAME=$verName",
|
||||||
|
)
|
||||||
|
targets("lspd")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,14 +118,58 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments.addAll(
|
||||||
|
arrayOf(
|
||||||
|
"-DCMAKE_CXX_FLAGS_DEBUG=-Og",
|
||||||
|
"-DCMAKE_C_FLAGS_DEBUG=-Og",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
proguardFiles("proguard-rules.pro")
|
proguardFiles("proguard-rules.pro")
|
||||||
}
|
|
||||||
}
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
cmake {
|
||||||
path("src/main/cpp/Android.mk")
|
val flags = arrayOf(
|
||||||
|
"-Wl,--exclude-libs,ALL",
|
||||||
|
"-ffunction-sections",
|
||||||
|
"-fdata-sections",
|
||||||
|
"-Wl,--gc-sections",
|
||||||
|
"-fno-unwind-tables",
|
||||||
|
"-fno-asynchronous-unwind-tables",
|
||||||
|
"-flto=thin",
|
||||||
|
"-Wl,--thinlto-cache-policy,cache_size_bytes=300m",
|
||||||
|
"-Wl,--thinlto-cache-dir=${buildDir.absolutePath}/.lto-cache",
|
||||||
|
)
|
||||||
|
cppFlags.addAll(flags)
|
||||||
|
cFlags.addAll(flags)
|
||||||
|
val configFlags = arrayOf(
|
||||||
|
"-Oz",
|
||||||
|
"-DNDEBUG"
|
||||||
|
).joinToString(" ")
|
||||||
|
arguments.addAll(
|
||||||
|
arrayOf(
|
||||||
|
"-DCMAKE_CXX_FLAGS_RELEASE=$configFlags",
|
||||||
|
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=$configFlags",
|
||||||
|
"-DCMAKE_C_FLAGS_RELEASE=$configFlags",
|
||||||
|
"-DCMAKE_C_FLAGS_RELWITHDEBINFO=$configFlags",
|
||||||
|
"-DDEBUG_SYMBOLS_PATH=${buildDir.absolutePath}/symbols",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path("src/main/jni/CMakeLists.txt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,22 +178,12 @@ android {
|
||||||
sourceCompatibility(androidSourceCompatibility)
|
sourceCompatibility(androidSourceCompatibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
all {
|
|
||||||
externalNativeBuild {
|
|
||||||
ndkBuild {
|
|
||||||
arguments += "NDK_OUT=${File(buildDir, ".cxx/$name").absolutePath}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
all {
|
all {
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
cmake {
|
||||||
arguments += "MODULE_NAME=${name.toLowerCase()}_$moduleBaseId"
|
arguments += "-DMODULE_NAME=${name.toLowerCase()}_$moduleBaseId"
|
||||||
arguments += "API=${name.toLowerCase()}"
|
arguments += "-DAPI=${name.toLowerCase()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildConfigField("String", "API", """"$name"""")
|
buildConfigField("String", "API", """"$name"""")
|
||||||
|
|
@ -137,8 +192,8 @@ android {
|
||||||
create("Riru") {
|
create("Riru") {
|
||||||
dimension = "api"
|
dimension = "api"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
cmake {
|
||||||
arguments += "API_VERSION=$moduleMaxRiruApiVersion"
|
arguments += "-DAPI_VERSION=$moduleMaxRiruApiVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -146,8 +201,8 @@ android {
|
||||||
create("Zygisk") {
|
create("Zygisk") {
|
||||||
dimension = "api"
|
dimension = "api"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
cmake {
|
||||||
arguments += "API_VERSION=1"
|
arguments += "-DAPI_VERSION=1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -157,10 +212,6 @@ android {
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// keep this dep since it affects ccache
|
|
||||||
implementation("dev.rikka.ndk:riru:26.0.0")
|
|
||||||
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
|
|
||||||
implementation("io.github.vvb2060.ndk:dobby:1.2")
|
|
||||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||||
implementation("de.upb.cs.swt:axml:2.1.2")
|
implementation("de.upb.cs.swt:axml:2.1.2")
|
||||||
compileOnly("androidx.annotation:annotation:1.3.0")
|
compileOnly("androidx.annotation:annotation:1.3.0")
|
||||||
|
|
@ -247,8 +298,12 @@ fun afterEval() = android.applicationVariants.forEach { variant ->
|
||||||
rename(".*\\.apk", "daemon.apk")
|
rename(".*\\.apk", "daemon.apk")
|
||||||
}
|
}
|
||||||
into("lib") {
|
into("lib") {
|
||||||
from("${buildDir}/intermediates/stripped_native_libs/$variantCapped/out/lib")
|
from("${buildDir}/intermediates/cmake/$variantCapped/obj") {
|
||||||
from("${project(":daemon").buildDir}/intermediates/ndkBuild/$buildTypeLowered/obj/local")
|
include("**/liblspd.so")
|
||||||
|
}
|
||||||
|
from("${project(":daemon").buildDir}/intermediates/cmake/$buildTypeLowered/obj") {
|
||||||
|
include("**/libdaemon.so")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val dexOutPath = if (buildTypeLowered == "release")
|
val dexOutPath = if (buildTypeLowered == "release")
|
||||||
"$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else
|
"$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@
|
||||||
-keepclasseswithmembers,includedescriptorclasses class * {
|
-keepclasseswithmembers,includedescriptorclasses class * {
|
||||||
native <methods>;
|
native <methods>;
|
||||||
}
|
}
|
||||||
-keepclasseswithmembers class org.lsposed.lspd.nativebridge.ClassLinker {
|
|
||||||
public static void onPostFixupStaticTrampolines(java.lang.Class);
|
|
||||||
}
|
|
||||||
-keepclasseswithmembers class org.lsposed.lspd.service.BridgeService {
|
-keepclasseswithmembers class org.lsposed.lspd.service.BridgeService {
|
||||||
public static boolean *(android.os.IBinder, int, long, long, int);
|
public static boolean *(android.os.IBinder, int, long, long, int);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
---
|
|
||||||
Checks: >
|
|
||||||
-*,
|
|
||||||
bugprone-*,
|
|
||||||
google-*,
|
|
||||||
misc-*,
|
|
||||||
modernize-*,
|
|
||||||
performance-*,
|
|
||||||
portability-*,
|
|
||||||
readability-*,
|
|
||||||
clang-analyzer-*,
|
|
||||||
llvm-include-order,
|
|
||||||
-modernize-use-trailing-return-type,
|
|
||||||
-readability-implicit-bool-conversion,
|
|
||||||
|
|
||||||
CheckOptions:
|
|
||||||
- key: readability-identifier-naming.ClassCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.ClassMemberCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.EnumCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.EnumConstantCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.EnumConstantPrefix
|
|
||||||
value: k
|
|
||||||
- key: readability-identifier-naming.FunctionCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.GlobalConstantCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.GlobalConstantPrefix
|
|
||||||
value: k
|
|
||||||
- key: readability-identifier-naming.StaticConstantCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.StaticConstantPrefix
|
|
||||||
value: k
|
|
||||||
- key: readability-identifier-naming.StaticVariableCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.StaticVariablePrefix
|
|
||||||
value: k
|
|
||||||
- key: readability-identifier-naming.MacroDefinitionCase
|
|
||||||
value: UPPER_CASE
|
|
||||||
- key: readability-identifier-naming.MemberCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.MemberSuffix
|
|
||||||
value: _
|
|
||||||
- key: readability-identifier-naming.NamespaceCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.ParameterCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.TypeAliasCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.TypedefCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.VariableCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.IgnoreMainLikeFunctions
|
|
||||||
value: 1
|
|
||||||
- key: readability-braces-around-statements.ShortStatementLines
|
|
||||||
value: 1
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
include src/main/cpp/external/DexBuilder/Android.mk
|
|
||||||
include src/main/cpp/external/yahfa/Android.mk
|
|
||||||
include src/main/cpp/main/Android.mk
|
|
||||||
|
|
||||||
$(call import-module,prefab/cxx)
|
|
||||||
$(call import-module,prefab/riru)
|
|
||||||
$(call import-module,prefab/dobby)
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
APP_CFLAGS := -Wall -Wextra
|
|
||||||
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
|
|
||||||
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__
|
|
||||||
APP_CPPFLAGS := -std=c++20
|
|
||||||
APP_CONLYFLAGS := -std=c18
|
|
||||||
APP_LDFLAGS := -Wl,--exclude-libs,ALL
|
|
||||||
APP_STL := none
|
|
||||||
|
|
||||||
ifneq ($(NDK_DEBUG),1)
|
|
||||||
APP_CFLAGS += -Oz -flto=thin
|
|
||||||
APP_CFLAGS += -Wno-unused -Wno-unused-parameter -Werror
|
|
||||||
APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
|
|
||||||
APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables
|
|
||||||
APP_LDFLAGS += -flto=thin -Wl,--thinlto-cache-policy,cache_size_bytes=300m -Wl,--thinlto-cache-dir=build/.lto-cache -Wl,--gc-sections -Wl,--strip-all
|
|
||||||
endif
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 6273ec71c0c4ded162f1569c94eac098c5f87e02
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
LOCAL_PATH := $(call my-dir)
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE := yahfa
|
|
||||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
|
||||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
|
|
||||||
LOCAL_SRC_FILES := src/HookMain.cpp src/trampoline.cpp
|
|
||||||
LOCAL_EXPORT_LDLIBS := -llog
|
|
||||||
LOCAL_STATIC_LIBRARIES := libcxx
|
|
||||||
include $(BUILD_STATIC_LIBRARY)
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#ifndef HOOK_MAIN_H
|
|
||||||
#define HOOK_MAIN_H
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
namespace yahfa {
|
|
||||||
constexpr uint32_t kAccPublic = 0x0001; // class, field, method, ic
|
|
||||||
constexpr uint32_t kAccPrivate = 0x0002; // field, method, ic
|
|
||||||
constexpr uint32_t kAccProtected = 0x0004; // field, method, ic
|
|
||||||
constexpr uint32_t kAccStatic = 0x0008; // field, method, ic
|
|
||||||
constexpr uint32_t kAccNative = 0x0100; // method
|
|
||||||
|
|
||||||
void init(JNIEnv *env, jclass clazz, jint sdkVersion);
|
|
||||||
|
|
||||||
jobject findMethodNative(JNIEnv *env, jclass clazz,
|
|
||||||
jclass targetClass, jstring methodName,
|
|
||||||
jstring methodSig);
|
|
||||||
|
|
||||||
jboolean backupAndHookNative(JNIEnv *env, jclass clazz,
|
|
||||||
jobject target, jobject hook,
|
|
||||||
jobject backup);
|
|
||||||
|
|
||||||
void *getArtMethod(JNIEnv *env, jobject jmethod);
|
|
||||||
|
|
||||||
uint32_t getAccessFlags(void* art_method);
|
|
||||||
|
|
||||||
void setAccessFlags(void* art_method, uint32_t access_flags);
|
|
||||||
|
|
||||||
void* getEntryPoint(void* art_method);
|
|
||||||
|
|
||||||
void setEntryPoint(void* art_method, void* entry_point);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // HOOK_MAIN_H
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
//
|
|
||||||
// Created by liuruikai756 on 05/07/2017.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef YAHFA_TAMPOLINE_H
|
|
||||||
#define YAHFA_TAMPOLINE_H
|
|
||||||
|
|
||||||
namespace yahfa {
|
|
||||||
void setupTrampoline();
|
|
||||||
|
|
||||||
void *genTrampoline(void *hookMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //YAHFA_TAMPOLINE_H
|
|
||||||
|
|
@ -1,213 +0,0 @@
|
||||||
#include "jni.h"
|
|
||||||
#include <cstring>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
#include "trampoline.h"
|
|
||||||
#include "HookMain.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace yahfa {
|
|
||||||
size_t OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
|
|
||||||
int SDKVersion;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr size_t OFFSET_access_flags_in_ArtMethod = 4;
|
|
||||||
constexpr uint32_t kAccCompileDontBother = 0x02000000;
|
|
||||||
constexpr uint32_t kAccFastInterpreterToInterpreterInvoke = 0x40000000;
|
|
||||||
|
|
||||||
size_t ArtMethodSize;
|
|
||||||
uint32_t kAccPreCompiled = 0x00200000;
|
|
||||||
|
|
||||||
jfieldID fieldArtMethod = nullptr;
|
|
||||||
|
|
||||||
constexpr inline uint32_t read32(void *addr) {
|
|
||||||
return *((uint32_t *) addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr inline void write32(void *addr, uint32_t value) {
|
|
||||||
*((uint32_t *) addr) = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr inline void *readAddr(void *addr) {
|
|
||||||
return *((void **) addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr inline void writeAddr(void *addr, void *value) {
|
|
||||||
*((void **) addr) = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setNonCompilable(void *method) {
|
|
||||||
uint32_t access_flags = getAccessFlags(method);
|
|
||||||
LOGI("setNonCompilable: access flags is 0x%x", access_flags);
|
|
||||||
access_flags |= kAccCompileDontBother;
|
|
||||||
if (SDKVersion >= __ANDROID_API_R__)
|
|
||||||
access_flags &= ~kAccPreCompiled;
|
|
||||||
setAccessFlags(method, access_flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPrivate(void *method) {
|
|
||||||
uint32_t access_flags = getAccessFlags(method);
|
|
||||||
if (!(access_flags & kAccStatic)) {
|
|
||||||
LOGI("setPrivate: access flags is 0x%x", access_flags);
|
|
||||||
access_flags |= kAccPrivate;
|
|
||||||
access_flags &= ~kAccProtected;
|
|
||||||
access_flags &= ~kAccPublic;
|
|
||||||
setAccessFlags(method, access_flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int doBackupAndHook(void *targetMethod, void *hookMethod, void *backupMethod) {
|
|
||||||
LOGI("target method is at %p, hook method is at %p, backup method is at %p",
|
|
||||||
targetMethod, hookMethod, backupMethod);
|
|
||||||
|
|
||||||
|
|
||||||
// set kAccCompileDontBother for a method we do not want the compiler to compile
|
|
||||||
// so that we don't need to worry about hotness_count_
|
|
||||||
setNonCompilable(targetMethod);
|
|
||||||
setNonCompilable(hookMethod);
|
|
||||||
|
|
||||||
if (backupMethod) {// do method backup
|
|
||||||
// have to copy the whole target ArtMethod here
|
|
||||||
// if the target method calls other methods which are to be resolved
|
|
||||||
// then ToDexPC would be invoked for the caller(origin method)
|
|
||||||
// in which case ToDexPC would use the entrypoint as a base for mapping pc to dex offset
|
|
||||||
// so any changes to the target method's entrypoint would result in a wrong dex offset
|
|
||||||
// and artQuickResolutionTrampoline would fail for methods called by the origin method
|
|
||||||
memcpy(backupMethod, targetMethod, ArtMethodSize);
|
|
||||||
setPrivate(backupMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace entry point
|
|
||||||
void *newEntrypoint = genTrampoline(hookMethod);
|
|
||||||
LOGI("origin ep is %p, new ep is %p",
|
|
||||||
getEntryPoint(targetMethod),
|
|
||||||
newEntrypoint
|
|
||||||
);
|
|
||||||
if (newEntrypoint) {
|
|
||||||
setEntryPoint(targetMethod, newEntrypoint);
|
|
||||||
} else {
|
|
||||||
LOGE("failed to allocate space for trampoline of target method");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SDKVersion >= __ANDROID_API_Q__) {
|
|
||||||
uint32_t access_flags = getAccessFlags(targetMethod);
|
|
||||||
// On API 29 whether to use the fast path or not is cached in the ART method structure
|
|
||||||
access_flags &= ~kAccFastInterpreterToInterpreterInvoke;
|
|
||||||
setAccessFlags(targetMethod, access_flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGI("hook and backup done");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void init(JNIEnv *env, [[maybe_unused]] jclass clazz, jint sdkVersion) {
|
|
||||||
SDKVersion = sdkVersion;
|
|
||||||
jclass classExecutable = env->FindClass("java/lang/reflect/Executable");
|
|
||||||
fieldArtMethod = env->GetFieldID(classExecutable, "artMethod", "J");
|
|
||||||
env->DeleteLocalRef(classExecutable);
|
|
||||||
LOGI("init to SDK %d", sdkVersion);
|
|
||||||
switch (sdkVersion) {
|
|
||||||
default:
|
|
||||||
LOGE("not compatible with SDK %d", sdkVersion);
|
|
||||||
case __ANDROID_API_FUTURE__:
|
|
||||||
// fallthrough
|
|
||||||
case __ANDROID_API_T__:
|
|
||||||
case 32: // stupid Google forgot to add 12L
|
|
||||||
case __ANDROID_API_S__:
|
|
||||||
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
|
|
||||||
roundUpToPtrSize(4 * 3 + 2 * 2) + pointer_size;
|
|
||||||
ArtMethodSize = roundUpToPtrSize(4 * 3 + 2 * 2) + pointer_size * 2;
|
|
||||||
kAccPreCompiled = 0x00800000;
|
|
||||||
break;
|
|
||||||
case __ANDROID_API_R__:
|
|
||||||
case __ANDROID_API_Q__:
|
|
||||||
case __ANDROID_API_P__:
|
|
||||||
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
|
|
||||||
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size;
|
|
||||||
ArtMethodSize = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 2;
|
|
||||||
break;
|
|
||||||
case __ANDROID_API_O_MR1__:
|
|
||||||
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
|
|
||||||
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 2;
|
|
||||||
ArtMethodSize = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 3;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
setupTrampoline();
|
|
||||||
}
|
|
||||||
|
|
||||||
void *getArtMethod(JNIEnv *env, jobject jmethod) {
|
|
||||||
if (jmethod == nullptr || fieldArtMethod == nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
} else {
|
|
||||||
return (void *) env->GetLongField(jmethod, fieldArtMethod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getAccessFlags(void *art_method) {
|
|
||||||
return read32((char *) art_method + OFFSET_access_flags_in_ArtMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAccessFlags(void *art_method, uint32_t access_flags) {
|
|
||||||
write32((char *) art_method + OFFSET_access_flags_in_ArtMethod, access_flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *getEntryPoint(void *art_method) {
|
|
||||||
return readAddr(
|
|
||||||
(char *) art_method + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setEntryPoint(void *art_method, void *entry_point) {
|
|
||||||
writeAddr((char *) art_method + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod,
|
|
||||||
entry_point);
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject findMethodNative(JNIEnv *env, [[maybe_unused]] jclass clazz,
|
|
||||||
jclass targetClass,
|
|
||||||
jstring methodName,
|
|
||||||
jstring methodSig) {
|
|
||||||
const char *c_methodName = env->GetStringUTFChars(methodName, nullptr);
|
|
||||||
const char *c_methodSig = env->GetStringUTFChars(methodSig, nullptr);
|
|
||||||
jobject ret = nullptr;
|
|
||||||
|
|
||||||
|
|
||||||
//Try both GetMethodID and GetStaticMethodID -- Whatever works :)
|
|
||||||
jmethodID method = env->GetMethodID(targetClass, c_methodName, c_methodSig);
|
|
||||||
if (!env->ExceptionCheck()) {
|
|
||||||
ret = env->ToReflectedMethod(targetClass, method, JNI_FALSE);
|
|
||||||
} else {
|
|
||||||
env->ExceptionClear();
|
|
||||||
method = env->GetStaticMethodID(targetClass, c_methodName, c_methodSig);
|
|
||||||
if (!env->ExceptionCheck()) {
|
|
||||||
ret = env->ToReflectedMethod(targetClass, method, JNI_TRUE);
|
|
||||||
} else {
|
|
||||||
env->ExceptionClear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
env->ReleaseStringUTFChars(methodName, c_methodName);
|
|
||||||
env->ReleaseStringUTFChars(methodSig, c_methodSig);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
jboolean backupAndHookNative(JNIEnv *env, [[maybe_unused]] jclass clazz,
|
|
||||||
jobject target, jobject hook,
|
|
||||||
jobject backup) {
|
|
||||||
|
|
||||||
if (!doBackupAndHook(getArtMethod(env, target),
|
|
||||||
getArtMethod(env, hook),
|
|
||||||
getArtMethod(env, backup)
|
|
||||||
)) {
|
|
||||||
env->NewGlobalRef(hook); // keep a global ref so that the hook method would not be GCed
|
|
||||||
if (backup) env->NewGlobalRef(backup);
|
|
||||||
return JNI_TRUE;
|
|
||||||
}
|
|
||||||
return JNI_FALSE;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
//
|
|
||||||
// Created by liuruikai756 on 05/07/2017.
|
|
||||||
//
|
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
#ifndef YAHFA_COMMON_H
|
|
||||||
#define YAHFA_COMMON_H
|
|
||||||
|
|
||||||
//#define DEBUG
|
|
||||||
//#define LOG_DISABLED
|
|
||||||
|
|
||||||
#ifdef LOG_DISABLED
|
|
||||||
#define LOGI(...)
|
|
||||||
#define LOGW(...)
|
|
||||||
#define LOGE(...)
|
|
||||||
#else
|
|
||||||
#define LOG_TAG "LSPosed"
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
|
|
||||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
|
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define LOGI(...)
|
|
||||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
|
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
|
||||||
#endif // DEBUG
|
|
||||||
#endif // LOG_DISABLED
|
|
||||||
|
|
||||||
#define pointer_size sizeof(void*)
|
|
||||||
#define roundUpToPtrSize(v) (v + pointer_size - 1 - ((v + pointer_size - 1) & (pointer_size - 1)))
|
|
||||||
|
|
||||||
#endif //YAHFA_COMMON_H
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
//
|
|
||||||
// Created by liuruikai756 on 05/07/2017.
|
|
||||||
//
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cerrno>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <atomic>
|
|
||||||
#include <bit>
|
|
||||||
#include <climits>
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
#include "trampoline.h"
|
|
||||||
|
|
||||||
static_assert(std::endian::native == std::endian::little, "Unsupported architecture");
|
|
||||||
|
|
||||||
namespace yahfa {
|
|
||||||
extern size_t OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
|
|
||||||
|
|
||||||
union Trampoline {
|
|
||||||
uintptr_t addr;
|
|
||||||
unsigned count: 12;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture");
|
|
||||||
static_assert(std::atomic_uintptr_t::is_always_lock_free, "Unsupported architecture");
|
|
||||||
|
|
||||||
// trampoline:
|
|
||||||
// 1. set eax/r0/x0 to the hook ArtMethod addr
|
|
||||||
// 2. jump into its entry point
|
|
||||||
#if defined(__i386__)
|
|
||||||
// b8 78 56 34 12 ; mov eax, 0x12345678 (addr of the hook method)
|
|
||||||
// ff 70 20 ; push DWORD PTR [eax + 0x20]
|
|
||||||
// c3 ; ret
|
|
||||||
unsigned char trampoline[] = "\xb8\x78\x56\x34\x12\xff\x70\x20\xc3";
|
|
||||||
#elif defined(__x86_64__)
|
|
||||||
// 48 bf 78 56 34 12 78 56 34 12 ; movabs rdi, 0x1234567812345678
|
|
||||||
// ff 77 20 ; push QWORD PTR [rdi + 0x20]
|
|
||||||
// c3 ; ret
|
|
||||||
unsigned char trampoline[] = "\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x20\xc3";
|
|
||||||
|
|
||||||
#elif defined(__arm__)
|
|
||||||
// 00 00 9F E5 ; ldr r0, [pc, #0]
|
|
||||||
// 20 F0 90 E5 ; ldr pc, [r0, 0x20]
|
|
||||||
// 78 56 34 12 ; 0x12345678 (addr of the hook method)
|
|
||||||
unsigned char trampoline[] = "\x00\x00\x9f\xe5\x20\xf0\x90\xe5\x78\x56\x34\x12";
|
|
||||||
|
|
||||||
#elif defined(__aarch64__)
|
|
||||||
// 60 00 00 58 ; ldr x0, 12
|
|
||||||
// 10 00 40 F8 ; ldr x16, [x0, #0x00]
|
|
||||||
// 00 02 1f d6 ; br x16
|
|
||||||
// 78 56 34 12
|
|
||||||
// 78 56 34 12 ; 0x1234567812345678 (addr of the hook method)
|
|
||||||
unsigned char trampoline[] = "\x60\x00\x00\x58\x10\x00\x40\xf8\x00\x02\x1f\xd6\x78\x56\x34\x12\x78\x56\x34\x12";
|
|
||||||
#endif
|
|
||||||
static std::atomic_uintptr_t trampoline_pool{0};
|
|
||||||
static std::atomic_flag trampoline_lock{false};
|
|
||||||
static constexpr size_t trampolineSize = roundUpToPtrSize(sizeof(trampoline));
|
|
||||||
static constexpr size_t pageSize = 4096;
|
|
||||||
static constexpr size_t trampolineNumPerPage = pageSize / trampolineSize;
|
|
||||||
|
|
||||||
static inline void FlushCache(void *addr, size_t size) {
|
|
||||||
__builtin___clear_cache((char *) addr, (char *) ((uintptr_t) addr + size));
|
|
||||||
}
|
|
||||||
|
|
||||||
void *genTrampoline(void *hookMethod) {
|
|
||||||
unsigned count;
|
|
||||||
uintptr_t addr;
|
|
||||||
while (true) {
|
|
||||||
auto tl = Trampoline{.addr = trampoline_pool.fetch_add(1, std::memory_order_release)};
|
|
||||||
count = tl.count;
|
|
||||||
addr = tl.addr & ~uintptr_t(0xfff);
|
|
||||||
if (addr == 0 || count >= trampolineNumPerPage) {
|
|
||||||
if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) {
|
|
||||||
trampoline_lock.wait(true, std::memory_order_acquire);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
addr = reinterpret_cast<uintptr_t>(mmap(nullptr, pageSize,
|
|
||||||
PROT_READ | PROT_WRITE | PROT_EXEC,
|
|
||||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
|
||||||
if (addr == reinterpret_cast<uintptr_t>(MAP_FAILED)) {
|
|
||||||
LOGE("mmap failed, errno = %s", strerror(errno));
|
|
||||||
trampoline_lock.clear(std::memory_order_release);
|
|
||||||
trampoline_lock.notify_all();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
count = 0;
|
|
||||||
tl.addr = addr;
|
|
||||||
tl.count = count + 1;
|
|
||||||
trampoline_pool.store(tl.addr, std::memory_order_release);
|
|
||||||
trampoline_lock.clear(std::memory_order_release);
|
|
||||||
trampoline_lock.notify_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOGI("trampoline: count = %u, addr = %zx, target = %zx", count, addr,
|
|
||||||
addr + count * trampolineSize);
|
|
||||||
addr = addr + count * trampolineSize;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
unsigned char *targetAddr = reinterpret_cast<unsigned char *>(addr);
|
|
||||||
memcpy(targetAddr, trampoline,
|
|
||||||
sizeof(trampoline)); // do not use trampolineSize since it's a rounded size
|
|
||||||
|
|
||||||
// replace with the actual ArtMethod addr
|
|
||||||
#if defined(__i386__)
|
|
||||||
memcpy(targetAddr + 1, &hookMethod, pointer_size);
|
|
||||||
|
|
||||||
#elif defined(__x86_64__)
|
|
||||||
memcpy((char*)targetAddr + 2, &hookMethod, pointer_size);
|
|
||||||
|
|
||||||
#elif defined(__arm__)
|
|
||||||
memcpy(targetAddr+8, &hookMethod, pointer_size);
|
|
||||||
|
|
||||||
#elif defined(__aarch64__)
|
|
||||||
memcpy(targetAddr + 12, &hookMethod, pointer_size);
|
|
||||||
|
|
||||||
#else
|
|
||||||
#error Unsupported architecture
|
|
||||||
#endif
|
|
||||||
FlushCache(targetAddr, sizeof(trampoline));
|
|
||||||
|
|
||||||
return targetAddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setupTrampoline() {
|
|
||||||
#if defined(__i386__)
|
|
||||||
trampoline[7] = (unsigned char) OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
|
|
||||||
#elif defined(__x86_64__)
|
|
||||||
trampoline[12] = (unsigned char)OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
|
|
||||||
#elif defined(__arm__)
|
|
||||||
trampoline[4] = (unsigned char)OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
|
|
||||||
#elif defined(__aarch64__)
|
|
||||||
trampoline[5] |=
|
|
||||||
((unsigned char) OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod) << 4;
|
|
||||||
trampoline[6] |=
|
|
||||||
((unsigned char) OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod) >> 4;
|
|
||||||
#else
|
|
||||||
#error Unsupported architecture
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
LOCAL_PATH := $(call my-dir)
|
|
||||||
define walk
|
|
||||||
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
|
|
||||||
endef
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE := lspd
|
|
||||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src
|
|
||||||
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/src))
|
|
||||||
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%) api/config.cpp
|
|
||||||
LOCAL_STATIC_LIBRARIES := cxx riru yahfa dobby dex_builder
|
|
||||||
ifeq ($(API), riru)
|
|
||||||
LOCAL_SRC_FILES += api/riru_main.cpp
|
|
||||||
else ifeq ($(API), zygisk)
|
|
||||||
LOCAL_SRC_FILES += api/zygisk_main.cpp
|
|
||||||
endif
|
|
||||||
LOCAL_CFLAGS += -DINJECTED_AID=${INJECTED_AID}
|
|
||||||
LOCAL_LDLIBS := -llog
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
|
||||||
|
|
||||||
$(LOCAL_PATH)/api/config.cpp : FORCE
|
|
||||||
$(file > $@,namespace lspd {)
|
|
||||||
$(file >> $@,extern const int versionCode = ${VERSION_CODE};)
|
|
||||||
$(file >> $@,extern const int apiVersion = ${API_VERSION};)
|
|
||||||
$(file >> $@,extern const char* const versionName = "${VERSION_NAME}";)
|
|
||||||
$(file >> $@,extern const char* const moduleName = "${MODULE_NAME}";)
|
|
||||||
$(file >> $@,})
|
|
||||||
FORCE: ;
|
|
||||||
|
|
@ -1,50 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// Created by 双草酸酯 on 12/19/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef LSPOSED_ART_METHOD_H
|
|
||||||
#define LSPOSED_ART_METHOD_H
|
|
||||||
|
|
||||||
#include <HookMain.h>
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
namespace art_method {
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(std::string, PrettyMethod, void *thiz, bool with_signature) {
|
|
||||||
if (thiz == nullptr) [[unlikely]]
|
|
||||||
return "null";
|
|
||||||
if (PrettyMethodSym) [[likely]]
|
|
||||||
return PrettyMethodSym(thiz, with_signature);
|
|
||||||
else return "null sym";
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string PrettyMethod(void *thiz) {
|
|
||||||
return PrettyMethod(thiz, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Setup(const SandHook::ElfImg &handle) {
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(PrettyMethod, "_ZN3art9ArtMethod12PrettyMethodEb");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LSPOSED_ART_METHOD_H
|
|
||||||
|
|
@ -1,149 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <jni_helper.h>
|
|
||||||
#include <base/object.h>
|
|
||||||
#include <art/runtime/mirror/class.h>
|
|
||||||
#include "runtime.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "jni_env_ext.h"
|
|
||||||
#include "context.h"
|
|
||||||
#include "jni/yahfa.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "HookMain.h"
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
|
|
||||||
class ClassLinker : public lspd::HookedObject {
|
|
||||||
private:
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
static auto MaybeDelayHook(void *clazz_ptr) {
|
|
||||||
std::vector<std::tuple<void*, void*>> out;
|
|
||||||
art::mirror::Class mirror_class(clazz_ptr);
|
|
||||||
auto class_def = mirror_class.GetClassDef();
|
|
||||||
if (!class_def) return out;
|
|
||||||
auto set = lspd::isClassHooked(class_def);
|
|
||||||
if (!set.empty()) [[unlikely]] {
|
|
||||||
LOGD("Pending hook for %p (%s)", clazz_ptr,
|
|
||||||
art::mirror::Class(clazz_ptr).GetDescriptor().c_str());
|
|
||||||
for (auto art_method : set) {
|
|
||||||
out.emplace_back(art_method, yahfa::getEntryPoint(art_method));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
static void FixTrampoline(const std::vector<std::tuple<void*, void*>>& methods) {
|
|
||||||
for (const auto &[art_method, old_trampoline] : methods) {
|
|
||||||
auto *new_trampoline = yahfa::getEntryPoint(art_method);
|
|
||||||
auto *backup = lspd::isHooked(art_method);
|
|
||||||
if (backup && new_trampoline != old_trampoline) {
|
|
||||||
yahfa::setEntryPoint(backup, new_trampoline);
|
|
||||||
yahfa::setEntryPoint(art_method, old_trampoline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CREATE_MEM_HOOK_STUB_ENTRIES(
|
|
||||||
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE",
|
|
||||||
void, FixupStaticTrampolines, (void * thiz, void * clazz_ptr), {
|
|
||||||
auto b = MaybeDelayHook(clazz_ptr);
|
|
||||||
backup(thiz, clazz_ptr);
|
|
||||||
FixTrampoline(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
CREATE_MEM_HOOK_STUB_ENTRIES(
|
|
||||||
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
|
|
||||||
void, FixupStaticTrampolinesWithThread,
|
|
||||||
(void * thiz, void * self, void * clazz_ptr), {
|
|
||||||
auto b = MaybeDelayHook(clazz_ptr);
|
|
||||||
backup(thiz, self, clazz_ptr);
|
|
||||||
FixTrampoline(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, MakeInitializedClassesVisiblyInitialized, void *thiz,
|
|
||||||
void *self, bool wait) {
|
|
||||||
if (MakeInitializedClassesVisiblyInitializedSym) [[likely]]
|
|
||||||
MakeInitializedClassesVisiblyInitializedSym(thiz, self, wait);
|
|
||||||
}
|
|
||||||
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetEntryPointsToInterpreter, void *thiz,
|
|
||||||
void *art_method) {
|
|
||||||
if (SetEntryPointsToInterpreterSym) [[likely]]
|
|
||||||
SetEntryPointsToInterpreterSym(thiz, art_method);
|
|
||||||
}
|
|
||||||
|
|
||||||
CREATE_HOOK_STUB_ENTRIES(
|
|
||||||
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv",
|
|
||||||
bool, ShouldUseInterpreterEntrypoint, (void * art_method,
|
|
||||||
const void *quick_code), {
|
|
||||||
if (quick_code != nullptr && lspd::isHooked(art_method)) [[unlikely]] {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return backup(art_method, quick_code);
|
|
||||||
});
|
|
||||||
|
|
||||||
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_to_interpreter_bridge, void*) {}
|
|
||||||
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_generic_jni_trampoline, void*) {}
|
|
||||||
|
|
||||||
CREATE_HOOK_STUB_ENTRIES("_ZN3art11interpreter29ShouldStayInSwitchInterpreterEPNS_9ArtMethodE",
|
|
||||||
bool, ShouldStayInSwitchInterpreter ,(void* art_method), {
|
|
||||||
if (lspd::isHooked(art_method)) [[unlikely]] {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return backup(art_method);
|
|
||||||
});
|
|
||||||
|
|
||||||
public:
|
|
||||||
// @ApiSensitive(Level.MIDDLE)
|
|
||||||
inline static void Setup(const SandHook::ElfImg &handle) {
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(SetEntryPointsToInterpreter,
|
|
||||||
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE");
|
|
||||||
|
|
||||||
lspd::HookSyms(handle, ShouldUseInterpreterEntrypoint, ShouldStayInSwitchInterpreter);
|
|
||||||
|
|
||||||
lspd::HookSyms(handle, FixupStaticTrampolinesWithThread, FixupStaticTrampolines);
|
|
||||||
|
|
||||||
RETRIEVE_FUNC_SYMBOL(art_quick_to_interpreter_bridge, "art_quick_to_interpreter_bridge");
|
|
||||||
RETRIEVE_FUNC_SYMBOL(art_quick_generic_jni_trampoline, "art_quick_generic_jni_trampoline");
|
|
||||||
|
|
||||||
LOGD("art_quick_to_interpreter_bridge = %p", art_quick_to_interpreter_bridgeSym);
|
|
||||||
LOGD("art_quick_generic_jni_trampoline = %p", art_quick_generic_jni_trampolineSym);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
static void SetEntryPointsToInterpreter(void *art_method) {
|
|
||||||
if (art_quick_to_interpreter_bridgeSym && art_quick_generic_jni_trampolineSym) [[likely]] {
|
|
||||||
if (yahfa::getAccessFlags(art_method) & yahfa::kAccNative) [[unlikely]] {
|
|
||||||
yahfa::setEntryPoint(art_method,
|
|
||||||
reinterpret_cast<void *>(art_quick_generic_jni_trampolineSym));
|
|
||||||
} else {
|
|
||||||
yahfa::setEntryPoint(art_method,
|
|
||||||
reinterpret_cast<void *>(art_quick_to_interpreter_bridgeSym));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SetEntryPointsToInterpreter(nullptr, art_method);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2013 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_
|
|
||||||
#define ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_
|
|
||||||
|
|
||||||
#include <iosfwd>
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
namespace gc {
|
|
||||||
// Which types of collections are able to be performed.
|
|
||||||
enum CollectorType {
|
|
||||||
// No collector selected.
|
|
||||||
kCollectorTypeNone,
|
|
||||||
// Non concurrent mark-sweep.
|
|
||||||
kCollectorTypeMS,
|
|
||||||
// Concurrent mark-sweep.
|
|
||||||
kCollectorTypeCMS,
|
|
||||||
// Semi-space / mark-sweep hybrid, enables compaction.
|
|
||||||
kCollectorTypeSS,
|
|
||||||
// Heap trimming collector, doesn't do any actual collecting.
|
|
||||||
kCollectorTypeHeapTrim,
|
|
||||||
// A (mostly) concurrent copying collector.
|
|
||||||
kCollectorTypeCC,
|
|
||||||
// The background compaction of the concurrent copying collector.
|
|
||||||
kCollectorTypeCCBackground,
|
|
||||||
// Instrumentation critical section fake collector.
|
|
||||||
kCollectorTypeInstrumentation,
|
|
||||||
// Fake collector for adding or removing application image spaces.
|
|
||||||
kCollectorTypeAddRemoveAppImageSpace,
|
|
||||||
// Fake collector used to implement exclusion between GC and debugger.
|
|
||||||
kCollectorTypeDebugger,
|
|
||||||
// A homogeneous space compaction collector used in background transition
|
|
||||||
// when both foreground and background collector are CMS.
|
|
||||||
kCollectorTypeHomogeneousSpaceCompact,
|
|
||||||
// Class linker fake collector.
|
|
||||||
kCollectorTypeClassLinker,
|
|
||||||
// JIT Code cache fake collector.
|
|
||||||
kCollectorTypeJitCodeCache,
|
|
||||||
// Hprof fake collector.
|
|
||||||
kCollectorTypeHprof,
|
|
||||||
// Fake collector for installing/removing a system-weak holder.
|
|
||||||
kCollectorTypeAddRemoveSystemWeakHolder,
|
|
||||||
// Fake collector type for GetObjectsAllocated
|
|
||||||
kCollectorTypeGetObjectsAllocated,
|
|
||||||
// Fake collector type for ScopedGCCriticalSection
|
|
||||||
kCollectorTypeCriticalSection,
|
|
||||||
};
|
|
||||||
} // namespace gc
|
|
||||||
} // namespace art
|
|
||||||
#endif // ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ART_RUNTIME_GC_GC_CAUSE_H_
|
|
||||||
#define ART_RUNTIME_GC_GC_CAUSE_H_
|
|
||||||
|
|
||||||
#include <iosfwd>
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
namespace gc {
|
|
||||||
// What caused the GC?
|
|
||||||
enum GcCause {
|
|
||||||
// Invalid GC cause used as a placeholder.
|
|
||||||
kGcCauseNone,
|
|
||||||
// GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before
|
|
||||||
// retrying allocation.
|
|
||||||
kGcCauseForAlloc,
|
|
||||||
// A background GC trying to ensure there is free memory ahead of allocations.
|
|
||||||
kGcCauseBackground,
|
|
||||||
// An explicit System.gc() call.
|
|
||||||
kGcCauseExplicit,
|
|
||||||
// GC triggered for a native allocation when NativeAllocationGcWatermark is exceeded.
|
|
||||||
// (This may be a blocking GC depending on whether we run a non-concurrent collector).
|
|
||||||
kGcCauseForNativeAlloc,
|
|
||||||
// GC triggered for a collector transition.
|
|
||||||
kGcCauseCollectorTransition,
|
|
||||||
// Not a real GC cause, used when we disable moving GC (currently for GetPrimitiveArrayCritical).
|
|
||||||
kGcCauseDisableMovingGc,
|
|
||||||
// Not a real GC cause, used when we trim the heap.
|
|
||||||
kGcCauseTrim,
|
|
||||||
// Not a real GC cause, used to implement exclusion between GC and instrumentation.
|
|
||||||
kGcCauseInstrumentation,
|
|
||||||
// Not a real GC cause, used to add or remove app image spaces.
|
|
||||||
kGcCauseAddRemoveAppImageSpace,
|
|
||||||
// Not a real GC cause, used to implement exclusion between GC and debugger.
|
|
||||||
kGcCauseDebugger,
|
|
||||||
// GC triggered for background transition when both foreground and background collector are CMS.
|
|
||||||
kGcCauseHomogeneousSpaceCompact,
|
|
||||||
// Class linker cause, used to guard filling art methods with special values.
|
|
||||||
kGcCauseClassLinker,
|
|
||||||
// Not a real GC cause, used to implement exclusion between code cache metadata and GC.
|
|
||||||
kGcCauseJitCodeCache,
|
|
||||||
// Not a real GC cause, used to add or remove system-weak holders.
|
|
||||||
kGcCauseAddRemoveSystemWeakHolder,
|
|
||||||
// Not a real GC cause, used to prevent hprof running in the middle of GC.
|
|
||||||
kGcCauseHprof,
|
|
||||||
// Not a real GC cause, used to prevent GetObjectsAllocated running in the middle of GC.
|
|
||||||
kGcCauseGetObjectsAllocated,
|
|
||||||
// GC cause for the profile saver.
|
|
||||||
kGcCauseProfileSaver,
|
|
||||||
};
|
|
||||||
} // namespace gc
|
|
||||||
} // namespace art
|
|
||||||
#endif // ART_RUNTIME_GC_GC_CAUSE_H_
|
|
||||||
|
|
@ -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
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LSPOSED_SCOPED_GC_CRITICAL_SECTION_H
|
|
||||||
#define LSPOSED_SCOPED_GC_CRITICAL_SECTION_H
|
|
||||||
|
|
||||||
#include "gc_cause.h"
|
|
||||||
#include "collector_type.h"
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
namespace gc {
|
|
||||||
|
|
||||||
class GCCriticalSection {
|
|
||||||
private:
|
|
||||||
[[maybe_unused]] void* self_;
|
|
||||||
[[maybe_unused]] const char* section_name_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ScopedGCCriticalSection {
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, void *thiz, void* self, GcCause cause, CollectorType collector_type) {
|
|
||||||
if (thiz == nullptr) [[unlikely]] return;
|
|
||||||
if (constructorSym) [[likely]]
|
|
||||||
return constructorSym(thiz, self, cause, collector_type);
|
|
||||||
}
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, void *thiz) {
|
|
||||||
if (thiz == nullptr) [[unlikely]] return;
|
|
||||||
if (destructorSym) [[likely]]
|
|
||||||
return destructorSym(thiz);
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
ScopedGCCriticalSection(void *self, GcCause cause, CollectorType collector_type) {
|
|
||||||
constructor(this, self, cause, collector_type);
|
|
||||||
}
|
|
||||||
~ScopedGCCriticalSection() {
|
|
||||||
destructor(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void Setup(const SandHook::ElfImg &handle) {
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_7GcCauseENS0_13CollectorTypeE");
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art2gc23ScopedGCCriticalSectionD2Ev");
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
[[maybe_unused]] GCCriticalSection critical_section_;
|
|
||||||
[[maybe_unused]] const char* old_no_suspend_reason_;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LSPOSED_SCOPED_GC_CRITICAL_SECTION_H
|
|
||||||
|
|
@ -1,52 +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 "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,58 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "jni.h"
|
|
||||||
#include "base/object.h"
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
|
|
||||||
class JNIEnvExt : lspd::HookedObject {
|
|
||||||
|
|
||||||
private:
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(jobject, NewLocalRef, void *thiz, void *mirror_ptr) {
|
|
||||||
return NewLocalRefSym(thiz, mirror_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, DeleteLocalRef, void *thiz, jobject obj) {
|
|
||||||
return DeleteLocalRefSym(thiz, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
JNIEnvExt(void *thiz) : HookedObject(thiz) {}
|
|
||||||
|
|
||||||
// @ApiSensitive(Level.MIDDLE)
|
|
||||||
inline static void Setup(const SandHook::ElfImg &handle) {
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(NewLocalRef, "_ZN3art9JNIEnvExt11NewLocalRefEPNS_6mirror6ObjectE");
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(DeleteLocalRef, "_ZN3art9JNIEnvExt14DeleteLocalRefEP8_jobject");
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject NewLocalRefer(void *mirror_ptr) {
|
|
||||||
return NewLocalRef(thiz_, mirror_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeleteLocalRef(jobject obj) {
|
|
||||||
DeleteLocalRef(thiz_, obj);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <context.h>
|
|
||||||
#include "base/object.h"
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
namespace mirror {
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
using namespace lspd;
|
|
||||||
|
|
||||||
class Class : public HookedObject {
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, void *thiz,
|
|
||||||
std::string *storage) {
|
|
||||||
if (GetDescriptorSym)
|
|
||||||
return GetDescriptorSym(thiz, storage);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void*, GetClassDef, void* thiz) {
|
|
||||||
if (GetClassDefSym) [[likely]]
|
|
||||||
return GetClassDefSym(thiz);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Class(void *thiz) : HookedObject(thiz) {}
|
|
||||||
|
|
||||||
// @ApiSensitive(Level.MIDDLE)
|
|
||||||
inline static void Setup(const SandHook::ElfImg &handle) {
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(GetDescriptor, "_ZN3art6mirror5Class13GetDescriptorEPNSt3__112"
|
|
||||||
"basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE");
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(GetClassDef, "_ZN3art6mirror5Class11GetClassDefEv");
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *GetDescriptor(std::string *storage) {
|
|
||||||
if (thiz_ && GetDescriptorSym) {
|
|
||||||
return GetDescriptor(thiz_, storage);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GetDescriptor() {
|
|
||||||
std::string storage;
|
|
||||||
return GetDescriptor(&storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *GetClassDef() {
|
|
||||||
if(thiz_ && GetClassDefSym)
|
|
||||||
return GetClassDef(thiz_);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace mirror
|
|
||||||
} // namespace art
|
|
||||||
|
|
@ -1,64 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <base/object.h>
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
|
|
||||||
class Thread : public lspd::HookedObject {
|
|
||||||
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(lspd::ObjPtr, DecodeJObject, void *thiz, jobject obj) {
|
|
||||||
if (DecodeJObjectSym)
|
|
||||||
return DecodeJObjectSym(thiz, obj);
|
|
||||||
else
|
|
||||||
return {.data=nullptr};
|
|
||||||
}
|
|
||||||
|
|
||||||
CREATE_FUNC_SYMBOL_ENTRY(void *, CurrentFromGdb) {
|
|
||||||
if (CurrentFromGdbSym) [[likely]]
|
|
||||||
return CurrentFromGdbSym();
|
|
||||||
else
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Thread(void *thiz) : HookedObject(thiz) {}
|
|
||||||
|
|
||||||
inline static Thread Current() {
|
|
||||||
return Thread(CurrentFromGdb());
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void Setup(const SandHook::ElfImg &handle) {
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(DecodeJObject,
|
|
||||||
"_ZNK3art6Thread13DecodeJObjectEP8_jobject");
|
|
||||||
RETRIEVE_FUNC_SYMBOL(CurrentFromGdb,
|
|
||||||
"_ZN3art6Thread14CurrentFromGdbEv");
|
|
||||||
}
|
|
||||||
|
|
||||||
void *DecodeJObject(jobject obj) {
|
|
||||||
if (thiz_ && DecodeJObjectSym) [[likely]] {
|
|
||||||
return DecodeJObject(thiz_, obj).data;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +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
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LSPOSED_THREAD_LIST_H
|
|
||||||
#define LSPOSED_THREAD_LIST_H
|
|
||||||
|
|
||||||
namespace art {
|
|
||||||
namespace thread_list {
|
|
||||||
|
|
||||||
class ScopedSuspendAll {
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, void *thiz, const char * cause, bool long_suspend) {
|
|
||||||
if (thiz == nullptr) [[unlikely]] return;
|
|
||||||
if (constructorSym) [[likely]]
|
|
||||||
return constructorSym(thiz, cause, long_suspend);
|
|
||||||
}
|
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, void *thiz) {
|
|
||||||
if (thiz == nullptr) [[unlikely]] return;
|
|
||||||
if (destructorSym) [[likely]]
|
|
||||||
return destructorSym(thiz);
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
ScopedSuspendAll(const char * cause, bool long_suspend) {
|
|
||||||
constructor(this, cause, long_suspend);
|
|
||||||
}
|
|
||||||
~ScopedSuspendAll() {
|
|
||||||
destructor(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void Setup(const SandHook::ElfImg &handle) {
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art16ScopedSuspendAllC2EPKcb");
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art16ScopedSuspendAllD2Ev");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LSPOSED_THREAD_LIST_H
|
|
||||||
|
|
@ -1,277 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "macros.h"
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include "config.h"
|
|
||||||
#include "native_hook.h"
|
|
||||||
#include "elf_util.h"
|
|
||||||
#include <concepts>
|
|
||||||
|
|
||||||
#define _uintval(p) reinterpret_cast<uintptr_t>(p)
|
|
||||||
#define _ptr(p) reinterpret_cast<void *>(p)
|
|
||||||
#define _align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1))
|
|
||||||
#define _align_down(x, n) ((x) & -(n))
|
|
||||||
#define _page_size 4096
|
|
||||||
#define _page_align(n) _align_up(static_cast<uintptr_t>(n), _page_size)
|
|
||||||
#define _ptr_align(x) _ptr(_align_down(reinterpret_cast<uintptr_t>(x), _page_size))
|
|
||||||
#define _make_rwx(p, n) ::mprotect(_ptr_align(p), \
|
|
||||||
_page_align(_uintval(p) + n) != _page_align(_uintval(p)) ? _page_align(n) + _page_size : _page_align(n), \
|
|
||||||
PROT_READ | PROT_WRITE | PROT_EXEC)
|
|
||||||
|
|
||||||
#define CONCATENATE(a, b) a##b
|
|
||||||
|
|
||||||
#define CREATE_HOOK_STUB_ENTRIES(SYM, RET, FUNC, PARAMS, DEF) \
|
|
||||||
inline static struct : public lspd::Hooker<RET PARAMS, decltype(CONCATENATE(SYM,_tstr))>{ \
|
|
||||||
inline static RET replace PARAMS DEF \
|
|
||||||
} FUNC
|
|
||||||
|
|
||||||
#define CREATE_MEM_HOOK_STUB_ENTRIES(SYM, RET, FUNC, PARAMS, DEF) \
|
|
||||||
inline static struct : public lspd::MemHooker<RET PARAMS, decltype(CONCATENATE(SYM,_tstr))>{ \
|
|
||||||
inline static RET replace PARAMS DEF \
|
|
||||||
} FUNC
|
|
||||||
|
|
||||||
#define RETRIEVE_FUNC_SYMBOL(name, ...) \
|
|
||||||
name##Sym = reinterpret_cast<name##Type>( \
|
|
||||||
lspd::Dlsym(handle, __VA_ARGS__))
|
|
||||||
|
|
||||||
#define RETRIEVE_MEM_FUNC_SYMBOL(name, ...) \
|
|
||||||
name##Sym = reinterpret_cast<name##Type::FunType>( \
|
|
||||||
lspd::Dlsym(handle, __VA_ARGS__))
|
|
||||||
|
|
||||||
#define RETRIEVE_FIELD_SYMBOL(name, ...) \
|
|
||||||
void *name = lspd::Dlsym(handle, __VA_ARGS__)
|
|
||||||
|
|
||||||
#define CREATE_FUNC_SYMBOL_ENTRY(ret, func, ...) \
|
|
||||||
typedef ret (*func##Type)(__VA_ARGS__); \
|
|
||||||
inline static ret (*func##Sym)(__VA_ARGS__); \
|
|
||||||
inline static ret func(__VA_ARGS__)
|
|
||||||
|
|
||||||
#define CREATE_MEM_FUNC_SYMBOL_ENTRY(ret, func, thiz, ...) \
|
|
||||||
using func##Type = lspd::MemberFunction<ret(__VA_ARGS__)>; \
|
|
||||||
inline static func##Type func##Sym; \
|
|
||||||
inline static ret func(thiz, ## __VA_ARGS__)
|
|
||||||
|
|
||||||
namespace lspd {
|
|
||||||
|
|
||||||
class ShadowObject {
|
|
||||||
|
|
||||||
public:
|
|
||||||
ShadowObject(void *thiz) : thiz_(thiz) {
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
inline void *Get() {
|
|
||||||
return thiz_;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
inline void Reset(void *thiz) {
|
|
||||||
thiz_ = thiz;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
inline operator bool() const {
|
|
||||||
return thiz_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void *thiz_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class HookedObject : public ShadowObject {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
HookedObject(void *thiz) : ShadowObject(thiz) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ObjPtr {
|
|
||||||
void *data;
|
|
||||||
};
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
inline void *Dlsym(void *handle, const char *name) {
|
|
||||||
return dlsym(handle, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
inline void *Dlsym(const SandHook::ElfImg &handle, const char *name) {
|
|
||||||
return handle.getSymbAddress<void *>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class H, class T, class ... Args>
|
|
||||||
inline void *Dlsym(H &&handle, T first, Args... last) {
|
|
||||||
auto ret = Dlsym(std::forward<H>(handle), first);
|
|
||||||
if (ret) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return Dlsym(std::forward<H>(handle), last...);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int HookFunction(void *original, void *replace, void **backup) {
|
|
||||||
_make_rwx(original, _page_size);
|
|
||||||
if constexpr (isDebug) {
|
|
||||||
Dl_info info;
|
|
||||||
if (dladdr(original, &info))
|
|
||||||
LOGD("Hooking %s (%p) from %s (%p)",
|
|
||||||
info.dli_sname ? info.dli_sname : "(unknown symbol)", info.dli_saddr,
|
|
||||||
info.dli_fname ? info.dli_fname : "(unknown file)", info.dli_fbase);
|
|
||||||
}
|
|
||||||
return DobbyHook(original, replace, backup);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int UnhookFunction(void *original) {
|
|
||||||
if constexpr (isDebug) {
|
|
||||||
Dl_info info;
|
|
||||||
if (dladdr(original, &info))
|
|
||||||
LOGD("Unhooking %s (%p) from %s (%p)",
|
|
||||||
info.dli_sname ? info.dli_sname : "(unknown symbol)", info.dli_saddr,
|
|
||||||
info.dli_fname ? info.dli_fname : "(unknown file)", info.dli_fbase);
|
|
||||||
}
|
|
||||||
return DobbyDestroy(original);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class, template<class, class...> class>
|
|
||||||
struct is_instance : public std::false_type {
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class...Ts, template<class, class...> class U>
|
|
||||||
struct is_instance<U<Ts...>, U> : public std::true_type {
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename Class, typename Return, typename T, typename... Args>
|
|
||||||
requires (std::is_same_v<T, void> || std::is_same_v<Class, T>)
|
|
||||||
inline static auto memfun_cast(Return (*func)(T *, Args...)) {
|
|
||||||
union {
|
|
||||||
Return (Class::*f)(Args...);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
decltype(func) p;
|
|
||||||
std::ptrdiff_t adj;
|
|
||||||
} data;
|
|
||||||
} u{.data = {func, 0}};
|
|
||||||
static_assert(sizeof(u.f) == sizeof(u.data), "Try different T");
|
|
||||||
return u.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::same_as<void> T, typename Return, typename... Args>
|
|
||||||
inline auto memfun_cast(Return (*func)(T *, Args...)) {
|
|
||||||
return memfun_cast<T>(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename, typename=void>
|
|
||||||
class MemberFunction;
|
|
||||||
|
|
||||||
template<typename This, typename Return, typename ... Args>
|
|
||||||
class MemberFunction<Return(Args...), This> {
|
|
||||||
using SelfType = MemberFunction<Return(This *, Args...), This>;
|
|
||||||
using ThisType = std::conditional_t<std::is_same_v<This, void>, SelfType, This>;
|
|
||||||
using MemFunType = Return(ThisType::*)(Args...);
|
|
||||||
public:
|
|
||||||
using FunType = Return (*)(This *, Args...);
|
|
||||||
private:
|
|
||||||
MemFunType f_ = nullptr;
|
|
||||||
public:
|
|
||||||
MemberFunction() = default;
|
|
||||||
|
|
||||||
MemberFunction(FunType f) : f_(memfun_cast<ThisType>(f)) {}
|
|
||||||
|
|
||||||
MemberFunction(MemFunType f) : f_(f) {}
|
|
||||||
|
|
||||||
Return operator()(This *thiz, Args... args) {
|
|
||||||
return (reinterpret_cast<ThisType *>(thiz)->*f_)(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline operator bool() {
|
|
||||||
return f_ != nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// deduction guide
|
|
||||||
template<typename This, typename Return, typename...Args>
|
|
||||||
MemberFunction(Return(*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
|
|
||||||
|
|
||||||
template<typename This, typename Return, typename...Args>
|
|
||||||
MemberFunction(Return(This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
|
|
||||||
|
|
||||||
template<typename, typename>
|
|
||||||
struct Hooker;
|
|
||||||
|
|
||||||
template<typename Ret, typename... Args, char... cs>
|
|
||||||
struct Hooker<Ret(Args...), tstring<cs...>> {
|
|
||||||
inline static Ret (*backup)(Args...) = nullptr;
|
|
||||||
|
|
||||||
inline static constexpr const char *sym = tstring<cs...>::c_str();
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename, typename>
|
|
||||||
struct MemHooker;
|
|
||||||
template<typename Ret, typename This, typename... Args, char... cs>
|
|
||||||
struct MemHooker<Ret(This, Args...), tstring<cs...>> {
|
|
||||||
inline static MemberFunction<Ret(Args...)> backup;
|
|
||||||
inline static constexpr const char *sym = tstring<cs...>::c_str();
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
concept HookerType = requires(T a) {
|
|
||||||
a.backup;
|
|
||||||
a.replace;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<HookerType T>
|
|
||||||
inline static bool HookSymNoHandle(void *original, T &arg) {
|
|
||||||
if (original) {
|
|
||||||
if constexpr(is_instance<decltype(arg.backup), MemberFunction>::value) {
|
|
||||||
void *backup;
|
|
||||||
HookFunction(original, reinterpret_cast<void *>(arg.replace), &backup);
|
|
||||||
arg.backup = reinterpret_cast<typename decltype(arg.backup)::FunType>(backup);
|
|
||||||
} else {
|
|
||||||
HookFunction(original, reinterpret_cast<void *>(arg.replace),
|
|
||||||
reinterpret_cast<void **>(&arg.backup));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename H, HookerType T>
|
|
||||||
inline static bool HookSym(H &&handle, T &arg) {
|
|
||||||
auto original = Dlsym(std::forward<H>(handle), arg.sym);
|
|
||||||
return HookSymNoHandle(original, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename H, HookerType T, HookerType...Args>
|
|
||||||
inline static bool HookSyms(H &&handle, T &first, Args &...rest) {
|
|
||||||
if (!(HookSym(std::forward<H>(handle), first) || ... || HookSym(std::forward<H>(handle),
|
|
||||||
rest))) {
|
|
||||||
LOGW("Hook Fails: %s", first.sym);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace lspd
|
|
||||||
|
|
||||||
using lspd::operator ""_tstr;
|
|
||||||
|
|
@ -1,363 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 - 2022 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
#include "macros.h"
|
|
||||||
#include <string>
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
#define JNI_START JNIEnv *env, [[maybe_unused]] jclass clazz
|
|
||||||
|
|
||||||
namespace JNIHelper {
|
|
||||||
template<class, template<class, class...> class>
|
|
||||||
struct is_instance : public std::false_type {
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class...Ts, template<class, class...> class U>
|
|
||||||
struct is_instance<U<Ts...>, U> : public std::true_type {
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class JUTFString {
|
|
||||||
public:
|
|
||||||
inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
inline JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env),
|
|
||||||
jstr_(jstr) {
|
|
||||||
if (env_ && jstr_) cstr_ = env_->GetStringUTFChars(jstr, nullptr);
|
|
||||||
else cstr_ = default_cstr;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline operator const char *() const { return cstr_; }
|
|
||||||
|
|
||||||
inline operator const std::string() const { return cstr_; }
|
|
||||||
|
|
||||||
inline operator const bool() const { return cstr_ != nullptr; }
|
|
||||||
|
|
||||||
inline auto get() const { return cstr_; }
|
|
||||||
|
|
||||||
inline ~JUTFString() {
|
|
||||||
if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_);
|
|
||||||
}
|
|
||||||
|
|
||||||
JUTFString(JUTFString &&other)
|
|
||||||
: env_(std::move(other.env_)), jstr_(std::move(other.jstr_)),
|
|
||||||
cstr_(std::move(other.cstr_)) {
|
|
||||||
other.cstr_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
JUTFString &
|
|
||||||
operator=(JUTFString &&other) {
|
|
||||||
if (&other != this) {
|
|
||||||
env_ = std::move(other.env_);
|
|
||||||
jstr_ = std::move(other.jstr_);
|
|
||||||
cstr_ = std::move(other.cstr_);
|
|
||||||
other.cstr_ = nullptr;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
JNIEnv *env_;
|
|
||||||
jstring jstr_;
|
|
||||||
const char *cstr_;
|
|
||||||
|
|
||||||
JUTFString(const JUTFString &) = delete;
|
|
||||||
|
|
||||||
JUTFString &operator=(const JUTFString &) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
concept JObject = std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>;
|
|
||||||
|
|
||||||
template<JObject T>
|
|
||||||
class ScopedLocalRef {
|
|
||||||
public:
|
|
||||||
ScopedLocalRef(JNIEnv *env, T localRef) : mEnv(env), mLocalRef(localRef) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedLocalRef(ScopedLocalRef &&s) noexcept: mEnv(s.mEnv), mLocalRef(s.release()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
template<JObject U>
|
|
||||||
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept: mEnv(s.mEnv), mLocalRef((T) s.release()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit ScopedLocalRef(JNIEnv *env) : mEnv(env), mLocalRef(nullptr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
~ScopedLocalRef() {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset(T ptr = nullptr) {
|
|
||||||
if (ptr != mLocalRef) {
|
|
||||||
if (mLocalRef != nullptr) {
|
|
||||||
mEnv->DeleteLocalRef(mLocalRef);
|
|
||||||
}
|
|
||||||
mLocalRef = ptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] T release() {
|
|
||||||
T localRef = mLocalRef;
|
|
||||||
mLocalRef = nullptr;
|
|
||||||
return localRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
T get() const {
|
|
||||||
return mLocalRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do not expose an empty constructor as it can easily lead to errors
|
|
||||||
// using common idioms, e.g.:
|
|
||||||
// ScopedLocalRef<...> ref;
|
|
||||||
// ref.reset(...);
|
|
||||||
// Move assignment operator.
|
|
||||||
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
|
|
||||||
reset(s.release());
|
|
||||||
mEnv = s.mEnv;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator bool() const {
|
|
||||||
return mLocalRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<JObject U>
|
|
||||||
friend
|
|
||||||
class ScopedLocalRef;
|
|
||||||
|
|
||||||
private:
|
|
||||||
JNIEnv *mEnv;
|
|
||||||
T mLocalRef;
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
concept ScopeOrRaw = std::is_same_v<T, U> || std::is_same_v<ScopedLocalRef<T>, U>;
|
|
||||||
template<typename T>
|
|
||||||
concept ScopeOrClass = ScopeOrRaw<jclass, T>;
|
|
||||||
template<typename T>
|
|
||||||
concept ScopeOrObject = ScopeOrRaw<jobject, T>;
|
|
||||||
|
|
||||||
inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {
|
|
||||||
if (auto exception = env->ExceptionOccurred()) {
|
|
||||||
env->ExceptionClear();
|
|
||||||
static jclass log = (jclass) env->NewGlobalRef(env->FindClass("android/util/Log"));
|
|
||||||
static jmethodID toString = env->GetStaticMethodID(log, "getStackTraceString",
|
|
||||||
"(Ljava/lang/Throwable;)Ljava/lang/String;");
|
|
||||||
auto str = (jstring) env->CallStaticObjectMethod(log, toString, exception);
|
|
||||||
env->DeleteLocalRef(exception);
|
|
||||||
return {env, str};
|
|
||||||
}
|
|
||||||
return {env, nullptr};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto unwrap_scope(T &&x) {
|
|
||||||
if constexpr (std::is_same_v<std::decay_t<T>, std::string_view>) return x.data();
|
|
||||||
else if constexpr (JNIHelper::is_instance<std::decay_t<T>, ScopedLocalRef>::value) return x.get();
|
|
||||||
else return std::forward<T>(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto wrap_scope(JNIEnv *env, T &&x) {
|
|
||||||
if constexpr (std::is_convertible_v<T, jobject>) {
|
|
||||||
return ScopedLocalRef(env, std::forward<T>(x));
|
|
||||||
} else return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
|
|
||||||
return ScopedLocalRef(env, env->NewStringUTF(sv.data()));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Func, typename ...Args>
|
|
||||||
requires(std::is_function_v<Func>)
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&... args) {
|
|
||||||
struct finally {
|
|
||||||
finally(JNIEnv *env) : env_(env) {}
|
|
||||||
|
|
||||||
~finally() {
|
|
||||||
if (auto exception = ClearException(env_)) {
|
|
||||||
LOGE("%s", JUTFString(env_, exception.get()).get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEnv *env_;
|
|
||||||
} _(env);
|
|
||||||
|
|
||||||
if constexpr (!std::is_same_v<void, std::invoke_result_t<Func, decltype(unwrap_scope(
|
|
||||||
std::forward<Args>(args)))...>>)
|
|
||||||
return wrap_scope(env, (env->*f)(unwrap_scope(std::forward<Args>(args))...));
|
|
||||||
else (env->*f)(unwrap_scope(std::forward<Args>(args))...);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_FindClass(JNIEnv *env, std::string_view name) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::FindClass, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrObject Object>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_GetObjectClass(JNIEnv *env, const Object &obj) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetObjectClass, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto
|
|
||||||
JNI_GetFieldID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetFieldID, clazz, name, sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_GetObjectField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetObjectField, clazz, fieldId);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto
|
|
||||||
JNI_GetMethodID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetMethodID, clazz, name, sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrObject Object, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallObjectMethod(JNIEnv *env, const Object &obj, Args &&... args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallObjectMethod, obj, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrObject Object, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallIntMethod(JNIEnv *env, const Object &obj, Args &&... args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallIntMethod, obj, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrObject Object, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallLongMethod(JNIEnv *env, const Object &obj, Args &&... args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallLongMethod, obj, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrObject Object, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallVoidMethod(JNIEnv *env, const Object &obj, Args &&...args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallVoidMethod, obj, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrObject Object, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallBooleanMethod(JNIEnv *env, const Object &obj, Args &&...args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallBooleanMethod, obj, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto
|
|
||||||
JNI_GetStaticFieldID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetStaticFieldID, clazz, name, sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_GetStaticObjectField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, clazz, fieldId);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_GetStaticIntField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, clazz, fieldId);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto
|
|
||||||
JNI_GetStaticMethodID(JNIEnv *env, const Class &clazz, std::string_view name,
|
|
||||||
std::string_view sig) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetStaticMethodID, clazz, name, sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallStaticVoidMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallStaticVoidMethod, clazz, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallStaticObjectMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallStaticObjectMethod, clazz, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallStaticIntMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallStaticIntMethod, clazz, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, clazz, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrRaw<jarray> Array>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::GetArrayLength, array);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class, typename ...Args>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_NewObject(JNIEnv *env, const Class &clazz, Args &&...args) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::NewObject, clazz, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<ScopeOrClass Class>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto
|
|
||||||
JNI_RegisterNatives(JNIEnv *env, const Class &clazz, const JNINativeMethod *methods, jint size) {
|
|
||||||
return JNI_SafeInvoke(env, &JNIEnv::RegisterNatives, clazz, methods, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto JNI_NewGlobalRef(JNIEnv *env, T &&x) requires(std::is_convertible_v<T, jobject>){
|
|
||||||
return (T) env->NewGlobalRef(std::forward<T>(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[maybe_unused]]
|
|
||||||
inline auto
|
|
||||||
JNI_NewGlobalRef(JNIEnv *env, const ScopedLocalRef<T> &x) requires(
|
|
||||||
std::is_convertible_v<T, jobject>){
|
|
||||||
return (T) env->NewGlobalRef(x.get());
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stddef.h> // for size_t
|
|
||||||
#include <unistd.h> // for TEMP_FAILURE_RETRY
|
|
||||||
#include <utility>
|
|
||||||
// A macro to disallow the copy constructor and operator= functions
|
|
||||||
// This must be placed in the private: declarations for a class.
|
|
||||||
//
|
|
||||||
// For disallowing only assign or copy, delete the relevant operator or
|
|
||||||
// constructor, for example:
|
|
||||||
// void operator=(const TypeName&) = delete;
|
|
||||||
// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken
|
|
||||||
// semantically, one should either use disallow both or neither. Try to
|
|
||||||
// avoid these in new code.
|
|
||||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
|
||||||
TypeName(const TypeName&) = delete; \
|
|
||||||
void operator=(const TypeName&) = delete
|
|
||||||
// The arraysize(arr) macro returns the # of elements in an array arr.
|
|
||||||
// The expression is a compile-time constant, and therefore can be
|
|
||||||
// used in defining new arrays, for example. If you use arraysize on
|
|
||||||
// a pointer by mistake, you will get a compile-time error.
|
|
||||||
template<typename T, size_t N>
|
|
||||||
[[gnu::always_inline]] constexpr inline size_t arraysize(T(&)[N]) {
|
|
||||||
return N;
|
|
||||||
}
|
|
||||||
// Current ABI string
|
|
||||||
#if defined(__arm__)
|
|
||||||
#define ABI_STRING "arm"
|
|
||||||
#elif defined(__aarch64__)
|
|
||||||
#define ABI_STRING "arm64"
|
|
||||||
#elif defined(__i386__)
|
|
||||||
#define ABI_STRING "x86"
|
|
||||||
#elif defined(__x86_64__)
|
|
||||||
#define ABI_STRING "x86_64"
|
|
||||||
#elif defined(__mips__) && !defined(__LP64__)
|
|
||||||
#define ABI_STRING "mips"
|
|
||||||
#elif defined(__mips__) && defined(__LP64__)
|
|
||||||
#define ABI_STRING "mips64"
|
|
||||||
#endif
|
|
||||||
// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private:
|
|
||||||
// declarations in a class.
|
|
||||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
|
||||||
TypeName(const TypeName&) = delete; \
|
|
||||||
void operator=(const TypeName&) = delete
|
|
||||||
|
|
@ -1,73 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wunused-value"
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <context.h>
|
|
||||||
#include "macros.h"
|
|
||||||
#include "jni_helper.h"
|
|
||||||
#include "logging.h"
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace lspd {
|
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
|
||||||
inline bool RegisterNativeMethodsInternal(JNIEnv *env,
|
|
||||||
const char *class_name,
|
|
||||||
const JNINativeMethod *methods,
|
|
||||||
jint method_count) {
|
|
||||||
|
|
||||||
auto clazz = Context::GetInstance()->FindClassFromCurrentLoader(env, class_name);
|
|
||||||
if (clazz.get() == nullptr) {
|
|
||||||
LOGF("Couldn't find class: %s", class_name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return JNI_RegisterNatives(env, clazz, methods, method_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
|
|
||||||
reinterpret_cast<to>
|
|
||||||
#else
|
|
||||||
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
|
|
||||||
(to)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef LSP_NATIVE_METHOD
|
|
||||||
#define LSP_NATIVE_METHOD(className, functionName, signature) \
|
|
||||||
{ #functionName, \
|
|
||||||
signature, \
|
|
||||||
_NATIVEHELPER_JNI_MACRO_CAST(void*) (Java_org_lsposed_lspd_nativebridge_## className ## _ ## functionName) \
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef LSP_DEF_NATIVE_METHOD
|
|
||||||
#define LSP_DEF_NATIVE_METHOD(ret, className, functionName, ...) \
|
|
||||||
extern "C" ret Java_org_lsposed_lspd_nativebridge_## className ## _ ## functionName (JNI_START, ## __VA_ARGS__)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define REGISTER_LSP_NATIVE_METHODS(class_name) \
|
|
||||||
RegisterNativeMethodsInternal(env, "org.lsposed.lspd.nativebridge." #class_name, gMethods, arraysize(gMethods))
|
|
||||||
|
|
||||||
} // namespace lspd
|
|
||||||
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
|
|
@ -1,46 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
#include <native_util.h>
|
|
||||||
#include <art/runtime/class_linker.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <HookMain.h>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include "art_class_linker.h"
|
|
||||||
|
|
||||||
namespace lspd {
|
|
||||||
LSP_DEF_NATIVE_METHOD(void, ClassLinker, setEntryPointsToInterpreter, jobject method) {
|
|
||||||
void *reflected_method = yahfa::getArtMethod(env, method);
|
|
||||||
LOGD("deoptimizing method: %p", reflected_method);
|
|
||||||
art::ClassLinker::SetEntryPointsToInterpreter(reflected_method);
|
|
||||||
LOGD("method deoptimized: %p", reflected_method);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JNINativeMethod gMethods[] = {
|
|
||||||
LSP_NATIVE_METHOD(ClassLinker, setEntryPointsToInterpreter,
|
|
||||||
"(Ljava/lang/reflect/Executable;)V")
|
|
||||||
};
|
|
||||||
|
|
||||||
void RegisterArtClassLinker(JNIEnv *env) {
|
|
||||||
REGISTER_LSP_NATIVE_METHODS(ClassLinker);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace lspd
|
|
||||||
|
|
@ -1,27 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace lspd {
|
|
||||||
|
|
||||||
void RegisterArtClassLinker(JNIEnv *);
|
|
||||||
|
|
||||||
} // namespace lspd
|
|
||||||
|
|
@ -1,241 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 - 2022 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "yahfa.h"
|
|
||||||
#include "HookMain.h"
|
|
||||||
#include "native_util.h"
|
|
||||||
#include "art/runtime/class_linker.h"
|
|
||||||
#include "art/runtime/thread_list.h"
|
|
||||||
#include "art/runtime/thread.h"
|
|
||||||
#include "art/runtime/gc/scoped_gc_critical_section.h"
|
|
||||||
#include <dex_builder.h>
|
|
||||||
#include <shared_mutex>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
|
|
||||||
namespace lspd {
|
|
||||||
namespace {
|
|
||||||
std::unordered_map<const void *, void*> hooked_methods_;
|
|
||||||
std::shared_mutex hooked_methods_lock_;
|
|
||||||
|
|
||||||
std::vector<std::pair<void *, void *>> jit_movements_;
|
|
||||||
std::shared_mutex jit_movements_lock_;
|
|
||||||
|
|
||||||
std::unordered_map<const void *, std::unordered_set<void*>> hooked_classes_;
|
|
||||||
std::shared_mutex hooked_classes_lock_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* isHooked(void *art_method) {
|
|
||||||
std::shared_lock lk(hooked_methods_lock_);
|
|
||||||
if (auto found = hooked_methods_.find(art_method); found != hooked_methods_.end()) {
|
|
||||||
return found->second;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void recordHooked(void *art_method, void *backup) {
|
|
||||||
{
|
|
||||||
std::unique_lock lk(hooked_methods_lock_);
|
|
||||||
hooked_methods_.emplace(art_method, backup);
|
|
||||||
}
|
|
||||||
auto clazz = art::mirror::Class(reinterpret_cast<void*>(*reinterpret_cast<uint32_t*>(art_method)));
|
|
||||||
{
|
|
||||||
std::unique_lock lk(hooked_classes_lock_);
|
|
||||||
hooked_classes_[clazz.GetClassDef()].emplace(art_method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::unordered_set<void*> &isClassHooked(void *clazz) {
|
|
||||||
static std::unordered_set<void*> empty;
|
|
||||||
std::shared_lock lk(hooked_classes_lock_);
|
|
||||||
if (auto found = hooked_classes_.find(clazz); found != hooked_classes_.end()) {
|
|
||||||
return found->second;
|
|
||||||
}
|
|
||||||
return empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(void, Yahfa, init, jint sdkVersion) {
|
|
||||||
yahfa::init(env, clazz, sdkVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jobject, Yahfa, findMethodNative, jclass targetClass,
|
|
||||||
jstring methodName, jstring methodSig) {
|
|
||||||
return yahfa::findMethodNative(env, clazz, targetClass, methodName,
|
|
||||||
methodSig);
|
|
||||||
}
|
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, backupAndHookNative, jobject target,
|
|
||||||
jobject hook, jobject backup, jboolean is_proxy) {
|
|
||||||
art::gc::ScopedGCCriticalSection section(art::Thread::Current().Get(),
|
|
||||||
art::gc::kGcCauseDebugger,
|
|
||||||
art::gc::kCollectorTypeDebugger);
|
|
||||||
art::thread_list::ScopedSuspendAll suspend("Yahfa Hook", false);
|
|
||||||
if (yahfa::backupAndHookNative(env, clazz, target, hook, backup)) {
|
|
||||||
auto *target_method = yahfa::getArtMethod(env, target);
|
|
||||||
auto *backup_method = yahfa::getArtMethod(env, backup);
|
|
||||||
recordHooked(target_method, backup_method);
|
|
||||||
if (!is_proxy) [[likely]] recordJitMovement(target_method, backup_method);
|
|
||||||
return JNI_TRUE;
|
|
||||||
}
|
|
||||||
return JNI_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, isHooked, jobject member) {
|
|
||||||
return lspd::isHooked(yahfa::getArtMethod(env, member)) != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jclass, Yahfa, buildHooker, jobject app_class_loader, jchar return_class,
|
|
||||||
jcharArray classes, jstring method_name, jstring hooker_name) {
|
|
||||||
static auto *kInMemoryClassloader = JNI_NewGlobalRef(env, JNI_FindClass(env,
|
|
||||||
"dalvik/system/InMemoryDexClassLoader"));
|
|
||||||
static jmethodID kInitMid = JNI_GetMethodID(env, kInMemoryClassloader, "<init>",
|
|
||||||
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
|
||||||
DexBuilder dex_file;
|
|
||||||
|
|
||||||
auto parameter_length = env->GetArrayLength(classes);
|
|
||||||
auto parameter_types = std::vector<TypeDescriptor>();
|
|
||||||
parameter_types.reserve(parameter_length);
|
|
||||||
std::string storage;
|
|
||||||
auto return_type =
|
|
||||||
return_class == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(
|
|
||||||
(char) return_class);
|
|
||||||
auto *params = env->GetCharArrayElements(classes, nullptr);
|
|
||||||
for (int i = 0; i < parameter_length; ++i) {
|
|
||||||
parameter_types.push_back(
|
|
||||||
params[i] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(
|
|
||||||
(char) params[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassBuilder cbuilder{dex_file.MakeClass("LspHooker_")};
|
|
||||||
cbuilder.set_source_file("LSP");
|
|
||||||
|
|
||||||
auto hooker_type =
|
|
||||||
TypeDescriptor::FromClassname(JUTFString(env, hooker_name).get());
|
|
||||||
|
|
||||||
auto *hooker_field = cbuilder.CreateField("hooker", hooker_type)
|
|
||||||
.access_flags(dex::kAccStatic)
|
|
||||||
.Encode();
|
|
||||||
|
|
||||||
auto hook_builder{cbuilder.CreateMethod(
|
|
||||||
JUTFString(env, method_name), Prototype{return_type, parameter_types})};
|
|
||||||
// allocate tmp frist because of wide
|
|
||||||
auto tmp{hook_builder.AllocRegister()};
|
|
||||||
hook_builder.BuildConst(tmp, parameter_types.size());
|
|
||||||
auto hook_params_array{hook_builder.AllocRegister()};
|
|
||||||
hook_builder.BuildNewArray(hook_params_array, TypeDescriptor::Object, tmp);
|
|
||||||
for (size_t i = 0U, j = 0U; i < parameter_types.size(); ++i, ++j) {
|
|
||||||
hook_builder.BuildBoxIfPrimitive(Value::Parameter(j), parameter_types[i],
|
|
||||||
Value::Parameter(j));
|
|
||||||
hook_builder.BuildConst(tmp, i);
|
|
||||||
hook_builder.BuildAput(Instruction::Op::kAputObject, hook_params_array,
|
|
||||||
Value::Parameter(j), tmp);
|
|
||||||
if (parameter_types[i].is_wide()) ++j;
|
|
||||||
}
|
|
||||||
auto handle_hook_method{dex_file.GetOrDeclareMethod(
|
|
||||||
hooker_type, "handleHookedMethod",
|
|
||||||
Prototype{TypeDescriptor::Object, TypeDescriptor::Object.ToArray()})};
|
|
||||||
hook_builder.AddInstruction(
|
|
||||||
Instruction::GetStaticObjectField(hooker_field->decl->orig_index, tmp));
|
|
||||||
hook_builder.AddInstruction(Instruction::InvokeVirtualObject(
|
|
||||||
handle_hook_method.id, tmp, tmp, hook_params_array));
|
|
||||||
if (return_type == TypeDescriptor::Void) {
|
|
||||||
hook_builder.BuildReturn();
|
|
||||||
} else if (return_type.is_primitive()) {
|
|
||||||
auto box_type{return_type.ToBoxType()};
|
|
||||||
const ir::Type *type_def = dex_file.GetOrAddType(box_type);
|
|
||||||
hook_builder.AddInstruction(
|
|
||||||
Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
|
|
||||||
hook_builder.BuildUnBoxIfPrimitive(tmp, box_type, tmp);
|
|
||||||
hook_builder.BuildReturn(tmp, false, return_type.is_wide());
|
|
||||||
} else {
|
|
||||||
const ir::Type *type_def = dex_file.GetOrAddType(return_type);
|
|
||||||
hook_builder.AddInstruction(
|
|
||||||
Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
|
|
||||||
hook_builder.BuildReturn(tmp, true);
|
|
||||||
}
|
|
||||||
[[maybe_unused]] auto *hook_method = hook_builder.Encode();
|
|
||||||
|
|
||||||
auto backup_builder{
|
|
||||||
cbuilder.CreateMethod("backup", Prototype{return_type, parameter_types})};
|
|
||||||
if (return_type == TypeDescriptor::Void) {
|
|
||||||
backup_builder.BuildReturn();
|
|
||||||
} else if (return_type.is_wide()) {
|
|
||||||
LiveRegister zero = backup_builder.AllocRegister();
|
|
||||||
LiveRegister zero_wide = backup_builder.AllocRegister();
|
|
||||||
backup_builder.BuildConstWide(zero, 0);
|
|
||||||
backup_builder.BuildReturn(zero, /*is_object=*/false, true);
|
|
||||||
} else {
|
|
||||||
LiveRegister zero = backup_builder.AllocRegister();
|
|
||||||
LiveRegister zero_wide = backup_builder.AllocRegister();
|
|
||||||
backup_builder.BuildConst(zero, 0);
|
|
||||||
backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false);
|
|
||||||
}
|
|
||||||
[[maybe_unused]] auto *back_method = backup_builder.Encode();
|
|
||||||
|
|
||||||
slicer::MemView image{dex_file.CreateImage()};
|
|
||||||
|
|
||||||
auto *dex_buffer = env->NewDirectByteBuffer(const_cast<void *>(image.ptr()), image.size());
|
|
||||||
auto my_cl = JNI_NewObject(env, kInMemoryClassloader, kInitMid,
|
|
||||||
dex_buffer, app_class_loader);
|
|
||||||
env->DeleteLocalRef(dex_buffer);
|
|
||||||
|
|
||||||
static jmethodID kMid = JNI_GetMethodID(env, kInMemoryClassloader, "loadClass",
|
|
||||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
|
||||||
if (!kMid) {
|
|
||||||
kMid = JNI_GetMethodID(env, kInMemoryClassloader, "findClass",
|
|
||||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
|
||||||
}
|
|
||||||
if (my_cl) {
|
|
||||||
auto target = JNI_CallObjectMethod(env, my_cl, kMid,
|
|
||||||
JNI_NewStringUTF(env, "LspHooker_"));
|
|
||||||
if (target) return (jclass) target.release();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static JNINativeMethod gMethods[] = {
|
|
||||||
LSP_NATIVE_METHOD(Yahfa, init, "(I)V"),
|
|
||||||
LSP_NATIVE_METHOD(Yahfa, findMethodNative,
|
|
||||||
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Executable;"),
|
|
||||||
LSP_NATIVE_METHOD(Yahfa, backupAndHookNative,
|
|
||||||
"(Ljava/lang/reflect/Executable;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;Z)Z"),
|
|
||||||
LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Executable;)Z"),
|
|
||||||
LSP_NATIVE_METHOD(Yahfa, buildHooker,
|
|
||||||
"(Ljava/lang/ClassLoader;C[CLjava/lang/String;Ljava/lang/String;)Ljava/lang/Class;"),
|
|
||||||
};
|
|
||||||
|
|
||||||
void RegisterYahfa(JNIEnv *env) {
|
|
||||||
REGISTER_LSP_NATIVE_METHODS(Yahfa);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace lspd
|
|
||||||
|
|
@ -1,67 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <art/runtime/runtime.h>
|
|
||||||
#include <art/runtime/jni_env_ext.h>
|
|
||||||
#include <dobby.h>
|
|
||||||
#include "symbol_cache.h"
|
|
||||||
#include "logging.h"
|
|
||||||
#include "native_api.h"
|
|
||||||
#include "native_hook.h"
|
|
||||||
#include "art/runtime/mirror/class.h"
|
|
||||||
#include "art/runtime/art_method.h"
|
|
||||||
#include "art/runtime/class_linker.h"
|
|
||||||
#include "art/runtime/thread.h"
|
|
||||||
#include "art/runtime/hidden_api.h"
|
|
||||||
#include "art/runtime/thread_list.h"
|
|
||||||
#include "art/runtime/gc/scoped_gc_critical_section.h"
|
|
||||||
#include "art/runtime/jit/jit_code_cache.h"
|
|
||||||
|
|
||||||
namespace lspd {
|
|
||||||
static std::atomic_bool installed = false;
|
|
||||||
|
|
||||||
void InstallInlineHooks() {
|
|
||||||
if (installed.exchange(true)) [[unlikely]] {
|
|
||||||
LOGD("Inline hooks have been installed, skip");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOGD("Start to install inline hooks");
|
|
||||||
const auto &handle_libart = *GetArt();
|
|
||||||
if (!handle_libart.isValid()) {
|
|
||||||
LOGE("Failed to fetch libart.so");
|
|
||||||
}
|
|
||||||
art::Runtime::Setup(handle_libart);
|
|
||||||
art::hidden_api::DisableHiddenApi(handle_libart);
|
|
||||||
art::art_method::Setup(handle_libart);
|
|
||||||
art::Thread::Setup(handle_libart);
|
|
||||||
art::ClassLinker::Setup(handle_libart);
|
|
||||||
art::mirror::Class::Setup(handle_libart);
|
|
||||||
art::JNIEnvExt::Setup(handle_libart);
|
|
||||||
art::thread_list::ScopedSuspendAll::Setup(handle_libart);
|
|
||||||
art::gc::ScopedGCCriticalSection::Setup(handle_libart);
|
|
||||||
art::jit::jit_code_cache::Setup(handle_libart);
|
|
||||||
GetArt(true);
|
|
||||||
LOGD("Inline hooks installed");
|
|
||||||
}
|
|
||||||
} // namespace lspd
|
|
||||||
|
|
||||||
|
|
@ -1,26 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
|
||||||
|
|
||||||
public interface IModuleContext {
|
|
||||||
|
|
||||||
String getApkPath();
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +1,3 @@
|
||||||
/*
|
|
||||||
* 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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
import android.content.res.XResources;
|
import android.content.res.XResources;
|
||||||
|
|
@ -42,26 +22,15 @@ public interface IXposedHookInitPackageResources extends IXposedMod {
|
||||||
*/
|
*/
|
||||||
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
|
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
|
||||||
|
|
||||||
/**
|
/** @hide */
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
final class Wrapper extends XC_InitPackageResources {
|
final class Wrapper extends XC_InitPackageResources {
|
||||||
private final IXposedHookInitPackageResources instance;
|
private final IXposedHookInitPackageResources instance;
|
||||||
private final String apkPath;
|
public Wrapper(IXposedHookInitPackageResources instance) {
|
||||||
|
|
||||||
public Wrapper(IXposedHookInitPackageResources instance, String apkPath) {
|
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.apkPath = apkPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
|
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
|
||||||
instance.handleInitPackageResources(resparam);
|
instance.handleInitPackageResources(resparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getApkPath() {
|
|
||||||
return apkPath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,3 @@
|
||||||
/*
|
|
||||||
* 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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
@ -43,26 +23,15 @@ public interface IXposedHookLoadPackage extends IXposedMod {
|
||||||
*/
|
*/
|
||||||
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
|
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
|
||||||
|
|
||||||
/**
|
/** @hide */
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
final class Wrapper extends XC_LoadPackage {
|
final class Wrapper extends XC_LoadPackage {
|
||||||
private final IXposedHookLoadPackage instance;
|
private final IXposedHookLoadPackage instance;
|
||||||
private final String apkPath;
|
public Wrapper(IXposedHookLoadPackage instance) {
|
||||||
|
|
||||||
public Wrapper(IXposedHookLoadPackage instance, String apkPath) {
|
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.apkPath = apkPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
|
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
|
||||||
instance.handleLoadPackage(lpparam);
|
instance.handleLoadPackage(lpparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getApkPath() {
|
|
||||||
return apkPath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,5 @@
|
||||||
/*
|
|
||||||
* 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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
|
||||||
import de.robv.android.xposed.callbacks.XCallback;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook the initialization of Zygote process(es), from which all the apps are forked.
|
* Hook the initialization of Zygote process(es), from which all the apps are forked.
|
||||||
*
|
*
|
||||||
|
|
@ -37,30 +14,16 @@ import de.robv.android.xposed.callbacks.XCallback;
|
||||||
public interface IXposedHookZygoteInit extends IXposedMod {
|
public interface IXposedHookZygoteInit extends IXposedMod {
|
||||||
/**
|
/**
|
||||||
* Called very early during startup of Zygote.
|
* Called very early during startup of Zygote.
|
||||||
*
|
|
||||||
* @param startupParam Details about the module itself and the started process.
|
* @param startupParam Details about the module itself and the started process.
|
||||||
* @throws Throwable everything is caught, but will prevent further initialization of the module.
|
* @throws Throwable everything is caught, but will prevent further initialization of the module.
|
||||||
*/
|
*/
|
||||||
void initZygote(StartupParam startupParam) throws Throwable;
|
void initZygote(StartupParam startupParam) throws Throwable;
|
||||||
|
|
||||||
/**
|
/** Data holder for {@link #initZygote}. */
|
||||||
* Data holder for {@link #initZygote}.
|
final class StartupParam {
|
||||||
*/
|
/*package*/ StartupParam() {}
|
||||||
final class StartupParam extends XCallback.Param {
|
|
||||||
/*package*/ StartupParam() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** The path to the module's APK. */
|
||||||
* @param callbacks
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public StartupParam(XposedBridge.CopyOnWriteSortedSet<? extends XCallback> callbacks) {
|
|
||||||
super(callbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The path to the module's APK.
|
|
||||||
*/
|
|
||||||
public String modulePath;
|
public String modulePath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -69,29 +32,4 @@ public interface IXposedHookZygoteInit extends IXposedMod {
|
||||||
*/
|
*/
|
||||||
public boolean startsSystemServer;
|
public boolean startsSystemServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
final class Wrapper extends XC_InitZygote {
|
|
||||||
private final IXposedHookZygoteInit instance;
|
|
||||||
private final StartupParam startupParam;
|
|
||||||
|
|
||||||
public Wrapper(IXposedHookZygoteInit instance, StartupParam startupParam) {
|
|
||||||
this.instance = instance;
|
|
||||||
this.startupParam = startupParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initZygote(StartupParam startupParam) throws Throwable {
|
|
||||||
// NOTE: parameter startupParam not used
|
|
||||||
// cause startupParam info is generated and saved along with instance here
|
|
||||||
instance.initZygote(this.startupParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getApkPath() {
|
|
||||||
return startupParam.modulePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,132 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
|
|
||||||
public class LspHooker {
|
|
||||||
public final XposedBridge.AdditionalHookInfo additionalInfo;
|
|
||||||
private final Executable method;
|
|
||||||
private final Method backup;
|
|
||||||
|
|
||||||
public LspHooker(XposedBridge.AdditionalHookInfo info, Executable origin, Method backup) {
|
|
||||||
this.additionalInfo = info;
|
|
||||||
this.method = origin;
|
|
||||||
this.backup = backup;
|
|
||||||
this.backup.setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object invokeOriginalMethod(Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException {
|
|
||||||
return backup.invoke(thisObject, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "RedundantSuppression"})
|
|
||||||
public Object handleHookedMethod(Object[] args) throws Throwable {
|
|
||||||
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
|
||||||
|
|
||||||
param.method = method;
|
|
||||||
|
|
||||||
if (Modifier.isStatic(method.getModifiers())) {
|
|
||||||
param.thisObject = null;
|
|
||||||
param.args = args;
|
|
||||||
} else {
|
|
||||||
param.thisObject = args[0];
|
|
||||||
param.args = new Object[args.length - 1];
|
|
||||||
System.arraycopy(args, 1, param.args, 0, args.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
|
|
||||||
final int callbacksLength = callbacksSnapshot.length;
|
|
||||||
if (callbacksLength == 0) {
|
|
||||||
try {
|
|
||||||
return backup.invoke(param.thisObject, param.args);
|
|
||||||
} catch (InvocationTargetException ite) {
|
|
||||||
throw ite.getCause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call "before method" callbacks
|
|
||||||
int beforeIdx = 0;
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
XposedBridge.log(t);
|
|
||||||
|
|
||||||
// reset result (ignoring what the unexpectedly exiting callback did)
|
|
||||||
param.setResult(null);
|
|
||||||
param.returnEarly = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (param.returnEarly) {
|
|
||||||
// skip remaining "before" callbacks and corresponding "after" callbacks
|
|
||||||
beforeIdx++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (++beforeIdx < callbacksLength);
|
|
||||||
|
|
||||||
// call original method if not requested otherwise
|
|
||||||
if (!param.returnEarly) {
|
|
||||||
try {
|
|
||||||
param.setResult(backup.invoke(param.thisObject, param.args));
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
param.setThrowable(e.getCause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call "after method" callbacks
|
|
||||||
int afterIdx = beforeIdx - 1;
|
|
||||||
do {
|
|
||||||
Object lastResult = param.getResult();
|
|
||||||
Throwable lastThrowable = param.getThrowable();
|
|
||||||
|
|
||||||
try {
|
|
||||||
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
XposedBridge.log(t);
|
|
||||||
|
|
||||||
// reset to last result (ignoring what the unexpectedly exiting callback did)
|
|
||||||
if (lastThrowable == null)
|
|
||||||
param.setResult(lastResult);
|
|
||||||
else
|
|
||||||
param.setThrowable(lastThrowable);
|
|
||||||
}
|
|
||||||
} while (--afterIdx >= 0);
|
|
||||||
|
|
||||||
// return
|
|
||||||
if (param.hasThrowable())
|
|
||||||
throw param.getThrowable();
|
|
||||||
else {
|
|
||||||
var result = param.getResult();
|
|
||||||
if (method instanceof Method) {
|
|
||||||
var returnType = ((Method) method).getReturnType();
|
|
||||||
if (!returnType.isPrimitive())
|
|
||||||
return returnType.cast(result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -28,8 +28,8 @@ import android.content.res.TypedArray;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.lsposed.lspd.BuildConfig;
|
import org.lsposed.lspd.BuildConfig;
|
||||||
|
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||||
import org.lsposed.lspd.nativebridge.ResourcesHook;
|
import org.lsposed.lspd.nativebridge.ResourcesHook;
|
||||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
|
||||||
|
|
||||||
import java.lang.reflect.AccessibleObject;
|
import java.lang.reflect.AccessibleObject;
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
|
|
@ -39,15 +39,11 @@ import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -78,10 +74,8 @@ public final class XposedBridge {
|
||||||
private static final Object[] EMPTY_ARRAY = new Object[0];
|
private static final Object[] EMPTY_ARRAY = new Object[0];
|
||||||
|
|
||||||
// built-in handlers
|
// built-in handlers
|
||||||
private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new NoValuesHashMap<>();
|
public static final CopyOnWriteArraySet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteArraySet<>();
|
||||||
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
|
/*package*/ static final CopyOnWriteArraySet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteArraySet<>();
|
||||||
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
|
|
||||||
/*package*/ static final CopyOnWriteSortedSet<XC_InitZygote> sInitZygoteCallbacks = new CopyOnWriteSortedSet<>();
|
|
||||||
|
|
||||||
private XposedBridge() {
|
private XposedBridge() {
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +118,7 @@ public final class XposedBridge {
|
||||||
ResourcesHook.makeInheritable(resClass, resClass.getDeclaredConstructors());
|
ResourcesHook.makeInheritable(resClass, resClass.getDeclaredConstructors());
|
||||||
ResourcesHook.makeInheritable(taClass, taClass.getDeclaredConstructors());
|
ResourcesHook.makeInheritable(taClass, taClass.getDeclaredConstructors());
|
||||||
ClassLoader myCL = XposedBridge.class.getClassLoader();
|
ClassLoader myCL = XposedBridge.class.getClassLoader();
|
||||||
dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass, taClass);
|
dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass.getName(), taClass.getName());
|
||||||
dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass");
|
dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass");
|
||||||
dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
|
dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
|
||||||
setObjectField(myCL, "parent", dummyClassLoader);
|
setObjectField(myCL, "parent", dummyClassLoader);
|
||||||
|
|
@ -180,7 +174,7 @@ public final class XposedBridge {
|
||||||
} else if (Proxy.isProxyClass(deoptimizedMethod.getDeclaringClass())) {
|
} else if (Proxy.isProxyClass(deoptimizedMethod.getDeclaringClass())) {
|
||||||
throw new IllegalArgumentException("Cannot deoptimize methods from proxy class: " + deoptimizedMethod);
|
throw new IllegalArgumentException("Cannot deoptimize methods from proxy class: " + deoptimizedMethod);
|
||||||
}
|
}
|
||||||
YahfaHooker.deoptMethodNative((Executable) deoptimizedMethod);
|
HookBridge.deoptimizeMethod((Executable) deoptimizedMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -211,28 +205,11 @@ public final class XposedBridge {
|
||||||
throw new IllegalArgumentException("Do not allow hooking inner methods");
|
throw new IllegalArgumentException("Do not allow hooking inner methods");
|
||||||
}
|
}
|
||||||
|
|
||||||
Executable targetMethod = (Executable) hookMethod;
|
|
||||||
|
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
throw new IllegalArgumentException("callback should not be null!");
|
throw new IllegalArgumentException("callback should not be null!");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean newMethod = false;
|
HookBridge.hookMethod((Executable) hookMethod, AdditionalHookInfo.class, callback.priority, callback);
|
||||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
|
||||||
synchronized (sHookedMethodCallbacks) {
|
|
||||||
callbacks = sHookedMethodCallbacks.get(targetMethod);
|
|
||||||
if (callbacks == null) {
|
|
||||||
callbacks = new CopyOnWriteSortedSet<>();
|
|
||||||
sHookedMethodCallbacks.put(targetMethod, callbacks);
|
|
||||||
newMethod = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callbacks.add(callback);
|
|
||||||
|
|
||||||
if (newMethod) {
|
|
||||||
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
|
|
||||||
YahfaHooker.hookMethod(targetMethod, additionalInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback.new Unhook(hookMethod);
|
return callback.new Unhook(hookMethod);
|
||||||
}
|
}
|
||||||
|
|
@ -247,13 +224,9 @@ public final class XposedBridge {
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
if (hookMethod instanceof Executable) {
|
||||||
synchronized (sHookedMethodCallbacks) {
|
HookBridge.unhookMethod((Executable) hookMethod, callback);
|
||||||
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
|
||||||
if (callbacks == null)
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
callbacks.remove(callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -305,12 +278,6 @@ public final class XposedBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clearLoadedPackages() {
|
|
||||||
synchronized (sLoadedPackageCallbacks) {
|
|
||||||
sLoadedPackageCallbacks.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a callback to be executed when the resources for an app are initialized.
|
* Adds a callback to be executed when the resources for an app are initialized.
|
||||||
*
|
*
|
||||||
|
|
@ -326,12 +293,6 @@ public final class XposedBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hookInitZygote(XC_InitZygote callback) {
|
|
||||||
synchronized (sInitZygoteCallbacks) {
|
|
||||||
sInitZygoteCallbacks.add(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basically the same as {@link Method#invoke}, but calls the original method
|
* Basically the same as {@link Method#invoke}, but calls the original method
|
||||||
* as it was before the interception by Xposed. Also, access permissions are not checked.
|
* as it was before the interception by Xposed. Also, access permissions are not checked.
|
||||||
|
|
@ -363,39 +324,7 @@ public final class XposedBridge {
|
||||||
throw new IllegalArgumentException("method must be of type Method or Constructor");
|
throw new IllegalArgumentException("method must be of type Method or Constructor");
|
||||||
}
|
}
|
||||||
|
|
||||||
return YahfaHooker.invokeOriginalMethod((Executable) method, thisObject, args);
|
return HookBridge.invokeOriginalMethod((Executable) method, thisObject, args);
|
||||||
}
|
|
||||||
|
|
||||||
private static class NoValuesHashMap<K, V> extends HashMap<K, V> {
|
|
||||||
@Override
|
|
||||||
public Collection values() {
|
|
||||||
return Collections.EMPTY_LIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<K> keySet() {
|
|
||||||
return Collections.EMPTY_SET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Entry<K, V>> entrySet() {
|
|
||||||
return Collections.EMPTY_SET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -449,10 +378,99 @@ public final class XposedBridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AdditionalHookInfo {
|
public static class AdditionalHookInfo {
|
||||||
public final CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
final Executable method;
|
||||||
|
final Object[][] callbacks;
|
||||||
|
|
||||||
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks) {
|
private AdditionalHookInfo(Executable method, Object[][] callbacks) {
|
||||||
|
this.method = method;
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Object callback(Object[] args) throws Throwable {
|
||||||
|
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
||||||
|
|
||||||
|
param.method = method;
|
||||||
|
|
||||||
|
if (Modifier.isStatic(method.getModifiers())) {
|
||||||
|
param.thisObject = null;
|
||||||
|
param.args = args;
|
||||||
|
} else {
|
||||||
|
param.thisObject = args[0];
|
||||||
|
param.args = new Object[args.length - 1];
|
||||||
|
System.arraycopy(args, 1, param.args, 0, args.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] callbacksSnapshot = callbacks[0];
|
||||||
|
final int callbacksLength = callbacksSnapshot.length;
|
||||||
|
if (callbacksLength == 0) {
|
||||||
|
try {
|
||||||
|
return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args);
|
||||||
|
} catch (InvocationTargetException ite) {
|
||||||
|
throw ite.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call "before method" callbacks
|
||||||
|
int beforeIdx = 0;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
XposedBridge.log(t);
|
||||||
|
|
||||||
|
// reset result (ignoring what the unexpectedly exiting callback did)
|
||||||
|
param.setResult(null);
|
||||||
|
param.returnEarly = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.returnEarly) {
|
||||||
|
// skip remaining "before" callbacks and corresponding "after" callbacks
|
||||||
|
beforeIdx++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (++beforeIdx < callbacksLength);
|
||||||
|
|
||||||
|
// call original method if not requested otherwise
|
||||||
|
if (!param.returnEarly) {
|
||||||
|
try {
|
||||||
|
param.setResult(HookBridge.invokeOriginalMethod(method, param.thisObject, param.args));
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
param.setThrowable(e.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call "after method" callbacks
|
||||||
|
int afterIdx = beforeIdx - 1;
|
||||||
|
do {
|
||||||
|
Object lastResult = param.getResult();
|
||||||
|
Throwable lastThrowable = param.getThrowable();
|
||||||
|
|
||||||
|
try {
|
||||||
|
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
XposedBridge.log(t);
|
||||||
|
|
||||||
|
// reset to last result (ignoring what the unexpectedly exiting callback did)
|
||||||
|
if (lastThrowable == null)
|
||||||
|
param.setResult(lastResult);
|
||||||
|
else
|
||||||
|
param.setThrowable(lastThrowable);
|
||||||
|
}
|
||||||
|
} while (--afterIdx >= 0);
|
||||||
|
|
||||||
|
// return
|
||||||
|
if (param.hasThrowable())
|
||||||
|
throw param.getThrowable();
|
||||||
|
else {
|
||||||
|
var result = param.getResult();
|
||||||
|
if (method instanceof Method) {
|
||||||
|
var returnType = ((Method) method).getReturnType();
|
||||||
|
if (!returnType.isPrimitive())
|
||||||
|
return returnType.cast(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ package de.robv.android.xposed;
|
||||||
import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
||||||
import static org.lsposed.lspd.deopt.PrebuiltMethodsDeopter.deoptResourceMethods;
|
import static org.lsposed.lspd.deopt.PrebuiltMethodsDeopter.deoptResourceMethods;
|
||||||
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
|
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
|
||||||
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
|
|
||||||
import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
|
|
||||||
import static de.robv.android.xposed.XposedBridge.sLoadedPackageCallbacks;
|
|
||||||
import static de.robv.android.xposed.XposedHelpers.callMethod;
|
import static de.robv.android.xposed.XposedHelpers.callMethod;
|
||||||
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
|
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
|
||||||
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||||
|
|
@ -58,8 +55,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
|
||||||
import de.robv.android.xposed.callbacks.XCallback;
|
import de.robv.android.xposed.callbacks.XCallback;
|
||||||
import hidden.HiddenApiBridge;
|
import hidden.HiddenApiBridge;
|
||||||
|
|
||||||
|
|
@ -219,80 +214,23 @@ public final class XposedInit {
|
||||||
return newRes;
|
return newRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to load all modules defined in <code>INSTALLER_DATA_BASE_DIR/conf/modules.list</code>
|
|
||||||
*/
|
|
||||||
private static final AtomicBoolean modulesLoaded = new AtomicBoolean(false);
|
|
||||||
private static final Object moduleLoadLock = new Object();
|
|
||||||
// @GuardedBy("moduleLoadLock")
|
|
||||||
private static final ArraySet<String> loadedModules = new ArraySet<>();
|
private static final ArraySet<String> loadedModules = new ArraySet<>();
|
||||||
|
|
||||||
public static ArraySet<String> getLoadedModules() {
|
synchronized public static ArraySet<String> getLoadedModules() {
|
||||||
synchronized (moduleLoadLock) {
|
|
||||||
return loadedModules;
|
return loadedModules;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadModules() {
|
synchronized public static void loadModules() {
|
||||||
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
|
|
||||||
if (hasLoaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
synchronized (moduleLoadLock) {
|
|
||||||
var moduleList = serviceClient.getModulesList();
|
var moduleList = serviceClient.getModulesList();
|
||||||
var newLoadedApk = new ArraySet<String>();
|
|
||||||
moduleList.forEach(module -> {
|
moduleList.forEach(module -> {
|
||||||
var apk = module.apkPath;
|
var apk = module.apkPath;
|
||||||
var name = module.packageName;
|
var name = module.packageName;
|
||||||
var file = module.file;
|
var file = module.file;
|
||||||
if (loadedModules.contains(apk)) {
|
if (loadModule(name, apk, file)) {
|
||||||
newLoadedApk.add(apk);
|
|
||||||
} else {
|
|
||||||
loadedModules.add(apk); // temporarily add it for XSharedPreference
|
loadedModules.add(apk); // temporarily add it for XSharedPreference
|
||||||
boolean loadSuccess = loadModule(name, apk, file);
|
|
||||||
if (loadSuccess) {
|
|
||||||
newLoadedApk.add(apk);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
loadedModules.clear();
|
|
||||||
loadedModules.addAll(newLoadedApk);
|
|
||||||
|
|
||||||
// refresh callback according to current loaded module list
|
|
||||||
pruneCallbacks();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// remove deactivated or outdated module callbacks
|
|
||||||
private static void pruneCallbacks() {
|
|
||||||
synchronized (moduleLoadLock) {
|
|
||||||
Object[] loadedPkgSnapshot = sLoadedPackageCallbacks.getSnapshot();
|
|
||||||
Object[] initPkgResSnapshot = sInitPackageResourcesCallbacks.getSnapshot();
|
|
||||||
Object[] initZygoteSnapshot = sInitZygoteCallbacks.getSnapshot();
|
|
||||||
for (Object loadedPkg : loadedPkgSnapshot) {
|
|
||||||
if (loadedPkg instanceof IModuleContext) {
|
|
||||||
if (!loadedModules.contains(((IModuleContext) loadedPkg).getApkPath())) {
|
|
||||||
sLoadedPackageCallbacks.remove((XC_LoadPackage) loadedPkg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Object initPkgRes : initPkgResSnapshot) {
|
|
||||||
if (initPkgRes instanceof IModuleContext) {
|
|
||||||
if (!loadedModules.contains(((IModuleContext) initPkgRes).getApkPath())) {
|
|
||||||
sInitPackageResourcesCallbacks.remove((XC_InitPackageResources) initPkgRes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Object initZygote : initZygoteSnapshot) {
|
|
||||||
if (initZygote instanceof IModuleContext) {
|
|
||||||
if (!loadedModules.contains(((IModuleContext) initZygote).getApkPath())) {
|
|
||||||
sInitZygoteCallbacks.remove((XC_InitZygote) initZygote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all so from an APK by reading <code>assets/native_init</code>.
|
* Load all so from an APK by reading <code>assets/native_init</code>.
|
||||||
|
|
@ -321,23 +259,18 @@ public final class XposedInit {
|
||||||
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
||||||
param.modulePath = apk;
|
param.modulePath = apk;
|
||||||
param.startsSystemServer = startsSystemServer;
|
param.startsSystemServer = startsSystemServer;
|
||||||
|
|
||||||
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
|
||||||
(IXposedHookZygoteInit) moduleInstance, param));
|
|
||||||
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moduleInstance instanceof IXposedHookLoadPackage) {
|
if (moduleInstance instanceof IXposedHookLoadPackage) {
|
||||||
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
|
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
|
||||||
(IXposedHookLoadPackage) moduleInstance, apk));
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moduleInstance instanceof IXposedHookInitPackageResources) {
|
if (moduleInstance instanceof IXposedHookInitPackageResources) {
|
||||||
hookResources();
|
hookResources();
|
||||||
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
|
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
|
||||||
(IXposedHookInitPackageResources) moduleInstance, apk));
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ package de.robv.android.xposed.callbacks;
|
||||||
|
|
||||||
import android.content.res.XResources;
|
import android.content.res.XResources;
|
||||||
|
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
import de.robv.android.xposed.IXposedHookInitPackageResources;
|
import de.robv.android.xposed.IXposedHookInitPackageResources;
|
||||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is only used for internal purposes, except for the {@link InitPackageResourcesParam}
|
* This class is only used for internal purposes, except for the {@link InitPackageResourcesParam}
|
||||||
|
|
@ -57,8 +58,8 @@ public abstract class XC_InitPackageResources extends XCallback implements IXpos
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public InitPackageResourcesParam(CopyOnWriteSortedSet<XC_InitPackageResources> callbacks) {
|
public InitPackageResourcesParam(CopyOnWriteArraySet<XC_InitPackageResources> callbacks) {
|
||||||
super(callbacks);
|
super(callbacks.toArray(new XCallback[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,59 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.robv.android.xposed.callbacks;
|
|
||||||
|
|
||||||
import de.robv.android.xposed.IXposedHookZygoteInit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is only used for internal purposes, except for the {@link StartupParam}
|
|
||||||
* subclass.
|
|
||||||
*/
|
|
||||||
public abstract class XC_InitZygote extends XCallback implements IXposedHookZygoteInit {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new callback with default priority.
|
|
||||||
*
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public XC_InitZygote() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new callback with a specific priority.
|
|
||||||
*
|
|
||||||
* @param priority See {@link XCallback#priority}.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public XC_InitZygote(int priority) {
|
|
||||||
super(priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void call(Param param) throws Throwable {
|
|
||||||
if (param instanceof StartupParam)
|
|
||||||
initZygote((StartupParam) param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -56,7 +56,7 @@ public abstract class XC_LayoutInflated extends XCallback {
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) {
|
public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) {
|
||||||
super(callbacks);
|
super((XCallback[]) callbacks.getSnapshot());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ package de.robv.android.xposed.callbacks;
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
|
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
import de.robv.android.xposed.IXposedHookLoadPackage;
|
import de.robv.android.xposed.IXposedHookLoadPackage;
|
||||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||||
|
|
||||||
|
|
@ -57,8 +59,8 @@ public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoa
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public LoadPackageParam(CopyOnWriteSortedSet<XC_LoadPackage> callbacks) {
|
public LoadPackageParam(CopyOnWriteArraySet<XC_LoadPackage> callbacks) {
|
||||||
super(callbacks);
|
super(callbacks.toArray(new XCallback[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import de.robv.android.xposed.IModuleContext;
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for Xposed callbacks.
|
* Base class for Xposed callbacks.
|
||||||
|
|
@ -36,7 +34,7 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||||
* This class only keeps a priority for ordering multiple callbacks.
|
* This class only keeps a priority for ordering multiple callbacks.
|
||||||
* The actual (abstract) callback methods are added by subclasses.
|
* The actual (abstract) callback methods are added by subclasses.
|
||||||
*/
|
*/
|
||||||
public abstract class XCallback implements Comparable<XCallback>, IModuleContext {
|
abstract public class XCallback {
|
||||||
/**
|
/**
|
||||||
* Callback priority, higher number means earlier execution.
|
* Callback priority, higher number means earlier execution.
|
||||||
*
|
*
|
||||||
|
|
@ -69,7 +67,7 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public final Object[] callbacks;
|
public final XCallback[] callbacks;
|
||||||
private Bundle extra;
|
private Bundle extra;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -83,8 +81,8 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
protected Param(CopyOnWriteSortedSet<? extends XCallback> callbacks) {
|
protected Param(XCallback[] callbacks) {
|
||||||
this.callbacks = callbacks.getSnapshot();
|
this.callbacks = callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -147,7 +145,7 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
||||||
|
|
||||||
for (int i = 0; i < param.callbacks.length; i++) {
|
for (int i = 0; i < param.callbacks.length; i++) {
|
||||||
try {
|
try {
|
||||||
((XCallback) param.callbacks[i]).call(param);
|
param.callbacks[i].call(param);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
XposedBridge.log(t);
|
XposedBridge.log(t);
|
||||||
}
|
}
|
||||||
|
|
@ -160,28 +158,6 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
||||||
protected void call(Param param) throws Throwable {
|
protected void call(Param param) throws Throwable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getApkPath() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int compareTo(XCallback other) {
|
|
||||||
if (this == other)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// order descending by priority
|
|
||||||
if (other.priority != this.priority)
|
|
||||||
return other.priority - this.priority;
|
|
||||||
// then randomly
|
|
||||||
else if (System.identityHashCode(this) < System.identityHashCode(other))
|
|
||||||
return -1;
|
|
||||||
else
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default priority, see {@link #priority}.
|
* The default priority, see {@link #priority}.
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ import org.lsposed.lspd.hooker.LoadedApkCstrHooker;
|
||||||
import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker;
|
import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker;
|
||||||
import org.lsposed.lspd.util.ParasiticManagerHooker;
|
import org.lsposed.lspd.util.ParasiticManagerHooker;
|
||||||
import org.lsposed.lspd.util.Utils;
|
import org.lsposed.lspd.util.Utils;
|
||||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
|
@ -77,7 +76,6 @@ public class Main {
|
||||||
|
|
||||||
public static void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
|
public static void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
|
||||||
// init logger
|
// init logger
|
||||||
YahfaHooker.init();
|
|
||||||
XposedBridge.initXResources();
|
XposedBridge.initXResources();
|
||||||
XposedInit.startsSystemServer = isSystem;
|
XposedInit.startsSystemServer = isSystem;
|
||||||
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
||||||
|
|
|
||||||
|
|
@ -1,103 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.lsposed.lspd.core.yahfa;
|
|
||||||
|
|
||||||
import org.lsposed.lspd.nativebridge.Yahfa;
|
|
||||||
import org.lsposed.lspd.util.Utils;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Executable;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class HookMain {
|
|
||||||
public static void backupAndHook(Executable target, Method hook, Method backup) {
|
|
||||||
Utils.logD(String.format("target=%s, hook=%s, backup=%s", target, hook, backup));
|
|
||||||
if (target == null) {
|
|
||||||
throw new IllegalArgumentException("null target method");
|
|
||||||
}
|
|
||||||
if (hook == null) {
|
|
||||||
throw new IllegalArgumentException("null hook method");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Modifier.isStatic(hook.getModifiers())) {
|
|
||||||
throw new IllegalArgumentException("Hook must be a static method: " + hook);
|
|
||||||
}
|
|
||||||
checkCompatibleMethods(target, hook, "Hook");
|
|
||||||
if (backup != null) {
|
|
||||||
if (!Modifier.isStatic(backup.getModifiers())) {
|
|
||||||
throw new IllegalArgumentException("Backup must be a static method: " + backup);
|
|
||||||
}
|
|
||||||
// backup is just a placeholder and the constraint could be less strict
|
|
||||||
checkCompatibleMethods(target, backup, "Backup");
|
|
||||||
}
|
|
||||||
if (!Yahfa.backupAndHookNative(target, hook, backup, Proxy.isProxyClass(target.getDeclaringClass()))) {
|
|
||||||
throw new RuntimeException("Failed to hook " + target + " with " + hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkCompatibleMethods(Executable original, Method replacement, String replacementName) {
|
|
||||||
ArrayList<Class<?>> originalParams;
|
|
||||||
if (original instanceof Method) {
|
|
||||||
originalParams = new ArrayList<>(Arrays.asList(original.getParameterTypes()));
|
|
||||||
} else if (original instanceof Constructor) {
|
|
||||||
originalParams = new ArrayList<>(Arrays.asList(original.getParameterTypes()));
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Type of target method is wrong");
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<Class<?>> replacementParams = new ArrayList<>(Arrays.asList(replacement.getParameterTypes()));
|
|
||||||
|
|
||||||
if (original instanceof Method
|
|
||||||
&& !Modifier.isStatic(original.getModifiers())) {
|
|
||||||
originalParams.add(0, original.getDeclaringClass());
|
|
||||||
} else if (original instanceof Constructor) {
|
|
||||||
originalParams.add(0, ((Constructor<?>) original).getDeclaringClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!Modifier.isStatic(replacement.getModifiers())) {
|
|
||||||
replacementParams.add(0, replacement.getDeclaringClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (original instanceof Method
|
|
||||||
&& !replacement.getReturnType().isAssignableFrom(((Method) original).getReturnType())) {
|
|
||||||
throw new IllegalArgumentException("Incompatible return types. " + "Original" + ": " + ((Method) original).getReturnType() + ", " + replacementName + ": " + replacement.getReturnType());
|
|
||||||
} else if (original instanceof Constructor) {
|
|
||||||
if (replacement.getReturnType().equals(Void.class)) {
|
|
||||||
throw new IllegalArgumentException("Incompatible return types. " + "<init>" + ": " + "V" + ", " + replacementName + ": " + replacement.getReturnType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalParams.size() != replacementParams.size()) {
|
|
||||||
throw new IllegalArgumentException("Number of arguments don't match. " + "Original" + ": " + originalParams.size() + ", " + replacementName + ": " + replacementParams.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < originalParams.size(); i++) {
|
|
||||||
if (!replacementParams.get(i).isAssignableFrom(originalParams.get(i))) {
|
|
||||||
throw new IllegalArgumentException("Incompatible argument #" + i + ": " + "Original" + ": " + originalParams.get(i) + ", " + replacementName + ": " + replacementParams.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -24,9 +24,8 @@ import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_BOOT_IMAGE;
|
||||||
import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_BOOT_IMAGE_MIUI_RES;
|
import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_BOOT_IMAGE_MIUI_RES;
|
||||||
import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_SYSTEM_SERVER;
|
import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_SYSTEM_SERVER;
|
||||||
|
|
||||||
import org.lsposed.lspd.nativebridge.Yahfa;
|
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||||
import org.lsposed.lspd.util.Utils;
|
import org.lsposed.lspd.util.Utils;
|
||||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -46,9 +45,9 @@ public class PrebuiltMethodsDeopter {
|
||||||
if (clazz == null) {
|
if (clazz == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Executable method = Yahfa.findMethodNative(clazz, caller[1], caller[2]);
|
Executable method = null;
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
YahfaHooker.deoptMethodNative(method);
|
HookBridge.deoptimizeMethod(method);
|
||||||
}
|
}
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
Utils.logE("error when deopting method: " + Arrays.toString(caller), throwable);
|
Utils.logE("error when deopting method: " + Arrays.toString(caller), throwable);
|
||||||
|
|
|
||||||
|
|
@ -1,28 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.lsposed.lspd.nativebridge;
|
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
|
||||||
|
|
||||||
public class ClassLinker {
|
|
||||||
|
|
||||||
public static native void setEntryPointsToInterpreter(Executable method);
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.lsposed.lspd.nativebridge;
|
||||||
|
|
||||||
|
import java.lang.reflect.Executable;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
public class HookBridge {
|
||||||
|
public static native boolean hookMethod(Executable hookMethod, Class<?> hooker, int priority, Object callback);
|
||||||
|
public static native boolean unhookMethod(Executable hookMethod, Object callback);
|
||||||
|
|
||||||
|
public static native boolean deoptimizeMethod(Executable method);
|
||||||
|
|
||||||
|
public static native Object invokeOriginalMethod(Executable method, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
|
||||||
|
}
|
||||||
|
|
@ -31,7 +31,7 @@ public class ResourcesHook {
|
||||||
|
|
||||||
public static native boolean makeInheritable(Class<?> clazz, Constructor<?>[] constructors);
|
public static native boolean makeInheritable(Class<?> clazz, Constructor<?>[] constructors);
|
||||||
|
|
||||||
public static native ClassLoader buildDummyClassLoader(ClassLoader parent, Class<?> resourceSuperClass, Class<?> typedArraySuperClass);
|
public static native ClassLoader buildDummyClassLoader(ClassLoader parent, String resourceSuperClass, String typedArraySuperClass);
|
||||||
|
|
||||||
public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes);
|
public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.lsposed.lspd.nativebridge;
|
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class Yahfa {
|
|
||||||
|
|
||||||
public static native boolean backupAndHookNative(Executable target, Method hook, Method backup, boolean isProxy);
|
|
||||||
|
|
||||||
// JNI.ToReflectedMethod() could return either Method or Constructor
|
|
||||||
public static native Executable findMethodNative(Class<?> targetClass, String methodName, String methodSig);
|
|
||||||
|
|
||||||
public static native void init(int sdkVersion);
|
|
||||||
|
|
||||||
public static native boolean isHooked(Executable member);
|
|
||||||
|
|
||||||
public static native Class<?> buildHooker(ClassLoader appClassLoader, char returnType, char[] params, String methodName, String hookerName);
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.lsposed.lspd.yahfa.dexmaker;
|
|
||||||
|
|
||||||
import org.lsposed.lspd.util.Utils;
|
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Member;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import de.robv.android.xposed.LspHooker;
|
|
||||||
import de.robv.android.xposed.XC_MethodHook;
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
|
||||||
|
|
||||||
public final class DynamicBridge {
|
|
||||||
private static final ConcurrentHashMap<Executable, LspHooker> hookedInfo = new ConcurrentHashMap<>();
|
|
||||||
private static final ConcurrentHashMap<Executable, Object> lockers = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public static void hookMethod(Executable hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
|
||||||
Utils.logD("hooking " + hookMethod);
|
|
||||||
|
|
||||||
synchronized (lockers.computeIfAbsent(hookMethod, (m) -> new Object())) {
|
|
||||||
var hooker = hookedInfo.getOrDefault(hookMethod, null);
|
|
||||||
if (hooker == null) {
|
|
||||||
Utils.logD("start to generate class for: " + hookMethod);
|
|
||||||
try {
|
|
||||||
final HookerDexMaker dexMaker = new HookerDexMaker();
|
|
||||||
dexMaker.start(hookMethod, additionalHookInfo);
|
|
||||||
hookedInfo.put(hookMethod, dexMaker.getHooker());
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Utils.logE("error occur when generating dex.", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (var callback : additionalHookInfo.callbacks.getSnapshot())
|
|
||||||
hooker.additionalInfo.callbacks.add((XC_MethodHook) callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
|
||||||
throws InvocationTargetException, IllegalAccessException {
|
|
||||||
if (!(method instanceof Executable)) {
|
|
||||||
throw new IllegalArgumentException("Only methods or constructors can be invoked.");
|
|
||||||
}
|
|
||||||
LspHooker hooker = hookedInfo.getOrDefault(method, null);
|
|
||||||
if (hooker == null) {
|
|
||||||
throw new IllegalStateException("method not hooked, cannot call original method.");
|
|
||||||
}
|
|
||||||
if (args == null) {
|
|
||||||
args = new Object[0];
|
|
||||||
}
|
|
||||||
return hooker.invokeOriginalMethod(thisObject, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +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) 2020 EdXposed Contributors
|
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.lsposed.lspd.yahfa.dexmaker;
|
|
||||||
|
|
||||||
import org.lsposed.lspd.core.yahfa.HookMain;
|
|
||||||
import org.lsposed.lspd.nativebridge.Yahfa;
|
|
||||||
import org.lsposed.lspd.util.Utils;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Executable;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import de.robv.android.xposed.LspHooker;
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public class HookerDexMaker {
|
|
||||||
|
|
||||||
public static final String METHOD_NAME_BACKUP = "backup";
|
|
||||||
public static final String FIELD_NAME_HOOKER = "hooker";
|
|
||||||
private static final HashMap<Class<?>, Character> descriptors = new HashMap<>() {{
|
|
||||||
put(int.class, 'I');
|
|
||||||
put(boolean.class, 'Z');
|
|
||||||
put(char.class, 'C');
|
|
||||||
put(long.class, 'J');
|
|
||||||
put(short.class, 'S');
|
|
||||||
put(float.class, 'F');
|
|
||||||
put(double.class, 'D');
|
|
||||||
put(byte.class, 'B');
|
|
||||||
put(void.class, 'V');
|
|
||||||
put(Object.class, 'L');
|
|
||||||
}};
|
|
||||||
|
|
||||||
private Class<?> mReturnType;
|
|
||||||
private Class<?>[] mActualParameterTypes;
|
|
||||||
|
|
||||||
private Executable mMember;
|
|
||||||
private XposedBridge.AdditionalHookInfo mHookInfo;
|
|
||||||
private LspHooker mHooker;
|
|
||||||
|
|
||||||
private static Class<?>[] getParameterTypes(Executable method, boolean isStatic) {
|
|
||||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
|
||||||
for (int i = 0; i < parameterTypes.length; ++i) {
|
|
||||||
parameterTypes[i] = parameterTypes[i].isPrimitive() ? parameterTypes[i] : Object.class;
|
|
||||||
}
|
|
||||||
if (isStatic) {
|
|
||||||
return parameterTypes;
|
|
||||||
}
|
|
||||||
int parameterSize = parameterTypes.length;
|
|
||||||
Class<?>[] newParameterTypes = new Class<?>[parameterSize + 1];
|
|
||||||
newParameterTypes[0] = Object.class;
|
|
||||||
System.arraycopy(parameterTypes, 0, newParameterTypes, 1, parameterSize);
|
|
||||||
return newParameterTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static char getDescriptor(Class<?> clazz) {
|
|
||||||
return descriptors.getOrDefault(clazz, 'L');
|
|
||||||
}
|
|
||||||
|
|
||||||
private static char[] getDescriptors(Class<?>[] classes) {
|
|
||||||
var descriptors = new char[classes.length];
|
|
||||||
for (int i = 0; i < classes.length; ++i) {
|
|
||||||
descriptors[i] = getDescriptor(classes[i]);
|
|
||||||
}
|
|
||||||
return descriptors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(Executable member, XposedBridge.AdditionalHookInfo hookInfo) throws Exception {
|
|
||||||
if (member instanceof Method) {
|
|
||||||
Method method = (Method) member;
|
|
||||||
mReturnType = method.getReturnType();
|
|
||||||
mActualParameterTypes = getParameterTypes(method, Modifier.isStatic(method.getModifiers()));
|
|
||||||
} else if (member instanceof Constructor) {
|
|
||||||
Constructor constructor = (Constructor) member;
|
|
||||||
mReturnType = void.class;
|
|
||||||
mActualParameterTypes = getParameterTypes(constructor, false);
|
|
||||||
}
|
|
||||||
mMember = member;
|
|
||||||
mHookInfo = hookInfo;
|
|
||||||
|
|
||||||
long startTime = System.nanoTime();
|
|
||||||
doMake(member instanceof Constructor ? "constructor" : member.getName());
|
|
||||||
long endTime = System.nanoTime();
|
|
||||||
Utils.logD("Hook time: " + (endTime - startTime) / 1e6 + "ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doMake(String methodName) throws Exception {
|
|
||||||
Class<?> hookClass = Yahfa.buildHooker(LspHooker.class.getClassLoader(), getDescriptor(mReturnType), getDescriptors(mActualParameterTypes), methodName, LspHooker.class.getCanonicalName());
|
|
||||||
if (hookClass == null) throw new IllegalStateException("Failed to hook " + methodName);
|
|
||||||
// Execute our newly-generated code in-process.
|
|
||||||
Method backupMethod = hookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
|
|
||||||
mHooker = new LspHooker(mHookInfo, mMember, backupMethod);
|
|
||||||
var hooker = hookClass.getDeclaredField(FIELD_NAME_HOOKER);
|
|
||||||
hooker.setAccessible(true);
|
|
||||||
hooker.set(null, mHooker);
|
|
||||||
Method hookMethod = hookClass.getMethod(methodName, mActualParameterTypes);
|
|
||||||
HookMain.backupAndHook(mMember, hookMethod, backupMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LspHooker getHooker() {
|
|
||||||
return mHooker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +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
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.lsposed.lspd.yahfa.hooker;
|
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import org.lsposed.lspd.nativebridge.ClassLinker;
|
|
||||||
import org.lsposed.lspd.nativebridge.Yahfa;
|
|
||||||
import org.lsposed.lspd.yahfa.dexmaker.DynamicBridge;
|
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedBridge.AdditionalHookInfo;
|
|
||||||
|
|
||||||
public class YahfaHooker {
|
|
||||||
public static void init() {
|
|
||||||
int sdkVersion = Build.VERSION.SDK_INT;
|
|
||||||
if (Build.VERSION.PREVIEW_SDK_INT != 0) {
|
|
||||||
sdkVersion += 1;
|
|
||||||
}
|
|
||||||
Yahfa.init(sdkVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void hookMethod(Executable method, AdditionalHookInfo additionalInfo) {
|
|
||||||
DynamicBridge.hookMethod(method, additionalInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Object invokeOriginalMethod(Executable method, Object thisObject, Object[] args) throws Throwable {
|
|
||||||
return DynamicBridge.invokeOriginalMethod(method, thisObject, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void deoptMethodNative(Executable method) {
|
|
||||||
ClassLinker.setEntryPointsToInterpreter(method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
project(lspd)
|
||||||
|
cmake_minimum_required(VERSION 3.4.1)
|
||||||
|
|
||||||
|
add_subdirectory(${EXTERNAL_ROOT} external)
|
||||||
|
|
||||||
|
configure_file(template/config.cpp src/config.cpp)
|
||||||
|
|
||||||
|
aux_source_directory(src SRC_LIST)
|
||||||
|
aux_source_directory(src/jni SRC_LIST)
|
||||||
|
if (${API} STREQUAL "riru")
|
||||||
|
set(SRC_LIST ${SRC_LIST} api/riru_main.cpp)
|
||||||
|
elseif (${API} STREQUAL "zygisk")
|
||||||
|
set(SRC_LIST ${SRC_LIST} api/zygisk_main.cpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(${PROJECT_NAME} SHARED ${SRC_LIST} ${CMAKE_CURRENT_BINARY_DIR}/src/config.cpp)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC include)
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE src)
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME} dobby dex_builder_static log lsplant_static)
|
||||||
|
|
||||||
|
if (DEFINED DEBUG_SYMBOLS_PATH)
|
||||||
|
set(DEBUG_SYMBOLS_PATH ${DEBUG_SYMBOLS_PATH}/${API})
|
||||||
|
message(STATUS "Debug symbols will be placed at ${DEBUG_SYMBOLS_PATH}")
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} --only-keep-debug $<TARGET_FILE:${PROJECT_NAME}>
|
||||||
|
${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
|
||||||
|
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:${PROJECT_NAME}>
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
|
||||||
|
$<TARGET_FILE:${PROJECT_NAME}>)
|
||||||
|
endif()
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
#ifndef RIRU_H
|
||||||
|
#define RIRU_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
typedef void(onModuleLoaded_v9)();
|
||||||
|
|
||||||
|
#ifndef RIRU_MODULE
|
||||||
|
typedef int(shouldSkipUid_v9)(int uid);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void(nativeForkAndSpecializePre_v9)(
|
||||||
|
JNIEnv *env, jclass cls, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags,
|
||||||
|
jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName,
|
||||||
|
jintArray *fdsToClose, jintArray *fdsToIgnore, jboolean *is_child_zygote,
|
||||||
|
jstring *instructionSet, jstring *appDataDir, jboolean *isTopApp,
|
||||||
|
jobjectArray *pkgDataInfoList,
|
||||||
|
jobjectArray *whitelistedDataInfoList, jboolean *bindMountAppDataDirs,
|
||||||
|
jboolean *bindMountAppStorageDirs);
|
||||||
|
|
||||||
|
typedef void(nativeForkAndSpecializePost_v9)(JNIEnv *env, jclass cls, jint res);
|
||||||
|
|
||||||
|
typedef void(nativeForkSystemServerPre_v9)(
|
||||||
|
JNIEnv *env, jclass cls, uid_t *uid, gid_t *gid, jintArray *gids, jint *runtimeFlags,
|
||||||
|
jobjectArray *rlimits, jlong *permittedCapabilities, jlong *effectiveCapabilities);
|
||||||
|
|
||||||
|
typedef void(nativeForkSystemServerPost_v9)(JNIEnv *env, jclass cls, jint res);
|
||||||
|
|
||||||
|
typedef void(nativeSpecializeAppProcessPre_v9)(
|
||||||
|
JNIEnv *env, jclass cls, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags,
|
||||||
|
jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName,
|
||||||
|
jboolean *startChildZygote, jstring *instructionSet, jstring *appDataDir,
|
||||||
|
jboolean *isTopApp, jobjectArray *pkgDataInfoList, jobjectArray *whitelistedDataInfoList,
|
||||||
|
jboolean *bindMountAppDataDirs, jboolean *bindMountAppStorageDirs);
|
||||||
|
|
||||||
|
typedef void(nativeSpecializeAppProcessPost_v9)(JNIEnv *env, jclass cls);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int supportHide;
|
||||||
|
int version;
|
||||||
|
const char *versionName;
|
||||||
|
onModuleLoaded_v9 *onModuleLoaded;
|
||||||
|
#ifndef RIRU_MODULE
|
||||||
|
shouldSkipUid_v9 *shouldSkipUid;
|
||||||
|
#else
|
||||||
|
void *unused;
|
||||||
|
#endif
|
||||||
|
nativeForkAndSpecializePre_v9 *forkAndSpecializePre;
|
||||||
|
nativeForkAndSpecializePost_v9 *forkAndSpecializePost;
|
||||||
|
nativeForkSystemServerPre_v9 *forkSystemServerPre;
|
||||||
|
nativeForkSystemServerPost_v9 *forkSystemServerPost;
|
||||||
|
nativeSpecializeAppProcessPre_v9 *specializeAppProcessPre;
|
||||||
|
nativeSpecializeAppProcessPost_v9 *specializeAppProcessPost;
|
||||||
|
} RiruModuleInfo;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int moduleApiVersion;
|
||||||
|
RiruModuleInfo moduleInfo;
|
||||||
|
} RiruVersionedModuleInfo;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int riruApiVersion;
|
||||||
|
void *unused;
|
||||||
|
const char *magiskModulePath;
|
||||||
|
int *allowUnload;
|
||||||
|
} Riru;
|
||||||
|
|
||||||
|
typedef RiruVersionedModuleInfo *(RiruInit_t)(Riru *);
|
||||||
|
|
||||||
|
#ifdef RIRU_MODULE
|
||||||
|
#define RIRUD_ADDRESS "rirud"
|
||||||
|
|
||||||
|
|
||||||
|
#if __cplusplus < 201103L
|
||||||
|
#define RIRU_EXPORT __attribute__((visibility("default"))) __attribute__((used))
|
||||||
|
#else
|
||||||
|
#define RIRU_EXPORT [[gnu::visibility("default")]] [[gnu::used]]
|
||||||
|
#endif
|
||||||
|
|
||||||
|
RIRU_EXPORT RiruVersionedModuleInfo *init(Riru *riru) ;
|
||||||
|
|
||||||
|
extern int riru_api_version;
|
||||||
|
extern const char *riru_magisk_module_path;
|
||||||
|
extern int *riru_allow_unload;
|
||||||
|
|
||||||
|
#if !__cplusplus && __STDC_VERSION__ < 199409L
|
||||||
|
#define RIRU_INLINE __attribute__((weak)) __inline__
|
||||||
|
#elif !__cplusplus
|
||||||
|
#define RIRU_INLINE __attribute__((weak)) inline extern
|
||||||
|
#else
|
||||||
|
#define RIRU_INLINE inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
RIRU_INLINE const char *riru_get_magisk_module_path() {
|
||||||
|
if (riru_api_version >= 24) {
|
||||||
|
return riru_magisk_module_path;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
RIRU_INLINE void riru_set_unload_allowed(int allowed) {
|
||||||
|
if (riru_api_version >= 25 && riru_allow_unload) {
|
||||||
|
*riru_allow_unload = allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef RIRU_INLINE
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //RIRU_H
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
#include "symbol_cache.h"
|
#include "symbol_cache.h"
|
||||||
|
|
||||||
#define RIRU_MODULE
|
#define RIRU_MODULE
|
||||||
#include <riru.h>
|
#include "riru.h"
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
int *allowUnload = nullptr;
|
int *allowUnload = nullptr;
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
#include "jni/zygisk.h"
|
#include "zygisk.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include "context.h"
|
#include "context.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
@ -328,7 +328,10 @@ namespace lspd {
|
||||||
auto *process = env_->FindClass("android/os/Process");
|
auto *process = env_->FindClass("android/os/Process");
|
||||||
auto *set_argv0 = env_->GetStaticMethodID(process, "setArgV0",
|
auto *set_argv0 = env_->GetStaticMethodID(process, "setArgV0",
|
||||||
"(Ljava/lang/String;)V");
|
"(Ljava/lang/String;)V");
|
||||||
JNI_CallStaticVoidMethod(env_, process, set_argv0, JNI_NewStringUTF(env_, "system_server"));
|
auto *name = env_->NewStringUTF("system_server");
|
||||||
|
env_->CallStaticVoidMethod(process, set_argv0, name);
|
||||||
|
env_->DeleteLocalRef(name);
|
||||||
|
env_->DeleteLocalRef(process);
|
||||||
}
|
}
|
||||||
Context::GetInstance()->OnNativeForkSystemServerPost(env_);
|
Context::GetInstance()->OnNativeForkSystemServerPost(env_);
|
||||||
if (*allowUnload) api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
if (*allowUnload) api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||||
|
|
@ -21,14 +21,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "symbol_cache.h"
|
#include "symbol_cache.h"
|
||||||
#include "base/object.h"
|
#include "utils/hook_helper.hpp"
|
||||||
#include "context.h"
|
#include "context.h"
|
||||||
#include "runtime.h"
|
#include "runtime.h"
|
||||||
|
|
||||||
namespace art {
|
namespace art {
|
||||||
|
|
||||||
namespace hidden_api {
|
namespace hidden_api {
|
||||||
|
using lsplant::operator""_tstr;
|
||||||
CREATE_FUNC_SYMBOL_ENTRY(void, DexFile_setTrusted, JNIEnv *env, jclass clazz,
|
CREATE_FUNC_SYMBOL_ENTRY(void, DexFile_setTrusted, JNIEnv *env, jclass clazz,
|
||||||
jobject j_cookie) {
|
jobject j_cookie) {
|
||||||
if (DexFile_setTrustedSym != nullptr) [[likely]] {
|
if (DexFile_setTrustedSym != nullptr) [[likely]] {
|
||||||
|
|
@ -36,7 +36,7 @@ namespace art {
|
||||||
DexFile_setTrustedSym(env, clazz, j_cookie);
|
DexFile_setTrustedSym(env, clazz, j_cookie);
|
||||||
Runtime::Current()->SetJavaDebuggable(false);
|
Runtime::Current()->SetJavaDebuggable(false);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
maybeSetTrusted(JNIEnv *env, jclass clazz, jobject class_loader, jobject j_cookie) {
|
maybeSetTrusted(JNIEnv *env, jclass clazz, jobject class_loader, jobject j_cookie) {
|
||||||
|
|
@ -54,7 +54,7 @@ namespace art {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CREATE_HOOK_STUB_ENTRIES(
|
CREATE_HOOK_STUB_ENTRY(
|
||||||
"_ZN3artL25DexFile_openDexFileNativeEP7_JNIEnvP7_jclassP8_jstringS5_iP8_jobjectP13_jobjectArray",
|
"_ZN3artL25DexFile_openDexFileNativeEP7_JNIEnvP7_jclassP8_jstringS5_iP8_jobjectP13_jobjectArray",
|
||||||
jobject, DexFile_openDexFileNative, (JNIEnv * env,
|
jobject, DexFile_openDexFileNative, (JNIEnv * env,
|
||||||
jclass clazz,
|
jclass clazz,
|
||||||
|
|
@ -71,7 +71,7 @@ namespace art {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE_HOOK_STUB_ENTRIES(
|
CREATE_HOOK_STUB_ENTRY(
|
||||||
"_ZN3artL34DexFile_openInMemoryDexFilesNativeEP7_JNIEnvP7_jclassP13_jobjectArrayS5_P10_jintArrayS7_P8_jobjectS5_",
|
"_ZN3artL34DexFile_openInMemoryDexFilesNativeEP7_JNIEnvP7_jclassP13_jobjectArrayS5_P10_jintArrayS7_P8_jobjectS5_",
|
||||||
jobject, DexFile_openInMemoryDexFilesNative, (JNIEnv * env,
|
jobject, DexFile_openInMemoryDexFilesNative, (JNIEnv * env,
|
||||||
jclass clazz,
|
jclass clazz,
|
||||||
|
|
@ -89,7 +89,7 @@ namespace art {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE_HOOK_STUB_ENTRIES(
|
CREATE_HOOK_STUB_ENTRY(
|
||||||
"_ZN3artL29DexFile_createCookieWithArrayEP7_JNIEnvP7_jclassP11_jbyteArrayii",
|
"_ZN3artL29DexFile_createCookieWithArrayEP7_JNIEnvP7_jclassP11_jbyteArrayii",
|
||||||
jobject, DexFile_createCookieWithArray, (JNIEnv * env,
|
jobject, DexFile_createCookieWithArray, (JNIEnv * env,
|
||||||
jclass clazz,
|
jclass clazz,
|
||||||
|
|
@ -102,7 +102,7 @@ namespace art {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE_HOOK_STUB_ENTRIES(
|
CREATE_HOOK_STUB_ENTRY(
|
||||||
"_ZN3artL36DexFile_createCookieWithDirectBufferEP7_JNIEnvP7_jclassP8_jobjectii",
|
"_ZN3artL36DexFile_createCookieWithDirectBufferEP7_JNIEnvP7_jclassP8_jobjectii",
|
||||||
jobject, DexFile_createCookieWithDirectBuffer, (JNIEnv * env,
|
jobject, DexFile_createCookieWithDirectBuffer, (JNIEnv * env,
|
||||||
jclass clazz,
|
jclass clazz,
|
||||||
|
|
@ -115,20 +115,20 @@ namespace art {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
inline void DisableHiddenApi([[maybe_unused]] const SandHook::ElfImg &handle) {
|
inline void DisableHiddenApi(const lsplant::HookHandler &handler) {
|
||||||
|
|
||||||
const int api_level = lspd::GetAndroidApiLevel();
|
const int api_level = lspd::GetAndroidApiLevel();
|
||||||
if (api_level < __ANDROID_API_P__) {
|
if (api_level < __ANDROID_API_P__) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DexFile_setTrustedSym = reinterpret_cast<decltype(DexFile_setTrustedSym)>(lspd::symbol_cache->setTrusted);
|
DexFile_setTrustedSym = reinterpret_cast<decltype(DexFile_setTrustedSym)>(lspd::symbol_cache->setTrusted);
|
||||||
lspd::HookSymNoHandle(lspd::symbol_cache->openDexFileNative, DexFile_openDexFileNative);
|
lsplant::HookSymNoHandle(handler, lspd::symbol_cache->openDexFileNative, DexFile_openDexFileNative);
|
||||||
lspd::HookSymNoHandle(lspd::symbol_cache->openInMemoryDexFilesNative,
|
lsplant::HookSymNoHandle(handler, lspd::symbol_cache->openInMemoryDexFilesNative,
|
||||||
DexFile_openInMemoryDexFilesNative);
|
DexFile_openInMemoryDexFilesNative);
|
||||||
if (api_level == __ANDROID_API_P__) {
|
if (api_level == __ANDROID_API_P__) {
|
||||||
lspd::HookSymNoHandle(lspd::symbol_cache->createCookieWithArray,
|
lsplant::HookSymNoHandle(handler, lspd::symbol_cache->createCookieWithArray,
|
||||||
DexFile_createCookieWithArray);
|
DexFile_createCookieWithArray);
|
||||||
lspd::HookSymNoHandle(lspd::symbol_cache->createCookieWithDirectBuffer,
|
lsplant::HookSymNoHandle(handler, lspd::symbol_cache->createCookieWithDirectBuffer,
|
||||||
DexFile_createCookieWithDirectBuffer);
|
DexFile_createCookieWithDirectBuffer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -20,11 +20,11 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <base/object.h>
|
#include "utils/hook_helper.hpp"
|
||||||
|
|
||||||
namespace art {
|
namespace art {
|
||||||
|
|
||||||
class Runtime : public lspd::HookedObject {
|
class Runtime {
|
||||||
private:
|
private:
|
||||||
inline static Runtime *instance_;
|
inline static Runtime *instance_;
|
||||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, void *thiz, bool value) {
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, void *thiz, bool value) {
|
||||||
|
|
@ -34,23 +34,22 @@ namespace art {
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Runtime(void *thiz) : HookedObject(thiz) {}
|
|
||||||
|
|
||||||
inline static Runtime *Current() {
|
inline static Runtime *Current() {
|
||||||
return instance_;
|
return instance_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetJavaDebuggable(bool value) {
|
void SetJavaDebuggable(bool value) {
|
||||||
SetJavaDebuggable(thiz_, value);
|
SetJavaDebuggable(this, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ApiSensitive(Level.LOW)
|
// @ApiSensitive(Level.LOW)
|
||||||
inline static void Setup(const SandHook::ElfImg &handle) {
|
inline static void Setup(const lsplant::HookHandler &handler) {
|
||||||
|
void **instance = nullptr;
|
||||||
RETRIEVE_FIELD_SYMBOL(instance, "_ZN3art7Runtime9instance_E");
|
RETRIEVE_FIELD_SYMBOL(instance, "_ZN3art7Runtime9instance_E");
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(SetJavaDebuggable, "_ZN3art7Runtime17SetJavaDebuggableEb");
|
RETRIEVE_MEM_FUNC_SYMBOL(SetJavaDebuggable, "_ZN3art7Runtime17SetJavaDebuggableEb");
|
||||||
void *thiz = *reinterpret_cast<void **>(instance);
|
void *thiz = *instance;
|
||||||
LOGD("_ZN3art7Runtime9instance_E = %p", thiz);
|
LOGD("_ZN3art7Runtime9instance_E = %p", thiz);
|
||||||
instance_ = new Runtime(thiz);
|
instance_ = reinterpret_cast<Runtime*>(thiz);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,12 +25,13 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "macros.h"
|
#include "macros.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "utils/hook_helper.hpp"
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
|
|
||||||
//#define LOG_DISABLED
|
//#define LOG_DISABLED
|
||||||
//#define DEBUG
|
//#define DEBUG
|
||||||
|
using lsplant::operator""_tstr;
|
||||||
|
|
||||||
inline bool constexpr Is64() {
|
inline bool constexpr Is64() {
|
||||||
#if defined(__LP64__)
|
#if defined(__LP64__)
|
||||||
|
|
@ -59,9 +60,7 @@ namespace lspd {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline static constexpr auto kEntryClassName = "org.lsposed.lspd.core.Main"_tstr;
|
inline static constexpr auto kEntryClassName = "org.lsposed.lspd.core.Main"_tstr;
|
||||||
inline static constexpr auto kClassLinkerClassName = "org.lsposed.lspd.nativebridge.ClassLinker"_tstr;
|
|
||||||
inline static constexpr auto kBridgeServiceClassName = "org.lsposed.lspd.service.BridgeService"_tstr;
|
inline static constexpr auto kBridgeServiceClassName = "org.lsposed.lspd.service.BridgeService"_tstr;
|
||||||
inline static constexpr auto kDexPath = "framework/lspd.dex"_tstr;
|
|
||||||
|
|
||||||
inline static constexpr auto kLibArtName = "libart.so"_tstr;
|
inline static constexpr auto kLibArtName = "libart.so"_tstr;
|
||||||
inline static constexpr auto kLibFwName = "libandroidfw.so"_tstr;
|
inline static constexpr auto kLibFwName = "libandroidfw.so"_tstr;
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include "utils/hook_helper.hpp"
|
||||||
|
|
||||||
// @ApiSensitive(Level.MIDDLE)
|
// @ApiSensitive(Level.MIDDLE)
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
@ -173,7 +174,7 @@ namespace android {
|
||||||
return {nullptr, 0u};
|
return {nullptr, 0u};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool setup(const SandHook::ElfImg &handle) {
|
static bool setup(const lsplant::HookHandler &handler) {
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(stringAt, LP_SELECT("_ZNK7android13ResStringPool8stringAtEjPj", "_ZNK7android13ResStringPool8stringAtEmPm"));
|
RETRIEVE_MEM_FUNC_SYMBOL(stringAt, LP_SELECT("_ZNK7android13ResStringPool8stringAtEjPj", "_ZNK7android13ResStringPool8stringAtEmPm"));
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(stringAtS, LP_SELECT("_ZNK7android13ResStringPool8stringAtEj", "_ZNK7android13ResStringPool8stringAtEm"));
|
RETRIEVE_MEM_FUNC_SYMBOL(stringAtS, LP_SELECT("_ZNK7android13ResStringPool8stringAtEj", "_ZNK7android13ResStringPool8stringAtEm"));
|
||||||
return !stringAtSym || !stringAtSSym;
|
return !stringAtSym || !stringAtSSym;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h> // for size_t
|
||||||
|
#include <unistd.h> // for TEMP_FAILURE_RETRY
|
||||||
|
#include <utility>
|
||||||
|
// The arraysize(arr) macro returns the # of elements in an array arr.
|
||||||
|
// The expression is a compile-time constant, and therefore can be
|
||||||
|
// used in defining new arrays, for example. If you use arraysize on
|
||||||
|
// a pointer by mistake, you will get a compile-time error.
|
||||||
|
template<typename T, size_t N>
|
||||||
|
[[gnu::always_inline]] constexpr inline size_t arraysize(T(&)[N]) {
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* 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) 2020 EdXposed Contributors
|
||||||
|
* Copyright (C) 2021 LSPosed Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include "dobby.h"
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunused-value"
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <context.h>
|
||||||
|
#include "macros.h"
|
||||||
|
#include "utils/jni_helper.hpp"
|
||||||
|
#include "logging.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#define _uintval(p) reinterpret_cast<uintptr_t>(p)
|
||||||
|
#define _ptr(p) reinterpret_cast<void *>(p)
|
||||||
|
#define _align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1))
|
||||||
|
#define _align_down(x, n) ((x) & -(n))
|
||||||
|
#define _page_size 4096
|
||||||
|
#define _page_align(n) _align_up(static_cast<uintptr_t>(n), _page_size)
|
||||||
|
#define _ptr_align(x) _ptr(_align_down(reinterpret_cast<uintptr_t>(x), _page_size))
|
||||||
|
#define _make_rwx(p, n) ::mprotect(_ptr_align(p), \
|
||||||
|
_page_align(_uintval(p) + n) != _page_align(_uintval(p)) ? _page_align(n) + _page_size : _page_align(n), \
|
||||||
|
PROT_READ | PROT_WRITE | PROT_EXEC)
|
||||||
|
|
||||||
|
namespace lspd {
|
||||||
|
|
||||||
|
[[gnu::always_inline]]
|
||||||
|
inline bool RegisterNativeMethodsInternal(JNIEnv *env,
|
||||||
|
const char *class_name,
|
||||||
|
const JNINativeMethod *methods,
|
||||||
|
jint method_count) {
|
||||||
|
|
||||||
|
auto clazz = Context::GetInstance()->FindClassFromCurrentLoader(env, class_name);
|
||||||
|
if (clazz.get() == nullptr) {
|
||||||
|
LOGF("Couldn't find class: %s", class_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return JNI_RegisterNatives(env, clazz, methods, method_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
|
||||||
|
reinterpret_cast<to>
|
||||||
|
#else
|
||||||
|
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
|
||||||
|
(to)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LSP_NATIVE_METHOD
|
||||||
|
#define LSP_NATIVE_METHOD(className, functionName, signature) \
|
||||||
|
{ #functionName, \
|
||||||
|
signature, \
|
||||||
|
_NATIVEHELPER_JNI_MACRO_CAST(void*) (Java_org_lsposed_lspd_nativebridge_## className ## _ ## functionName) \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define JNI_START [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz
|
||||||
|
|
||||||
|
#ifndef LSP_DEF_NATIVE_METHOD
|
||||||
|
#define LSP_DEF_NATIVE_METHOD(ret, className, functionName, ...) \
|
||||||
|
extern "C" ret Java_org_lsposed_lspd_nativebridge_## className ## _ ## functionName (JNI_START, ## __VA_ARGS__)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define REGISTER_LSP_NATIVE_METHODS(class_name) \
|
||||||
|
RegisterNativeMethodsInternal(env, "org.lsposed.lspd.nativebridge." #class_name, gMethods, arraysize(gMethods))
|
||||||
|
|
||||||
|
inline int HookFunction(void *original, void *replace, void **backup) {
|
||||||
|
_make_rwx(original, _page_size);
|
||||||
|
if constexpr (isDebug) {
|
||||||
|
Dl_info info;
|
||||||
|
if (dladdr(original, &info))
|
||||||
|
LOGD("Hooking %s (%p) from %s (%p)",
|
||||||
|
info.dli_sname ? info.dli_sname : "(unknown symbol)", info.dli_saddr,
|
||||||
|
info.dli_fname ? info.dli_fname : "(unknown file)", info.dli_fbase);
|
||||||
|
}
|
||||||
|
return DobbyHook(original, replace, backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int UnhookFunction(void *original) {
|
||||||
|
if constexpr (isDebug) {
|
||||||
|
Dl_info info;
|
||||||
|
if (dladdr(original, &info))
|
||||||
|
LOGD("Unhooking %s (%p) from %s (%p)",
|
||||||
|
info.dli_sname ? info.dli_sname : "(unknown symbol)", info.dli_saddr,
|
||||||
|
info.dli_fname ? info.dli_fname : "(unknown file)", info.dli_fbase);
|
||||||
|
}
|
||||||
|
return DobbyDestroy(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lspd
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
@ -43,45 +43,6 @@ namespace lspd {
|
||||||
}();
|
}();
|
||||||
return api_level;
|
return api_level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template<char... chars>
|
|
||||||
struct tstring : public std::integer_sequence<char, chars...> {
|
|
||||||
inline constexpr static const char *c_str() {
|
|
||||||
return str_;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline constexpr operator std::string_view() const {
|
|
||||||
return c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
constexpr static char str_[]{chars..., '\0'};
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T, T... chars>
|
|
||||||
inline constexpr tstring<chars...> operator ""_tstr() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<char... as, char... bs>
|
|
||||||
inline constexpr tstring<as..., bs...>
|
|
||||||
operator+(const tstring<as...> &, const tstring<bs...> &) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<char... as>
|
|
||||||
inline constexpr auto operator+(const std::string &a, const tstring<as...> &) {
|
|
||||||
char b[]{as..., '\0'};
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<char... as>
|
|
||||||
inline constexpr auto operator+(const tstring<as...> &, const std::string &b) {
|
|
||||||
char a[]{as..., '\0'};
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
|
|
@ -19,19 +19,23 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include "jni_helper.h"
|
#include "config.h"
|
||||||
#include "jni/art_class_linker.h"
|
#include "utils/jni_helper.hpp"
|
||||||
#include "jni/yahfa.h"
|
|
||||||
#include "jni/resources_hook.h"
|
#include "jni/resources_hook.h"
|
||||||
#include <art/runtime/jni_env_ext.h>
|
|
||||||
#include "context.h"
|
#include "context.h"
|
||||||
#include "native_hook.h"
|
#include "native_hook.h"
|
||||||
|
#include "elf_util.h"
|
||||||
|
#include "jni/hook_bridge.h"
|
||||||
#include "jni/native_api.h"
|
#include "jni/native_api.h"
|
||||||
#include "service.h"
|
#include "service.h"
|
||||||
|
#include <sys/mman.h>
|
||||||
#include "symbol_cache.h"
|
#include "symbol_cache.h"
|
||||||
|
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <native_util.h>
|
||||||
|
|
||||||
|
using namespace lsplant;
|
||||||
|
|
||||||
static_assert(FS_IOC_SETFLAGS == LP_SELECT(0x40046602, 0x40086602));
|
static_assert(FS_IOC_SETFLAGS == LP_SELECT(0x40046602, 0x40086602));
|
||||||
|
|
||||||
|
|
@ -96,15 +100,17 @@ namespace lspd {
|
||||||
void Context::Init() {
|
void Context::Init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::Init(JNIEnv *env) {
|
void Context::Init(JNIEnv *env, const lsplant::InitInfo& initInfo) {
|
||||||
|
if (!lsplant::Init(env, initInfo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (auto entry_class = FindClassFromLoader(env, GetCurrentClassLoader(),
|
if (auto entry_class = FindClassFromLoader(env, GetCurrentClassLoader(),
|
||||||
kEntryClassName)) {
|
kEntryClassName)) {
|
||||||
entry_class_ = JNI_NewGlobalRef(env, entry_class);
|
entry_class_ = JNI_NewGlobalRef(env, entry_class);
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterResourcesHook(env);
|
RegisterResourcesHook(env);
|
||||||
RegisterArtClassLinker(env);
|
RegisterHookBridge(env);
|
||||||
RegisterYahfa(env);
|
|
||||||
RegisterNativeAPI(env);
|
RegisterNativeAPI(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,9 +185,22 @@ namespace lspd {
|
||||||
instance->HookBridge(*this, env);
|
instance->HookBridge(*this, env);
|
||||||
|
|
||||||
if (application_binder) {
|
if (application_binder) {
|
||||||
InstallInlineHooks();
|
lsplant::InitInfo initInfo{
|
||||||
Init(env);
|
.inline_hooker = [](auto t, auto r) {
|
||||||
|
void* bk = nullptr;
|
||||||
|
return HookFunction(t, r, &bk) == RS_SUCCESS ? bk : nullptr;
|
||||||
|
},
|
||||||
|
.inline_unhooker = [](auto t) {
|
||||||
|
return UnhookFunction(t) == RT_SUCCESS ;
|
||||||
|
},
|
||||||
|
.art_symbol_resolver = [](auto symbol) {
|
||||||
|
return GetArt()->getSymbAddress<void*>(symbol);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
InstallInlineHooks(initInfo);
|
||||||
|
Init(env, initInfo);
|
||||||
FindAndCall(env, "forkSystemServerPost", "(Landroid/os/IBinder;)V", application_binder);
|
FindAndCall(env, "forkSystemServerPost", "(Landroid/os/IBinder;)V", application_binder);
|
||||||
|
GetArt(true);
|
||||||
} else {
|
} else {
|
||||||
LOGI("skipped system server");
|
LOGI("skipped system server");
|
||||||
GetArt(true);
|
GetArt(true);
|
||||||
|
|
@ -236,11 +255,23 @@ namespace lspd {
|
||||||
auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr}
|
auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr}
|
||||||
: instance->RequestBinder(env, nice_name);
|
: instance->RequestBinder(env, nice_name);
|
||||||
if (binder) {
|
if (binder) {
|
||||||
InstallInlineHooks();
|
lsplant::InitInfo initInfo{
|
||||||
|
.inline_hooker = [](auto t, auto r) {
|
||||||
|
void* bk = nullptr;
|
||||||
|
return HookFunction(t, r, &bk) == RS_SUCCESS ? bk : nullptr;
|
||||||
|
},
|
||||||
|
.inline_unhooker = [](auto t) {
|
||||||
|
return UnhookFunction(t) == RT_SUCCESS;
|
||||||
|
},
|
||||||
|
.art_symbol_resolver = [](auto symbol){
|
||||||
|
return GetArt()->getSymbAddress<void*>(symbol);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
InstallInlineHooks(initInfo);
|
||||||
auto [dex_fd, size] = instance->RequestLSPDex(env, binder);
|
auto [dex_fd, size] = instance->RequestLSPDex(env, binder);
|
||||||
LoadDex(env, dex_fd, size);
|
LoadDex(env, dex_fd, size);
|
||||||
close(dex_fd);
|
close(dex_fd);
|
||||||
Init(env);
|
Init(env, initInfo);
|
||||||
LOGD("Done prepare");
|
LOGD("Done prepare");
|
||||||
FindAndCall(env, "forkAndSpecializePost",
|
FindAndCall(env, "forkAndSpecializePost",
|
||||||
"(Ljava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
|
"(Ljava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
|
||||||
|
|
@ -248,6 +279,7 @@ namespace lspd {
|
||||||
binder);
|
binder);
|
||||||
LOGD("injected xposed into %s", process_name.get());
|
LOGD("injected xposed into %s", process_name.get());
|
||||||
setAllowUnload(false);
|
setAllowUnload(false);
|
||||||
|
GetArt(true);
|
||||||
} else {
|
} else {
|
||||||
auto context = Context::ReleaseInstance();
|
auto context = Context::ReleaseInstance();
|
||||||
auto service = Service::ReleaseInstance();
|
auto service = Service::ReleaseInstance();
|
||||||
|
|
@ -26,8 +26,9 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <lsplant.hpp>
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "jni_helper.h"
|
#include "utils/jni_helper.hpp"
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
class Context {
|
class Context {
|
||||||
|
|
@ -43,7 +44,7 @@ namespace lspd {
|
||||||
|
|
||||||
inline jobject GetCurrentClassLoader() const { return inject_class_loader_; }
|
inline jobject GetCurrentClassLoader() const { return inject_class_loader_; }
|
||||||
|
|
||||||
inline ScopedLocalRef<jclass>
|
inline lsplant::ScopedLocalRef<jclass>
|
||||||
FindClassFromCurrentLoader(JNIEnv *env, std::string_view className) const {
|
FindClassFromCurrentLoader(JNIEnv *env, std::string_view className) const {
|
||||||
return FindClassFromLoader(env, GetCurrentClassLoader(), className);
|
return FindClassFromLoader(env, GetCurrentClassLoader(), className);
|
||||||
};
|
};
|
||||||
|
|
@ -108,9 +109,9 @@ namespace lspd {
|
||||||
|
|
||||||
void LoadDex(JNIEnv *env, int fd, size_t size);
|
void LoadDex(JNIEnv *env, int fd, size_t size);
|
||||||
|
|
||||||
void Init(JNIEnv *env);
|
void Init(JNIEnv *env, const lsplant::InitInfo& initInfo);
|
||||||
|
|
||||||
static ScopedLocalRef<jclass> FindClassFromLoader(JNIEnv *env, jobject class_loader,
|
static lsplant::ScopedLocalRef<jclass> FindClassFromLoader(JNIEnv *env, jobject class_loader,
|
||||||
std::string_view class_name);
|
std::string_view class_name);
|
||||||
|
|
||||||
static void setAllowUnload(bool unload);
|
static void setAllowUnload(bool unload);
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
/*
|
||||||
|
* 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) 2020 EdXposed Contributors
|
||||||
|
* Copyright (C) 2021 LSPosed Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "hook_bridge.h"
|
||||||
|
#include "native_util.h"
|
||||||
|
#include "lsplant.hpp"
|
||||||
|
#include "unordered_map"
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
using namespace lsplant;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct HookItem {
|
||||||
|
jobject backup {nullptr};
|
||||||
|
jobjectArray callbacks {nullptr};
|
||||||
|
std::multiset<jint> priorities {};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_mutex hooked_lock;
|
||||||
|
// Rehashing invalidates iterators, changes ordering between elements, and changes which buckets elements appear in, but does not invalidate pointers or references to elements.
|
||||||
|
std::unordered_map<jmethodID, HookItem> hooked_methods;
|
||||||
|
|
||||||
|
jmethodID invoke = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace lspd {
|
||||||
|
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jobject hookMethod,
|
||||||
|
jclass hooker, jint priority, jobject callback) {
|
||||||
|
bool newHook = false;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
struct finally {
|
||||||
|
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
||||||
|
bool &newHook;
|
||||||
|
~finally() {
|
||||||
|
auto finish = std::chrono::steady_clock::now();
|
||||||
|
if (newHook) {
|
||||||
|
LOGV("New hook took %lldus",
|
||||||
|
std::chrono::duration_cast<std::chrono::microseconds>(finish - start).count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
.newHook = newHook
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
auto target = env->FromReflectedMethod(hookMethod);
|
||||||
|
HookItem * hook_item = nullptr;
|
||||||
|
{
|
||||||
|
std::shared_lock lk(hooked_lock);
|
||||||
|
if (auto found = hooked_methods.find(target); found != hooked_methods.end()) {
|
||||||
|
hook_item = &found->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hook_item) {
|
||||||
|
std::unique_lock lk(hooked_lock);
|
||||||
|
hook_item = &hooked_methods[target];
|
||||||
|
if (!hook_item->callbacks) {
|
||||||
|
newHook = true;
|
||||||
|
hook_item->callbacks = (jobjectArray) env->NewGlobalRef(
|
||||||
|
env->NewObjectArray(1, env->FindClass("[Ljava/lang/Object;"),
|
||||||
|
env->NewObjectArray(0, JNI_FindClass(env, "java/lang/Object"), nullptr)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newHook) {
|
||||||
|
auto init = env->GetMethodID(hooker, "<init>", "(Ljava/lang/reflect/Executable;[[Ljava/lang/Object;)V");
|
||||||
|
auto callback_method = env->ToReflectedMethod(hooker, env->GetMethodID(hooker, "callback",
|
||||||
|
"([Ljava/lang/Object;)Ljava/lang/Object;"),
|
||||||
|
false);
|
||||||
|
auto hooker_object = env->NewObject(hooker, init, hookMethod, hook_item->callbacks);
|
||||||
|
hook_item->backup = lsplant::Hook(env, hookMethod, hooker_object, callback_method);
|
||||||
|
env->DeleteLocalRef(hooker_object);
|
||||||
|
}
|
||||||
|
env->MonitorEnter(hook_item->callbacks);
|
||||||
|
auto insert_point = hook_item->priorities.emplace(priority);
|
||||||
|
auto old_array = (jobjectArray) env->GetObjectArrayElement(hook_item->callbacks, 0);
|
||||||
|
auto new_array = env->NewObjectArray(static_cast<jint>(hook_item->priorities.size()), env->FindClass("java/lang/Object"), nullptr);
|
||||||
|
for (auto [i, current, passed] = std::make_tuple(0, hook_item->priorities.begin(), false); current != hook_item->priorities.end(); ++current, ++i) {
|
||||||
|
if (current == insert_point) {
|
||||||
|
env->SetObjectArrayElement(new_array, i, callback);
|
||||||
|
passed = true;
|
||||||
|
} else {
|
||||||
|
auto element = env->GetObjectArrayElement(old_array, i - passed);
|
||||||
|
env->SetObjectArrayElement(new_array, i, element);
|
||||||
|
env->DeleteLocalRef(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env->SetObjectArrayElement(hook_item->callbacks, 0, new_array);
|
||||||
|
env->DeleteLocalRef(old_array);
|
||||||
|
env->DeleteLocalRef(new_array);
|
||||||
|
env->MonitorExit(hook_item->callbacks);
|
||||||
|
return hook_item->backup ? JNI_TRUE : JNI_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jobject hookMethod, jobject callback) {
|
||||||
|
auto target = env->FromReflectedMethod(hookMethod);
|
||||||
|
HookItem * hook_item = nullptr;
|
||||||
|
{
|
||||||
|
std::shared_lock lk(hooked_lock);
|
||||||
|
if (auto found = hooked_methods.find(target); found != hooked_methods.end()) {
|
||||||
|
hook_item = &found->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hook_item) return JNI_FALSE;
|
||||||
|
env->MonitorEnter(hook_item->callbacks);
|
||||||
|
auto old_array = (jobjectArray) env->GetObjectArrayElement(hook_item->callbacks, 0);
|
||||||
|
auto new_array = env->NewObjectArray(static_cast<jint>(hook_item->priorities.size() - 1), env->FindClass("java/lang/Object"), nullptr);
|
||||||
|
auto to_remove = hook_item->priorities.end();
|
||||||
|
for (auto [i, current, passed] = std::make_tuple(0, hook_item->priorities.begin(), false); current != hook_item->priorities.end(); ++current, ++i) {
|
||||||
|
auto element = env->GetObjectArrayElement(old_array, i);
|
||||||
|
if (env->IsSameObject(element, callback)) {
|
||||||
|
to_remove = current;
|
||||||
|
passed = true;
|
||||||
|
} else {
|
||||||
|
if (i - passed >= hook_item->priorities.size() - 1) {
|
||||||
|
return JNI_FALSE;
|
||||||
|
}
|
||||||
|
env->SetObjectArrayElement(new_array, i - passed, element);
|
||||||
|
}
|
||||||
|
env->DeleteLocalRef(element);
|
||||||
|
}
|
||||||
|
bool removed = false;
|
||||||
|
if (to_remove != hook_item->priorities.end()) {
|
||||||
|
hook_item->priorities.erase(to_remove);
|
||||||
|
env->SetObjectArrayElement(hook_item->callbacks, 0, new_array);
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
env->DeleteLocalRef(old_array);
|
||||||
|
env->DeleteLocalRef(new_array);
|
||||||
|
env->MonitorExit(hook_item->callbacks);
|
||||||
|
return removed ? JNI_TRUE : JNI_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, deoptimizeMethod, jobject hookMethod,
|
||||||
|
jclass hooker, jint priority, jobject callback) {
|
||||||
|
return lsplant::Deoptimize(env, hookMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeOriginalMethod, jobject hookMethod,
|
||||||
|
jobject thiz, jobjectArray args) {
|
||||||
|
auto target = env->FromReflectedMethod(hookMethod);
|
||||||
|
HookItem * hook_item = nullptr;
|
||||||
|
{
|
||||||
|
std::shared_lock lk(hooked_lock);
|
||||||
|
if (auto found = hooked_methods.find(target); found != hooked_methods.end()) {
|
||||||
|
hook_item = &found->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jobject to_call = hookMethod;
|
||||||
|
if (hook_item && hook_item->backup) {
|
||||||
|
to_call = hook_item->backup;
|
||||||
|
}
|
||||||
|
return env->CallObjectMethod(to_call, invoke, thiz, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JNINativeMethod gMethods[] = {
|
||||||
|
LSP_NATIVE_METHOD(HookBridge, hookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"),
|
||||||
|
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
|
||||||
|
LSP_NATIVE_METHOD(HookBridge, deoptimizeMethod, "(Ljava/lang/reflect/Executable;)Z"),
|
||||||
|
LSP_NATIVE_METHOD(HookBridge, invokeOriginalMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
|
||||||
|
};
|
||||||
|
|
||||||
|
void RegisterHookBridge(JNIEnv *env) {
|
||||||
|
auto method = env->FindClass("java/lang/reflect/Method");
|
||||||
|
invoke = env->GetMethodID(
|
||||||
|
method, "invoke",
|
||||||
|
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||||
|
env->DeleteLocalRef(method);
|
||||||
|
REGISTER_LSP_NATIVE_METHODS(HookBridge);
|
||||||
|
}
|
||||||
|
} // namespace lspd
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
//
|
||||||
|
// Created by loves on 3/13/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
namespace lspd {
|
||||||
|
void RegisterHookBridge(JNIEnv* env);
|
||||||
|
}
|
||||||
|
|
@ -23,12 +23,14 @@
|
||||||
//
|
//
|
||||||
#include "native_api.h"
|
#include "native_api.h"
|
||||||
#include "native_util.h"
|
#include "native_util.h"
|
||||||
#include "jni_helper.h"
|
#include "utils/jni_helper.hpp"
|
||||||
#include "../native_api.h"
|
#include "../native_api.h"
|
||||||
|
|
||||||
|
using namespace lsplant;
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
LSP_DEF_NATIVE_METHOD(void, NativeAPI, recordNativeEntrypoint, jstring jstr) {
|
LSP_DEF_NATIVE_METHOD(void, NativeAPI, recordNativeEntrypoint, jstring jstr) {
|
||||||
JUTFString str(env, jstr);
|
lsplant::JUTFString str(env, jstr);
|
||||||
RegisterNativeLib(str);
|
RegisterNativeLib(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,25 +19,24 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <dex_builder.h>
|
#include "dex_builder.h"
|
||||||
#include <art/runtime/thread.h>
|
#include "framework/androidfw/resource_types.h"
|
||||||
#include <art/runtime/mirror/class.h>
|
#include "elf_util.h"
|
||||||
#include <framework/androidfw/resource_types.h>
|
|
||||||
#include <HookMain.h>
|
|
||||||
#include <elf_util.h>
|
|
||||||
#include "native_util.h"
|
#include "native_util.h"
|
||||||
#include "resources_hook.h"
|
#include "resources_hook.h"
|
||||||
|
|
||||||
|
using namespace lsplant;
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
static constexpr const char *kXResourcesClassName = "android/content/res/XResources";
|
static constexpr const char *kXResourcesClassName = "android/content/res/XResources";
|
||||||
|
|
||||||
typedef int32_t (*TYPE_GET_ATTR_NAME_ID)(void *, int);
|
using TYPE_GET_ATTR_NAME_ID = int32_t (*)(void *, int);
|
||||||
|
|
||||||
typedef char16_t *(*TYPE_STRING_AT)(const void *, int32_t, size_t *);
|
using TYPE_STRING_AT = char16_t *(*)(const void *, int32_t, size_t *);
|
||||||
|
|
||||||
typedef void(*TYPE_RESTART)(void *);
|
using TYPE_RESTART = void (*)(void *);
|
||||||
|
|
||||||
typedef int32_t (*TYPE_NEXT)(void *);
|
using TYPE_NEXT = int32_t (*)(void *);
|
||||||
|
|
||||||
static jclass classXResources;
|
static jclass classXResources;
|
||||||
static jmethodID methodXResourcesTranslateAttrId;
|
static jmethodID methodXResourcesTranslateAttrId;
|
||||||
|
|
@ -65,7 +64,11 @@ namespace lspd {
|
||||||
"_ZNK7android12ResXMLParser18getAttributeNameIDEm")))) {
|
"_ZNK7android12ResXMLParser18getAttributeNameIDEm")))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return android::ResStringPool::setup(fw);
|
return android::ResStringPool::setup(HookHandler{
|
||||||
|
.art_symbol_resolver = [&](auto s) {
|
||||||
|
return fw.template getSymbAddress<void*>(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, initXResourcesNative) {
|
LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, initXResourcesNative) {
|
||||||
|
|
@ -97,29 +100,14 @@ namespace lspd {
|
||||||
// @ApiSensitive(Level.MIDDLE)
|
// @ApiSensitive(Level.MIDDLE)
|
||||||
LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, makeInheritable, jclass target_class,
|
LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, makeInheritable, jclass target_class,
|
||||||
jobjectArray constructors) {
|
jobjectArray constructors) {
|
||||||
if (target_class) {
|
if (lsplant::MakeClassInheritable(env, target_class)) {
|
||||||
static auto class_clazz = JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/Class"));
|
|
||||||
static jfieldID java_lang_Class_accessFlags = JNI_GetFieldID(
|
|
||||||
env, class_clazz, "accessFlags", "I");
|
|
||||||
jint access_flags = env->GetIntField(target_class, java_lang_Class_accessFlags);
|
|
||||||
env->SetIntField(target_class, java_lang_Class_accessFlags, access_flags & ~kAccFinal);
|
|
||||||
for (auto i = 0; i < env->GetArrayLength(constructors); ++i) {
|
|
||||||
auto constructor = env->GetObjectArrayElement(constructors, i);
|
|
||||||
void *method = yahfa::getArtMethod(env, constructor);
|
|
||||||
uint32_t flags = yahfa::getAccessFlags(method);
|
|
||||||
if ((flags & yahfa::kAccPublic) == 0 && (flags & yahfa::kAccProtected) == 0) {
|
|
||||||
flags |= yahfa::kAccProtected;
|
|
||||||
flags &= ~yahfa::kAccPrivate;
|
|
||||||
}
|
|
||||||
yahfa::setAccessFlags(method, flags);
|
|
||||||
}
|
|
||||||
return JNI_TRUE;
|
return JNI_TRUE;
|
||||||
}
|
}
|
||||||
return JNI_FALSE;
|
return JNI_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jobject, ResourcesHook, buildDummyClassLoader, jobject parent,
|
LSP_DEF_NATIVE_METHOD(jobject, ResourcesHook, buildDummyClassLoader, jobject parent,
|
||||||
jobject resource_super_class, jobject typed_array_super_class) {
|
jstring resource_super_class, jstring typed_array_super_class) {
|
||||||
using namespace startop::dex;
|
using namespace startop::dex;
|
||||||
static auto in_memory_classloader = JNI_NewGlobalRef(env, JNI_FindClass(env,
|
static auto in_memory_classloader = JNI_NewGlobalRef(env, JNI_FindClass(env,
|
||||||
"dalvik/system/InMemoryDexClassLoader"));
|
"dalvik/system/InMemoryDexClassLoader"));
|
||||||
|
|
@ -127,17 +115,13 @@ namespace lspd {
|
||||||
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
||||||
DexBuilder dex_file;
|
DexBuilder dex_file;
|
||||||
|
|
||||||
std::string storage;
|
|
||||||
auto current_thread = art::Thread::Current();
|
|
||||||
ClassBuilder xresource_builder{
|
ClassBuilder xresource_builder{
|
||||||
dex_file.MakeClass("xposed.dummy.XResourcesSuperClass")};
|
dex_file.MakeClass("xposed.dummy.XResourcesSuperClass")};
|
||||||
xresource_builder.setSuperClass(TypeDescriptor::FromDescriptor(art::mirror::Class(
|
xresource_builder.setSuperClass(TypeDescriptor::FromClassname(JUTFString(env, resource_super_class).get()));
|
||||||
current_thread.DecodeJObject(resource_super_class)).GetDescriptor(&storage)));
|
|
||||||
|
|
||||||
ClassBuilder xtypearray_builder{
|
ClassBuilder xtypearray_builder{
|
||||||
dex_file.MakeClass("xposed.dummy.XTypedArraySuperClass")};
|
dex_file.MakeClass("xposed.dummy.XTypedArraySuperClass")};
|
||||||
xtypearray_builder.setSuperClass(TypeDescriptor::FromDescriptor(art::mirror::Class(
|
xtypearray_builder.setSuperClass(TypeDescriptor::FromClassname(JUTFString(env, typed_array_super_class).get()));
|
||||||
current_thread.DecodeJObject(typed_array_super_class)).GetDescriptor(&storage)));
|
|
||||||
|
|
||||||
slicer::MemView image{dex_file.CreateImage()};
|
slicer::MemView image{dex_file.CreateImage()};
|
||||||
|
|
||||||
|
|
@ -222,7 +206,7 @@ namespace lspd {
|
||||||
LSP_NATIVE_METHOD(ResourcesHook, makeInheritable,
|
LSP_NATIVE_METHOD(ResourcesHook, makeInheritable,
|
||||||
"(Ljava/lang/Class;[Ljava/lang/reflect/Constructor;)Z"),
|
"(Ljava/lang/Class;[Ljava/lang/reflect/Constructor;)Z"),
|
||||||
LSP_NATIVE_METHOD(ResourcesHook, buildDummyClassLoader,
|
LSP_NATIVE_METHOD(ResourcesHook, buildDummyClassLoader,
|
||||||
"(Ljava/lang/ClassLoader;Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/ClassLoader;"),
|
"(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/ClassLoader;"),
|
||||||
LSP_NATIVE_METHOD(ResourcesHook, rewriteXmlReferencesNative,
|
LSP_NATIVE_METHOD(ResourcesHook, rewriteXmlReferencesNative,
|
||||||
"(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V")
|
"(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V")
|
||||||
};
|
};
|
||||||
|
|
@ -23,10 +23,15 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "native_api.h"
|
#include "native_api.h"
|
||||||
|
#include "logging.h"
|
||||||
#include "symbol_cache.h"
|
#include "symbol_cache.h"
|
||||||
|
#include "utils/hook_helper.hpp"
|
||||||
|
#include <sys/mman.h>
|
||||||
#include <dobby.h>
|
#include <dobby.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <base/object.h>
|
#include <dlfcn.h>
|
||||||
|
#include "native_util.h"
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Module: define xposed_native file in /assets, each line is a .so file name
|
* Module: define xposed_native file in /assets, each line is a .so file name
|
||||||
|
|
@ -44,26 +49,33 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
|
|
||||||
|
using lsplant::operator""_tstr;
|
||||||
std::list<NativeOnModuleLoaded> moduleLoadedCallbacks;
|
std::list<NativeOnModuleLoaded> moduleLoadedCallbacks;
|
||||||
std::list<std::string> moduleNativeLibs;
|
std::list<std::string> moduleNativeLibs;
|
||||||
std::unique_ptr<void, std::function<void(void *)>> protected_page(
|
std::unique_ptr<void, std::function<void(void *)>> protected_page(
|
||||||
mmap(nullptr, _page_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0),
|
mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0),
|
||||||
[](void *ptr) { munmap(ptr, _page_size); });
|
[](void *ptr) { munmap(ptr, 4096); });
|
||||||
|
|
||||||
const auto[entries] = []() {
|
const auto[entries] = []() {
|
||||||
auto *entries = new(protected_page.get()) NativeAPIEntries{
|
auto *entries = new(protected_page.get()) NativeAPIEntries{
|
||||||
.version = 2,
|
.version = 2,
|
||||||
.hookFunc = HookFunction,
|
.hookFunc = &HookFunction,
|
||||||
.unhookFunc = UnhookFunction
|
.unhookFunc = &UnhookFunction,
|
||||||
};
|
};
|
||||||
|
|
||||||
mprotect(protected_page.get(), _page_size, PROT_READ);
|
mprotect(protected_page.get(), 4096, PROT_READ);
|
||||||
return std::make_tuple(entries);
|
return std::make_tuple(entries);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
void RegisterNativeLib(const std::string &library_name) {
|
void RegisterNativeLib(const std::string &library_name) {
|
||||||
static bool initialized = []() {
|
static bool initialized = []() {
|
||||||
return InstallNativeAPI();
|
return InstallNativeAPI({
|
||||||
|
.inline_hooker = [](auto t, auto r) {
|
||||||
|
void* bk = nullptr;
|
||||||
|
return HookFunction(t, r, &bk) ? bk : nullptr;
|
||||||
|
},
|
||||||
|
});
|
||||||
}();
|
}();
|
||||||
if (!initialized) [[unlikely]] return;
|
if (!initialized) [[unlikely]] return;
|
||||||
LOGD("native_api: Registered %s", library_name.c_str());
|
LOGD("native_api: Registered %s", library_name.c_str());
|
||||||
|
|
@ -74,12 +86,11 @@ namespace lspd {
|
||||||
if (fullString.length() >= ending.length()) {
|
if (fullString.length() >= ending.length()) {
|
||||||
return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(),
|
return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(),
|
||||||
ending));
|
ending));
|
||||||
} else {
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
CREATE_HOOK_STUB_ENTRIES(
|
CREATE_HOOK_STUB_ENTRY(
|
||||||
"__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv",
|
"__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv",
|
||||||
void*, do_dlopen, (const char* name, int flags, const void* extinfo,
|
void*, do_dlopen, (const char* name, int flags, const void* extinfo,
|
||||||
const void* caller_addr), {
|
const void* caller_addr), {
|
||||||
|
|
@ -121,10 +132,10 @@ namespace lspd {
|
||||||
return handle;
|
return handle;
|
||||||
});
|
});
|
||||||
|
|
||||||
bool InstallNativeAPI() {
|
bool InstallNativeAPI(const lsplant::HookHandler & handler) {
|
||||||
LOGD("InstallNativeAPI: %p", symbol_cache->do_dlopen);
|
LOGD("InstallNativeAPI: %p", symbol_cache->do_dlopen);
|
||||||
if (symbol_cache->do_dlopen) [[likely]] {
|
if (symbol_cache->do_dlopen) [[likely]] {
|
||||||
HookSymNoHandle(symbol_cache->do_dlopen, do_dlopen);
|
HookSymNoHandle(handler, symbol_cache->do_dlopen, do_dlopen);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -27,7 +27,8 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <base/object.h>
|
|
||||||
|
#include "utils/hook_helper.hpp"
|
||||||
|
|
||||||
typedef int (*HookFunType)(void *func, void *replace, void **backup);
|
typedef int (*HookFunType)(void *func, void *replace, void **backup);
|
||||||
|
|
||||||
|
|
@ -44,7 +45,7 @@ typedef struct {
|
||||||
typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries);
|
typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries);
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
bool InstallNativeAPI();
|
bool InstallNativeAPI(const lsplant::HookHandler& handler);
|
||||||
|
|
||||||
void RegisterNativeLib(const std::string &library_name);
|
void RegisterNativeLib(const std::string &library_name);
|
||||||
}
|
}
|
||||||
|
|
@ -18,21 +18,28 @@
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
* Copyright (C) 2021 LSPosed Contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#include <dlfcn.h>
|
||||||
|
#include <string>
|
||||||
#include "jni.h"
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <dobby.h>
|
||||||
|
#include "symbol_cache.h"
|
||||||
|
#include "logging.h"
|
||||||
|
#include "native_api.h"
|
||||||
|
#include "native_hook.h"
|
||||||
|
#include "art/runtime/hidden_api.h"
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
|
static std::atomic_bool installed = false;
|
||||||
|
|
||||||
void* isHooked(void* art_method);
|
void InstallInlineHooks(const lsplant::HookHandler& handler) {
|
||||||
|
if (installed.exchange(true)) [[unlikely]] {
|
||||||
std::vector<std::pair<void*, void*>> getJitMovements();
|
LOGD("Inline hooks have been installed, skip");
|
||||||
|
return;
|
||||||
const std::unordered_set<void*> &isClassHooked(void *clazz);
|
}
|
||||||
|
LOGD("Start to install inline hooks");
|
||||||
void RegisterYahfa(JNIEnv *);
|
art::Runtime::Setup(handler);
|
||||||
|
art::hidden_api::DisableHiddenApi(handler);
|
||||||
|
LOGD("Inline hooks installed");
|
||||||
|
}
|
||||||
} // namespace lspd
|
} // namespace lspd
|
||||||
|
|
||||||
|
|
@ -20,9 +20,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <dobby.h>
|
#include "utils/hook_helper.hpp"
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
void InstallInlineHooks();
|
void InstallInlineHooks(const lsplant::HookHandler &handler);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -23,12 +23,14 @@
|
||||||
|
|
||||||
#include <dobby.h>
|
#include <dobby.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include "base/object.h"
|
#include "config.h"
|
||||||
#include "service.h"
|
#include "service.h"
|
||||||
#include "context.h"
|
#include "context.h"
|
||||||
#include "jni_helper.h"
|
#include "utils/jni_helper.hpp"
|
||||||
#include "symbol_cache.h"
|
#include "symbol_cache.h"
|
||||||
|
|
||||||
|
using namespace lsplant;
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
jboolean
|
jboolean
|
||||||
Service::exec_transact_replace(jboolean *res, JNIEnv *env, [[maybe_unused]] jobject obj,
|
Service::exec_transact_replace(jboolean *res, JNIEnv *env, [[maybe_unused]] jobject obj,
|
||||||
|
|
@ -51,13 +51,13 @@ namespace lspd {
|
||||||
void InitService(JNIEnv *env);
|
void InitService(JNIEnv *env);
|
||||||
|
|
||||||
void HookBridge(const Context& context, JNIEnv *env);
|
void HookBridge(const Context& context, JNIEnv *env);
|
||||||
ScopedLocalRef<jobject> RequestBinder(JNIEnv *env, jstring nice_name);
|
lsplant::ScopedLocalRef<jobject> RequestBinder(JNIEnv *env, jstring nice_name);
|
||||||
|
|
||||||
ScopedLocalRef<jobject> RequestSystemServerBinder(JNIEnv *env);
|
lsplant::ScopedLocalRef<jobject> RequestSystemServerBinder(JNIEnv *env);
|
||||||
|
|
||||||
ScopedLocalRef<jobject> RequestApplicationBinderFromSystemServer(JNIEnv *env, const ScopedLocalRef<jobject> &system_server_binder);
|
lsplant::ScopedLocalRef<jobject> RequestApplicationBinderFromSystemServer(JNIEnv *env, const lsplant::ScopedLocalRef<jobject> &system_server_binder);
|
||||||
|
|
||||||
std::tuple<int, size_t> RequestLSPDex(JNIEnv *env, const ScopedLocalRef<jobject> &binder);
|
std::tuple<int, size_t> RequestLSPDex(JNIEnv *env, const lsplant::ScopedLocalRef<jobject> &binder);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline static std::unique_ptr<Service> instance_ = std::make_unique<Service>();
|
inline static std::unique_ptr<Service> instance_ = std::make_unique<Service>();
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
namespace lspd {
|
||||||
|
const int versionCode = ${VERSION_CODE};
|
||||||
|
const int apiVersion = ${API_VERSION};
|
||||||
|
const char* const versionName = "${VERSION_NAME}";
|
||||||
|
const char* const moduleName = "${MODULE_NAME}";
|
||||||
|
}
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
/build
|
/build
|
||||||
|
/.cxx
|
||||||
|
|
|
||||||
|
|
@ -65,12 +65,6 @@ android {
|
||||||
versionName = verName
|
versionName = verName
|
||||||
multiDexEnabled = false
|
multiDexEnabled = false
|
||||||
|
|
||||||
externalNativeBuild {
|
|
||||||
ndkBuild {
|
|
||||||
arguments += "-j${Runtime.getRuntime().availableProcessors()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildConfigField("int", "API_CODE", "$apiCode")
|
buildConfigField("int", "API_CODE", "$apiCode")
|
||||||
buildConfigField(
|
buildConfigField(
|
||||||
"String",
|
"String",
|
||||||
|
|
@ -87,15 +81,58 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments.addAll(
|
||||||
|
arrayOf(
|
||||||
|
"-DCMAKE_CXX_FLAGS_DEBUG=-Og",
|
||||||
|
"-DCMAKE_C_FLAGS_DEBUG=-Og"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
proguardFiles("proguard-rules.pro")
|
proguardFiles("proguard-rules.pro")
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
val flags = arrayOf(
|
||||||
|
"-Wl,--exclude-libs,ALL",
|
||||||
|
"-ffunction-sections",
|
||||||
|
"-fdata-sections",
|
||||||
|
"-Wl,--gc-sections",
|
||||||
|
"-fno-unwind-tables",
|
||||||
|
"-fno-asynchronous-unwind-tables",
|
||||||
|
"-flto=thin",
|
||||||
|
"-Wl,--thinlto-cache-policy,cache_size_bytes=300m",
|
||||||
|
"-Wl,--thinlto-cache-dir=${buildDir.absolutePath}/.lto-cache",
|
||||||
|
)
|
||||||
|
cppFlags.addAll(flags)
|
||||||
|
cFlags.addAll(flags)
|
||||||
|
val configFlags = arrayOf(
|
||||||
|
"-Oz",
|
||||||
|
"-DNDEBUG"
|
||||||
|
).joinToString(" ")
|
||||||
|
arguments.addAll(
|
||||||
|
arrayOf(
|
||||||
|
"-DCMAKE_CXX_FLAGS_RELEASE=$configFlags",
|
||||||
|
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=$configFlags",
|
||||||
|
"-DCMAKE_C_FLAGS_RELEASE=$configFlags",
|
||||||
|
"-DCMAKE_C_FLAGS_RELWITHDEBINFO=$configFlags",
|
||||||
|
"-DDEBUG_SYMBOLS_PATH=${buildDir.absolutePath}/symbols",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
cmake {
|
||||||
path("src/main/cpp/Android.mk")
|
path("src/main/jni/CMakeLists.txt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,12 +141,30 @@ android {
|
||||||
sourceCompatibility(androidSourceCompatibility)
|
sourceCompatibility(androidSourceCompatibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
defaultConfig {
|
||||||
all {
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
cmake {
|
||||||
arguments += "NDK_OUT=${File(buildDir, ".cxx/$name").absolutePath}"
|
arguments += "-DEXTERNAL_ROOT=${File(rootDir.absolutePath, "external")}"
|
||||||
}
|
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||||
|
val flags = arrayOf(
|
||||||
|
"-Wall",
|
||||||
|
"-Werror",
|
||||||
|
"-Qunused-arguments",
|
||||||
|
"-Wno-gnu-string-literal-operator-template",
|
||||||
|
"-fno-rtti",
|
||||||
|
"-fvisibility=hidden",
|
||||||
|
"-fvisibility-inlines-hidden",
|
||||||
|
"-fno-exceptions",
|
||||||
|
"-fno-stack-protector",
|
||||||
|
"-fomit-frame-pointer",
|
||||||
|
"-Wno-builtin-macro-redefined",
|
||||||
|
"-Wno-unused-value",
|
||||||
|
"-D__FILE__=__FILE_NAME__",
|
||||||
|
)
|
||||||
|
cppFlags("-std=c++20", *flags)
|
||||||
|
cFlags("-std=c18", *flags)
|
||||||
|
arguments("-DANDROID_STL=none")
|
||||||
|
targets("daemon")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +220,6 @@ afterEvaluate {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
|
|
||||||
implementation("com.android.tools.build:apksig:$agpVersion")
|
implementation("com.android.tools.build:apksig:$agpVersion")
|
||||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||||
compileOnly("androidx.annotation:annotation:1.3.0")
|
compileOnly("androidx.annotation:annotation:1.3.0")
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
LOCAL_PATH := $(call my-dir)
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE := daemon
|
|
||||||
LOCAL_C_INCLUDES := ../core/src/main/cpp/main/include/
|
|
||||||
LOCAL_SRC_FILES := logcat.cpp obfuscation.cpp
|
|
||||||
LOCAL_STATIC_LIBRARIES := cxx dex_builder
|
|
||||||
LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
|
|
||||||
LOCAL_LDLIBS := -llog -landroid
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
|
||||||
|
|
||||||
include ../core/src/main/cpp/external/DexBuilder/Android.mk
|
|
||||||
$(call import-module,prefab/cxx)
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
APP_CFLAGS := -Wall -Wextra
|
|
||||||
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
|
|
||||||
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__
|
|
||||||
APP_CPPFLAGS := -std=c++20
|
|
||||||
APP_CONLYFLAGS := -std=c18
|
|
||||||
APP_LDFLAGS := -Wl,--exclude-libs,ALL
|
|
||||||
APP_STL := none
|
|
||||||
|
|
||||||
ifneq ($(NDK_DEBUG),1)
|
|
||||||
APP_CFLAGS += -Oz -flto=thin
|
|
||||||
APP_CFLAGS += -Wno-unused -Wno-unused-parameter -Werror
|
|
||||||
APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
|
|
||||||
APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables
|
|
||||||
APP_LDFLAGS += -flto=thin -Wl,--thinlto-cache-policy,cache_size_bytes=10m -Wl,--thinlto-cache-dir=build/.lto-cache -Wl,--gc-sections -Wl,--strip-all
|
|
||||||
endif
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
cmake_minimum_required(VERSION 3.4.1)
|
||||||
|
project(daemon)
|
||||||
|
|
||||||
|
add_subdirectory(${EXTERNAL_ROOT} external)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
logcat.cpp
|
||||||
|
obfuscation.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(${PROJECT_NAME} SHARED ${SOURCES})
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE lsplant_static dex_builder_static android log)
|
||||||
|
|
||||||
|
if (DEFINED DEBUG_SYMBOLS_PATH)
|
||||||
|
message(STATUS "Debug symbols will be placed at ${DEBUG_SYMBOLS_PATH}")
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} --only-keep-debug $<TARGET_FILE:${PROJECT_NAME}>
|
||||||
|
${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
|
||||||
|
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:${PROJECT_NAME}>
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
|
||||||
|
$<TARGET_FILE:${PROJECT_NAME}>)
|
||||||
|
endif()
|
||||||
|
|
@ -224,7 +224,7 @@ void Logcat::ProcessBuffer(struct log_msg *buf) {
|
||||||
shortcut = true;
|
shortcut = true;
|
||||||
}
|
}
|
||||||
if (verbose_ && (shortcut || buf->id() == log_id::LOG_ID_CRASH ||
|
if (verbose_ && (shortcut || buf->id() == log_id::LOG_ID_CRASH ||
|
||||||
entry.pid == my_pid_ || tag == "Magisk"sv ||
|
entry.pid == my_pid_ || tag == "Magisk"sv || tag == "Dobby"sv ||
|
||||||
tag.starts_with("Riru"sv) || tag.starts_with("zygisk"sv) ||
|
tag.starts_with("Riru"sv) || tag.starts_with("zygisk"sv) ||
|
||||||
tag.starts_with("LSPosed"sv))) [[unlikely]] {
|
tag.starts_with("LSPosed"sv))) [[unlikely]] {
|
||||||
verbose_print_count_ += PrintLogLine(entry, verbose_file_.get());
|
verbose_print_count_ += PrintLogLine(entry, verbose_file_.get());
|
||||||
|
|
@ -51,13 +51,13 @@ struct log_msg {
|
||||||
struct logger;
|
struct logger;
|
||||||
struct logger_list;
|
struct logger_list;
|
||||||
|
|
||||||
long android_logger_get_log_size(struct logger* logger);
|
[[gnu::weak]] long android_logger_get_log_size(struct logger* logger);
|
||||||
int android_logger_set_log_size(struct logger *logger, unsigned long size);
|
[[gnu::weak]] int android_logger_set_log_size(struct logger *logger, unsigned long size);
|
||||||
struct logger_list *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid);
|
[[gnu::weak]] struct logger_list *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid);
|
||||||
void android_logger_list_free(struct logger_list *logger_list);
|
[[gnu::weak]] void android_logger_list_free(struct logger_list *logger_list);
|
||||||
int android_logger_list_read(struct logger_list *logger_list, struct log_msg *log_msg);
|
[[gnu::weak]] int android_logger_list_read(struct logger_list *logger_list, struct log_msg *log_msg);
|
||||||
struct logger *android_logger_open(struct logger_list *logger_list, log_id_t id);
|
[[gnu::weak]] struct logger *android_logger_open(struct logger_list *logger_list, log_id_t id);
|
||||||
int android_log_processLogBuffer(struct logger_entry *buf, AndroidLogEntry *entry);
|
[[gnu::weak]] int android_log_processLogBuffer(struct logger_entry *buf, AndroidLogEntry *entry);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* 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) 2020 EdXposed Contributors
|
||||||
|
* Copyright (C) 2021 LSPosed Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LOGGING_H
|
||||||
|
#define _LOGGING_H
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#ifndef LOG_TAG
|
||||||
|
#define LOG_TAG "LSPosed"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef LOG_DISABLED
|
||||||
|
#define LOGD(...)
|
||||||
|
#define LOGV(...)
|
||||||
|
#define LOGI(...)
|
||||||
|
#define LOGW(...)
|
||||||
|
#define LOGE(...)
|
||||||
|
#else
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#define LOGD(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__)
|
||||||
|
#define LOGV(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOGD(...)
|
||||||
|
#define LOGV(...)
|
||||||
|
#endif
|
||||||
|
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // _LOGGING_H
|
||||||
|
|
@ -34,7 +34,9 @@
|
||||||
#include "slicer/reader.h"
|
#include "slicer/reader.h"
|
||||||
#include "slicer/writer.h"
|
#include "slicer/writer.h"
|
||||||
#include "obfuscation.h"
|
#include "obfuscation.h"
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
using namespace lsplant;
|
||||||
namespace {
|
namespace {
|
||||||
std::mutex init_lock{};
|
std::mutex init_lock{};
|
||||||
std::string obfuscated_signature;
|
std::string obfuscated_signature;
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "jni_helper.h"
|
#include "utils/jni_helper.hpp"
|
||||||
|
|
||||||
class WA: public dex::Writer::Allocator {
|
class WA: public dex::Writer::Allocator {
|
||||||
// addr: {size, fd}
|
// addr: {size, fd}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
project(external)
|
||||||
|
|
||||||
|
find_program(CCACHE ccache)
|
||||||
|
|
||||||
|
if (CCACHE)
|
||||||
|
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
|
||||||
|
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
macro(SET_OPTION option value)
|
||||||
|
set(${option} ${value} CACHE INTERNAL "" FORCE)
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
SET_OPTION(DOBBY_GENERATE_SHARED OFF)
|
||||||
|
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
SET_OPTION(DOBBY_DEBUG OFF)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(LIBCXX_SOURCES
|
||||||
|
algorithm.cpp
|
||||||
|
any.cpp
|
||||||
|
atomic.cpp
|
||||||
|
barrier.cpp
|
||||||
|
bind.cpp
|
||||||
|
charconv.cpp
|
||||||
|
chrono.cpp
|
||||||
|
condition_variable.cpp
|
||||||
|
condition_variable_destructor.cpp
|
||||||
|
debug.cpp
|
||||||
|
exception.cpp
|
||||||
|
filesystem/directory_iterator.cpp
|
||||||
|
filesystem/int128_builtins.cpp
|
||||||
|
filesystem/operations.cpp
|
||||||
|
functional.cpp
|
||||||
|
future.cpp
|
||||||
|
hash.cpp
|
||||||
|
ios.cpp
|
||||||
|
iostream.cpp
|
||||||
|
locale.cpp
|
||||||
|
memory.cpp
|
||||||
|
mutex.cpp
|
||||||
|
mutex_destructor.cpp
|
||||||
|
new.cpp
|
||||||
|
optional.cpp
|
||||||
|
random.cpp
|
||||||
|
regex.cpp
|
||||||
|
shared_mutex.cpp
|
||||||
|
stdexcept.cpp
|
||||||
|
string.cpp
|
||||||
|
strstream.cpp
|
||||||
|
system_error.cpp
|
||||||
|
thread.cpp
|
||||||
|
typeinfo.cpp
|
||||||
|
utility.cpp
|
||||||
|
valarray.cpp
|
||||||
|
variant.cpp
|
||||||
|
vector.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
list(TRANSFORM LIBCXX_SOURCES PREPEND cxx/src/)
|
||||||
|
|
||||||
|
set(LIBCXX_EXPORT_FLAGS)
|
||||||
|
set(LIBCXX_FLAGS
|
||||||
|
-fvisibility-global-new-delete-hidden
|
||||||
|
-fvisibility=hidden
|
||||||
|
-fvisibility-inlines-hidden
|
||||||
|
-DLIBCXX_BUILDING_LIBCXXABI
|
||||||
|
-D_LIBCPP_NO_EXCEPTIONS
|
||||||
|
-D_LIBCPP_NO_RTTI
|
||||||
|
-D_LIBCPP_BUILDING_LIBRARY
|
||||||
|
-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS
|
||||||
|
-D__STDC_FORMAT_MACROS
|
||||||
|
)
|
||||||
|
set(LIBCXX_EXPORT_INCLUDES cxx/include)
|
||||||
|
set(LIBCXX_INCLUDES)
|
||||||
|
|
||||||
|
set(LIBCXXABI_SOURCES
|
||||||
|
abort_message.cpp
|
||||||
|
cxa_aux_runtime.cpp
|
||||||
|
cxa_default_handlers.cpp
|
||||||
|
cxa_exception_storage.cpp
|
||||||
|
cxa_guard.cpp
|
||||||
|
cxa_handlers.cpp
|
||||||
|
cxa_noexception.cpp
|
||||||
|
cxa_thread_atexit.cpp
|
||||||
|
cxa_unexpected.cpp
|
||||||
|
cxa_vector.cpp
|
||||||
|
cxa_virtual.cpp
|
||||||
|
stdlib_exception.cpp
|
||||||
|
stdlib_new_delete.cpp
|
||||||
|
stdlib_stdexcept.cpp
|
||||||
|
stdlib_typeinfo.cpp
|
||||||
|
)
|
||||||
|
list(TRANSFORM LIBCXXABI_SOURCES PREPEND cxx/src/abi/)
|
||||||
|
set(LIBCXXABI_FLAGS
|
||||||
|
-D_LIBCXXABI_NO_EXCEPTIONS
|
||||||
|
-Wno-macro-redefined
|
||||||
|
-Wno-unknown-attributes
|
||||||
|
-DHAS_THREAD_LOCAL)
|
||||||
|
set(LIBCXXABI_INCLUDES cxx/include/abi)
|
||||||
|
|
||||||
|
add_library(cxx STATIC ${LIBCXX_SOURCES} ${LIBCXXABI_SOURCES})
|
||||||
|
target_compile_options(cxx PUBLIC ${LIBCXX_EXPORT_FLAGS})
|
||||||
|
target_compile_options(cxx PRIVATE ${LIBCXX_FLAGS} ${LIBCXXABI_FLAGS} -ffunction-sections -fdata-sections)
|
||||||
|
target_include_directories(cxx PUBLIC ${LIBCXX_EXPORT_INCLUDES})
|
||||||
|
target_include_directories(cxx PRIVATE ${LIBCXX_INCLUDES} ${LIBCXXABI_INCLUDES})
|
||||||
|
|
||||||
|
link_libraries(cxx)
|
||||||
|
|
||||||
|
add_subdirectory(lsplant/lsplant/src/main/jni)
|
||||||
|
add_subdirectory(dobby)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 0aa67c3ffea069bdb58fe3b5e7aad06934afb292
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue