一切都始于我想要分析一些MeP代码的时候。我通常在IDA Pro中做逆向工作,但是有一小部分处理器IDA并不支持。幸运的是,objdump可以支持这些小众的处理器架构。经过一番摸索之后,我确定将这些反汇编代码移植到IDA中会比直接在objdump的输出中做一些标注和修改更好一些。
过程
互联网上很少有关于编写IDA处理模块的资料。SDK说明文档太简单了(只是让你去读示例代码和头文件)关联到两个文档:Online gide已经找不到了和Chris Eagle写的《IDA权威指南》。
打开这本书关于编写处理器模块的章节(19章),在多次失败的尝试之后你可能会打退堂鼓(只是记录一下缺乏相关的文档做不出来)。
Chris Eagle中《IDA权威指南》中提到:
编写处理器模块的难处在于processor_t结构包含56个需要被初始化的字段,而且其中26个字段是函数指针,其中一个指针指向了一个指针数组,里面又包含了59个字段指向不同的结构(asm_t)需要被初始化。是不是很简单啊?
但是,我不是那么容易放弃的,继续读下去并逐渐熟悉了创建一个处理器模块的过程。我不打算详细的描述这个过程,因为Chris已经中书上写的很清楚了,但我会给出一个简要的提纲。
IDA处理器模块
处理器模块由四个部分组成。“分析器”解析机器码的二进制数据并生成指令信息。“仿真器”使用这些信息来帮助IDA做下一步的分析。举个例子,如一个指令引用数据,你的模块可以告诉IDA查找那个地址上的数据。如果那个指令执行函数调用,你的模块可以让IDA创建一个函数。与它的名字相反,它其实并没有真正的模拟指令集。“输出者”只是给出分析器生成的数据,向用户输出汇编代码。最后是架构信息,虽然在别的地方不认为它是一个组件,但我认为它是一个组件。这些不是代码,但是是一些静态结构告诉IDA一些有用的信息比如寄存器的名字,指令助记符,对齐等等。
CGEN
MeP的binutils(objdump)是CGEN框架机器生成的。CGEN试图将编写CPU(assemblers,disassemblers,simulators等等)的工具抽象成一些编写CPU的定义。这些定义使用Scheme语言对CPU(包括硬件元素,指令集,操作符等等)进行描述。 CGEN为所有需要的CPU工具进行定义并输出C/C++代码。开始我想绕开CGEN只是将binutils代码包含到IDA模块中。理论上你的模块没有必要依照上面的方法。你可以让分析器记录二进制数据,仿真器什么也不做,输出器使用bunutils去生成完整的一行然后进行输出。然而这样做的话,你本质上并没有使用到IDA的强大功能(寻找交叉引用,栈空间布局等等)。没用使用到CGEN CPU定义给出的信息也是很可惜的。理论上这些定义足够强大能够生成RTL代码来完成这个过程,所以我们会尽可能的向IDA提供信息。
CGEN生成器
生成器也是用Scheme语言写的(CGEN的文档将生成器定义为“应用”)。我之前一行功能代码也没有写过,所以我花了一天的时间去理解一个很小的代码库。CGEN自定义了一个叫做COS的对象系统。CPU相关的所有定义都变成了对象,并且每个生成器都给这些对象一个输出自己的方法。例如:模拟器会给操作对象一个“生成代码来获得值”的方法。然后通过指令的语义来生成C代码会用到这个对象的方法。就像一个软件工程师一样,我将模拟器,反汇编器,架构描述相关的代码单独分割出来,然后写代码将他们整合到一起来生成IDA模块的各个组件。
分析器作为基础来模拟指令解码生成器。我必须修改CGEN来记录指令语法中指定操作数的顺序(只有一个地方是修改CGEN自身,其他都是添加的)。然后我重写了模拟器从指令中提取操作数的方法来填充IDA的”cmd”结构(需要被指定的操作数)。
模拟器使用了模拟模块生成最基本的信息,这是最难写的地方(在代码复杂度方面)。主要问题在于当模拟器生成后期望代码有序运行并存储状态信息,IDA的模拟器并不存储状态信息,并且IDA无法保证模拟器像指令描述的那样运行。这意味着我们不能依赖于状态,我们的模拟器只能基于指令单独运行。由于我们只关心通过模拟器寻找数据和代码引用,我们可以做如下简化:
1.任何条件都有可能被剥离并且所有路径都可能被采用 2.使用从寄存器取出的任何值将会使模拟器停止并立即返回 3.对寄存器设置任何的值都将会对其值进行评估,但是会将结果抛弃
第一点允许我们不需要条件即可找到引用。举个例子,一个条件分支将会允许代码引用被生成。第二点说的是由于我们不知道状态,所以任何依赖于寄存器值的条件没有被剔除的话将会使查找引用变得困难。其实我们可以一直使用这个方法来找到偏移引用,但是我们必须知道只有加减法的使用以及单寄存器的使用会增加复杂性。第三点允许我们捕获内存读取的动作。通过这些简化方法,我们可以知道在状态未知的情况下找到的任何内存的读写和任何PC读写都能够被转换成交叉引用。
输出器使用语法分析(binutils的操作码构建器)作为基础。它读取指令序列来输出正确的括号序列,命令等等。我只是替换了硬件对象生成输出方法来生成IDA输出函数。
结果
在所有基础层级,你的生成模块会输出你所预期的跟objdump中一样的输出。分析器会找到操作数的正确类型。模拟器试图找到所有常量地址和加法指令的引用(代码引用和数据引用)。输出器会输出所有正确的指令,如果需要的话还会输出操作数正确的类型/大小/名字。
无法正确执行的最主要的东西是没有办法保持对栈指针的追踪。另外也没有做到跳转和调用分支的标识(需要CF_CALL标签)。也没有办法标识指令如果没有继续执行流的话(需要CF_STOP标签)(添加这些东西其实是件小事,但难点在于将其添加到其他生成器而不引入模拟器代码。由于人工标识指令很容易,所以我决定不实现)。
使用
一旦你生成IDA模块组件,你仍然需要人工编写processor_t结构,与notify()函数相关,并且实现特殊的输出函数(按CPU定义的来)。然后你可以从binutils拷贝CGEN头并用IDA SDK进行编译。将MeP模块作为例子。你可以重复大部分未生成的代码(只改变一些字符串和常量)。如果你在运行过程中碰到任何问题,请联系我。我并没有在MeP以外的任何情况下做测试由于我太懒了,但是我希望这个代码能够更通用一些。
下载
CGEN代码下载: https://github.com/yifanlu/cgen
MeP模块下载: https://github.com/yifanlu/toshiba-mep-idp
MeP模块有基础的栈追踪功能并且可以人工添加调用标识。我有时间的时候,我会继续为大家提供剩余的IDA CGEN模块支持。
* 原文链接: yifan.lu ,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)