欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

ARM固件基址定位工具开发

来源:本站整理 作者:佚名 时间:2020-02-14 TAG: 我要投稿

一、摘要
现在iot安全已经是一个热点了,嵌入式设备固件的逆向分析也成了师傅们比较关心的一个话题。
如果是普通的可执行文件,或者是动态链接库文件(例如路由器安全研究的情形下我们拿到了完整的文件系统),它们都是标准的二进制格式(比如elf),丢进IDA后,IDA就把工作统统给我们做好了。
但是有的时候我们拿到的固件压根就不是标准的二进制格式,IDA不知道它的架构和指令集。架构可以通过查芯片文档得知,因此我们可以在IDA最初的配置界面选择对应的反汇编处理器,但是问题是IDA并不知道固件的加载基址,也就无法建立正确的交叉引用,我们再逆起来就会很砍脑壳。
我们要做的就是尝试编写一个工具来自动判定固件的加载基址。咱们就针对32bit小端序的ARM固件来聊一下。
 
二、原理
用的原理是北京理工大学朱瑞瑾博士的论文《arm设备固件装载基址定位的研究》第三章介绍的方法,并在其基础上做了一些改进和优化。
这篇论文个人感觉写的蛮好的,第三章介绍的方法是基于函数入口表的基址定位方法,这个北理的师傅在论文里讲的已经很详细了,因此我们下面就简单的概括一下就好:
(1)基本思路
首先什么是个函数入口表呢?就我个人的理解,这是论文里自己定义的一个概念,其实说白了就是把一堆函数的内存地址(绝对地址,不是相对偏移)放了个表里,或者是放了一个结构体数组中各结构体的某个固定成员变量中。这种例子其实我们很熟悉,比如GOT表中的函数地址、中断向量表中的函数地址,都是这样存储的。
嵌入式设备的ARM固件中就有很多这种入口表,虽然里面的函数地址不一定全都是“很漂亮的函数”,但是很大一部分都是“很漂亮的函数”。漂亮的函数意思就是它的汇编代码的前几条指令,是典型的规范的序言指令,在x86架构下是push ebp/mov ebp,esp,在arm架构下,arm模式常常是STMFD,thumb模式常常是PUSH,这个如果有什么疑问百度了解一下ARM指令集即可。
值得注意的是,入口表放的是绝对地址啊。也就是说如果加载基址错误,那表中地址跳过去第一条指令大概率就不会是STMFD或PUSH。
那反过来想,如果说我们先假设一个加载基址,然后按入口表中的地址去看对应位置的第一条指令,如果入口表中大部分的地址的第一条指令都正好是STMFD或PUSH,那就说明这个假设的加载基址很可能就是正确的基址。
这样已经得到了一个猜测疑似基址的手段,那如果一个入口表在用多个假设基址去判断的时候,发现出现了多个疑似基址,那该选哪一个呢?论文给的解决方法是,用所有的入口表挨个去找疑似基址,然后比比在各个入口表找到的疑似基址中,哪个基址出现的最多,哪个就是我们要的。
(2)具体实施方法
基本思路中抛出了两个我们需要解决的问题:1、怎么得到咱们要的入口表 2、怎么去匹配序言指令
1、找入口表
论文设计了一个FIND-FET算法,不是很难。原理就是定了三条规则,满足三条规则就认为这是个入口表。
规则一:根据统计分析,一个入口表中的入口地址分布都比较紧凑,因此我们给一个地址差的范围,最高地址和最低地址差值应当小于一个界限。
规则二:地址不能重复(这个不用多说,很显然)
规则三:地址应当是4的整数倍或奇数,因为ARM模式指令地址是4的整数倍,THUMB模式指令地址是奇数。
注意:pc寄存器置值后,会判断最低位是否为1,为1则表示开启thumb模式,因此存的函数地址是奇数,但是存对应指令的地址还是偶数的(奇数减1)。
具体算法是用了滑动窗口,两个参数wnd和gap_max分别表示窗口大小(表项数)和一个表项中除了函数地址外剩余成员变量的最大个数(也就是两个函数地址间的最大间隔数)。
从头遍历偏移作为窗口起始地址,定下窗口起始地址后依次尝试gap为0~gap_max,然后判定窗口是否符合三个规则,不符合就增加gap再试,符合就滑动窗口找出一个完整的入口表。找到一个入口表后,就把偏移的遍历位置设到找出的表后面的位置,继续遍历寻找。
2、匹配序言
先对入口表都排序去重,假设最小值为fet[0]最大为fet[n-1],那么加载基址显然位于fet[n-1]-file_size ~ fet[0]这个范围内,然后就遍历这个范围内的每个值作为假设的基址base,入口地址 – base就得到一个文件偏移,然后读文件相应偏移处的hex来判断是不是STMFD和PUSH的机器码。
注意:1.奇数匹配thumb的push,偶数匹配arm的stmfd    2.上面说过了,thumb时入口表中地址值是奇数,但是存储指令的实际地址是偶数,别忘了考虑上。
push机器码第二个字节是0xb5,stmfd机器码的二三字节是0x2d和0xe9
三、改进和优化
按照原文中的方法设计出来,在具体运行的时候还是有诸多局限性,我们对以下方面做了优化:
    · 滑动窗口遍历机制
    · 候选基址的选择
    · 入口地址紧凑度
    · 候选基址遍历范围
(1)滑动窗口遍历机制:粗糙模式/精细模式
原文中,如果一个滑动窗口匹配上了三条规则,并滑动至三条规则匹配失败为止,就算找出一个完整的入口表了。
而当找出一个完整的入口表后,按原文给出的算法,将不再尝试其它gap值,并且,找下一个入口表时,用的滑动窗口的起始地址也是直接移动到了刚刚找出的表的后面,而不再尝试其之间的文件偏移位置。
而我自己做实测的时候发现,这样做会造成遗漏。这种遗漏在固件二进制文件较小时,非常容易造成最终判断结果的错误。
因此我在实现时做了修改,修改后仍会尝试未尝试的gap值和未尝试的文件偏移,效果更加精确。
但是这样改动的同时用时也会剧增,因此最终方案是设置了一个开关参数来由调用者决定扫描模式。
(2)候选基址的选择:简洁模式
实际上,多数固件加载基址的第三位hex一般都是0,也就是以000结尾,而原算法会一股脑全部输出。
改动后调用者可以选择开启简洁模式,只扫描000结尾的候选基址,初次扫描建议开启这个选项。
(3)入口地址紧凑度
即规则一中的差值界限。论文推荐的值是0x10000,我改成了0x1000,感觉效果更好一点。

[1] [2] [3] [4]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载