头头
还记得第一次面试老大问我用过ida的哪些快捷键(后来我想到他不是要我背C键是啥意思),我答F5,因为我不记得其他的了,而且一般我是用鼠标点,工作后发现在频繁使用时必定会需要快捷键,这东西也没必要背,用到自然记住,不用自然遗忘,但是还是想贴张图:
对IDA的使用,只有《IDA Pro权威指南》,毕竟它只是一个工具,能有一本书专门讲述它已经能证明它的成功了,除此的话,它的官方文档还是有必要再看一遍的,特别是它的反编译常见错误基本总结了会遇到的问题的解决办法,它的文档也可以直接从ida的图形界面打开:
脚本编写
ida支持通过Native(C/C++等)/IDC/Python扩展,我只学过IDC和Python,嗯前者也早忘了,所以最常使用的是Python。对于简单功能或测试可以直接在ida的输出窗口编写python代码,在复杂一点可以用今年的插件比赛出现的IPyIDA,它把IPython和IDA缝合了,安装好后按<Shift-.>
即可:

再复杂一点就要用IDE开发了,我把Python3安装在了IDA目录下,使用Pycharm开发,做如下配置:

此处添加了一个库路径,它就是看雪下下来后解压出的那样,里面是ida.py等文件,方便使用。由于此时开发无法直接使用pycharm调试,推荐安装pydevd-pycharm库,之后在代码中添加如下代码:
import pydevd_pycharm
pydevd_pycharm.settrace('localhost', port=5555, stdoutToServer=True, stderrToServer=True)
ida运行该代码将会连接位于"localhost:5555"的调试器,再使用pycharm监听即可调试:

关于IDAPython的API可见官方文档,不过写的挺简单的,而且不同版本一直在变化,例如最常用的,根据一些字符串/调试信息对函数重命名,可很容易使用python实现,经分析该函数第2个参数代表命令名称,第5个参数代表命令的函数:
因此可编写脚本,ida本身未提供参数相关的接口,对此可使用idahunt(或flare),它通过一些传参特征追踪参数,另外也可以直接用hexrays的decompile接口获取参数,此处使用idahunt:
from idaapi import *
from idahunt.ida_helper import *
import re
def main():
func_addr = 0x144F490
all_used_addr = get_xrefs(func_addr)
for used_addr in all_used_addr:
try:
arg_val = get_call_arguments(used_addr)[1]
new_func_name = get_strlit_contents(arg_val)
new_func_name = new_func_name.decode().replace(' ', '_')
if not re.match(r'^\w+$', new_func_name):
print('invalid func name', new_func_name)
continue
arg_val = get_call_arguments(used_addr)[4]
func = get_func(arg_val)
ori_func_name = get_ea_name(func.start_ea)
if ori_func_name.startswith('sub_'):
new_func_name = ori_func_name.replace('sub', new_func_name)
print('ori name: ', ori_func_name, 'new name:', new_func_name)
set_name(func.start_ea, new_func_name)
except Exception as e:
print(f'addr:{used_addr} err:{e}')
if '__main__' == __name__:
main()
运行后结果如下:
尽管自己不写但很多插件会使用C/C++开发,对此可能需要我们自己编译为动态库,IDA对此有两套SDK,一套是针对本身的IDA SDK,另一套是针对反编译的Hexrays SDK,一般后者会被放置在plugins/hexrays_sdk
下,前者通常需要自己解压,我也喜欢把它放在plugins目录:
betamao@DESKTOP:~/IDA7.5SP3/plugins$ ls -lh | grep -E 'sdk'
drwxrwxrwx 1 betamao betamao 4.0K Jul 15 16:32 hexrays_sdk
drwxrwxrwx 1 betamao betamao 4.0K Jul 15 11:27 idasdk75
在编译时,指定按需指定这两个目录即可,如HexRaysCodeXplorer只提供旧版本的bin文件,需要自己编译,在Windows下修改配置文件:
<PropertyGroup Label="UserMacros">
<IDADIR>D:\User\bm\IDA7.5SP3</IDADIR>
<IDASDK>$(IDADIR)\plugins\idasdk75</IDASDK>
</PropertyGroup>
后选择构建的目标架构即可,一般我们需要编译32位和64位的版本,64位的库文件名需要以64结尾。
⚠️:1.有时需要涉及到C/CPP写的插件,主要是别人写的但是有bug或者需要自己添加功能,此时可用VS编译调试版,再用VS附加到ida上,在需要的位置打断点,运行插件即可调试。
2.尽管7.0已经发布很多年了还是有些有用的插件用不兼容的api,此时要么开启兼容模式要么自己手动移植,移植时可直接运行插件,不兼容的位置通常会报错并给予提示,再参考7.0 API porting guide和Porting from IDAPython 6.x-7.3, to 7.4即可,若是python插件还可能存在2升3的不兼容不多说。
类型与签名
类型信息
在逆向时经常遇到使用开源库,此时就可以获取到一部分变量的类型信息,IDA支持直接把这些信息导入,直接在File->Load File-> Parse C header file...(Ctrl+F9)
里导入即可,并且ida也能处理宏定义与include指令,如果在gcc里使用-I指定include的路径,也可以在Options->Compiler
里指定,如下可见它也能指定一些环境相关的宏:

