- 献词
- 致谢
- 前言
- 第一部分 IDA 简介
- 第 1 章 反汇编简介
- 第 2 章 逆向与反汇编工具
- 第 3 章 IDA Pro 背景知识
- 第二部分 IDA 基本用法
- 第 4 章 IDA 入门
- 第 5 章 IDA 数据显示窗口
- 第 6 章 反汇编导航
- 第 7 章 反汇编操作
- 第 8 章 数据类型与数据结构
- 第 9 章 交叉引用与绘图功能
- 第 10 章 IDA 的多种面孔
- 第三部分 IDA 高级应用
- 第 11 章 定制 IDA
- 第 12 章 使用 FLIRT 签名来识别库
- 第 13 章 扩展 IDA 的知识
- 第 14 章 修补二进制文件及其他 IDA 限制
- 第四部分 扩展 IDA 的功能
- 第 15 章 编写 IDA 脚本
- 第 16 章 IDA 软件开发工具包
- 第 17 章 IDA 插件体系结构
- 第 18 章 二进制文件与 IDA 加载器模块
- 第 19 章 IDA 处理器模块
- 第五部分 实际应用
- 第 20 章 编译器变体
- 第 21 章 模糊代码分析
- 第 22 章 漏洞分析
- 第 23 章 实用 IDA 插件
- 第六部分 IDA 调试器
- 第 24 章 IDA 调试器
- 第 25 章 反汇编器/ 调试器集成
- 第 26 章 其他调试功能
- 附录 A 使用 IDA 免费版本 5.0
- 附录 B IDC/SDK 交叉引用
2.2 摘要工具
由于我们的目标是对二进制程序文件进行逆向工程,因此,在对文件进行初步分类后,需要用更高级的工具来提取详尽的信息。本节讨论的工具不仅能识别它们所处理的文件的格式,更重要的是还能够理解某一特定的文件格式,并且能够解析它们的输入文件,提取出这些输入文件所包含的非常特别的信息。
2.2.1 nm
将源文件编译成目标文件时,编译器必须嵌入一些全局(外部)符号的位置信息,以便链接器在组合目标文件以创建可执行文件时,能够解析对这些符号的引用。除非被告知要去除最终的可执行文件中的符号,否则,链接器通常会将目标文件中的符号带入最终的可执行文件中。根据 nm
手册的描述,这一实用工具的作用是“列举目标文件中的符号”。
使用 nm
检查中间目标文件(扩展名为.o 的文件,而非可执行文件)时,默认输出结果是在这个文件中声明的任何函数和全局变量的名称。 nm
实用工具的样本输出如下所示。
idabook# gcc -c ch2_example.c idabook# nm ch2_example.o U __stderrp U exit U fprintf 00000038 T get_max 00000000 t hidden 00000088 T main 00000000 D my_initialized_global 00000004 C my_unitialized_global U printf U rand U scanf U srand U time 00000010 T usage idabook#
从中可以看到, nm
列出了每一个符号以及与符号有关的一些信息。其中的字母表示所列举的符号的类型。前面的例子中出现了以下字母,下面逐一解释。
U
,未定义符号,通常为外部符号引用。
T
,在文本部分定义的符号,通常为函数名称。
t
,在文本部分定义的局部符号。在 C 程序中,这个符号通常等同于一个静态函数。
D
,已初始化的数据值。
C
,未初始化的数据值。
说明 大写字母表示全局符号,小写字母则表示局部符号。请参阅
nm
手册了解有关字母代码的详细解释。
如果使用 nm
列举可执行文件中的符号,将会有更多信息显示出来。在链接过程中,符号被解析成虚拟地址(如有可能)。因此,这时运行 nm
,将可获得更多信息。下面是使用 nm
处理一个可执行文件所得到的部分输出。
idabook# gcc -o ch2_example ch2_example.c idabook# nm ch2_example <. . .> U exit U fprintf 080485c0 t frame_dummy 08048644 T get_max 0804860c t hidden 08048694 T main 0804997c D my_initialized_global 08049a9c B my_unitialized_global 08049a80 b object.2 08049978 d p.0 U printf U rand U scanf U srand U time 0804861c T usage idabook#
在这个例子中,一些符号(如 main
)被分配了虚拟地址,链接过程引入了一些新的符号(如 frame_dummy
),另一些符号(如 my_unitialized_global
)的类型发生了改变,其他符号由于继续引用外部符号,仍旧为未定义符号。在这个例子中,我们检测的文件属于动态链接二进制文件,为此,未定义的符号将在 C 语言共享库中定义。欲了解更多有关 nm
的信息,请参阅 nm
手册。
2.2.2 ldd
创建可执行文件时,必须解析该文件引用的任何库函数的地址。链接器通过两种方法解析对库函数的调用: 静态链接 (static linking)和 动态链接 (dynamic linking )。链接器的命令行参数决定具体使用哪一种方法。一个可执行文件可能为静态链接、动态链接,或二者兼而有之1 。
1. 欲了解更多有关链接的信息,请参阅 John Levine 的著作 Linkers and Loaders (San Francisco:Morgan Kaufmann,2000 )。
如果要求使用静态链接,链接器会将应用程序的目标文件和所需的库文件组合起来,生成一个可执行文件。这样,在运行时就不需要确定库代码的位置,因为它已经包含在可执行文件中了。静态链接的优点包括:函数调用更快一些;发布二进制文件更加容易,因为这时不需要对用户系统中库函数的可用性做出任何假设。其缺点包括:生成的可执行文件更大;如果库组件发生改变,对程序进行升级会更加困难,因为一旦库发生变化,程序就必须重新链接。从逆向工程的角度看,静态链接使问题更加复杂。在分析一个静态链接二进制文件时,要回答“这个二进制文件链接了哪些库”和“这些函数中哪一个是二进制函数”可不那么容易。我们将在第 12 章讨论在对静态链接代码进行逆向工程时遇到的挑战。
动态链接与静态链接不同。使用动态链接时,链接器不需要复制它需要的任何库。相反,链接器只需将对所需库(通常为.so 或.dll 文件)的引用插入到最终的可执行文件中。因此,这时生成的可执行文件也更小一些。而且,使用动态链接时升级库代码也变得简单多了,因为只需要维护一个库(被许多二进制文件引用),如果需要升级库代码,用新版本的库替换过时的库,就可以立即更新每一个引用该库的二进制文件。使用动态链接的一个缺点在于,它需要更加复杂的加载过程。因为这时必须定位所有所需的库,并将其加载到内存中,而不是加载一个包含全部库代码的静态链接文件。动态链接的另一个缺点是,供应商不仅需要发布他们自己的可执行文件,而且必须发布该文件所需的所有库文件。如果一个系统无法提供程序所需的全部库文件,在这个系统上运行该程序将会导致错误。
下面的输出说明了一个程序的动态和静态链接版本的创建过程、生成的二进制文件的大小,以及如何使用 file
工具识别这两个二进制文件。
idabook# gcc -o ch2_example_dynamic ch2_example.c idabook# gcc -o ch2_example_static ch2_example.c –static idabook# ls -l ch2_example_* -rwxr-xr-x 1 root wheel 6017 Sep 26 11:24 ch2_example_dynamic -rwxr-xr-x 1 root wheel 167987 Sep 26 11:23 ch2_example_static idabook# file ch2_example_* ch2_example_dynamic: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), dynamically linked (uses shared libs), not stripped ch2_example_static: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), statically linked, not stripped idabook#
为了确保动态链接正常运行,动态链接二进制文件必须指明它需要的库文件,以及需要这些文件中的哪些特定资源。因此,与静态链接二进制文件不同,我们可轻易确定一个动态链接二进制文件所依赖的库文件。 ldd
(list dynamic dependencies )是一个简单的实用工具,可用来列举任何可执行文件所需的动态库。在下面这个例子中,我们使用 ldd
确定 Apache Web 服务器所依赖的库。
idabook# ldd /usr/local/sbin/httpd /usr/local/sbin/httpd: libm.so.4 => /lib/libm.so.4 (0x280c5000) libaprutil-1.so.2 => /usr/local/lib/libaprutil-1.so.2 (0x280db000) libexpat.so.6 => /usr/local/lib/libexpat.so.6 (0x280ef000) libiconv.so.3 => /usr/local/lib/libiconv.so.3 (0x2810d000) libapr-1.so.2 => /usr/local/lib/libapr-1.so.2 (0x281fa000) libcrypt.so.3 => /lib/libcrypt.so.3 (0x2821a000) libpthread.so.2 => /lib/libpthread.so.2 (0x28232000) libc.so.6 => /lib/libc.so.6 (0x28257000) idabook#
Linux 和 BSD 系统均提供 ldd
工具。在 OS X 系统上,使用 otool
工具,并带上–L 选项( otool–L
文件名),即可实现类似的功能。在 Windows 系统中,可以使用 Visual Studio 工具套件中的实用 1 工具 dumpbin
列举某文件所依赖的库,形式为: dumpbin /dependents
文件名。
2.2.3 objdump
与专用的 ldd
不同, objdump
的功能非常多样。显示与目标文件有关的信息是 objdump
的功能。这是一个相当宽泛的目标, objdump
为此提供了大量命令行选项(超过 30 个),以提取目标文件中的各种信息。 objdump
可用于显示以下与目标文件有关的信息(以及其他更多信息)。
节头部 ,程序文件每节的摘要信息。
专用头部 ,程序内存分布信息,还有运行时加载器所需的其他信息,包括由 ldd 等工具生成的库列表。
调试信息 ,提取出程序文件中的任何调试信息。
符号信息 ,以类似
nm
的方式转储符号表信息。反汇编代码清单 ,
objdump
对文件中标记为代码的部分执行线性扫描反汇编。反汇编 x86 代码时,objdump
可以生成 AT & T 或 Intel 语法,并可以将反汇编代码保存在文本文件中。这样的文本文件叫做反汇编死代码清单(dead listing ),尽管这些文件可用于实施逆向工程,但它们很难有效导航,也无法以一致且无错的方式被修改。
objdump
是 GNU binutils2 工具套件的一部分,用户可以在 Linux、FreeBSD 和 Windows(通过 Cygwin)系统中找到这个工具。 objdump
依靠二进制文件描述符库 libbfd (二进制工具的一个组件)来访问目标文件,因此,它能够解析 libbfd 支持的文件格式(ELF 、PE 等)。另外,一个名为 readelf
的实用工具也可用于解析 ELF 文件。 readelf
的大多数功能与 objdump
相同,它们之间的主要区别在于 readelf
并不依赖 libbfd 。
2. 参见 http://www.gnu.org/software/binutils/ 。
2.2.4 otool
otool
可用于解析与 OS X Mach-O 二进制文件有关的信息,因此,可简单将其描述为 OS X 系统下的类似于 objdump
的实用工具。下面的代码说明了如何使用 otool
显示一个 Mach-O 二进制文件的动态库依赖关系,从而执行类似于 ldd 的功能。
idabook# file osx_example osx_example: Mach-O executable ppc idabook# otool -L osx_example osx_example: /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.4.0) /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.1.5)
otool
可用于显示与文件的头部和符号表有关的信息,并对文件的代码部分进行反汇编。欲了解更多有关 otool
功能的信息,请参阅相关手册。
2.2.5 dumpbin
dumpbin
是微软 Visual Studio 工具套件中的一个命令行实用工具。与 otool
和 objdump
一样, dumpbin
可以显示大量与 Windows PE 文件有关的信息。下面的例子说明了如何使用 dumpbin
以类似于 ldd
的方式显示 Windows 计算器程序的动态依赖关系。
$ dumpbin /dependents calc.exe Microsoft (R) COFF/PE Dumper Version 8.00.50727.762 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file calc.exe File Type: EXECUTABLE IMAGE Image has the following dependencies: SHELL32.dll msvcrt.dll ADVAPI32.dll KERNEL32.dll GDI32.dll USER32.dll
dumpbin
的其他选项可从 PE 二进制文件的各个部分提取信息,包括符号、导入的函数名、导出的函数名和反汇编代码。欲了解更多有关如何使用 dumpbin
的信息,请访问 Mircrosoft Developer Network(MSDN)3 。
3. 参见 http://msdn.microsoft.com/en-us/library/clh23y6c(VS.71).aspx 。
2.2.6 c++filt
由于每一个重载的函数都使用与原函数相同的名称,因此,支持函数重载的语言必须拥有一种机制,以区分同一个函数的许多重载版本。下面的 C++ 实例展示了一个名为 demo
的函数的几个重载版本的原型:
void demo(void); void demo(int x); void demo(double x); void demo(int x, double y); void demo(double x, int y); void demo(char* str);
通常,一个目标文件中不能有两个名称相同的函数。为支持重载,编译器将描述函数参数类型的信息合并到函数的原始名称中,从而为重载函数生成唯一的函数名称。为名称完全相同的函数生成唯一名称的过程叫做名称改编(name mangling)4 。如果使用 nm
转储前面的 C++ 代码的已编译版本中的符号,将得到如下结果(有删减,以突出显示 demo 的重载版本):
4. 有关名称改编的概述,请参考 http://en.wikipedia.org/wiki/Name_mangling 。
idabook# g++ -o cpp_test cpp_test.cpp idabook# nm cpp_test | grep demo 0804843c T _Z4demoPc 08048400 T _Z4demod 08048428 T _Z4demodi 080483fa T _Z4demoi 08048414 T _Z4demoid 080483f4 T _Z4demov
C++ 标准没有为名称改编方案制定标准,因此,编译器设计人员必须自己制定标准。为了译解上面列出的 demo
函数的重载版本,我们需要一个能够理解编译器(这里为 g++ )的名称改编方案的工具, c++filt
正是这样一个实用工具。 c++filt
将每个输入的名称看成是改编后的名称(mangled name ),并设法确定用于生成该名称的编译器。如果这个名称是一个合法的改编名称,那么, c++filt
就输出改编之前的原始名称;如果 c++filt
无法识别一个改编名称,那它就按原样输出该名称。
如果将上面 nm
输出的结果交给 c++filt
处理,将可以得到这些函数的原始名称,如下所示:
idabook# nm cpp_test | grep demo | c++filt 0804843c T demo(char*) 08048400 T demo(double) 08048428 T demo(double, int) 080483fa T demo(int) 08048414 T demo(int, double) 080483f4 T demo()
值得注意的是,改编名称可能包含其他与函数有关的信息,正常情况下,nm 无法显示这些信息。在逆向工程过程中,这些信息可能非常重要。在更复杂的情况下,这些额外信息中可能还包含与类名称或函数调用约定有关的信息。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论