Wait to hook static methods once their trampolines are fixed up
This commit is contained in:
parent
e7cdc25ab1
commit
18bd0a8c0f
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include <JNIHelper.h>
|
||||
#include <base/object.h>
|
||||
#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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <base/object.h>
|
||||
#include <config_manager.h>
|
||||
|
||||
namespace art {
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include "logging.h"
|
||||
|
||||
namespace edxp {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@
|
|||
#include <jni/art_class_linker.h>
|
||||
#include <jni/art_heap.h>
|
||||
#include <jni/edxp_yahfa.h>
|
||||
#include <dlfcn.h>
|
||||
#include <native_hook.h>
|
||||
#include <jni/framework_zygote.h>
|
||||
#include <jni/edxp_resources_hook.h>
|
||||
#include <dl_util.h>
|
||||
#include <art/runtime/jni_env_ext.h>
|
||||
#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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include <mutex>
|
||||
|
||||
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() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
#include <config_manager.h>
|
||||
#include <art/runtime/runtime.h>
|
||||
#include <dl_util.h>
|
||||
#include <art/runtime/jni_env_ext.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<Member, XposedBridge.AdditionalHookInfo>
|
||||
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.
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in New Issue