xposed: native-hook与加固hook

native-hook

xposed进行native hook较为麻烦, 需要第三方的hook框架(例如dobby), 但现在lsposed已经能够原生支持native层hook

Native Hook · LSPosed/LSPosed Wiki

在普通xposed模块编写的基础上添加了以下几个步骤:

  1. 正常创建一个xposed模块, 模块中加载之后要注入的so

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package org.lsposed.example

    class 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)
    }
    }
    }
    }
  2. 在AndroidManifest.xml下额外添加两行

    1
    2
    3
    4
    <application ...
    android:multiArch="true"
    android:extractNativeLibs="false"
    >
  3. 右击main目录, 选择Add C++ to Module, 之后即会在main目录下创建一个cpp文件夹(之后build.gradle中会自动添加一些配置, 有需要可以进行更改, 一般默认即可)

  4. 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);
  5. 编写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) {
    // hooks on `libtarget.so`
    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;
    // system hooks
    hook_func((void*) fopen, (void*) fake_fopen, (void**) &backup_fopen);
    return on_library_loaded;
    }
  6. 在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) {
// hooks on `libtarget.so`
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);
//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;
// system hooks
//hook_func((void*) fopen, (void*) fake_fopen, (void**) &backup_fopen);
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
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.

# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)

# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("xposed")

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
xposed.cpp)

# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)

编译安装成功达到目标

代码分析

有三个关键接口, 也就是hook的三个节点

  1. native_init, 这是Native Hook的初始化函数, 其中可进行对系统库函数的hook

    例如hook fopen

    1
    hook_func((void*) fopen, (void*) fake_fopen, (void**) &backup_fopen);
  2. JNI_OnLoad, 这是JNI的入口函数,当Java虚拟机加载本地库时会被调用

    可以在这个位置hook jni相关接口函数

    1
    hook_func((void *)env->functions->FindClass, (void *)fake_FindClass, (void **)&backup_FindClass);
  3. 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 接受三个参数:

  1. void *func: 需要被 Hook 的函数的地址
  2. void *replace: 用于替换目标函数的新函数地址
  3. 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");
//com.stub.StubApp
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
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");
//Log.i(TAG, initActivityClazz.toString());
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");
}
});
}
}
}