Skip to content

Commit

Permalink
chore: use lsplant when no hook bridge is available
Browse files Browse the repository at this point in the history
  • Loading branch information
cinit committed Aug 3, 2024
1 parent 4787952 commit c6a0e3b
Show file tree
Hide file tree
Showing 11 changed files with 875 additions and 3 deletions.
1 change: 1 addition & 0 deletions app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ add_library(qauxv SHARED
qauxv_core/HostInfo.cc
qauxv_core/NativeCoreBridge.cc
qauxv_core/linker_utils.cc
qauxv_core/LsplantBridge.cc

utils/shared_memory.cpp
utils/auto_close_fd.cc
Expand Down
76 changes: 76 additions & 0 deletions app/src/main/cpp/qauxv_core/LsplantBridge.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// Created by sulfate on 2024-08-03.
//

#include <string>
#include <string_view>
#include <vector>

#include <jni.h>
#include <android/log.h>

#include <fmt/format.h>
#include "natives_utils.h"
#include "utils/MemoryDexLoader.h"

#include "lsplant.hpp"

static bool sLsplantInitSuccess = false;

extern "C"
JNIEXPORT void JNICALL
Java_io_github_qauxv_util_hookimpl_lsplant_LsplantBridge_nativeInitializeLsplant(JNIEnv* env, jclass clazz) {
if (sLsplantInitSuccess) {
return;
}
using namespace qauxv;
if (!InitLibArtElfView()) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "libart symbol resolver init failed");
return;
}
if (!InitLSPlantImpl(env)) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "lsplant init failed");
return;
}
sLsplantInitSuccess = true;
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_github_qauxv_util_hookimpl_lsplant_LsplantBridge_nativeHookMethod(JNIEnv* env, jclass clazz, jobject target, jobject callback, jobject context) {
if (!sLsplantInitSuccess) {
env->ThrowNew(env->FindClass("java/lang/IllegalAccessException"), "lsplant not initialized");
return nullptr;
}
return ::lsplant::Hook(env, target, context, callback);
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_io_github_qauxv_util_hookimpl_lsplant_LsplantBridge_nativeIsMethodHooked(JNIEnv* env, jclass clazz, jobject target) {
if (!sLsplantInitSuccess) {
env->ThrowNew(env->FindClass("java/lang/IllegalAccessException"), "lsplant not initialized");
return false;
}
return ::lsplant::IsHooked(env, target);
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_io_github_qauxv_util_hookimpl_lsplant_LsplantBridge_nativeUnhookMethod(JNIEnv* env, jclass clazz, jobject target) {
if (!sLsplantInitSuccess) {
env->ThrowNew(env->FindClass("java/lang/IllegalAccessException"), "lsplant not initialized");
return false;
}
return ::lsplant::UnHook(env, target);
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_io_github_qauxv_util_hookimpl_lsplant_LsplantBridge_nativeDeoptimizeMethod(JNIEnv* env, jclass clazz, jobject target) {
if (!sLsplantInitSuccess) {
env->ThrowNew(env->FindClass("java/lang/IllegalAccessException"), "lsplant not initialized");
return false;
}
return ::lsplant::Deoptimize(env, target);
}
11 changes: 11 additions & 0 deletions app/src/main/cpp/qauxv_core/Natives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1003,3 +1003,14 @@ Java_io_github_qauxv_util_Natives_open(JNIEnv *env,
}
return result;
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_github_qauxv_util_Natives_callObjectMethod(JNIEnv* env, jclass clazz, jobject method, jobject obj, jobjectArray args) {
if (method == nullptr) {
env->ThrowNew(env->FindClass("java/lang/NullPointerException"), "method is null");
return nullptr;
}
jmethodID methodId = env->FromReflectedMethod(method);
return env->CallObjectMethod(obj, methodId, args);
}
12 changes: 9 additions & 3 deletions app/src/main/cpp/utils/MemoryDexLoader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@

#include "utils/Log.h"

namespace qauxv {

static FileMemMap sLibArtFileMap;
static utils::ElfView sLibArtElfView;
static std::mutex sLibArtViewInitMutex;
static void* sLibArtBaseAddress = nullptr;

static std::string GetLibArtPath() {
std::string GetLibArtPath() {
utils::ProcessView processView;
if (processView.readProcess(getpid()) != 0) {
return {};
Expand All @@ -49,7 +51,7 @@ static std::string GetLibArtPath() {
return {};
}

static bool InitLibArtElfView() {
bool InitLibArtElfView() {
if (sLibArtElfView.IsValid()) {
return true;
}
Expand All @@ -65,7 +67,7 @@ static bool InitLibArtElfView() {
return sLibArtElfView.IsValid();
}

static bool InitLSPlantImpl(JNIEnv* env) {
bool InitLSPlantImpl(JNIEnv* env) {
const auto initProc = [env] {
::lsplant::InitInfo sLSPlantInitInfo = {
.inline_hooker = [](auto t, auto r) {
Expand Down Expand Up @@ -104,8 +106,11 @@ static bool InitLSPlantImpl(JNIEnv* env) {
return sLSPlantInitializeResult;
}

}

extern "C" JNIEXPORT jobject JNICALL
Java_io_github_qauxv_util_dyn_MemoryDexLoader_nativeCreateClassLoaderWithDexBelowOreo(JNIEnv* env, jclass clazz, jbyteArray dex_file, jobject parent) {
using namespace qauxv;
// This method is only used for Android 8.0 and below.
if (dex_file == nullptr) {
env->ThrowNew(env->FindClass("java/lang/NullPointerException"), "dex_file is null");
Expand Down Expand Up @@ -189,6 +194,7 @@ Java_io_github_qauxv_util_dyn_MemoryDexLoader_nativeCreateDexFileFormBytesBelowO
jbyteArray dex_bytes,
jobject defining_context,
jstring jstr_name) {
using namespace qauxv;
// This method is only used for Android 8.0 and below.
if (dex_bytes == nullptr) {
env->ThrowNew(env->FindClass("java/lang/NullPointerException"), "dex_file is null");
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/cpp/utils/MemoryDexLoader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <string>
#include <string_view>

#include <jni.h>

namespace qauxv {

std::string GetLibArtPath();

bool InitLibArtElfView();

bool InitLSPlantImpl(JNIEnv* env);

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.github.qauxv.util.Natives;
import io.github.qauxv.util.hookimpl.InMemoryClassLoaderHelper;
import io.github.qauxv.util.hookimpl.LibXposedNewApiByteCodeGenerator;
import io.github.qauxv.util.hookimpl.lsplant.LsplantHookImpl;
import java.lang.reflect.Field;

public class StartupRoutine {
Expand Down Expand Up @@ -64,6 +65,10 @@ public static void execPostStartupInit(Context ctx, Object step, String lpwReser
HostInfo.getHostInfo().getVersionName(), HostInfo.getHostInfo().getVersionCode());
StartupInfo.getLoaderService().setClassLoaderHelper(InMemoryClassLoaderHelper.INSTANCE);
LibXposedNewApiByteCodeGenerator.init();
if (StartupInfo.getHookBridge() == null) {
Log.w("HookBridge is null, fallback to embedded LSPlant.");
LsplantHookImpl.initializeLsplantHookBridge();
}
MainHook.getInstance().performHook(ctx, step);
}

Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/io/github/qauxv/util/Natives.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import android.system.StructUtsname;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.mmkv.MMKV;
import io.github.qauxv.BuildConfig;
import io.github.qauxv.loader.hookapi.IHookBridge;
Expand All @@ -40,6 +41,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -182,6 +184,9 @@ public static int read(int fd, byte[] buf, int len) throws IOException {
*/
public static native Object allocateInstanceImpl(Class<?> clazz);

public static native Object callObjectMethod(@NonNull Member method, @Nullable Object obj, @NonNull Object... args)
throws InvocationTargetException;

/**
* Invoke an instance method non-virtually (i.e. without calling the overridden method).
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2024 QAuxiliary developers
* https://github.com/cinit/QAuxiliary
*
* This software is an opensource software: you can redistribute it
* and/or modify it under the terms of the General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version as published
* by QAuxiliary contributors.
*
* This software 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 General Public License for more details.
*
* You should have received a copy of the General Public License
* along with this software.
* If not, see
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/

package io.github.qauxv.util.hookimpl.lsplant;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.lang.reflect.Member;

public class LsplantBridge {

private LsplantBridge() {
throw new AssertionError("No instance for you!");
}

static native void nativeInitializeLsplant() throws RuntimeException;

// return backup method if success, or null if failed
@Nullable
static native Member nativeHookMethod(@NonNull Member target, @NonNull Member callback, @NonNull Object context) throws RuntimeException;

static native boolean nativeIsMethodHooked(@NonNull Member target) throws RuntimeException;

static native boolean nativeUnhookMethod(@NonNull Member target) throws RuntimeException;

static native boolean nativeDeoptimizeMethod(@NonNull Member target) throws RuntimeException;

}
Loading

0 comments on commit c6a0e3b

Please sign in to comment.