从 sub_xxxx 到逻辑命名:剥离符号表二进制文件的动态分析恢复技巧
2
0
0
0
在逆向分析日常工作中,最令分析师头疼的莫过于遇到被 Stripped(剥离符号表) 的二进制文件。打开 IDA Pro,映入眼帘的是成百上千个以 sub_ 开头的无意义函数名。虽然静态分析可以通过 F.L.I.R.T. (Fast Library Identification and Recognition Technology) 识别标准库函数,但对于业务逻辑代码,动态分析往往是更高效的突破口。
本文将深入探讨如何通过动态分析手段,从行为特征中强行“审讯”出这些函数的真实意图。
一、 核心逻辑:函数名的本质是“语义总结”
在没有符号表的情况下,我们恢复的并不是原始的源码变量名,而是函数的语义标签。动态分析的优势在于:你可以观察函数的输入(Input)、输出(Output)以及它对系统环境的影响(Side Effects)。
二、 动态恢复的关键技术路径
1. 系统调用与库函数追踪(The Breadcrumbs)
剥离符号表通常只针对用户代码,底层的系统调用(Syscalls)和动态链接库(libc, WinAPI)调用是无法隐藏的。
- 策略:通过追踪
sub_xxxx内部调用了哪些已知函数。 - 示例:如果一个函数频繁调用
socket,connect,send,那么它可以被初步命名为network_sender或init_connection。 - 工具:使用
ltrace跟踪库函数调用,使用strace跟踪系统调用。
2. 字符串与日志信息的“回溯法”
即使符号表没了,开发者留在二进制文件中的硬编码字符串、报错信息(Log messages)或断言(Assert)往往还在。
- 动态观察:运行程序,触发某个功能,观察控制台输出。
- 关联分析:使用 Frida Hook 所有可疑的
sub_xxxx,记录它们的执行序列。如果点击“登录”按钮后,紧接着执行了sub_401234并且输出了 "Login failed",那么该函数大概率就是验证逻辑。
3. 参数与返回值的“形态学分析”
通过动态调试器(GDB/x64dbg)或代码注入工具,观察函数在栈和寄存器中的表现。
- 特征识别:
- 参数包含
0xEDB88320(CRC32 多项式)?命名为calc_crc32。 - 参数是一个结构体,且第一个字段是
0x7f 0x45 0x4c 0x46(ELF Magic)?命名为parse_elf_header。
- 参数包含
- Frida 脚本示例:
Interceptor.attach(Module.findBaseAddress("demo").add(0x1234), { onEnter: function(args) { console.log("Called sub_1234"); console.log(hexdump(args[0])); // 观察第一个参数是否为已知数据结构 }, onLeave: function(retval) { console.log("Return value: " + retval); } });
4. 内存读写监控(Hardware Breakpoints)
关键数据(如解密后的配置、用户输入)通常存储在固定的内存区域。
- 技术点:对关键内存地址下“硬件写入断点”。
- 逻辑:当程序写入这块内存时,回溯调用栈(Call Stack)。通过调用栈,你可以顺藤摸瓜找到负责解密、编码或数据处理的核心函数。
三、 高阶技巧:行为指纹与污点分析
- Diffing 思想:如果你有该软件的旧版本(未剥离符号)或者同架构下的类似组件,可以使用 Bindiff 或 Diaphora 进行代码比对。动态分析可以辅助验证比对结果的准确性。
- 插桩技术(Instrumentation):使用 Intel PIN 或 QEMU 进行指令级追踪。通过记录函数执行的控制流图(CFG),比对已知算法(如 AES, Zlib)的执行特征。
- 污点分析(Taint Analysis):标记用户输入数据为“污点”,观察数据流向了哪些
sub_xxxx。这些函数即为“数据处理相关”函数。
四、 总结:实战工作流建议
- 快速扫描:先用
strings提取所有字符串,寻找报错模板。 - 基准定位:Hook
read/write或recv/send,顺着调用栈向上找 3-5 层。 - 假设与验证:给函数起一个临时名字(如
maybe_decrypt),修改参数或返回值观察程序是否崩溃或改变行为。 - 循环迭代:随着识别的函数增多,你会发现剩下的
sub_xxxx越来越少,逻辑闭环逐渐清晰。
逆向工程是一场关于拼图的心理战。动态分析为你提供了观察每一块拼图“颜色”和“形状”的机会,而不仅仅是看它那模糊的边缘。