IDA Pro操作指南
摘要工具
nm
将源文件编译成目标文件时,编译器必须嵌入一些全局(外部)符号的位置信息,以便链接器在组合目标文件以创建可执行文件时,能够解析对这些符号的引用。除非被告知要去除最终的可执行文件中的符号,否则,链接器通常会将目标文件中的符号带入最终的可执行文件中。根据nm手册的描述,这一实用工具的目的是“列举目标文件中的符号”。使用rm检查中间目标文件(扩展名为.0的文件,而非可执行文件)时,默认输出结果是在这个文件中声明的任何函数和全局变量的名称。nm实用工具的样本输出如下所示。
从中可以看到,m列出了每一个符号以及与符号有关的一些信息。其中的字母表示所列举的
符号的类型。前面的例子中出现了以下字母,下面逐一解释。
U,未定义符号,通常为外部符号引用。
T,在文本部分定义的符号,通常为函数名称。
t,在文本部分定义的局部符号。在C程序中,这个符号通常等同于一个静态函数。
D,已初始化的数据值。
C,未初始化的数据值。
大写字母表示全局符号,小写字母则表示局部符号。请参阅nm手册了解有关字母代码的详细解释。
如果使用nm列举可执行文件中的符号,将会有更多信息显示出来。在链接过程中,符号被解析成虚拟地址(如有可能)。因此,这时运行nm,将可获得更多信息。
ldd
创建可执行文件时,必须解析该文件引用的任何库函数的地址。链接器通过两种方法解析对库函数的调用:静态链接(static linking)和动态链接(dynamic linking)。链接器的命令行参数决定具体使用哪一种方法。一个可执行文件可能为静态链接、动态链接,或二者兼而有之。
此时查看两种方式编译的可执行文件大小:
使用ldd查看:
objdump
objdump可以显示大量目标文件有关的信息,超过30个命令行选项分别查看。
- 节头部,程序文件每节的摘要信息。
- 专用头部,程序内存分布信息,还有运行时加载器所需的其他信息,包括由ldd等工具生成的库列表。
- 调试信息,提取出程序文件中的任何调试信息。
- 符号信息,以类似nm的方式转储符号表信息。
- 反汇编代码清单,objdump对文件中标记为代码的部分执行线性扫描反汇编。反汇编x86代码时,objdump可以生成AT&T或Intel语法,并可以将反汇编代码保存在文本文件中。这样的文本文件叫做反汇编死代码清单(dead listing),尽管这些文件可用于实施逆向工程,但它们很难有效导航,也无法以一致且无错的方式被修改。
readelf
和objdump类似,但不依赖libbfd
深度检测工具
到目前为止,我们已经讨论了一些工具,利用这些工具,可以在对文件的内部结构知之甚少的情况下对文件进行粗略分析,也可以在深入了解文件的结构之后,从文件中提取出特定的信息。在这一节中,我们将介绍一些专用于从任何格式的文件中提取出特定信息的工具。
strings
有时候,提出一些与文件内容有关的常规性问题,即那些不需要了解文件结构即可回答的问题,对我们会有一定帮助。例如:“这个文件包含字符串吗?”当然,在回答这个问题之前,必须先回答这个问题:“到底什么是字符串?”我们将字符串简单定义为由可打印字符组成的连续字符序列。通常,在这一定义的基础上,还需要指定一个最小长度和一个特定的字符集。因此,可以搜索至少包含4个连续可打印ASCII字符的字符串,并将结果在控制台打印出来。通常,搜索这类字符串不会受到文件结构的限制。在ELF二进制文件中搜索字符串就像在微软Word文档中搜索字符串一样简单。
- 需要牢记的是,使用strings处理可执行文件时,默认情况下,strings仅仅扫描文件中可加载的、经初始化的部分。使用命令行参数-a可迫使strings扫描整个文件。
- strings不会指出字符串在文件中的位置。使用命令行参数-t可令strings显示所发现的每一个字符串的文件偏移量信息。
- 许多文件使用了其他字符集。使用命令行参数-e可使strings搜索更广泛的字符,如16位Unicode字符。
反汇编器
如前所述,有很多工具都可以生成二进制目标文件的死列表形式的反汇编代码。PE、ELF和MACH-0文件可分别使用dupbin、objdump和otoo1进行反汇编。但是,它们中的任何一个都无法处理任意格式的二进制文件。有时候,你会遇到一些并不采用常用文件格式的二进制文件,在这种情况下,就需要一些能够从用户指定的偏移量开始反汇编过程的工具。
ndisasm
ndisasm是一款用于x86指令集的流式反汇编器,ndisasm属于NASM工具集。
安装方法:sudo apt-get install nasm
我们通过VIPER生成一段shellcode,然后进行反编译:
通过ndisasm进行反编译:
由于流式反汇编非常灵活,因此它的用途相当广泛。例如,在分析网络数据包中可能包含shellcode的计算机网络攻击时,就可以采用流式反汇编器来反汇编数据包中包含shellcode的部分以分析恶意负载的行为。另外一种情况是分析那些格式未知的ROM镜像。ROM中有些部分是数据,其他部分则为代码,可以使用流式反汇编器来反汇编镜像中的代码。
IDA
主要数据显示窗口
Names窗口