例如在分析Android时经常需要导入jni.h,就可以用这种方式(默认会找不到stdio.h等文件,此时可以删掉该依赖,也可以在compiler options中添加库的搜索路径)。另外也可以使用tilib读.h去生成til文件,和上面的方法一样,只是需要将参数写入配置文件。不过我一般不会用这两种方法,因为项目打了总是需要修复各种错误。一种办法是先把这些库带符号编译,之后再通过IDA导出符号。例如,已知某文件使用了openssl,那么查看它的版本:
bm@top: / # busybox strings /lib/libcrypto.so.1.1 | grep -i openssl
OpenSSL 1.1.1k 25 Mar 2021
可知版本是1.1.1k,那么编译带符号的同版本:
wget https://github.com/openssl/openssl/archive/refs/tags/OpenSSL_1_1_1k.zip
unzip OpenSSL_1_1_1k.zip
cd openssl-OpenSSL_1_1_1k/
./config -d
make
之后在IDA里打开,通过LocalTypes(Shift-F1)窗口导出它的所有类型信息:

再在目标项目里导入这个刚生成的类型信息文件:

现在有了结构体的定义,就可以对变量进行定义了。其实当编译好带符号的库时,还有如下方法,它们效果类似:
- 在Produce里生成IDC文件,再导入,不过它只有Structure窗口里的类型,可能不全
- 直接把库的.til文件导入,用ida打开idb或i64时,它会生成.til文件,这个文件中可能没有函数签名信息(我测试时没有,待进一步研究)
- 在Produce里生成头文件,其实和上面演示的方法一样
枚举类型
对枚举类型的识别也是很重要的操作,如下右侧为原始的反编译信息,根据定义signal的第一个参数信号值,它是一个无符号整数,无符号信息时这里只会显示数字需要查源码进行对照,而将其创建为枚举类型则可以由ida转换为有意义的数,如左侧在创建enum后在数字上按m选择对应的枚举即可:
再来看看如何新建枚举,最方便的方法当然还是在本地类型窗口里像C代码一样定义新枚举(本地类型需手动点击同步到结构体/枚举类型),也可以直接在枚举窗口(shift+f10)定义,在该窗口还能直接同步类库里已存在的枚举:
⚠️:从7.7开始,ida可使用libclang去解析任意复杂的头文件了(cpp的头文件需为hpp),不过它还是无法正确处理虚继承-_-
签名信息
上面的方法只能迁移结构体,而函数的参数和返回值信息(签名)不会被迁移,对此可以写个脚本实现这部分功能,它首先需要把库里的函数签名导出,再去目标里做匹配。
from ida_hexrays import decompile
from ida_typeinf import tinfo_t
func = decompile(0x11)
func.type
...
不不不!事实上til本身可以包含函数的签名信息,还是要想办法生成til文件,不过现在可以配合上面的带调试信息的二进制文件了,首先分析导出.c和.h文件:

