当使用 XposedBridge.hookMethod 这个 api对java函数进行hook时:
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
if (!(hookMethod instanceof Executable)) {
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod);
} else if (Modifier.isAbstract(hookMethod.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod);
} else if (hookMethod.getDeclaringClass().getClassLoader() == XposedBridge.class.getClassLoader()) {
throw new IllegalArgumentException("Do not allow hooking inner methods");
} else if (hookMethod.getDeclaringClass() == Method.class && hookMethod.getName().equals("invoke")) {
throw new IllegalArgumentException("Cannot hook Method.invoke");
}
if (callback == null) {
throw new IllegalArgumentException("callback should not be null!");
}
if (!HookBridge.hookMethod(false, (Executable) hookMethod, LSPosedBridge.NativeHooker.class, callback.priority, callback)) {
log("Failed to hook " + hookMethod);
return null;
}
return callback.new Unhook(hookMethod);
}
会调用 HookBridge.hookMethod 这个内部api,实现在native层:
public class HookBridge {
public static native boolean hookMethod(boolean useModernApi, Executable hookMethod, Class<?> hooker, int priority, Object callback);
public static native boolean unhookMethod(boolean useModernApi, Executable hookMethod, Object callback);
public static native boolean deoptimizeMethod(Executable method);
public static native <T> T allocateObject(Class<T> clazz) throws InstantiationException;
public static native Object invokeOriginalMethod(Executable method, Object thisObject, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
public static native <T> Object invokeSpecialMethod(Executable method, char[] shorty, Class<T> clazz, Object thisObject, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
@FastNative
public static native boolean instanceOf(Object obj, Class<?> clazz);
@FastNative
public static native boolean setTrusted(Object cookie);
public static native Object[][] callbackSnapshot(Class<?> hooker_callback, Executable method);
}
native层方法定义如下:
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jboolean useModernApi, jobject hookMethod,
jclass hooker, jint priority, jobject callback) {
bool newHook = false;
#ifndef NDEBUG
struct finally {
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
bool &newHook;
~finally() {
auto finish = std::chrono::steady_clock::now();
if (newHook) {
LOGV("New hook took {}us",
std::chrono::duration_cast<std::chrono::microseconds>(finish - start).count());
}
}
} finally {
.newHook = newHook
};
#endif
auto target = env->FromReflectedMethod(hookMethod); //ArtMethod 结构体
HookItem * hook_item = nullptr;
hooked_methods.lazy_emplace_l(target, [&hook_item](auto &it) {
hook_item = it.second.get();
}, [&hook_item, &target, &newHook](const auto &ctor) {
auto ptr = std::make_unique<HookItem>();
hook_item = ptr.get();
ctor(target, std::move(ptr));
newHook = true;
});
if (newHook) {
auto init = env->GetMethodID(hooker, "<init>", "(Ljava/lang/reflect/Executable;)V");
auto callback_method = env->ToReflectedMethod(hooker, env->GetMethodID(hooker, "callback", //LSPosedBridge.NativeHooker
"([Ljava/lang/Object;)Ljava/lang/Object;"),
false);
auto hooker_object = env->NewObject(hooker, init, hookMethod); //新建 NativeHooker 对象,传入 目标hookMethod
hook_item->SetBackup(lsplant::Hook(env, hookMethod, hooker_object, callback_method)); //传入的目标method 和 NativeHooker对象
env->DeleteLocalRef(hooker_object);
}
jobject backup = hook_item->GetBackup();
if (!backup) return JNI_FALSE;
JNIMonitor monitor(env, backup);
if (useModernApi) {
if (before_method_field == nullptr) {
auto callback_class = JNI_GetObjectClass(env, callback);
callback_ctor = JNI_GetMethodID(env, callback_class, "<init>", "(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V");
before_method_field = JNI_GetFieldID(env, callback_class, "beforeInvocation", "Ljava/lang/reflect/Method;");
after_method_field = JNI_GetFieldID(env, callback_class, "afterInvocation", "Ljava/lang/reflect/Method;");
}
auto before_method = JNI_GetObjectField(env, callback, before_method_field);
auto after_method = JNI_GetObjectField(env, callback, after_method_field);
auto callback_type = ModuleCallback {
.before_method = env->FromReflectedMethod(before_method),
.after_method = env->FromReflectedMethod(after_method),
};
hook_item->modern_callbacks.emplace(priority, callback_type);
} else {
hook_item->legacy_callbacks.emplace(priority, env->NewGlobalRef(callback));
}
return JNI_TRUE;
}
关键处是调用 lsplant::Hook(env, hookMethod, hooker_object, callback_method) 这个接口进行HOOK操作。
[[maybe_unused]] jobject Hook(JNIEnv *env, jobject target_method, jobject hooker_object, //lsplant 处理JAVA函数hook的地方
jobject callback_method) {
if (!target_method || !JNI_IsInstanceOf(env, target_method, executable)) {
LOGE("target method is not an executable");
return nullptr;
}
if (!callback_method || !JNI_IsInstanceOf(env, callback_method, executable)) {
LOGE("callback method is not an executable");
return nullptr;
}
jmethodID hook_method = nullptr;
jmethodID backup_method = nullptr;
jfieldID hooker_field = nullptr;
auto target_class =
JNI_Cast<jclass>(JNI_CallObjectMethod(env, target_method, method_get_declaring_class)); //获取target方法的申明类
constexpr static uint32_t kAccClassIsProxy = 0x00040000;
bool is_proxy = JNI_GetIntField(env, target_class, class_access_flags) & kAccClassIsProxy;
auto *target = ArtMethod::FromReflectedMethod(env, target_method); // ArtMethod 结构体
bool is_static = target->IsStatic();
if (IsHooked(target, true)) { //从 sharedHashMap缓存中查找
LOGW("Skip duplicate hook");
return nullptr;
}
ScopedLocalRef<jclass> built_class{env};
{
auto callback_name =
JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_method, method_get_name));
JUTFString callback_method_name(callback_name);
auto target_name =
JNI_Cast<jstring>(JNI_CallObjectMethod(env, target_method, method_get_name));
JUTFString target_method_name(target_name);
auto callback_class = JNI_Cast<jclass>(
JNI_CallObjectMethod(env, callback_method, method_get_declaring_class));
auto callback_class_loader =
JNI_CallObjectMethod(env, callback_class, class_get_class_loader);
auto callback_class_name =
JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_class, class_get_name));
JUTFString class_name(callback_class_name);
if (!JNI_IsInstanceOf(env, hooker_object, callback_class)) {
LOGE("callback_method is not a method of hooker_object");
return nullptr;
}
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
env,
BuildDex(env, callback_class_loader,
__builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method)
: ArtMethod::GetMethodShorty(env, target_method),
is_static, target->IsConstructor() ? "constructor" : target_method_name.get(),
class_name.get(), callback_method_name.get()));
if (!built_class || !hooker_field || !hook_method || !backup_method) {
LOGE("Failed to generate hooker");
return nullptr;
}
}
auto reflected_hook = JNI_ToReflectedMethod(env, built_class, hook_method, is_static);
auto reflected_backup = JNI_ToReflectedMethod(env, built_class, backup_method, is_static);
JNI_CallVoidMethod(env, reflected_backup, set_accessible, JNI_TRUE);
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook);
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup);
JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object);
if (DoHook(target, hook, backup)) {
std::apply(
[backup_method, target_method_id = env->FromReflectedMethod(target_method)](auto... v) {
((*v == target_method_id &&
(LOGD("Propagate internal used method because of hook"), *v = backup_method)) ||
...);
},
kInternalMethods);
jobject global_backup = JNI_NewGlobalRef(env, reflected_backup);
RecordHooked(target, target->GetDeclaringClass()->GetClassDef(), global_backup, backup);
if (!is_proxy) [[likely]] {
RecordJitMovement(target, backup);
}
// Always record backup as deoptimized since we dont want its entrypoint to be updated
// by FixupStaticTrampolines on hooker class
// Used hook's declaring class here since backup's is no longer the same with hook's
RecordDeoptimized(hook->GetDeclaringClass()->GetClassDef(), backup);
return global_backup;
}
return nullptr;
}
这个函数先使用 BuildDex 接口动态生成HOOK类和方法,然后创建ClassLoader加载这个DEX,详情可以分析 BuildDex 内部,会创建 LSPHooker_ 类名和hook的方法名。然后调用 DoHook来HOOK函数:
bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
ScopedGCCriticalSection section(art::Thread::Current(), art::gc::kGcCauseDebugger,
art::gc::kCollectorTypeDebugger);
ScopedSuspendAll suspend("LSPlant Hook", false);
LOGV("Hooking: target = %s(%p), hook = %s(%p), backup = %s(%p)", target->PrettyMethod().c_str(),
target, hook->PrettyMethod().c_str(), hook, backup->PrettyMethod().c_str(), backup);
if (auto *entrypoint = GenerateTrampolineFor(hook); !entrypoint) { // 获取 hook方法的 entrypoint
LOGE("Failed to generate trampoline");
return false;
// NOLINTNEXTLINE
} else {
LOGV("Generated trampoline %p", entrypoint);
target->SetNonCompilable(); //设置不编译jit
hook->SetNonCompilable();
// copy after setNonCompilable
backup->CopyFrom(target);
target->ClearFastInterpretFlag(); //设置 flag
target->SetEntryPoint(entrypoint); //替换 entrypoint
if (!backup->IsStatic()) backup->SetPrivate();
LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p", target,
target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(),
backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint());
return true;
}
}
所以 HOOK的核心是 替换方法的 entrypoint,那么接着看 替换的 entrypoint 是怎么生成的:
void *GenerateTrampolineFor(art::ArtMethod *hook) {
unsigned count;
uintptr_t address;
while (true) {
auto tl = Trampoline{.address = trampoline_pool.fetch_add(1, std::memory_order_release)};
count = tl.count;
address = tl.address & ~kAddressMask;
if (address == 0 || count >= kTrampolineNumPerPage) {
if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) {
trampoline_lock.wait(true, std::memory_order_acquire);
continue;
}
address = reinterpret_cast<uintptr_t>(mmap(nullptr, kPageSize,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (address == reinterpret_cast<uintptr_t>(MAP_FAILED)) {
PLOGE("mmap trampoline");
trampoline_lock.clear(std::memory_order_release);
trampoline_lock.notify_all();
return nullptr;
}
count = 0;
tl.address = address;
tl.count = count + 1;
trampoline_pool.store(tl.address, std::memory_order_release);
trampoline_lock.clear(std::memory_order_release);
trampoline_lock.notify_all();
}
LOGV("trampoline: count = %u, address = %zx, target = %zx", count, address,
address + count * kTrampolineSize);
address = address + count * kTrampolineSize;
break;
}
auto *address_ptr = reinterpret_cast<char *>(address);
std::memcpy(address_ptr, trampoline.data(), trampoline.size()); //拷贝前3条指令,用于跳转, 接着后面应该是一个 trampoline 跳转地址!,里面保存了跳转地址。
*reinterpret_cast<art::ArtMethod **>(address_ptr + art_method_offset) = hook;
__builtin___clear_cache(address_ptr, reinterpret_cast<char *>(address + trampoline.size()));
return address_ptr;
}
实际上就是创建了一个内存映射,生成3个跳板指令,第4个指令保存HOOK方法的 ArtMethod结构体指针,trampoline生成代码:
consteval inline auto GetTrampoline() {
if constexpr (kArch == Arch::kArm) {
return std::make_tuple("\x00\x00\x9f\xe5\x00\xf0\x90\xe5\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{32u}, uintptr_t{8u});
}
if constexpr (kArch == Arch::kArm64) {
return std::make_tuple(
"\x60\x00\x00\x58\x10\x00\x40\xf8\x00\x02\x1f\xd6\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{44u}, uintptr_t{12u});
}
if constexpr (kArch == Arch::kX86) {
return std::make_tuple("\xb8\x78\x56\x34\x12\xff\x70\x00\xc3"_uarr,
// NOLINTNEXTLINE
uint8_t{56u}, uintptr_t{1u});
}
if constexpr (kArch == Arch::kX86_64) {
return std::make_tuple("\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x00\xc3"_uarr,
// NOLINTNEXTLINE
uint8_t{96u}, uintptr_t{2u});
}
if constexpr (kArch == Arch::kRiscv64) {
return std::make_tuple(
"\x17\x05\x00\x00\x03\x35\xc5\x00\x67\x00\x05\x00\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{84u}, uintptr_t{12u});
}
}
auto [trampoline, entry_point_offset, art_method_offset] = GetTrampoline();
上述根据不同平台生成了不同的跳板指令。
测试HOOK前和HOOK后 系统函数 Thread.dispatchUncaughtException 的ArtMethod 结构体中 entrypoint地址指向:
1、HOOk前:
指向了libart.so这个art虚拟机so
2、HOOK后
指向了一段匿名可读可写可执行内存,长度为0x1000,这个是lsposed映射的一段内存作为跳板地址。
接着dump出内存查看前3个指令:
从当前指令 + 0xC处,取地址保存到 X0,根据前面的分析,这是HOOk方法的 ArtMethod结构体指针,接着取出 ArtMethod + 0x20处的值,这个值正是HOOk方法的ArtMethod对应的 entrypoint。最后跳转到这个entrypoint执行。
Lsposed检测
首先Lsposed在初始化时,会HOOK一些系统函数:
public class Startup {
private static void startBootstrapHook(boolean isSystem) {
Utils.logD("startBootstrapHook starts: isSystem = " + isSystem);
LSPosedHelper.hookMethod(CrashDumpHooker.class, Thread.class, "dispatchUncaughtException", Throwable.class);
if (isSystem) {
LSPosedHelper.hookAllMethods(HandleSystemServerProcessHooker.class, ZygoteInit.class, "handleSystemServerProcess");
} else {
LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openDexFile");
LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openInMemoryDexFile");
LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openInMemoryDexFiles");
}
LSPosedHelper.hookConstructor(LoadedApkCtorHooker.class, LoadedApk.class,
ActivityThread.class, ApplicationInfo.class, CompatibilityInfo.class,
ClassLoader.class, boolean.class, boolean.class, boolean.class);
LSPosedHelper.hookMethod(LoadedApkCreateCLHooker.class, LoadedApk.class, "createOrUpdateClassLoaderLocked", List.class);
LSPosedHelper.hookAllMethods(AttachHooker.class, ActivityThread.class, "attach");
}
public static void bootstrapXposed() {
// Initialize the Xposed framework
try {
startBootstrapHook(XposedInit.startsSystemServer);
XposedInit.loadLegacyModules();
} catch (Throwable t) {
Utils.logE("error during Xposed initialization", t);
}
}
public static void initXposed(boolean isSystem, String processName, String appDir, ILSPApplicationService service) {
// init logger
ApplicationServiceClient.Init(service, processName);
XposedBridge.initXResources();
XposedInit.startsSystemServer = isSystem;
LSPosedContext.isSystemServer = isSystem;
LSPosedContext.appDir = appDir;
LSPosedContext.processName = processName;
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
}
}
这里检测 dispatchUncaughtException 函数是否被HOOK:
/*
ldr x0, #0xc
ldur x16, [x0, #0x20]
br x16
*/
static bool checkLsposedHeader(uintptr_t func_addr)
{
//ldr 58000060
unsigned int ins1 = *(unsigned int*)func_addr;
unsigned int op = (ins1 & 0xbf000000) >> 24;
if(op != 0x18)return false; //ldr指令
unsigned int imme = ((ins1 >> 5) << 2) & 0x1fffff; //常量,应该是 0xC
unsigned int rn1 = ins1 & 0x1f; //寄存器,X0
if(imme != 0xC) return false;
// LDUR 指令 F8420001 111 1100 0010
unsigned int ins2 = *(unsigned int*)(func_addr + sizeof(int));
unsigned int op2 = (ins2 >> 21);
unsigned int Xd = ins2 & 0x1f; //目标寄存器
unsigned int Xn = (ins2 >> 5) & 0x1f; //源寄存器
unsigned int imme2 = (ins2 >> 12) & 0x1FF; //9位带符号数,这里不处理符号,对应ArtMethod中entry_point偏移
if(op2 != 0x7C2) return false;
//br 指令
unsigned int ins3 = *(unsigned int*)(func_addr + sizeof(int)*2);
unsigned int op3 = ins3 & 0xfffffc1f; //br指令,应该是 0xd61f0000
unsigned int rn3 = (ins3 >> 5) & 0x1f; // rn3 应该是X16
if(op3 != 0xd61f0000) return false;
return Xd == rn3 && Xn == rn1;
}
bool checkLsPosedHook(JNIEnv* env) {
auto thread = env->FindClass("java/lang/Thread");
if (!thread) {
__android_log_print(ANDROID_LOG_INFO, "native-android", "Failed to found Executable");
return false;
}
auto clazz = env->FindClass("java/lang/Class");
jmethodID get_declared_constructors = env->GetMethodID(clazz, "getDeclaredConstructors",
"()[Ljava/lang/reflect/Constructor;");
//获取相连连个构造函数,计算得到ArtMethod结构体大小,这里可以省略
auto constructors = static_cast<jobjectArray>(env->CallObjectMethod(thread,
get_declared_constructors));
jsize length = env->GetArrayLength(constructors);
if (length < 2) {
__android_log_print(ANDROID_LOG_INFO, "native-android", "Throwable has less than 2 constructors");
return false;
}
jobject first_ctor = env->GetObjectArrayElement(constructors, 0);
jobject second_ctor = env->GetObjectArrayElement(constructors, 1);
//反射得到 ArtMethod指针
jmethodID first = env->FromReflectedMethod(first_ctor);
jmethodID second = env->FromReflectedMethod(second_ctor);
//计算大小
size_t art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
__android_log_print(ANDROID_LOG_INFO, "native-android", "ArtMethod size: %zu", art_method_size);
// entry_point 指针是结构体最后一个元素
size_t entry_point_offset = art_method_size - sizeof(void*);
size_t data_offset = entry_point_offset - sizeof(void*);
//获取待检测的方法
//待检测的方法 Thread.dispatchUncaughtException Thread.class.getDeclaredMethod("dispatchUncaughtException", Throwable.class);
jmethodID get_declared_method = env->GetMethodID(clazz, "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
auto throwable = env->FindClass("java/lang/Throwable");
jstring dispatchUncaughtException = env->NewStringUTF("dispatchUncaughtException");
jobjectArray paramTypes = env->NewObjectArray(1, env->FindClass("java/lang/Class"), nullptr);
env->SetObjectArrayElement(paramTypes, 0, throwable);
jobject m = env->CallObjectMethod(thread, get_declared_method, dispatchUncaughtException, paramTypes);
jmethodID art_method = env->FromReflectedMethod(m); //需要确定这个是指针还是下标
__android_log_print(ANDROID_LOG_INFO, "native-android", "art_method: %02llx", (long long)art_method);
//输出 entry_point
auto* p = (long long *)(((char *)art_method) + entry_point_offset);
__android_log_print(ANDROID_LOG_INFO, "native-android", "offset %d = %02llx\n" , entry_point_offset, *p);
//检测 entry_point 指向的地址是否是跳板
bool hooked = checkLsposedHeader(*p); //检测地址的前三个指令是否有inlinehook
if(hooked) {
//有跳板特征,可以进一步提取hook函数
auto hook = (jmethodID)*(long long *)((*p) + 12); //这个取到hook函数的ArtMethod
jobject hookMethod = env->ToReflectedMethod(thread, hook, false);
jclass methodClass = env->FindClass("java/lang/reflect/Method");
jmethodID getDeclaringClassMethod = env->GetMethodID(methodClass, "getDeclaringClass", "()Ljava/lang/Class;");
jobject declaringClass = env->CallObjectMethod(hookMethod, getDeclaringClassMethod);
jmethodID getClassNameMethod = env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");
auto className = (jstring) env->CallObjectMethod(declaringClass, getClassNameMethod);
//应该是 LSPHooker_
char* lspHookClassName = const_cast<char *>(env->GetStringUTFChars(className, nullptr));
__android_log_print(ANDROID_LOG_INFO, "native-android", "lspHookClassName = %s\n" , lspHookClassName);
env->ReleaseStringUTFChars(className, lspHookClassName);
env->DeleteLocalRef(methodClass);
}
env->DeleteLocalRef(dispatchUncaughtException);
env->DeleteLocalRef(throwable);
env->DeleteLocalRef(thread);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(paramTypes);
return hooked;
}
打印日志如下:
成功获取到 hook方法的ArtMethod结构体,并且获取到 HOOK方法的类 LSPHooker_ 。通过这个类可以获取到对应的 ClassLoader,从而可以找到Xposed的接口口 de.robv.android.xposed.XposedHelpers。