GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- 逆向技巧 之 IDA未知函数如何识别

什么是未知函数

在逆向中有一类函数没有名字,但是客观存在,ida会给这类函数重命名一个函数名称,sub_xxxx,如何准确识别此类函数至关重要。

 如何快速识别函数

 

 

sub_xxxx 函数识别操作方法

一、先看调用了什么函数(最快)

text
看到这些调用组合 -> 直接判定

malloc + realloc + free + std::terminate
-> 动态容器扩容函数

__cxa_allocate_exception + __cxa_throw + std::logic_error
-> STL异常包装函数

__cxa_begin_catch + __cxa_end_catch
-> 异常捕获处理函数

__dynamic_cast + strcmp
-> RTTI类型匹配函数

pthread_mutex_lock + pthread_mutex_unlock
-> 线程安全操作函数

_ReadStatusReg(TPIDR_EL0)
-> ARM64运行时/TLS相关函数

memcpy + memset + operator new + operator delete
-> 对象构造/复制函数

fprintf + fflush + abort
-> 错误诊断/断言函数

二、看魔数(第二快)

text
函数体内出现这些数值 -> 直接判定

22 或 0x16
-> std::string SSO操作

4 或 0x4(与字符串操作同时出现)
-> std::wstring SSO操作

0xFFFFFFFFFFFFFFF0
-> 16字节内存对齐操作

0x1000
-> 页式Arena分配器

0xFF0 / 0xFFF0 / 4032 / 4048
-> Arena页剩余空间阈值检查

0x434C4E47432B2B
-> libcxxabi异常魔数 "GNUC++"

0x7FFFFFFFFFFFFFE6 / 0x3FFFFFFFFFFFFFF0
-> std::string/wstring最大长度检查

23 或 0x17
-> std::string容量分配边界

三、看参数结构(第三快)

text
参数是 _QWORD* 且函数内访问 [0][1][2]
-> 三元组结构体 大概率是容器或string对象
   [0]=数据指针/flags
   [1]=size/end指针
   [2]=capacity指针

参数是 char** 且函数内访问 [0][1][2]
-> 输出缓冲区结构
   [0]=缓冲区基址
   [1]=当前写入位置
   [2]=总容量
   -> 判定为某种emit/print/序列化函数

参数是 __int64 且访问 +4912 偏移
-> C++符号解码器的parser状态
   -> 判定为demangler内部函数

参数只有一个 __int64 且访问大量固定正偏移
如 +16 +24 +32 +40 +48
-> AST节点操作函数

四、看字符串常量(直接定性)

text
出现这些字符串 -> 直接命名

"libunwind: %s - %s\n"          -> libunwind诊断函数
"getRegister"                   -> 寄存器读取函数
"unsupported arm64 register"    -> ARM64寄存器错误处理
"stoi" / "stol" / "stoul"       -> STL字符串转整数函数
": out of range"                -> STL越界异常构建
": no conversion"               -> STL无效参数异常构建
"operator "                     -> C++符号解码operator节点
"decltype("                     -> C++符号解码decltype节点
"template<"                     -> C++符号解码模板节点
" [enable_if:"                  -> C++符号解码SFINAE节点
"enum" "struct" "union"         -> elaborated type解析
"allocator<T>::allocate"        -> 分配器容量超限错误
"unwind_phase2"                 -> libunwind第二阶段展开

五、看控制流形状(快速分类)

text
函数体只有一条jmp
-> thunk/桩函数 j_前缀 不用分析

函数体只有ret或xor返回0
-> nullsub空函数 跳过

有两层嵌套if且都检查同一个指针的不同位
-> SSO string判断 std::string或std::wstring操作

有 offset - 阈值 <= 大负数 的无符号比较
后跟 malloc(0x1000)
-> Arena分配器扩容逻辑

有 v >> 3 和 v >> 2 同时出现
-> vector/容器的按元素大小计算指针
   8字节元素容器

switch case -2,-1,0,29,30,31,32,34
-> ARM64寄存器编号映射函数

while循环体内有 & 0x80000000 检查
-> libunwind栈帧遍历函数

六、看vtable调用偏移(C++虚函数定位)

text
*(vtable + 0)   -> 析构函数
*(vtable + 8)   -> 析构函数(带delete)
*(vtable + 16)  -> 第一个虚函数
*(vtable + 32)  -> 常见print前置/emit函数
*(vtable + 40)  -> 常见print后置/emit函数
*(vtable + 56)  -> search_above_dst(RTTI搜索)

节点偏移规律:
node + 8   -> 节点类型标志(4字节int)
node + 16  -> 第一个子节点或字符串指针
node + 24  -> 第二个子节点
node + 32  -> 第三个子节点
-> 判定为AST节点操作函数

七、FLIRT签名匹配操作

text
IDA操作步骤:
1. File -> Load File -> FLIRT Signature File
2. 选择对应平台的 .sig 文件
   iOS/Android ARM64 -> 用 ios_arm64.sig 或 android_ndk.sig
3. 自动识别标准库函数

手动生成签名:
1. 找到已知的.a静态库
2. pelf -> sigmake -> 生成.sig
3. 加载到IDA

常用签名库来源:
- IDA自带 sig目录
- https://github.com/push0ebp/sig-database
- https://github.com/Maktm/FLIRTDB

八、Bindiff/Diaphora 交叉比对

text
操作步骤:
1. 找到开源版本的同名库(libc++/libunwind/libc++abi)
2. 编译相同架构版本
3. 用Bindiff或Diaphora对比两个idb
4. 相似度>0.9的函数直接采用开源名称

适用场景:
- 目标二进制静态链接了开源库
- libc++ libunwind libc++abi libstdc++
- OpenSSL mbedTLS等常见第三方库

九、优先级顺序总结

text
拿到一个 sub_xxxx 按以下顺序操作:

第1步  看有没有字符串常量            5秒内得出结论
第2步  看调用了哪些已知API            10秒内得出结论
第3步  看参数个数和类型结构           15秒内得出结论
第4步  看函数内的魔数                 20秒内得出结论
第5步  看控制流形状和switch结构       30秒内得出结论
第6步  看vtable偏移和调用模式         1分钟内得出结论
第7步  FLIRT签名自动匹配             批量处理
第8步  Bindiff交叉比对               深度分析时使用

能在前3步确认的不要拖到后面
多个维度同时印证才能提高准确率
单一维度匹配要保持怀疑

posted on 2026-04-09 07:18  GKLBB  阅读(27)  评论(0)    收藏  举报