diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/art/ClassLinker.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/art/ClassLinker.java index f566c64d..5d2eeeb9 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/art/ClassLinker.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/art/ClassLinker.java @@ -1,9 +1,16 @@ package com.elderdrivers.riru.edxp.art; +import com.elderdrivers.riru.common.KeepAll; + import java.lang.reflect.Member; -public class ClassLinker { +import de.robv.android.xposed.XposedBridge; + +public class ClassLinker implements KeepAll { public static native void setEntryPointsToInterpreter(Member method); + public static void onPostFixupStaticTrampolines(Class clazz) { + XposedBridge.hookPendingMethod(clazz); + } } diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/ClassUtils.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/ClassUtils.java new file mode 100644 index 00000000..3421d1ce --- /dev/null +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/ClassUtils.java @@ -0,0 +1,73 @@ +package com.elderdrivers.riru.edxp.util; + +import de.robv.android.xposed.XposedHelpers; + +public class ClassUtils { + + public enum ClassStatus { + kNotReady(0), // Zero-initialized Class object starts in this state. + kRetired(1), // Retired, should not be used. Use the newly cloned one instead. + kErrorResolved(2), + kErrorUnresolved(3), + kIdx(4), // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_. + kLoaded(5), // DEX idx values resolved. + kResolving(6), // Just cloned from temporary class object. + kResolved(7), // Part of linking. + kVerifying(8), // In the process of being verified. + kRetryVerificationAtRuntime(9), // Compile time verification failed, retry at runtime. + kVerifyingAtRuntime(10), // Retrying verification at runtime. + kVerified(11), // Logically part of linking; done pre-init. + kSuperclassValidated(12), // Superclass validation part of init done. + kInitializing(13), // Class init in progress. + kInitialized(14); // Ready to go. + + private final int status; + + ClassStatus(int status) { + this.status = status; + } + + static ClassStatus withValue(int value) { + for (ClassStatus status : ClassStatus.values()) { + if (status.status == value) { + return status; + } + } + return kNotReady; + } + } + + /** + * enum class ClassStatus : uint8_t { + * kNotReady = 0, // Zero-initialized Class object starts in this state. + * kRetired = 1, // Retired, should not be used. Use the newly cloned one instead. + * kErrorResolved = 2, + * kErrorUnresolved = 3, + * kIdx = 4, // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_. + * kLoaded = 5, // DEX idx values resolved. + * kResolving = 6, // Just cloned from temporary class object. + * kResolved = 7, // Part of linking. + * kVerifying = 8, // In the process of being verified. + * kRetryVerificationAtRuntime = 9, // Compile time verification failed, retry at runtime. + * kVerifyingAtRuntime = 10, // Retrying verification at runtime. + * kVerified = 11, // Logically part of linking; done pre-init. + * kSuperclassValidated = 12, // Superclass validation part of init done. + * kInitializing = 13, // Class init in progress. + * kInitialized = 14, // Ready to go. + * kLast = kInitialized + * }; + */ + public static ClassStatus getClassStatus(Class clazz) { + if (clazz == null) { + return ClassStatus.kNotReady; + } + int status = XposedHelpers.getIntField(clazz, "status"); + return ClassStatus.withValue((int) (Integer.toUnsignedLong(status) >> (32 - 4))); + } + + + public static boolean isInitialized(Class clazz) { + return getClassStatus(clazz) == ClassStatus.kInitialized; + } + +} diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/Unsafe.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/Unsafe.java new file mode 100644 index 00000000..ab788317 --- /dev/null +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/Unsafe.java @@ -0,0 +1,122 @@ +/* + * Copyright 2014-2015 Marvin Wißfeld + * + * 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. + */ +package com.elderdrivers.riru.edxp.util; + + +import android.util.Log; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public final class Unsafe { + private static final String TAG = "Unsafe"; + + private static Object unsafe; + private static Class unsafeClass; + + private static Method arrayBaseOffsetMethod, + arrayIndexScaleMethod, + getIntMethod, + getLongMethod; + + private volatile static boolean supported = false; + + private static Class objectArrayClass = Object[].class; + + static { + try { + unsafeClass = Class.forName("sun.misc.Unsafe"); + Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + unsafe = theUnsafe.get(null); + } catch (Exception e) { + try { + final Field theUnsafe = unsafeClass.getDeclaredField("THE_ONE"); + theUnsafe.setAccessible(true); + unsafe = theUnsafe.get(null); + } catch (Exception e2) { + Log.w(TAG, "Unsafe not found o.O"); + } + } + if (unsafe != null) { + try { + arrayBaseOffsetMethod = unsafeClass.getDeclaredMethod("arrayBaseOffset", Class.class); + arrayIndexScaleMethod = unsafeClass.getDeclaredMethod("arrayIndexScale", Class.class); + getIntMethod = unsafeClass.getDeclaredMethod("getInt", Object.class, long.class); + getLongMethod = unsafeClass.getDeclaredMethod("getLong", Object.class, long.class); + supported = true; + } catch (Exception e) { + } + } + } + + public static boolean support() { + return supported; + } + + private Unsafe() { + } + + @SuppressWarnings("unchecked") + public static int arrayBaseOffset(Class cls) { + try { + return (int) arrayBaseOffsetMethod.invoke(unsafe, cls); + } catch (Exception e) { + return 0; + } + } + + @SuppressWarnings("unchecked") + public static int arrayIndexScale(Class cls) { + try { + return (int) arrayIndexScaleMethod.invoke(unsafe, cls); + } catch (Exception e) { + return 0; + } + } + + @SuppressWarnings("unchecked") + public static int getInt(Object array, long offset) { + try { + return (int) getIntMethod.invoke(unsafe, array, offset); + } catch (Exception e) { + return 0; + } + } + + @SuppressWarnings("unchecked") + public static long getLong(Object array, long offset) { + try { + return (long) getLongMethod.invoke(unsafe, array, offset); + } catch (Exception e) { + return 0; + } + } + + public static long getObjectAddress(Object obj) { + try { + Object[] array = new Object[]{obj}; + if (arrayIndexScale(objectArrayClass) == 8) { + return getLong(array, arrayBaseOffset(objectArrayClass)); + } else { + return 0xffffffffL & getInt(array, arrayBaseOffset(objectArrayClass)); + } + } catch (Exception e) { + Utils.logE("get object address error", e); + return -1; + } + } +} diff --git a/edxp-core/src/main/cpp/main/include/art/runtime/class_linker.h b/edxp-core/src/main/cpp/main/include/art/runtime/class_linker.h index e6c71b48..816d8518 100644 --- a/edxp-core/src/main/cpp/main/include/art/runtime/class_linker.h +++ b/edxp-core/src/main/cpp/main/include/art/runtime/class_linker.h @@ -3,6 +3,9 @@ #include #include +#include "runtime.h" +#include "jni_env_ext.h" +#include "edxp_context.h" namespace art { @@ -24,6 +27,11 @@ namespace art { return ConstructorBackup(thiz, intern_table); } + CREATE_HOOK_STUB_ENTRIES(void, FixupStaticTrampolines, void *thiz, void *clazz_ptr) { + FixupStaticTrampolinesBackup(thiz, clazz_ptr); + edxp::Context::GetInstance()->CallOnPostFixupStaticTrampolines(clazz_ptr); + } + public: ClassLinker(void *thiz) : HookedObject(thiz) {} @@ -35,6 +43,8 @@ namespace art { HOOK_FUNC(Constructor, "_ZN3art11ClassLinkerC2EPNS_11InternTableE"); RETRIEVE_FUNC_SYMBOL(SetEntryPointsToInterpreter, "_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE"); + + HOOK_FUNC(FixupStaticTrampolines, "_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE"); } ALWAYS_INLINE void SetEntryPointsToInterpreter(void *art_method) const { diff --git a/edxp-core/src/main/cpp/main/include/art/runtime/jni_env_ext.h b/edxp-core/src/main/cpp/main/include/art/runtime/jni_env_ext.h new file mode 100644 index 00000000..1f34f6cf --- /dev/null +++ b/edxp-core/src/main/cpp/main/include/art/runtime/jni_env_ext.h @@ -0,0 +1,29 @@ + +#pragma once + +#include "jni.h" +#include "base/object.h" + +namespace art { + + class JNIEnvExt : edxp::HookedObject { + + private: + CREATE_FUNC_SYMBOL_ENTRY(jobject, NewLocalRef, void *env, void *mirror_ptr) { + return NewLocalRefSym(env, mirror_ptr); + } + + public: + JNIEnvExt(void *thiz) : HookedObject(thiz) {} + + static void Setup(void *handle, HookFunType hook_func) { + RETRIEVE_FUNC_SYMBOL(NewLocalRef, "_ZN3art9JNIEnvExt11NewLocalRefEPNS_6mirror6ObjectE"); + } + + jobject NewLocalRefer(void *mirror_ptr) { + return NewLocalRef(thiz_, mirror_ptr); + } + }; + + +} \ No newline at end of file diff --git a/edxp-core/src/main/cpp/main/include/art/runtime/runtime.h b/edxp-core/src/main/cpp/main/include/art/runtime/runtime.h index bf9ec09c..17404d6b 100644 --- a/edxp-core/src/main/cpp/main/include/art/runtime/runtime.h +++ b/edxp-core/src/main/cpp/main/include/art/runtime/runtime.h @@ -2,6 +2,7 @@ #pragma once #include +#include namespace art { diff --git a/edxp-core/src/main/cpp/main/include/config.h b/edxp-core/src/main/cpp/main/include/config.h index c9a6c2c9..f0e8cb2e 100644 --- a/edxp-core/src/main/cpp/main/include/config.h +++ b/edxp-core/src/main/cpp/main/include/config.h @@ -21,6 +21,7 @@ namespace edxp { "/system/framework/eddalvikdx.jar:" "/system/framework/eddexmaker.jar"; static constexpr const char *kEntryClassName = "com.elderdrivers.riru.edxp.core.Main"; + static constexpr const char *kClassLinkerClassName = "com.elderdrivers.riru.edxp.art.ClassLinker"; static constexpr const char *kSandHookClassName = "com.swift.sandhook.SandHook"; static constexpr const char *kSandHookNeverCallClassName = "com.swift.sandhook.ClassNeverCall"; diff --git a/edxp-core/src/main/cpp/main/include/dl_util.h b/edxp-core/src/main/cpp/main/include/dl_util.h index 74c9bb15..6eb05079 100644 --- a/edxp-core/src/main/cpp/main/include/dl_util.h +++ b/edxp-core/src/main/cpp/main/include/dl_util.h @@ -1,6 +1,7 @@ #pragma once +#include #include "logging.h" namespace edxp { diff --git a/edxp-core/src/main/cpp/main/src/edxp_context.cpp b/edxp-core/src/main/cpp/main/src/edxp_context.cpp index 3e210630..37d6a0bc 100644 --- a/edxp-core/src/main/cpp/main/src/edxp_context.cpp +++ b/edxp-core/src/main/cpp/main/src/edxp_context.cpp @@ -7,11 +7,10 @@ #include #include #include -#include -#include #include #include #include +#include #include "edxp_context.h" #include "config_manager.h" @@ -33,10 +32,21 @@ namespace edxp { return inject_class_loader_; } + void Context::CallOnPostFixupStaticTrampolines(void *class_ptr) { + if (post_fixup_static_mid_ != nullptr) { + JNIEnv *env; + vm_->GetEnv((void **) (&env), JNI_VERSION_1_4); + art::JNIEnvExt env_ext(env); + jobject clazz = env_ext.NewLocalRefer(class_ptr); + JNI_CallStaticVoidMethod(env, class_linker_class_, post_fixup_static_mid_, clazz); + } + } + void Context::LoadDexAndInit(JNIEnv *env, const char *dex_path) { if (LIKELY(initialized_)) { return; } + jclass classloader = JNI_FindClass(env, "java/lang/ClassLoader"); jmethodID getsyscl_mid = JNI_GetStaticMethodID( env, classloader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); @@ -55,6 +65,15 @@ namespace edxp { return; } inject_class_loader_ = env->NewGlobalRef(my_cl); + + // initialize pending methods related + env->GetJavaVM(&vm_); + class_linker_class_ = (jclass) env->NewGlobalRef( + FindClassFromLoader(env, kClassLinkerClassName)); + post_fixup_static_mid_ = JNI_GetStaticMethodID(env, class_linker_class_, + "onPostFixupStaticTrampolines", + "(Ljava/lang/Class;)V"); + entry_class_ = (jclass) (env->NewGlobalRef( FindClassFromLoader(env, GetCurrentClassLoader(), kEntryClassName))); @@ -131,6 +150,10 @@ namespace edxp { } } + ALWAYS_INLINE JavaVM *Context::GetJavaVM() const { + return vm_; + } + ALWAYS_INLINE void Context::SetAppDataDir(jstring app_data_dir) { app_data_dir_ = app_data_dir; } diff --git a/edxp-core/src/main/cpp/main/src/edxp_context.h b/edxp-core/src/main/cpp/main/src/edxp_context.h index 8b00f2a0..45369f21 100644 --- a/edxp-core/src/main/cpp/main/src/edxp_context.h +++ b/edxp-core/src/main/cpp/main/src/edxp_context.h @@ -2,6 +2,7 @@ #pragma once #include +#include namespace edxp { @@ -14,10 +15,14 @@ namespace edxp { jobject GetCurrentClassLoader() const; + void CallOnPostFixupStaticTrampolines(void *class_ptr); + void PrepareJavaEnv(JNIEnv *env); void FindAndCall(JNIEnv *env, const char *method_name, const char *method_sig, ...) const; + JavaVM *GetJavaVM() const; + void SetAppDataDir(jstring app_data_dir); void SetNiceName(jstring nice_name); @@ -51,6 +56,9 @@ namespace edxp { jclass entry_class_ = nullptr; jstring app_data_dir_ = nullptr; jstring nice_name_ = nullptr; + JavaVM *vm_ = nullptr; + jclass class_linker_class_ = nullptr; + jmethodID post_fixup_static_mid_ = nullptr; Context() {} diff --git a/edxp-core/src/main/cpp/main/src/jni/art_class_linker.cpp b/edxp-core/src/main/cpp/main/src/jni/art_class_linker.cpp index 0b9f4231..5ad62d20 100644 --- a/edxp-core/src/main/cpp/main/src/jni/art_class_linker.cpp +++ b/edxp-core/src/main/cpp/main/src/jni/art_class_linker.cpp @@ -28,7 +28,7 @@ namespace edxp { }; void RegisterArtClassLinker(JNIEnv *env) { - REGISTER_EDXP_NATIVE_METHODS("com.elderdrivers.riru.edxp.art.ClassLinker"); + REGISTER_EDXP_NATIVE_METHODS(kClassLinkerClassName); } } \ No newline at end of file diff --git a/edxp-core/src/main/cpp/main/src/native_hook.cpp b/edxp-core/src/main/cpp/main/src/native_hook.cpp index d0c6166b..a3d6d790 100644 --- a/edxp-core/src/main/cpp/main/src/native_hook.cpp +++ b/edxp-core/src/main/cpp/main/src/native_hook.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "logging.h" #include "native_hook.h" @@ -55,6 +56,7 @@ namespace edxp { art::gc::Heap::Setup(art_handle.Get(), hook_func); art::ClassLinker::Setup(art_handle.Get(), hook_func); art::mirror::Class::Setup(art_handle.Get(), hook_func); + art::JNIEnvExt::Setup(art_handle.Get(), hook_func); LOGI("Inline hooks installed"); } diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DexMakerUtils.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DexMakerUtils.java index 8d18a1e9..9d9f3e8a 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DexMakerUtils.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DexMakerUtils.java @@ -5,9 +5,11 @@ import android.os.Build; import android.text.TextUtils; import com.elderdrivers.riru.edxp.config.ConfigManager; -import com.elderdrivers.riru.edxp.core.yahfa.HookMain; +import com.elderdrivers.riru.edxp.util.ClassUtils; +import com.elderdrivers.riru.edxp.util.Utils; import java.lang.reflect.Member; +import java.lang.reflect.Modifier; import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; @@ -246,15 +248,19 @@ public class DexMakerUtils { } public static Member findMethodNative(Member hookMethod) { - MethodInfo methodInfo = new MethodInfo(hookMethod); - Class declaringClass = methodInfo.getClassForSure(); - Member reflectMethod = (Member) HookMain.findMethod( - declaringClass, methodInfo.methodName, methodInfo.methodSig); - if (reflectMethod == null) { - DexLog.e("method not found: name=" - + methodInfo.methodName + ", sig=" + methodInfo.methodSig); - reflectMethod = hookMethod; + if (shouldDelayHook(hookMethod)) { + Utils.logD("solo: " + hookMethod + " hooking delayed."); + return null; } - return reflectMethod; + return hookMethod; + } + + private static boolean shouldDelayHook(Member hookMethod) { + if (hookMethod == null) { + return false; + } + Class declaringClass = hookMethod.getDeclaringClass(); + return Modifier.isStatic(hookMethod.getModifiers()) + && !ClassUtils.isInitialized(declaringClass); } } diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/MethodInfo.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/MethodInfo.java index 27ea86e0..73e615a0 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/MethodInfo.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/MethodInfo.java @@ -55,16 +55,6 @@ public class MethodInfo { methodSig = builder.toString(); } - public Class getClassForSure() { - try { - // TODO does initialize make sense? - return Class.forName(className, true, classLoader); - } catch (Throwable throwable) { - DexLog.e("error when getClassForSure", throwable); - return null; - } - } - public static String getDescStr(Class clazz) { if (clazz.equals(boolean.class)) { return "Z"; diff --git a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java index 37cee9e9..fdaea140 100644 --- a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java +++ b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import dalvik.system.InMemoryDexClassLoader; import de.robv.android.xposed.XC_MethodHook.MethodHookParam; @@ -231,12 +232,32 @@ public final class XposedBridge { AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType); Member reflectMethod = EdXpConfigGlobal.getHookProvider().findMethodNative(hookMethod); - hookMethodNative(reflectMethod, declaringClass, slot, additionalInfo); + if (reflectMethod != null) { + hookMethodNative(reflectMethod, declaringClass, slot, additionalInfo); + } else { + synchronized (pendingLock) { + sPendingHookMethods.put(hookMethod, additionalInfo); + } + } } return callback.new Unhook(hookMethod); } + private static final ConcurrentHashMap + sPendingHookMethods = new ConcurrentHashMap<>(); + private static final Object pendingLock = new Object(); + + public static void hookPendingMethod(Class clazz) { + synchronized (pendingLock) { + for (Member member : sPendingHookMethods.keySet()) { + if (member.getDeclaringClass().equals(clazz)) { + hookMethodNative(member, clazz, 0, sPendingHookMethods.get(member)); + } + } + } + } + /** * Removes the callback for a hooked method/constructor. *