我们正在为IDA Pro开发NIOS II处理器模块

图片

IDA Pro反汇编程序界面截图

IDA Pro是著名的反汇编程序,已被全世界的信息安全研究人员使用了很多年。 我们在Positive Technologies也使用此工具。 此外,我们能够为NIOS II微处理器体系结构开发自己的反汇编器处理器模块 ,从而提高了代码分析的速度和便利性。

今天,我将介绍该项目的历史,并说明最终发生了什么。

背景知识


一切始于2016年,当时我们必须开发自己的处理器模块以在一项任务中分析固件。 开发是从最相关的手册《 Nios II经典处理器参考指南》进行的。 总的来说,这项工作大约花了两个星期。

该处理器模块是为IDA 6.9版本开发的。 为了提高速度,选择了IDA Python。 在处理器模块所在的位置(IDA Pro安装目录中的procs子目录)中,有三个Python模块:msp430,ebc,spu。 在其中,您可以看到如何布置模块以及如何实现基本的拆卸功能:

  • 解析指令和操作数,
  • 他们的简化和展示,
  • 创建偏移量,交叉引用以及它们引用的代码和数据
  • 处理开关结构,
  • 使用stack和stack变量处理操作。

那时大概实现了这种功能。 幸运的是,该工具在完成​​另一项任务的过程中很有用,一年后,该工具得到了积极的使用和完善。

我决定在PHDays 8与社区分享创建处理器模块的经验。该演讲引起了人们的兴趣(视频报告已发布在PHDays网站上),甚至IDA Pro Ilfak Gilfanov的创建者也出席了会议。 他的问题之一是是否实现了对IDA Pro版本7的支持,当时还不存在,但是在性能提高后,我保证会适当发布该模块。 这就是乐趣的开始。

现在是Intel的最新手册 ,用于验证和检查错误。 我对模块进行了重大修改,添加了许多新功能,包括解决了以前无法克服的问题。 好吧,当然,我增加了对IDA Pro第七版的支持。 这是发生了什么事。

NIOS II软件模型


NIOS II是为Altera FPGA(现为Intel的一部分)开发的软件处理器。 从程序的角度来看,它具有以下特征:小字节序的字节顺序,32位地址空间,32位指令集(即4个字节),32个通用寄存器和32个专用寄存器用于对每个命令进行编码。

反汇编和代码参考


因此,我们在IDA Pro中打开了一个新文件,其中包含用于NIOS II处理器的固件。 安装模块后,我们将在IDA Pro处理器列表中看到它。 处理器的选择如图所示。



假设该模块甚至还没有实现命令的基本分析。 假设每个命令占用4个字节,我们将字节分为四个字节,那么一切将看起来像这样。



在实现了对指令和操作数进行解码,在屏幕上显示它们并分析控制传递指令的基本功能之后,将上面示例中设置的字节转换为以下代码。



从示例中可以看出,交叉引用也是从控制传递命令生成的(在这种情况下,您可以看到条件跳转和过程调用)。

可以在处理器模块中实现的有用属性之一是命令注释。 如果禁用字节值的输出并启用注释的输出,则同一部分代码将已经是这样。



在这里,如果您第一次遇到了新体系结构的汇编代码,则可以使用注释来了解发生了什么。 此外,代码示例将采用相同的形式-带有注释,以便不看NIOS II手册,而是立即了解代码部分中正在发生的事情(作为示例给出)。

伪指令和命令简化


一些NIOS II命令是伪指令。 这些团队没有单独的操作码,它们本身被建模为其他团队的特殊情况。 在拆卸过程中,将简化指令-用伪指令替换某些组合。 NIOS II中的伪指令通常可以分为四种类型:

  • 当其中一个来源为零(r0)并且可以从考虑中删除时,
  • 当团队的价值为负而团队被相反的团队取代时,
  • 当条件逆转时,
  • 当分两部分(最小和最老)分别输入32位偏移量时,将其替换为一个命令。

