Frida Hook so 动态链接库文件 .init_array 段中的函数

在开始之前,我们先回顾下 so 的大概加载流程

do_dlopen -> soinfo::call_constructors() -> call_array -> call_function

call_array 负责按顺序调用 .init_array 或 .fini_array 中注册的函数指针,这些函数通常是在 .so 加载后初始化时或卸载前需要执行的钩子函数。

也就是说,我们想 hook 住 .init_array 里面的函数就必须在 call_array 执行之前,一般我们会想到 hook android_dlopen_ext 函数,就像下面这样:

Interceptor.attach(Module.getExportByName(null, 'android_dlopen_ext'), {
    onEnter: function (args) {
        this.libname = Memory.readUtf8String(args[0]);
        console.log("[*] dlopen called for:", this.libname);
    },
    onLeave: function (ret) {
        if (!this.libname.includes("libloader.so")) return;
        const module = Process.findModuleByName("libloader.so");

        // 基本信息打印
        // console.log(`[+] Process ID: ${Process.id}, Arch: ${Process.arch}`);
        // console.log(`[+] Pointer size: ${Process.pointerSize}`);
        // console.log("[+] Module base:", module.base);
    }
});

实际运行之后,你会发现根本无法 hook 到 .init_array 里面的函数,其原因是这个时机太晚,.init_array 里面的函数早就执行过了。为了解决这个问题,我们可以尝试去 hook call_constructors 函数

do_dlopen()
  ├─ mmap 加载 .so 到内存
  ├─ 创建 soinfo 实例
  ├─ 加载依赖库(递归 DT_NEEDED)
  ├─ 解析符号 & 重定位(relocations)
  ├─ 此时 so 已成功加载到内存(可以访问代码和数据段)
  └─ call_constructors()  ← 仅此时开始运行构造器逻辑

整个加载过程都是由 Android 系统内部的 linker 进程来完成,linker 是 Android 系统用来加载和链接 .so 动态库的核心组件。

我用的是 Android 9 arm64 系统,所以 linker 程序所在的位置是 /system/bin/linker64 ,我们把它从手机里面拿出来用 IDA64 分析一下,在函数窗口处搜索 call_constructors

可以看到它的偏移是 0X2FAC4,有了偏移就好办了,我们直接写一段 Frida hook 代码

// Android 9.0 64-bit linker64 call_constructors .text	000000000002FAC4
// so大概加载流程 do_dlopen -> soinfo::call_constructors() -> call_array -> call_function 
// call_constructors 这是个非常好的时机 在 call_array(.init_array) 之前
Interceptor.attach(Process.getModuleByName("linker64").base.add(0x2FAC4), {
    onEnter(args) {
        var soinfo = this.context.x0;
        var nameFlag = Memory.readU8(soinfo.add(416)); //十进制偏移
        var namePtr;

        if ((nameFlag & 1) != 0) {
            namePtr = Memory.readPointer(soinfo.add(432));
        } else {
            namePtr = soinfo.add(417);
        }

        var nameStr = Memory.readUtf8String(namePtr);
        if (nameStr.indexOf("libloader.so") !== -1) {
            var moduleBase = Memory.readPointer(soinfo.add(16));
            console.log("[*]call_constructors Module base address: " + moduleBase);
            console.log("[*]call_constructors libloader.so constructors running");

            //尝试hook .init_array 里面的函数
            Interceptor.attach(moduleBase.add(0x571C8), {
                onEnter: function (args) {
                    console.log("[*] 0x571C8 called");
                },
                onLeave: function (retval) {
                    console.log("[*] sub_0x571C8 returned ");
                }
            });
        }
    }
});

运行之后你会发现成功 hook 住了 .init_array 里面的函数,你可能会想问,这里面的偏移都是怎么找出来的?关于这些偏移信息是怎么找的,请参考 Android linker 源码以及 IDA64 解析出来的 C 伪代码,再配合上 ChatGPT 你就会懂了。

linker 源码地址:

https://cs.android.com/android/platform/superproject/main/+/main:bionic/linker/linker_soinfo.cpp

最后,我再附上一个 hook call_array 的 Frida 代码,可以打印出 so 的所有 .init_arrray 里面储存的函数

function hook_call_array(soBase) {
    // Android 9.0 64-bit linker64 call_array .text	000000000002F734
    // void call_array(const char* array_name __unused, F* functions, size_t count,bool reverse, const char* realpath) 
    Interceptor.attach(Process.getModuleByName("linker64").base.add(0x2F734), {
        onEnter(args) {
            const arrayName = args[0].readCString();
            const funcs = args[1];
            const count = args[2].toInt32();
            const realpath = args[3].readCString();

            console.log(`\n=== call_array(${arrayName}) ===`);
            console.log(`path: ${realpath}`);
            console.log(`count: ${count}`);
            console.log(`functions ptr: ${funcs}\n`);

            // 取当前 so 的 base 地址
            const module = Process.findModuleByAddress(ptr(soBase));
            if (!module) {
                console.warn("未找到模块信息,无法计算偏移");
                return;
            }

            const base = module.base;
            console.log(`[${module.name}] base: ${base}\n构造函数列表:`);

            for (let i = 0; i < count; i++) {
                const funcPtr = funcs.add(i * Process.pointerSize).readPointer();
                const offset = funcPtr.sub(base);
                console.log(` [${i}] => ${funcPtr}  (offset: ${offset})`);
            }

            console.log("=== end ===\n");
        }
    });
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注