文章总结: 本文详细介绍了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 文件,保存为
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、原理深入解析)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论