diff --git a/.gitmodules b/.gitmodules index 24e26c12..8d7d6f5b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "service"] path = service url = https://github.com/Xposed-Modules-Repo/XposedService.git +[submodule "core/src/main/cpp/external/DexBuilder"] + path = core/src/main/cpp/external/DexBuilder + url = https://github.com/LSPosed/DexBuilder.git diff --git a/core/src/main/cpp/external/CMakeLists.txt b/core/src/main/cpp/external/CMakeLists.txt index f40f49b9..4236d77a 100644 --- a/core/src/main/cpp/external/CMakeLists.txt +++ b/core/src/main/cpp/external/CMakeLists.txt @@ -17,4 +17,7 @@ target_include_directories(dobby PUBLIC Dobby/include) target_include_directories(dobby PUBLIC Dobby/builtin-plugin/BionicLinkerRestriction) add_subdirectory(SandHook) -target_include_directories(sandhook.lspd PUBLIC SandHook) \ No newline at end of file +target_include_directories(sandhook.lspd PUBLIC SandHook) + +add_subdirectory(DexBuilder) +target_include_directories(dex_builder PUBLIC DexBuilder) \ No newline at end of file diff --git a/core/src/main/cpp/external/DexBuilder b/core/src/main/cpp/external/DexBuilder new file mode 160000 index 00000000..3d8b2807 --- /dev/null +++ b/core/src/main/cpp/external/DexBuilder @@ -0,0 +1 @@ +Subproject commit 3d8b28079de54a7312786e36f40af26f1ee72d0f diff --git a/core/src/main/cpp/main/CMakeLists.txt b/core/src/main/cpp/main/CMakeLists.txt index 5c57ada5..ab3f0018 100644 --- a/core/src/main/cpp/main/CMakeLists.txt +++ b/core/src/main/cpp/main/CMakeLists.txt @@ -29,4 +29,4 @@ add_library(riru_lspd SHARED ${SRC_LIST} ${SRC_JNI_LIST}) find_package(riru REQUIRED CONFIG) find_library(log-lib log) -target_link_libraries(riru_lspd yahfa riru::riru android dobby sandhook.lspd ${log-lib}) \ No newline at end of file +target_link_libraries(riru_lspd yahfa riru::riru android dobby sandhook.lspd dex_builder ${log-lib}) \ No newline at end of file diff --git a/core/src/main/cpp/main/src/context.cpp b/core/src/main/cpp/main/src/context.cpp index 0896e8b5..b0cbce81 100644 --- a/core/src/main/cpp/main/src/context.cpp +++ b/core/src/main/cpp/main/src/context.cpp @@ -133,7 +133,7 @@ namespace lspd { jclass Context::FindClassFromLoader(JNIEnv *env, jobject class_loader, std::string_view class_name) { if (class_loader == nullptr) return nullptr; - static jclass clz = JNI_FindClass(env, "dalvik/system/DexClassLoader"); + static auto clz = (jclass)env->NewGlobalRef(env->FindClass( "dalvik/system/DexClassLoader")); static jmethodID mid = JNI_GetMethodID(env, clz, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); jclass ret = nullptr; diff --git a/core/src/main/cpp/main/src/jni/yahfa.cpp b/core/src/main/cpp/main/src/jni/yahfa.cpp index 3b596721..a0ca4bc3 100644 --- a/core/src/main/cpp/main/src/jni/yahfa.cpp +++ b/core/src/main/cpp/main/src/jni/yahfa.cpp @@ -27,23 +27,26 @@ #include "art/runtime/thread_list.h" #include "art/runtime/thread.h" #include "art/runtime/gc/scoped_gc_critical_section.h" +#include namespace lspd { - + using namespace startop::dex; LSP_DEF_NATIVE_METHOD(void, Yahfa, init, jint sdkVersion) { Java_lab_galaxy_yahfa_HookMain_init(env, clazz, sdkVersion); } LSP_DEF_NATIVE_METHOD(jobject, Yahfa, findMethodNative, jclass targetClass, - jstring methodName, jstring methodSig) { + jstring methodName, jstring methodSig) { return Java_lab_galaxy_yahfa_HookMain_findMethodNative(env, clazz, targetClass, methodName, methodSig); } LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, backupAndHookNative, jobject target, - jobject hook, jobject backup) { - art::gc::ScopedGCCriticalSection section(art::Thread::Current().Get(), art::gc::kGcCauseDebugger, art::gc::kCollectorTypeDebugger); + jobject hook, jobject backup) { + art::gc::ScopedGCCriticalSection section(art::Thread::Current().Get(), + art::gc::kGcCauseDebugger, + art::gc::kCollectorTypeDebugger); art::thread_list::ScopedSuspendAll suspend("Yahfa Hook", false); return Java_lab_galaxy_yahfa_HookMain_backupAndHookNative(env, clazz, target, hook, backup); } @@ -56,14 +59,129 @@ namespace lspd { return lspd::isHooked(getArtMethodYahfa(env, member)); } + LSP_DEF_NATIVE_METHOD(jclass, Yahfa, buildHooker, jobject app_class_loader, jclass return_class, jobjectArray classes) { + static auto in_memory_classloader = (jclass)env->NewGlobalRef(env->FindClass( "dalvik/system/InMemoryDexClassLoader")); + static jmethodID initMid = JNI_GetMethodID(env, in_memory_classloader, "", + "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"); + DexBuilder dex_file; + + auto parameter_length = env->GetArrayLength(classes); + auto parameter_types = std::vector(); + parameter_types.reserve(parameter_length); + std::string storage; + auto current_thread = art::Thread::Current(); + auto return_type = TypeDescriptor::FromDescriptor(art::mirror::Class(current_thread.DecodeJObject(return_class)).GetDescriptor(&storage)); + for (int i = 0; i < parameter_length; ++i) { + auto param = (jclass) env->GetObjectArrayElement(classes, i); + auto *param_ref = current_thread.DecodeJObject(param); + auto descriptor = art::mirror::Class(param_ref).GetDescriptor(&storage); + parameter_types.push_back(TypeDescriptor::FromDescriptor(descriptor)); + } + + ClassBuilder cbuilder{dex_file.MakeClass("LSPHooker")}; + cbuilder.set_source_file("LSP"); + + auto hooker_type = + TypeDescriptor::FromClassname("de.robv.android.xposed.LspHooker"); + + auto *hooker_field = cbuilder.CreateField("hooker", hooker_type) + .access_flags(dex::kAccStatic) + .Encode(); + + auto setupBuilder{cbuilder.CreateMethod( + "setup", Prototype{TypeDescriptor::Void, hooker_type})}; + setupBuilder + .AddInstruction(Instruction::SetStaticObjectField( + hooker_field->decl->orig_index, Value::Parameter(0))) + .BuildReturn() + .Encode(); + + auto hookBuilder{cbuilder.CreateMethod( + "hook", Prototype{return_type, parameter_types})}; + // allocate tmp frist because of wide + auto tmp{hookBuilder.AllocRegister()}; + hookBuilder.BuildConst(tmp, parameter_types.size()); + auto hook_params_array{hookBuilder.AllocRegister()}; + hookBuilder.BuildNewArray(hook_params_array, TypeDescriptor::Object, tmp); + for (size_t i = 0u, j = 0u; i < parameter_types.size(); ++i, ++j) { + hookBuilder.BuildBoxIfPrimitive(Value::Parameter(j), parameter_types[i], + Value::Parameter(j)); + hookBuilder.BuildConst(tmp, i); + hookBuilder.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()})}; + hookBuilder.AddInstruction( + Instruction::GetStaticObjectField(hooker_field->decl->orig_index, tmp)); + hookBuilder.AddInstruction(Instruction::InvokeVirtualObject( + handle_hook_method.id, tmp, tmp, hook_params_array)); + if (return_type == TypeDescriptor::Void) { + hookBuilder.BuildReturn(); + } else if (return_type.is_primitive()) { + auto box_type{return_type.ToBoxType()}; + const ir::Type *type_def = dex_file.GetOrAddType(box_type); + hookBuilder.AddInstruction( + Instruction::Cast(tmp, Value::Type(type_def->orig_index))); + hookBuilder.BuildUnBoxIfPrimitive(tmp, box_type, tmp); + hookBuilder.BuildReturn(tmp, false, return_type.is_wide()); + } else { + const ir::Type *type_def = dex_file.GetOrAddType(return_type); + hookBuilder.AddInstruction( + Instruction::Cast(tmp, Value::Type(type_def->orig_index))); + hookBuilder.BuildReturn(tmp, true); + } + auto *hook_method = hookBuilder.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); + } + auto *back_method = backup_builder.Encode(); + + slicer::MemView image{dex_file.CreateImage()}; + std::unique_ptr buffer = std::make_unique(image.size()); + memcpy(buffer.get(), image.ptr(), image.size()); + + auto dex_buffer = env->NewDirectByteBuffer(buffer.get(), image.size()); + jobject my_cl = JNI_NewObject(env, in_memory_classloader, initMid, + dex_buffer, app_class_loader); + + static jmethodID mid = JNI_GetMethodID(env, in_memory_classloader, "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + if (!mid) { + mid = JNI_GetMethodID(env, in_memory_classloader, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + } + jobject target = env->CallObjectMethod(my_cl, mid, env->NewStringUTF("LSPHooker")); +// LOGD("Created %zd", image.size()); + if (target) { + return (jclass) target; + } + 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/Member;"), + "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Member;"), LSP_NATIVE_METHOD(Yahfa, backupAndHookNative, - "(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"), + "(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"), LSP_NATIVE_METHOD(Yahfa, recordHooked, "(Ljava/lang/reflect/Member;)V"), - LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Member;)Z") + LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Member;)Z"), + LSP_NATIVE_METHOD(Yahfa, buildHooker, "(Ljava/lang/ClassLoader;Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/Class;"), }; void RegisterYahfa(JNIEnv *env) { diff --git a/core/src/main/java/io/github/lsposed/lspd/nativebridge/Yahfa.java b/core/src/main/java/io/github/lsposed/lspd/nativebridge/Yahfa.java index d40d0ac6..330c94ac 100644 --- a/core/src/main/java/io/github/lsposed/lspd/nativebridge/Yahfa.java +++ b/core/src/main/java/io/github/lsposed/lspd/nativebridge/Yahfa.java @@ -35,4 +35,6 @@ public class Yahfa { public static native void recordHooked(Member member); public static native boolean isHooked(Member member); + + public static native Class buildHooker(ClassLoader appClassLoader, Class returnType, Class[] params); } diff --git a/core/src/main/java/io/github/lsposed/lspd/yahfa/dexmaker/HookerDexMaker.java b/core/src/main/java/io/github/lsposed/lspd/yahfa/dexmaker/HookerDexMaker.java index f2caf279..bd2ef657 100644 --- a/core/src/main/java/io/github/lsposed/lspd/yahfa/dexmaker/HookerDexMaker.java +++ b/core/src/main/java/io/github/lsposed/lspd/yahfa/dexmaker/HookerDexMaker.java @@ -25,6 +25,7 @@ import android.os.Build; import io.github.lsposed.lspd.BuildConfig; import io.github.lsposed.lspd.core.yahfa.HookMain; +import io.github.lsposed.lspd.nativebridge.Yahfa; import io.github.lsposed.lspd.util.ProxyClassLoader; import java.io.File; @@ -70,10 +71,11 @@ public class HookerDexMaker { hookerTypeId.getMethod(TypeId.OBJECT, "handleHookedMethod", objArrayTypeId); private FieldId mHookerFieldId; + private Class mReturnType; + private Class[] mActualParameterTypes; private TypeId mHookerTypeId; private TypeId[] mParameterTypeIds; - private Class[] mActualParameterTypes; private TypeId mReturnTypeId; private DexMaker mDexMaker; @@ -112,15 +114,14 @@ public class HookerDexMaker { public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo, ClassLoader appClassLoader) throws Exception { - Class returnType; boolean isStatic; if (member instanceof Method) { Method method = (Method) member; isStatic = Modifier.isStatic(method.getModifiers()); - returnType = method.getReturnType(); - if (returnType.equals(Void.class) || returnType.equals(void.class) - || returnType.isPrimitive()) { - mReturnTypeId = TypeId.get(returnType); + mReturnType = method.getReturnType(); + if (mReturnType.equals(Void.class) || mReturnType.equals(void.class) + || mReturnType.isPrimitive()) { + mReturnTypeId = TypeId.get(mReturnType); } else { // all others fallback to plain Object for convenience mReturnTypeId = TypeId.OBJECT; @@ -149,7 +150,11 @@ public class HookerDexMaker { mAppClassLoader = appClassLoader; mAppClassLoader = new ProxyClassLoader(mAppClassLoader, getClass().getClassLoader()); } + + long startTime = System.nanoTime(); doMake(member.getDeclaringClass().getName()); + long endTime = System.nanoTime(); + DexLog.d("Hook time: " + (endTime - startTime) / 1e6 + "ms"); } @SuppressWarnings("ResultOfMethodCallIgnored") @@ -158,14 +163,16 @@ public class HookerDexMaker { Class hookClass = null; // Generate a Hooker class. String className = CLASS_NAME_PREFIX; - if (canCache) { - mDexMaker = new DexMaker(); - // className is also used as dex file name - // so it should be different from each other + hookClass = Yahfa.buildHooker(mAppClassLoader, mReturnType, mActualParameterTypes); + if (canCache && hookClass == null) { String suffix = DexMakerUtils.getSha1Hex(mMember.toString()); className = className + suffix; String dexFileName = className + ".jar"; File dexFile = new File(serviceClient.getCachePath(dexFileName)); + DexLog.d("dex builder failed, generating " + dexFileName); + mDexMaker = new DexMaker(); + // className is also used as dex file name + // so it should be different from each other if (dexFile.exists()) { try { // if file exists, reuse it and skip generating