导出后,它的.c文件前面会生成函数声明,把有名称的声明粘贴到.h文件里:

使用tilib去生成til文件,使用时需要把tilib
命令行工具和ida.hlp
文件放在同一目录,之后可以使用-h
可查看帮助信息,最简单的用法是tilib -c -hlibjson-c.h libjson-c.til
(文件名最多只能含一个.否则会出错),如果顺利会显示成功,一般都会出错,此时根据报错的点修改.h文件即可:
betamao@DESKTOP ~\win # .\tilib64.exe -c -Cc1 -hlibjson-c.h t.til
Error libjson-c:87: Undefined type name '__va_list_tag'
Error libjson-c:90: Syntax error near: __gnuc_va_list
Error libjson-c:102: Syntax error near: va_list
Error libjson-c:541: Can't open include file 'defs.h'
# 此处可见87行报了__va_list_tag未定义,这中错误非常常见,是因为它先声明结构体,
# 并在后面定义,修改时把定义位置前移到使用前即可
betamao@DESKTOP ~\win # .\tilib64.exe -vvvv -c -hlibjson-c.h t.til
Type Information Library Utility v1.227 Copyright (c) 2000-2020 Hex-Rays
16:51:36 Parsing the header file libjson-c...
Error libjson-c:728: Variable 'json_object_to_fd' has already been defined
previous definition at libjson-c:727
Error libjson-c:735: Variable 'json_c_visit' has already been defined
previous definition at libjson-c:734
16:51:36 Total 2 errors
# 此处是有两个同名函数,对此只能做取舍留一个了
betamao@DESKTOP ~\win # .\tilib64.exe -vvvv -c -hlibjson-c.h t.til
Type Information Library Utility v1.227 Copyright (c) 2000-2020 Hex-Rays
16:52:30 Parsing the header file libjson-c...
16:52:30 Sorting the type information library...
16:52:30 Writing the type information library...
16:52:30 Done
# 生成成功
生成成功后,可使用tilib -l json-c.til
查看包含的信息:
betamao@DESKTOP ~\win # .\tilib64.exe -l json-c.til
TYPE INFORMATION LIBRARY CONTENTS
Description: libjson-c64
Flags : 0003 compressed macro_table_present
Base tils :
Compiler : Unknown
sizeof(near*) = 4 sizeof(far*) = 6 near code, near data
default_align = 0 sizeof(bool) = 1 sizeof(long) = 4 sizeof(llong) = 8
sizeof(enum) = 4 sizeof(int) = 4 sizeof(short) = 2
sizeof(long double) = 0
SYMBOLS
00000004 00000008 $CFAC977D1FF097D47BD6BA025CE625B1 _ISalnum;
FFFFFFFF 00000000 json_object *__cdecl json_object_array_bsearch(const json_object *key, const json_object *jso, int (*sort_fn)(const void *, const void *));
FFFFFFFF 00000000 int __cdecl json_object_array_del_idx(json_object *jso, size_t idx, size_t count);
FFFFFFFF 00000000 void __cdecl json_object_array_delete(json_object *jso);
FFFFFFFF 00000000 void __cdecl json_object_array_entry_free(void *data);
FFFFFFFF 00000000 json_object *__cdecl json_object_array_get_idx(const json_object *jso, size_t idx);
...
TYPES
00000010 struct __attribute__((aligned(8))) $5303E8BE5D6BB6190793420F7F6CF7D6 {$677E9CE920B1CD24EA75AFEC3994F1B2 cint;json_object_int_type cint_type;};
FFFFFFFF typedef int json_c_shallow_copy_fn(json_object_0 *, json_object_0 *, const char *, size_t, json_object_0 **);
FFFFFFFF typedef int json_c_visit_userfunc(json_object_0 *, int, json_object_0 *, const char *, size_t *, void *);
00000048 struct json_object {json_type o_type;uint32_t _ref_count;json_object_private_delete_fn *_delete;json_object_to_json_string_fn *_to_json_string;printbuf *_pb;data o;json_object_delete_fn *_user_delete;void *_userdata;};
00000048 typedef json_object json_object_0;
FFFFFFFF typedef void json_object_delete_fn(json_object *, void *);
...
MACROS
__int16 short
...
之后,将该til文件放入$(IDADIR)/til/pc/
下,在目标二进制里按<Shift+F11>
打开类库窗口,加载刚生成的til,可见已成功应用:
由于til里有类型信息,其实上一节的手动导入也不必要了。
另外在有时可能会遇到一些不常见的调用约定,如之前分析AWVS时遇到的Delphi先使用eax,edx,ecx再使用栈传参,对此可以通过@REG的方式指定类型:
int func@<eax>(int x@<eax>, int y@<edx>, int z@<ecx>, int a,...);
注:IDA SDK里还有两个工具,loadint能修改指令说明,idsutils可以根据dll/ar生成函数调用信息(是否使用stdcall,若使用需要清空多少栈空间),感觉没啥用...
使用技巧
全局搜索
ida自带了搜索字节序列/字符串/立即数等功能,但是对于伪代码的的搜索能力较弱(Alt+T可搜索当前函数伪代码的内容),如想静态获取一个结构体某个域在哪里被赋值的,一个CPP虚函数在哪里被调用的,此时内建的搜索就无能为力了,若此时拥有类型信息较完善的代码(自己分析或存在调试符号),则可利用上文提到的导出功能将所有伪代码到处,从而实现全局搜索,如:

