xposed 05 - so函数hook

本文讨论如何hook目标apk中的 so 中的函数。

实现 so hook 的一个主要思想,就是将目标apk当成是我们自己编写的就行,就像开发中hook系统调用一样。有了这个思维,思路会宽广许多。

so 打开流程

在 Android 中加载一个so会使用到 System.loadLibrary 方法。

在 native 中,一般加载 so 是使用的 dlopen 方法。

由于 System.loadLibrary 最终也会调用到 dlopen 方法,所以我们先看 dlopen 方法流程。

dlopen 会调用到 do_dlopen 里面去:

1937  void* do_dlopen(const char* name, int flags,
1938                  const android_dlextinfo* extinfo,
1939                  const void* caller_addr) {
1945    ...
2011  
2012    ProtectedDataGuard guard;
2013    soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
2014    loading_trace.End();
2015  
2016    if (si != nullptr) {
2017      void* handle = si->to_handle();
2018      ...
2021      si->call_constructors();
2022      ...
2026      return handle;
2027    }
2028  
2029    return nullptr;
2030  }

核心逻辑就是使用 find_library 方法获取到 so 相关信息,然后调用其 call_constructors 方法。

call_constructors 里面会调用 init 与 init_array 相关代码:

388  void soinfo::call_constructors() {
389    ...
417  
418    // DT_INIT should be called before DT_INIT_ARRAY if both are present.
419    call_function("DT_INIT", init_func_, get_realpath());
420    call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());
421  
422    ...
425  }

这就是为啥 init 与 init_array 会在so加载后就执行的原因了。

我们再分析  System.loadLibrary 流程,它会调用到 LoadNativeLibrary 方法:

796  bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
797                                    const std::string& path,
798                                    jobject class_loader,
799                                    jstring library_path,
800                                    std::string* error_msg) {
801    ...
866    void* handle = android::OpenNativeLibrary(env,
867                                              runtime_->GetTargetSdkVersion(),
868                                              path_str,
869                                              class_loader,
870                                              library_path,
871                                              &needs_native_bridge,
872                                              error_msg);
873  
874    ...
915    bool was_successful = false;
916    void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
917    if (sym == nullptr) {
918      VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
919      was_successful = true;
920    } else {
921      // Call JNI_OnLoad.  We have to override the current class
922      // loader, which will always be "null" since the stuff at the
923      // top of the stack is around Runtime.loadLibrary().  (See
924      // the comments in the JNI FindClass function.)
925      ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
926      self->SetClassLoaderOverride(class_loader);
927  
928      VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
929      typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
930      JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
931      int version = (*jni_on_load)(this, nullptr);
932  
933      ...
959    return was_successful;
960  }

函数分为两个片段,第一个是 OpenNativeLibrary,它会调用到 dlopen 方法,也就是我们上面分析的过程。

第二个片段,会先找到 JNI_OnLoad 符号,然后调用这个函数,这就是为啥 JNI_OnLoad 会紧跟在 init 与 init_array 后面执行的原因。

hook 函数

有了上面的基础,我们再来介绍一下如何做 so 的 hook。arm 的hook会比较麻烦,但是好在有 github,我们可以使用开源的一些库做到开箱即用。

hook 分两种,一种是 plt hook,一种是 inline hook。这两种先简单介绍,后面我们开篇 elf 的时候在细说。简单来说,plt hook 只能 hook plt 表中存在的函数。inline hook 可以 hook 几乎所有的函数。

看一个例子来理解为啥要有这两种 hook:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_nativehooktarget_MainActivity_hookMe(JNIEnv
                                                      *env,
                                                      jobject thiz
) {
    if (test_hook("hookMe")) {
        __android_log_print(4, "hook_so", "hookMe success");
    } else {
        __android_log_print(4, "hook_so", "hookMe failed");
    }

}

在同一个文件里面写这两个方法:

Java_com_example_nativehooktarget_MainActivity_hookMe
test_hook

编译后,看其汇编:

95c:   91181800        add     x0, x0, #0x606
    if (test_hook("hookMe")) {
 960:   97ffffa8        bl      800 <_Z9test_hookPKc>
 964:   36000100        tbz     w0, #0, 984 <Java_com_example_nativehooktarget_MainActivity_hookMe+0x40>

看到对 test_hook 函数的调用直接使用了绝对地址 800,这样的函数,我们是没有办法使用 plt hook 的,因为 plt 表里面根本就没有这个函数的符号。

本来是想使用 inline hook 框架,字节开源的,虽然肯定比内部的版本号低了些,但是够用了:

https://github.com/bytedance/android-inline-hook

但是发现它不是纯 native 的,还需要在 java 里面初始化,由于 classLoader 的问题,用起来会非常的麻烦。虽然说 xposed 将模块的代码注入了app进程,但是它们还是使用了不同的 classLoader,so的加载也是与 classLoader有关,就会遇见各种奇葩问题。

所以还是使用老版的 sandhook,将代码 copy 进来,在 cmakelists.txt 里面配置,然后编写hook代码:

//
// Created by root on 12/9/23.
//

#include "jni.h"
#include <cstring>
#include <android/log.h>
#include "sandhook_native.h"

void *orig = nullptr;

typedef char *(*type_t)(char *, char *);

char* proxy(char *str1, char *str2) {
    // invoke origin method
    char * result = ((type_t) orig)(str1, str2);
    if (strcmp(str2, "test_hook") == 0) {
        return str1;
    }
    __android_log_print(4, "hook_so", "proxy origin result %s", result);
    return result;
}

void do_hook_test_hook() {
    const char *libc_path = "/system/lib64/libc.so";
    orig = SandInlineHookSym(libc_path, "strstr", reinterpret_cast<void *>(&proxy));

    __android_log_print(4, "hook_so", "hook result %p", orig);
}

extern "C" jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    do_hook_test_hook();
    return JNI_VERSION_1_6;
}

这里我们选择 hook libc 中的 strstr 函数,由于 sandhook 的限制,我们只能将hook时机放在JNI_OnLoad 处。

编译好模块的 so 后,将其 push 到目标 app 的 lib下:

/data/app/com.example.nativehooktarget-prsFV1IVkibTNbuAUhlI-w==/lib/arm64/libsohook.so

这样,我们就可以在模块里面加载这个 so,然后让其加载后自动 hook:

XposedHelpers.findAndHookMethod(
                "java.lang.Runtime",
                loadPackageParam.classLoader,
                "loadLibrary0",
                ClassLoader.class,
                String.class,
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        XposedBridge.invokeOriginalMethod(param.method, param.thisObject, new Object[] {
                                param.args[0], "sohook"
                        });
                        Log.e("hook_so", "beforeHookedMethod");
                    }
                });