1 | void bar(int j, int k){ |
1 | gcc -m32 -O0 -fno-stack-protector -fno-pie -no-pie -fcf-protection=none -fomit-frame-pointer stack_frame.c -o b.out #-fomit-frame-pointer指定不使用EBP |
1 | gcc -m32 -O0 -fno-stack-protector -fno-pie -no-pie -fcf-protection=none stack_frame.c -o a.out |


此时分析一下栈的情况:
我们可以清楚的看出此时的栈帧
变量 | 偏移量 | |
---|---|---|
z | [esp] | 局 |
y | [esp+4] | 部 |
buffer | [esp+8] | |
x | [esp+72] | 变 |
padding(可能存在) | [esp+76] | 量 |
saved eip | [esp+80] | —– |
a | [esp+84] | 参 |
b | [esp+88] | |
c | [esp+92] | 数 |
最后收尾部分返回调用函数的时候需要从从栈顶弹出所需返回地址,也就是保存的eip指令: | ||
![]() |
||
计算一下正好是88个字节,回收了这些地址: | ||
![]() |
使用EBP
我们同样跟进到demo_stackframe函数:
同样是申请了80字节的空间,不过前面多了两步操作栈底指针和栈顶指针的步骤。
此时我们来分析一下栈帧的情况:
可以看到大致上是和之前差不多的,只不过这里多了4个字节的保存的ebp的地址
这里使用一个专用的帧指针,所有变量相对于帧指针寄存器的偏移量得以计算出来。许多时候(尽管并无要求),正偏移量用于访问函数参数,而负偏移量则用于访问局部变量。 使用专用的指针,我们可以自由更改栈指针,而不至影响帧内其他变量的偏移量。
调用bar函数后来到收尾部分,在使用EBP的程序中返回原函数地址的步骤有所不同,一般的步骤有以下两条指令:
1 | mov ebp, esp |
因为非常常用,所以x86体系提供了一个新的指令leave来直接完成这个操作
IDA栈视图
很明显,栈帧是一个运行时概念,没有栈和运行中的程序,栈帧就不可能存在。话虽如此,但这并不意味着你在使用IDA之类的工具进行静态分析时,就可以忽略栈帧的概念。二进制文件中包含配置每个函数的栈帧所需的全部代码。通过仔细分析这段代码,我们可以深入了解任何承数的栈帧的结构,即使这个函数并未运行。
我们以gcc编译的以下函数为例:
1 | void bar(int x, int y){} |
我们可以提前预估一下需要的栈空间(4+64+4+4=76)