实现了前两种类型,因为替换条件不会带来任何特殊的情况,并且32位偏移量具有比手册中提供的更多的选项。

例如,对于第一个视图,请考虑代码。



可以看出,这里经常发现在计算中使用零寄存器。 如果仔细看这个例子,您会注意到,除了控制权转移以外的所有命令都是将值简单地输入特定寄存器的选项。

在实现了伪指令的处理之后,我们获得了相同的代码部分,但现在看起来更具可读性,而不是or和add命令的变体,而得到mov命令的变体。



堆栈变量


NIOS II体系结构支持堆栈,除了堆栈指针sp外,还有指向fp堆栈帧的指针。 考虑一个使用堆栈的小型过程的示例。



显然,为堆栈上的局部变量保留了空间。 可以假定寄存器ra被存储在堆栈变量中,然后从堆栈变量中恢复。

在向跟踪堆栈指针中的更改并创建堆栈变量的模块添加功能之后,相同的示例将如下所示。



现在,代码看起来更加清晰了,您已经可以命名堆栈变量并通过遵循交叉引用来解析它们的用途。 该示例中的函数的类型为__fastcall,并将其在寄存器r4和r5中的参数推入堆栈以调用类型为_stdcall的子过程。

32位数字和偏移量


NIOS II的独特之处在于,在一次操作中,即在执行单个命令时,最多可以注册2个字节(16位)大小的直接值。 另一方面,处理器寄存器和地址空间是32位的,也就是说,要寻址,必须在寄存器中输入4个字节。

为了解决这个问题,使用了两部分位移。 PowerPC的处理器中使用了类似的机制:偏移量由最旧的和最年轻的两部分组成,并通过两个命令输入到寄存器中。 在PowerPC中,如下所示。



尽管实际上,地址是在第二条命令中配置的,但这种方式是由两个团队形成交叉链接。 在计算交叉引用的数量时,有时可能会很麻烦。

较老零件的偏移属性使用非标准类型的HIGHA16,对于较年轻零件-LOW16,有时使用类型HIGH16。



32位两部分数字的计算没有什么复杂的。 在形成两个独立团队的操作数方面存在困难。 所有这些处理都落在处理器模块上。 在IDA SDK中没有有关如何实现此操作的示例(尤其是在Python中)。

在有关PHDays的报告中,偏见是一个未解决的问题。 为了解决该问题,我们作弊:仅从最小的部分开始32位偏移。 基数被计算为最旧的部分,向左移16位。



通过这种方法,仅通过输入32位偏移量下部的命令来形成交叉引用。

基数在offset属性中可见,并且对该属性进行了标记以便将其视为数字,因此不会形成对地址本身的大量交叉引用,我们将其作为基数。



在NIOS II的代码中,找到了以下用于在寄存器中输入32位数字的机制。 首先,使用movhi命令将偏移量的最旧部分输入到寄存器中。 然后,较年轻的部分加入其中。 这可以通过三种方式(通过命令)完成:添加addi,减去subi,逻辑OR ori。

例如,在代码的下一部分中,将寄存器设置为32位数字,然后在调用函数之前将其输入到寄存器-参数中。



添加偏移量计算后,我们得到此代码块的以下表示形式。



生成的32位偏移显示在命令旁边,以输入其下部。 这个例子是非常说明性的,我们甚至可以简单地将次要部分和最高部分相加就可以轻松计算出头脑中的所有32位数字。 从价值来看,它们很可能不是偏见。

考虑进入较年轻部分时使用减法的情况。 在此示例中,将无法确定移动中的最终32位数字(偏移)。



在应用32位数字的计算后,我们得到以下形式。



在这里,我们看到,如果地址在地址空间中,则会在其上形成偏移,并且由于次要部分和高级部分的连接而形成的值不再显示在附近。 在这里,它们的偏移量为“ 10/22/08”。 为了使其余的偏移量指向有效地址,让我们稍微增加段。



在增加该段之后,我们得到的是,现在所有计算出的32位数字都是偏移量,表示有效地址。