注意,这里需要使用app的 classLoader,param.thisObject 就是 app 的classLoader,且为了避免循环调用,所以需要使用 invokeOriginalMethod 来调用 loadLibrary0 方法。

这里我们在加载目标 so 之前,先加载我们的 so,然后 hook 目标so中的方法。

最后hook结果如下:

I  hook result 0x7473fc2000
E  beforeHookedMethod
I  proxy origin result (null)
I  proxy origin result (null)
I  junk code
I  junk code
I  _init success
I  junk code
I  junk code
I  my_init_array1 success
I  proxy origin result (null)
I  proxy origin result (null)
I  proxy origin result (null)
I  proxy origin result (null)
I  proxy origin result (null)
I  proxy origin result (null)
I  junk code
I  junk code
I  hookMe success

可以看到,对 so 中的hook都成功了。

so 中函数主动调用

也是使用 sandhook 中的 api:

void *libnativebase = SandGetModuleBase("libnative-lib.so");

在加上函数的偏移地址即可:

unsigned long tmpaddr = (unsigned long) libnativebase + 0xf67c;
void *testhookaddr = reinterpret_cast<void *>(tmpaddr);
testhookfunction = reinterpret_cast<testhook>(testhookaddr);
LOGD("libnative-lib.so base:%p,testfuncaddr:%p",libnativebase,(void*)tmpaddr);

不过,用起来还是感觉很蛋疼,而且这些代码都还没考虑 32与64 的区别。

总的来说,这个可以自己玩玩,总感觉差点什么东西,比如,我想使用地址来hook,但是就必须要等到so加载之后才行,这样就hook不到初始化时间,就有点烦。或许将 shadowhook 改一下,用起来会更舒服。

  • 44
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
xposedinstaller-magisk.apk 是一种用于安卓系统的应用程序。XposedInstaller 是一个开源框架,允许用户对安卓系统进行深度定制和修改。而Magisk则是一个用于Root安卓设备的工具。 XposedInstaller 提供了一种非常灵活的方式,使用户能够安装模块来修改和增强安卓系统的功能。用户可以通过XposedInstaller来激活各种模块,并通过模块的定制设置来实现自定义的功能修改。这些功能包括更改系统界面、优化系统性能、增加新的特性等。XposedInstaller 不需要修改系统文件,因此,用户可以在不更改系统的情况下,实现对安卓系统的定制。 Magisk 则是一个用于Root安卓设备的工具。Root是指获取对安卓系统的完全控制权限,使用户可以执行更多高级操作的过程。Magisk 提供了一种相对较安全的方式来Root设备,并且保留了对一些应用程序正常工作的兼容性。通过Magisk,用户可以安装和管理Root权限,以及通过安装模块来定制和优化设备的功能。此外,Magisk 还提供了隐藏Root权限的功能,可以在需要时临时隐藏Root状态,以满足某些应用程序的要求。 XposedInstaller 和 Magisk 可以结合使用,以实现更高级的安卓系统定制。用户可以使用Magisk来Root设备,并通过XposedInstaller来安装和激活各种模块来修改系统功能。这种组合提供了用户对安卓系统广泛的自定义能力,并且相对安全,使用户可以更好地控制和个性化自己的设备。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值