适用于IDA Pro的CGEN框架介绍

一切都始于我想要分析一些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以外的任何情况下做测试由于我太懒了,但是我希望这个代码能够更通用一些。

* 原文链接:yifan.lu,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

原文发布于微信公众号 - FreeBuf(freebuf)

原文发表时间:2016-01-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏极客猴

常用Python标准库

众所周知,Python有庞大的库资源,有官方标准库以及第三方的扩展库。每个库都一把利器,能帮助我们快速处理某方面的问题。作为一名python的初学者,当把基本的...

1793
来自专栏FreeBuf

如何利用IDA Python浏览WINDOWS内核

当我去参加安全会议时,我总喜欢跟大神们讨论关于逆向工程方面的东西。因为这样我就可以从别人的经验中了解到他们是如何自动化实现那些繁琐操作的了。值得一提的是,很多人...

1394
来自专栏老九学堂

【新手必读】Java初学者,你遇到的问题都在这了

很多小伙伴初次接触Java时往往会感觉十分迷茫,在这里老九君收集并解答了同学们遇到的一些基础问题,希望能对大家的Java学习之路有所帮助。 初识篇 1、什么是J...

3456
来自专栏性能与架构

Redis 新数据结构 - Streams

1. 为什么添加 Streams 数据流? Stream 数据流的使用越来越多,Redis 的作者 antirez 也在积极思考,如何让 redis 能够很好的...

3976
来自专栏PPV课数据科学社区

适用于 PHP 开发人员的 Python 基础知识

您是一名 PHP 开发人员。您在过去 五年(或更长时间)中可能一直都编写应用程序,您已经将许多想像变成了可能 — 电子商务系统、简单内容管理系统、Twitte...

37515
来自专栏玉树芝兰

如何让Jupyter Notebook支持多种编程语言?

不满意Jupyter Notebook只有Python 2环境,还打算让它支持Python 3与R?没问题,本文一步步帮助你实现这个愿望。

1731
来自专栏CSDN技术头条

QtQuick 系列教程之 QML 与 C++ 交互

QML 作为一种灵活高效的界面开发语言已经越来越得到业界的认可。QML 负责界面,C++ 负责逻辑,这也是 Qt 官方推荐的开发方式。那么 QML 与 C++ ...

1923
来自专栏程序你好

结构型设计模式:外观设计模式

Facade(外观)模式为子系统中的各类(或结构与方法)提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用。

792
来自专栏WeTest质量开放平台团队的专栏

Unity手游崩溃异常如何捕获--C#及JVM捕获

C#脚本未捕获的异常,与Android和Native未捕获异常很大的区别是,未捕获异常不会照成引用的闪退。所以,C#脚本的异常危害相对较小,但是同样更加容易存在...

1703
来自专栏Ldpe2G的个人博客

ScalaMP ---- 模仿 OpenMp 的一个简单并行计算框架

1003

扫码关注云+社区

领取腾讯云代金券