当解决复杂的逆向问题时,我们常使用radare2或IDA等成熟工具进行反汇编和调试。但有时也需要深入挖掘并了解它们是如何运作的。
编写一些反汇编脚本对于自动化某些流程非常有用,并且可以形成自己的逆向工具链。至少,这是我现在正在尝试的事情。
配置环境
如标题所说的那样,你需要先安装Python 3。如果你无法确定是否安装了Python 3,可以运行如下命令:
其中capstone是反汇编引擎,将使用脚本和pyelftools来帮助解析ELF文件。
配置完成后,我将以一个基本的逆向问题为例进行演示。
用GCC/Clang进行编译:
创建脚本
首先,来看一下二进制文件中不同的节(section)。
上述脚本遍历了所有的节,并说明了文件加载位置,这些将在后续非常有用。运行此脚本将获得如下结果:
其中大多数无需关注,但有一些节需要特别注意。.text包含指令(操作码);.data用于在编译时初始化字符串和常量;.plt是过程链接表,而.got是全局偏移表。 不清楚之处请阅读ELF文件格式及其内部结构。
既然.text有操作码,那么可以从该地址开始反汇编二进制文件。
代码很简单吧(我是这么认为的哈),下面来跑跑看这段代码吧~
加粗的部分其实相当有趣呢。[rip + 0x200916]的地址相当于[0x6ca + 0x200916],后者的计算结果为0x200fe0。那么第一次调用函数是在0x200fe0吗?这个功能有什么用呢?
为此,需要来重定位一下。引自linuxbase.org
重定位是将符号引用与符号定义连接的过程。例如,当程序调用函数时,相关的调用指令必须在执行时将控制转移到正确的目标地址。可重定位文件需要有“重定位入口”,因为它们包含描述如何修改每个节内容的信息,从而允许可执行文件和共享对象文件保存进程程序映像的正确信息。
为了找到这些重定位入口,编写脚本如下:
首先遍历各个节,检查它是否属于RelocationSection类型。 然后遍历每个节的符号表中的重定位。最终运行结果如下:
还记得之前在0x200fe0处的函数调用吗?是的,这就是对那个大名鼎鼎的__libc_start_main的调用。参考linuxbase.org
__libc_start_main()函数会执行任何必要的执行环境的初始化,传入适当的参数调用main函数,并处理main()的返回。如果main()函数返回,则返回值应传递给exit()函数。
它的定义如下:
再来回顾一下我们的反汇编结果:
但这次是在lea或Load Effective Address指令处,它将一些地址[rip + 0xe6]加载到rdi寄存器中。 [rip + 0xe6]的计算结果为0x7aa,恰好是main()函数的地址!你问 我怎么知道的? 因为__libc_start_main()在执行任何操作之后,最终会跳转到rdi处的函数,这通常就是main()函数。它看起来就像这样。
如果想查看main的反汇编,可以在之前编写的脚本(disas1.py)的输出中寻找0x7aa。
根据之前的验证,每个调用指令都指向那些有重定位入口的函数。因此,每次调用它们的重定位会获取如下结果:
综上所述,这个问题是不是开始有些头绪啦。这里我要重点讲一讲反汇编的关键节(section),这些都是不言而喻的。
填充*pw字符串的循环:
这看起来有点儿像strcmp():
我不确定为什么它会在这里使用put? 我也许遗漏一些东西,或许是printf调用了put,不过我可能是错的。 我还用radare2确认那些位置实际上是"haha yes!"和"nah dude"字符串。
总结
这确实花了一些时间呢,不过我还是完成了。如果你是一个初学者,对这些感到非常的困惑,或者甚至不知道本文在讲些什么,这都无伤大雅。要掌握反汇编需要多多实践,我也不是很擅长呢。
网友评论