上面提到,当使用逻辑或命令时,还有另一种用于计算偏移量的选项。 这是一个示例代码,其中以此方式计算两个偏移量。



然后,将在r8寄存器中求值的那个压入堆栈。

转换之后,很明显,在这种情况下,寄存器被配置为过程开始的地址,即过程的地址被压入堆栈。



相对于基础的读写


在此之前,我们考虑了使用两个命令输入的32位数字既可以是数字又可以是偏移量的情况。 在下面的示例中,将基数输入到寄存器的上部,然后对其进行读取或写入。



处理完这种情况后,我们从读写命令本身获取了变量的偏移量。 此外,根据操作的维数,设置变量本身的大小。



开关结构


在二进制文件中找到的开关结构可以简化分析。 例如,通过在交换器构造内部进行选择的情况数,您可以本地化负责处理特定协议或命令系统的交换器。 因此,出现了识别开关本身及其参数的任务。 考虑下面的代码部分。



执行线程在jmp r2寄存器转换时停止。 此外,还有一些代码块,这些代码块中有来自数据的链接,并且在每个块的末尾都有一个跳转到相同标签的代码。 显然,这是一个switch构造,这些单独的块可以处理其中的特定情况。 在上方,您还可以查看案件数量检查和默认跳转。

添加开关处理后,此代码将如下所示。



现在将显示跳转本身,表的地址带有偏移量,案例数,以及每种情况下的对应数。

带有选项偏移量的表本身如下。 为了节省空间,给出了前五个元素。



实际上,开关的处理在于返回代码并搜索其所有组件。 即,描述了一些交换机组织方案。 有时,这些计划可能会有例外。 这可能是在现有处理器模块中无法识别看似清晰的开关的情况的原因。 事实证明,真正的开关根本不属于处理器模块内部定义的方案。 当巡回赛似乎在那儿时,仍然有可能的选择,但是巡回赛中没有其他团队参与其中,或者主团队被重新安排,或者被转会打破了。

NIOS II处理器模块可以通过主命令之间的“外部”指令,主命令的重新排列位置和断路来识别开关。 在执行路径上使用返回路径,并考虑到可能中断电路的转换,并安装了内部变量,这些变量指示识别器的不同状态。 结果,可以识别出固件中大约10种不同的交换机组织选项。

定制说明


NIOS II体系结构中有一个有趣的功能-自定义指令。 它可以访问NIOS II体系结构中可能存在的256个用户定义的指令。 在其工作中,除通用寄存器外,定制指令还可以访问一组特殊的32个定制寄存器。 在实现了用于解析定制命令的逻辑之后,我们得到以下形式。



您可能会注意到,最后两个指令具有相同的指令编号,并且似乎执行相同的操作。

根据习惯说明,有单独的手册 。 据他介绍,自定义指令集最全面和最新的选项之一是用于处理浮点的NIOS II浮点硬件2组件(FPH2)指令集。 在实现FPH2命令的解析之后,该示例将如下所示。



从最后两个团队的助记符中,我们确保它们确实执行相同的操作-fadds命令。

按寄存器值转换


在所研究的固件中,经常会遇到根据寄存器的值执行跳转的情况,在该寄存器中输入了32位偏移量,该偏移量确定了跳转的位置。

考虑一段代码。



在最后一行中,寄存器的值有一个跳跃,而很明显,从示例的第一行开始的过程地址首先输入到寄存器中。 在这种情况下,很明显跳转到了开始。

添加跳转的识别功能后,将获得以下形式。



在jmp r8命令旁边,显示可能进行跳转的地址。 团队和跳转发生的地址之间也将形成交叉引用。 在这种情况下,链接在第一行中可见,而跳转本身从最后一行开始执行。

GP寄存器值(全局指针),保存和加载


通常使用配置为某个地址的全局指针,并相对于其寻址变量。 NIOS II使用gp(全局指针)寄存器存储全局指针。 通常,在某些时候,在固件的初始化过程中,地址值输入到gp寄存器中。 处理器模块可以处理这种情况; 为了说明这一点,下面是代码示例和在处理器模块中启用调试消息时的IDA Pro输出窗口。