当然,有时我们也会需要在单个反编译的函数中搜索,再使用这种方式就太重了,这时可直接在反编译窗口使用Alt+T
去搜索当前函数伪代码...
为了方便需要将单个C分割成独立文件,这里就不贴代码了...
链表操作
主要是CONTAINING_RECORD
识别,在很多代码中会使用双向链表,如linux内核的list_head
:
struct list_head {
struct list_head *next, *prev;
};
对这种结构如果能识别出宏则看着会舒服很多,ida自然支持,但它只有三种情况能推测出来,见文档,主要是第一种情况,注意一定要手动(Y->Enter)确定两个类型:

调试脚本
ida的调试是真的难用,但它的伪代码是真的好用,所以还是有用它的需求,辣么,首先,它的调试功能还是很丰富滴:
例如它可以通过python脚本编写条件断点,使用硬件断点,也能设置触发后的行为,如通过指令/代码块/函数级别追踪...
但是总的来说还是很烂,必要时还是用gdb/windbg/r2调试舒服点,这时可以利用ida的分析数据,使用如dwarfexport/pwndbg-idascript/FakePDB/r2ida等插件...
字符串
支持中文,可在options->general的strings里设置编码
结构体引用
有时想知道一些结构体在哪些地方被引用了,或在哪里被赋值的,又不想调试时,可以直接导出C代码,再全局搜索...
CPP类
从7.2开始ida可以更好(处理层级结构,虚表等)的解析c++啦,使用时__cppobj
属性修饰struct,要点如下:
// 1. 用__cppobj修饰对象 2. 虚函数表的名字必须是__vftable
struct __cppobj base1 {base1_vtbl *__vftable /*VFT*/;int data;};
// 3. 虚函数表类型的名称必须是 CLASSNAME_vtbl
struct /*VFT*/ base1_vtbl {int (*b1)(base1 *__hidden this);};
// 4. 派生类按酱紫的写法,可用virtual/public修饰
struct __cppobj derived : base1, base2 {}
// 5. 若有多个虚函数表,则用 CLASSNAME_XXXX_vtbl XXXX表示在派生类中的16进制偏移,如下,这样会被自动应用的
struct /*VFT*/ derived_vtbl {int (*b1)(base1 *__hidden this);};
struct /*VFT*/ derived_0008_vtabl {int (*c1)(base2 *__hidden this); }
也可以先用class
关键字定义类,它会自动生成类实例的结构体和虚函数表的结构体,如:
class base1{
int a;
virtual int fa();
}
class derived: base1{}
之后再小心编辑,例如添加构造和析构函数等。除了定义内存布局,其实c++类的逆向还有一个关键点就是虚表的识别,详见我之前写的相关文章,ida在识别出typeinfo指针后就会自动解析虚表,所以知道从哪入手了趴...
注:据我所知当前它还无法正确处理虚继承,如果可以请告诉我!!!
结构体对齐
ida默认的对齐大小可能与实际编译出来的不一致,ida支持两个gcc的两个属性来设置:
1.__attribute__((packed))
:作用于结构体,用于关闭默认的对齐,这是往小的变
2.__attribute__((aligned(4)))
: 可作用于struct或field,用于指定结构体整体或里面某个域的对齐方式,对齐必须为2的整次幂,这是往大的变
struct __attribute__((packed)) H // 64位下
{
void* a; // 0x00
__attribute__((aligned(16))) void *c; // 0x10 若没有aligned(16)属性,指针应该按8字节对齐,即应为0x08
int b; // 0x18
void * d; // 0x1c 若没有packed属性,指针应该按8字节对齐,即应为0x20
};
常见错误处理
1.XXXX: call analysis failed
: 反编译时该行的函数调用分析失败,先点进被调函数反编译它,再回到原来想要反编译的函数即可。
2.XXXX: too big function
: 函数大小超过限制,可修改hexray配置里的MAX_FUNCSIZE
来允许反编译更大的函数。
3.todo...
命令行批处理
有时需要分析大量文件,且过程基本相同,此时再单个打开分析效率很低,可使用它的文本模式,即idat/idat64
来进行批处理,如idat -B xxx
即可直接生成分析数据库,同时也可以通过-S
指定自动运行的分析脚本,更多参数用-h
查看,实际上嘛它并没有"批",所以还需要配合shell等脚本来实现,当然有现成的工具ncc group的idahunt已经实现了,例如某服务用大量CGI实现,就可这样批量导出伪代码来批量审计:
python idahunt.py --inputdir Z:\nwork\cgi-bin \ # 扫描的这个目录下的所有文件
--analyse \ # 开始分析
--scripts Z:\Src\PycharmProjects\ida\export_c.py \ # 导出伪代码的脚本
--filter "filters\sf.py" \ # 指定过滤器,因为我的脚本会把c文件生成在同目录,所以在这里过滤掉这类文件
--cleanup # 清除.asm文件,批处理模式一定会生成这个文件很烦,删掉!
还有些有用的技巧,可见IDA Pro Tips to Add to Your Bag of Tricks
插件
IDA哒插件很多很有名啊,上面已经提到一些,而每年的插件比赛也会有很多好用的插件,这里记录一些感觉有意思的插件,这里面大多没用过也没有深入分析它的功能,先记在这里有时间再看:
- idaref:用于查看指令解释
- bincat:静态二进制分析工具包
- HexRaysCodeXplorer:很好用的结构体/类重构工具,CPP那篇文章里有描述
- Kam1n0:二进制分析平台,不明觉厉
- deREferencing:实现了更友好的寄存器和堆栈视图,类似pwndbg会让调试更好看
- Karta:识别开源库,可以和上面提到的指纹匹配配合
- heap-viewer:ptmalloc可视化插件
- retdec:反编译插件
- Ponce:符号执行工具
- labeless:将ida分析信息同步到其他调试器
- autore:一个小脚本,可以对只调用一个非dummy函数的函数进行重命名,也能列出函数调用的所有非dummy函数,方便在不看代码时猜测功能
- capa:用于猜测程序功能的
- SmartJump:跳转增强,可以直接在跳转窗口里输入表达式
- lighthouse:使用drcov作为输入的覆盖率插件