so脱壳全流程(识别加壳、FridaDump、原理深入解析)

admin 2026-04-16 04:12:06 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细介绍了AndroidSO文件脱壳全流程,包括使用IDA识别加壳特征(如无效的section定义和红色代码块)、利用FridaDump工具进行内存转储、使用SoFixer修复ELF文件结构。关键步骤包括修改设备连接配置、执行Python脚本获取内存数据、修复后使用IDA正常分析。提供了具体的命令示例和原理解析,适用于移动安全研究人员进行逆向分析。 综合评分: 85 文章分类: 逆向分析,移动安全,安全工具,二进制安全,恶意软件


查找目标 SO(单个 & 批量打印所有 SO)

除了用来脱壳 so ,也可以用 dump_so.js 中的函数查找 so 或打印所有 so 信息。

执行 dump_so.js 脚本

frida -H 127.0.0.1:1234 -F -l dump_so.js

输出如下:

[Remote::cyrus]-> rpc.exports.findmodule("libGameVMP.so")
{
    "base": "0x7b6ae0e000",
    "name": "libGameVMP.so",
    "path": "/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libGameVMP.so",
    "size": 462848
}
[Remote::cyrus]-> rpc.exports.allmodule()
[
    {
        "base": "0x6545887000",
        "name": "app_process64",
        "path": "/system/bin/app_process64",
        "size": 40960
    },
    {
        "base": "0x7c69419000",
        "name": "linker64",
        "path": "/system/bin/linker64",
        "size": 225280
    },
    ...
]

Frida Dump 中 SO 脱壳流程解析

1、使用 Frida 连接目标 Android 进程,加载 dump_so.js 脚本。

def read_frida_js_source():
    with open("dump_so.js", "r") as f:
        return f.read()

def on_message(message, data):
    pass

if __name__ == "__main__":
    # device: frida.core.Device = frida.get_usb_device()
    device = frida.get_device_manager().add_remote_device("127.0.0.1:1234")
    pid = device.get_frontmost_application().pid
    session: frida.core.Session = device.attach(pid)
    script = session.create_script(read_frida_js_source())
    script.on('message', on_message)
    script.load()

2、在 dump_so.js 的 dumpmodule 中获取目标 .so 文件的基地址和大小,返回内存中的 so 数据

rpc.exports = {
    findmodule: function(so_name) {
        var libso = Process.findModuleByName(so_name);
        return libso;
    },
    dumpmodule: function(so_name) {
        // 根据 so_name 查找已加载的模块(共享库)
        var libso = Process.findModuleByName(so_name);

        // 如果没找到对应模块,返回 -1 表示失败
        if (libso == null) {
            return -1;
        }

        // 修改模块内存权限为 可读(r)、可写(w)、可执行(x)
        // 这样后面才能安全地读取和修改该内存区域
        Memory.protect(ptr(libso.base), libso.size, 'rwx');

        // 从模块基址开始,读取整个模块大小的字节数组
        var libso_buffer = ptr(libso.base).readByteArray(libso.size);

        // 把读取到的字节数组缓存到 libso 对象的 buffer 属性,方便后续使用
        libso.buffer = libso_buffer;

        // 返回读取到的字节数组
        return libso_buffer;
    },
    allmodule: function() {
        return Process.enumerateModules()
    },
    arch: function() {
        return Process.arch;
    }
}

3、从内存中转储目标 .so 文件,保存为 .dump.so。

module_buffer = script.exports.dumpmodule(origin_so_name)
if module_buffer != -1:
    dump_so_name = origin_so_name + ".dump.so"
    print(dump_so_name)

    with open(dump_so_name, "wb") as f:
        f.write(module_buffer)
        f.close()

4、使用 SoFixer 工具修复转储的内存数据,重建 ELF 文件结构,使 IDA 可以正常识别。

5、下载修复后的 .so 文件到本地,清理设备上的临时文件。

