Wait to hook static methods once their trampolines are fixed up

This commit is contained in:
solohsu 2019-06-05 00:05:40 +08:00
parent e7cdc25ab1
commit 18bd0a8c0f
15 changed files with 319 additions and 25 deletions

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -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);
}
};
}

View File

@ -2,6 +2,7 @@
#pragma once
#include <base/object.h>
#include <config_manager.h>
namespace art {

View File

@ -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";

View File

@ -1,6 +1,7 @@
#pragma once
#include <dlfcn.h>
#include "logging.h"
namespace edxp {

View File

@ -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;
}

View File

@ -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() {}

View File

@ -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);
}
}

View File

@ -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");
}

View File

@ -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);
}
}

View File

@ -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";

View File

@ -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.
*