在此示例中,处理器模块在新数据库中查找并计算gp寄存器的值。 关闭idb数据库时,gp值存储在数据库中。



加载现有的idb数据库时,如果已经找到gp值,则会从数据库中加载它,如以下示例中的调试消息所示。



关于GP的读写


常见的操作是相对于gp寄存器偏移的读取和写入操作。 例如,在下面的示例中,将执行三个读取和一个此类记录。



由于我们已经获得了存储在gp寄存器中的地址值,因此我们可以解决这种读写问题。

在添加了针对gp寄存器的读写情况的处理后,我们得到了更方便的图片。



在这里,您可以查看正在访问的变量,跟踪其使用并确定其用途。

相对于GP的寻址


gp寄存器还有另一个用途,用于寻址变量。



例如,在这里我们看到寄存器相对于gp寄存器配置为某些变量或数据区域。

添加识别此类情况的功能,转换为偏移量并添加交叉引用之后,我们得到以下形式。



在这里,您已经可以看到相对于gp寄存器配置了哪些区域,并且更清楚了正在发生什么。

相对于sp的寻址


类似地,在下面的示例中,寄存器相对于寄存器sp-指向堆栈的指针被调整到某些存储区。



显然,寄存器已调整为某些局部变量。 这种情况(在过程调用之前将参数设置为本地缓冲区)很常见。

添加处理(将直接值转换为偏移量)后,我们获得以下形式。



现在清楚的是,在过程调用之后,将从那些在函数调用之前将地址作为参数传递的变量中加载值。

从代码到结构字段的交叉引用


定义结构并在IDA Pro中使用它们可以促进代码分析。



查看代码的这一部分,我们可以了解到field_8字段正在递增,并且可能是事件发生的计数器。 如果读写字段在代码中相距很远,则交叉引用会有所帮助。

考虑一下结构本身。



正如我们所看到的,尽管可以访问结构的字段,但是从代码到结构的元素之间没有交叉引用。

处理完这种情况后,对于我们而言,一切将如下所示。



现在有交叉引用来构造来自使用这些字段的特定团队的字段。创建前向和后向交叉引用,并且您可以通过各种过程来跟踪读取结构字段的值和输入位置的位置。

手册与现实之间的差异


在本手册中,解码某些命令时,某些位必须采用严格定义的值。例如,对于来自eret异常的返回命令,位22–26应该为0x1E。



这是一个固件中此命令的示例。



在具有类似上下文的地方打开另一个固件,我们遇到了不同的情况。



尽管已处理所有命令,但这些字节并未自动转换为命令。从环境,甚至是相似的地址来看,这应该是同一支团队。让我们仔细看一下字节。这是相同的eret命令,除了位22–26不等于0x1E,而是等于零。

我们必须修正此命令的分析。现在它与手册并不完全对应,但是与实际相对应。



IDA 7支持


从IDA 7.0开始,Python IDA为常规脚本提供的API发生了很大变化。至于处理器模块,这些变化是巨大的。尽管如此,NIOS II处理器模块仍可以针对版本7进行改装,并且可以成功地在其中使用。



唯一无法理解的时刻:在IDA 7中的NIOS II下加载新的二进制文件时,不会发生IDA 6.9中出现的初始自动分析。

结论


除了基本的反汇编功能(SDK中的示例)外,处理器模块还实现了许多有助于代码浏览器工作的不同功能。显然,所有这些操作都可以手动完成,但是,例如,当固件文件大小为几兆字节的二进制文件上有成千上万个不同类型的偏移量时,为什么要花时间在上面?让处理器模块为我们做到这一点。毕竟,使用交叉引用快速浏览已研究代码的令人愉快的功能是什么!正如我们所知,这使IDA成为一种方便而有趣的工具。正面技术作者安东·多夫曼

Anton Dorfman)发布

Source: https://habr.com/ru/post/zh-CN424085/


All Articles