def fix_so(arch, origin_so_name, so_name, base, size):
    if arch == "arm":
        os.system("adb push android/SoFixer32 /data/local/tmp/SoFixer")
    elif arch == "arm64":
        os.system("adb push android/SoFixer64 /data/local/tmp/SoFixer")
    os.system("adb shell chmod +x /data/local/tmp/SoFixer")
    os.system("adb push " + so_name + " /data/local/tmp/" + so_name)
    print("adb shell /data/local/tmp/SoFixer -m " + base + " -s /data/local/tmp/" + so_name + " -o /data/local/tmp/" + so_name + ".fix.so")
    os.system("adb shell /data/local/tmp/SoFixer -m " + base + " -s /data/local/tmp/" + so_name + " -o /data/local/tmp/" + so_name + ".fix.so")
    os.system("adb pull /data/local/tmp/" + so_name + ".fix.so " + origin_so_name + "_" + base + "_" + str(size) + "_fix.so")
    os.system("adb shell rm /data/local/tmp/" + so_name)
    os.system("adb shell rm /data/local/tmp/" + so_name + ".fix.so")
    os.system("adb shell rm /data/local/tmp/SoFixer")

    return origin_so_name + "_" + base + "_" + str(size) + "_fix.so"

为什么要用 SoFixer 进行修复

dump 下来的 .so 是执行视图(段为主),而 IDA 需要的是链接视图(节为主),SoFixer 就是桥梁,用来还原链接视图结构。

如何定位内存中的目标 SO

Frida 在 Android 上枚举模块(如 Process.enumerateModules())时,核心机制是:👉 遍历 linker(动态链接器)内部维护的 soinfo 链表,dlopen 成功后,linker 会将 .so 加入 solist。

frida-gum 是 Frida 内部用来实现这些功能的核心组件,Frida-Gum 是 Frida 的底层动态插桩引擎,提供跨平台的 C/C++ 接口。

开源地址:https://github.com/frida/frida-gum

frida 在 android 下 Process.enumerateModules() 的调用链大概如下:

gum_android_enumerate_modules
  └── 枚举 Android 中已加载模块的统一入口,对外暴露 API。

  └── gum_enumerate_soinfo
        ├── gum_linker_api_get
        │     └── 获取 linker API(dlopen、solist 等)的单例结构。
        │
        │     └── gum_linker_api_try_init
        │           └── 初始化 linker API,识别 linker 结构,并提取关键符号地址。
        │
        │           └── gum_android_get_linker_module
        │                 └── 获取 linker 自身的 GumModule 实例(包含 ELF 基址等信息)。
        │
        │                 └── gum_try_init_linker_module   ← maps查找linker
        │                       └── 遍历 /proc/self/maps,查找 `/linker` 或 `/linker64` 映射段,
        │                           构造用于后续符号查找的 `GumModule`。
        │
        └── for (si = api->solist_get_head (); carry_on && si != NULL; si = next)
              └── 遍历 linker 内部维护的 `soinfo` 链表,代表所有已加载模块(包括 `dlopen` 的模块)。

              └── gum_emit_module_from_soinfo
                    └── 将每个 `soinfo` 对象转换为 `GumModule` 结构,提取模块名、基址、路径、大小等信息。

                    └── 回调用户传入的 func(GumModule*),最终将模块信息传给调用方

https://github.com/frida/frida-gum/blob/d83ae3ea30f7de5dad23d763a0724b5e9d451e47/gum/backend-linux/gumandroid.c#L917

所以 frida 是通过 solist 找到内存中的 so 信息的。

脱壳点:solist 与 soinfo

脱壳的关键:定位解密后的 .so 在内存中的地址和大小,dump 出来再修复结构即可。

solist 是 linker 中的静态变量,把 linker64 拉取到本地:

 adb pull  /apex/com.android.runtime/bin/linker64

可以看到 solist 位于 .bss 段,其真实符号是 __dl__ZL6solist

引用链接

[1] 一文搞懂如何使用 Frida Hook Android App: https://cyrus-studio.github.io/blog/posts/%E4%B8%80%E6%96%87%E6%90%9E%E6%87%82%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-frida-hook-android-app/

推荐阅读

移动安全领域的AI开源模型和框架

Python字节码反编译工具(逆向分析)

Python字节码反编译逆向分析(高级篇)

IDA Pro动态调试Android应用的完整实战

Flutter App抓包(原理分析和绕过SSL检测)

Dex2C把Java转Native(Android代码加固)

鸿蒙(HarmonyOS Next)APP逆向分析工具

鸿蒙(HarmonyOS Next)APP逆向分析方法

Android应用加固工具完整代码实现(加固实战)


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:哆啦安全 《so脱壳全流程(识别加壳、Frida Dump、原理深入解析)》

评论:0   参与:  0