native-hook xposed进行native hook较为麻烦, 需要第三方的hook框架(例如dobby), 但现在lsposed已经能够原生支持native层hook
Native Hook · LSPosed/LSPosed Wiki
在普通xposed模块编写的基础上添加了以下几个步骤:
正常创建一个xposed模块, 模块中加载之后要注入的so
1 2 3 4 5 6 7 8 9 10 11 12 13 package org.lsposed.exampleclass MainHook : IXposedHookLoadPackage { override fun handleLoadPackage (lpparam: XC_LoadPackage .LoadPackageParam ) { if (lpparam.packageName == "org.lsposed.target" ) { try { System.loadLibrary("example" ) } catch (e: Throwable) { LogUtil.e(e) } } } }
在AndroidManifest.xml下额外添加两行
1 2 3 4 <application ... android:multiArch ="true" android:extractNativeLibs ="false" >
右击main目录, 选择Add C++ to Module, 之后即会在main目录下创建一个cpp文件夹(之后build.gradle中会自动添加一些配置, 有需要可以进行更改, 一般默认即可)
cpp文件夹内创建一个头文件, 名字任意内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef int (*HookFunType) (void *func, void *replace, void **backup) ;typedef int (*UnhookFunType) (void *func) ;typedef void (*NativeOnModuleLoaded) (const char *name, void *handle) ;typedef struct { uint32_t version; HookFunType hook_func; UnhookFunType unhook_func; } NativeAPIEntries; typedef NativeOnModuleLoaded (*NativeInit) (const NativeAPIEntries *entries) ;
编写hook主逻辑cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <jni.h> #include <string> #include <dlfcn.h> #include "lspnative.h" static HookFunType hook_func = nullptr ;int (*backup)();int fake () { return backup () + 1 ; } FILE *(*backup_fopen)(const char *filename, const char *mode); FILE *fake_fopen (const char *filename, const char *mode) { if (strstr (filename, "banned" )) return nullptr ; return backup_fopen (filename, mode); } jclass (*backup_FindClass)(JNIEnv *env, const char *name);jclass fake_FindClass (JNIEnv *env, const char *name) { if (!strcmp (name, "dalvik/system/BaseDexClassLoader" )) return nullptr ; return backup_FindClass (env, name); } void on_library_loaded (const char *name, void *handle) { if (std::string (name).ends_with ("libtarget.so" )) { void *target = dlsym (handle, "target_fun" ); hook_func (target, (void *) fake, (void **) &backup); } } extern "C" [[gnu::visibility ("default" )]] [[gnu::used]]jint JNI_OnLoad (JavaVM *jvm, void *) { JNIEnv *env = nullptr ; jvm->GetEnv ((void **)&env, JNI_VERSION_1_6); hook_func ((void *)env->functions->FindClass, (void *)fake_FindClass, (void **)&backup_FindClass); return JNI_VERSION_1_6; } extern "C" [[gnu::visibility ("default" )]] [[gnu::used]]NativeOnModuleLoaded native_init (const NativeAPIEntries *entries) { hook_func = entries->hook_func; hook_func ((void *) fopen, (void *) fake_fopen, (void **) &backup_fopen); return on_library_loaded; }
在assets目录下创建native_init文件, 其中填写实现hook逻辑的so文件名字
实际应用 以frida labs的lab-9为例
但check_flag函数始终返回1
首先完成上面介绍的步骤
xposed入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.example.xposed;import android.util.Log;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.callbacks.XC_LoadPackage;public class main implements IXposedHookLoadPackage { private final String TAG = "[LuoXposed]" ; @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (lpparam.packageName.equals("com.ad2001.a0x9" )) { try { System.loadLibrary("xposed" ); }catch (Exception e){ e.printStackTrace(); } } } }
native-hook代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <jni.h> #include <string> #include <dlfcn.h> #include "lspnative.h" static HookFunType hook_func = nullptr ;int64_t (*backup)();int64_t fake () { return 0x539 ; } void on_library_loaded (const char *name, void *handle) { if (std::string (name).compare ("liba0x9.so" )) { void *target = dlsym (handle, "Java_com_ad2001_a0x9_MainActivity_check_1flag" ); hook_func (target, (void *) fake, (void **) &backup); } } extern "C" [[gnu::visibility ("default" )]] [[gnu::used]]jint JNI_OnLoad (JavaVM *jvm, void *) { JNIEnv *env = nullptr ; jvm->GetEnv ((void **)&env, JNI_VERSION_1_6); return JNI_VERSION_1_6; } extern "C" [[gnu::visibility ("default" )]] [[gnu::used]]NativeOnModuleLoaded native_init (const NativeAPIEntries *entries) { hook_func = entries->hook_func; return on_library_loaded; }
Cmakelist.txt, 这里就是默认的配置, 没有更改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 cmake_minimum_required (VERSION 3.22 .1 )project ("xposed" )add_library (${CMAKE_PROJECT_NAME} SHARED xposed.cpp) target_link_libraries (${CMAKE_PROJECT_NAME} android log)
编译安装成功达到目标
代码分析 有三个关键接口, 也就是hook的三个节点
native_init , 这是Native Hook的初始化函数, 其中可进行对系统库函数的hook
例如hook fopen
1 hook_func ((void *) fopen, (void *) fake_fopen, (void **) &backup_fopen);
JNI_OnLoad , 这是JNI的入口函数,当Java虚拟机加载本地库时会被调用
可以在这个位置hook jni相关接口函数
1 hook_func ((void *)env->functions->FindClass, (void *)fake_FindClass, (void **)&backup_FindClass);
on_library_loaded , 一个回调函数,当某个库被加载时会被调用, 在此处hook特定so中的特定函数, 使用dlsym通过句柄找到对应的函数地址
1 hook_func (target, (void *) fake, (void **) &backup);
hook_func 通过上述代码可以看到hook的核心是hook_func函数
1 typedef int (*HookFunType) (void *func, void *replace, void **backup) ;
hook_func
接受三个参数:
void *func
: 需要被 Hook 的函数的地址
void *replace
: 用于替换目标函数的新函数地址
void **backup
: 用于保存目标函数原地址的指针
由于有保存原函数地址到backup, 所以替换的新函数也可以通过backup指针调用原有的函数
加固hook LSPosed在注入进程时App的Application类并未完成加载, 这也就导致真实用于加载App业务相关类的ClassLoader并未出现, hook找到的目标是加固壳的ClassLoader, 所以直接对应用中的函数进行hook的话就会导致ClassNotFoundException 错误
有两类解决方法
方法一 通过对Android下的加固应用进行分析,可以知道壳程序通常是通过在应用进程最先获得执行权限的Application类中的attachBaseContext和onCreate函数中完成对真实Dex的释放以及ClassLoader的切换.
故可以通过对加固应用Application类的attachBaseContext或onCreate函数进行Hook,来得到真实App的上下文,再通过上下文来获取真实代码释放后的ClassLoader,用于后续的函数Hook.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package com.example.luoxposeddemo;import android.app.Application;import android.content.Context;import android.util.Log;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedBridge;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage;public class LuoHook implements IXposedHookLoadPackage { private final String TAG = "[LuoXposed]" ; @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (lpparam.packageName.equals("com.hay.dreamlover" )) { Log.i(TAG, "Enter Hook" ); Class applicationClazz = lpparam.classLoader.loadClass("com.stub.StubApp" ); XposedHelpers.findAndHookMethod(applicationClazz, "attachBaseContext" , Context.class, new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Log.i(TAG, "Enter com.stub.StubApp.attachBaseContext" ); } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); Log.i(TAG, "Leave com.stub.StubApp.attachBaseContext" ); Context context = (Context) param.args[0 ]; ClassLoader finalClassLoader = context.getClassLoader(); Class initActivityClazz = finalClassLoader.loadClass("com.fanwe.hybrid.activity.InitActivity" ); XposedBridge.hookAllMethods(initActivityClazz, "onCreate" , new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Log.i(TAG, "attachBaseContext Enter com.fanwe.hybrid.activity.InitActivity.onCreate" ); } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); Log.i(TAG, "attachBaseContext Leave com.fanwe.hybrid.activity.InitActivity.onCreate" ); } }); } }); } } }
方法二 上面的方法在厂商对加固过程有改动时, 就需要花费时间对脚本进行修改
方法二则是一个更通用的解决任意加固应用函数的Hook方式
回顾app启动相关的知识
App被Zygote进程孵化后,通过ActivityThread.main函数进入App的世界.
ActivityThread这个类十分重要,它会根据ActivityManager发送的请求对activities、broadcast Receviers等操作进行调度和执行.
其中performLaunchActivity()函数用于响应Activity相关的操作.
另外ActivityThread类中还存在着一个Application类型的mInitialApplication成员,应用程序中有且只有一个Application组件,而Application对象中就存储着当前的ClassLoader,考虑到App在响应Activity消息时,真实App的代码已经被释放到内存中,此时通过mInitialApplication成员获取应用当前的ClassLoader,即可完成对真实App业务代码的Hook.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.example.luoxposeddemo;import android.app.Application;import android.content.Context;import android.util.Log;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedBridge;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage;public class LuoHook implements IXposedHookLoadPackage { private final String TAG = "[LuoXposed]" ; @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (lpparam.packageName.equals("com.hay.dreamlover" )) { Log.i(TAG, "Enter Hook" ); Class activityThreadClazz = lpparam.classLoader.loadClass("android.app.ActivityThread" ); XposedBridge.hookAllMethods(activityThreadClazz, "performLaunchActivity" , new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Log.i(TAG, "Enter android.app.ActivityThread.performLaunchActivity" ); Application mInitialApplication = (Application) XposedHelpers.getObjectField(param.thisObject, "mInitialApplication" ); ClassLoader finalClassLoader = mInitialApplication.getClassLoader(); Class initActivityClazz = finalClassLoader.loadClass("com.fanwe.hybrid.activity.InitActivity" ); XposedBridge.hookAllMethods(initActivityClazz, "onCreate" , new XC_MethodHook () { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Log.i(TAG, "performLaunchActivity Enter com.fanwe.hybrid.activity.InitActivity.onCreate" ); } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); Log.i(TAG, "performLaunchActivity Leave com.fanwe.hybrid.activity.InitActivity.onCreate" ); } }); } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); Log.i(TAG, "Leave android.app.ActivityThread.performLaunchActivity" ); } }); } } }