本文对真实的VxWorks固件进行分析。首先介绍VxWorks系统,随后从设备固件提取到加载地址和固件格式的分析,并结合VxWorks源码对一些常用函数进行符号恢复。
VxWorks是美国风河(WindRiver)公司1983年设计的一款实时操作系统(Real Time Operating System,RTOS),多用于各种嵌入式和IoT设备,也用于工控设备、通信、军事、航空、卫星通讯等高精尖技术。VxWorks操作系统不同于一般的Linux,它自己实现了一套进程通信、任务调度、内存管理和中断机制,通常VxWorks文件系统与内核系统合并到一起编译成一整个固件。
对于VxWorks系统安全性研究,研究难点主要体现在以下几个方面:
1. VxWorks闭源,对其进行编译需要风河公司的商业软件进行编译,无法获取源码和编译参数,资料较少,需要投入大量精力进行逆向分析;
2. 风河公司使用自家编译器,并非GNU提供的发行版gcc,编译逻辑和汇编特征与通常的Linux gcc有所不同;
3. 对于无符号的VxWorks固件函数的精准识别与名称恢复比较困难;
4. 不同厂商不同设备的固件可能是定制化的,不同的固件存在较大差异;
5. VxWorks系统模拟困难,调试困难。
由于这些研究难点的存在,国内外对其研究成果极其少。
从实际的某路由器设备VxWorks系统进行分析,包含固件提取、固件分析过程研究记录。
2.1 提取固件
现有的设备固件提取方法一般有以下几种:
1. 通过UART调试接口获取shell直接进行调试;
2. 热风枪拆下芯片,通过编程器离线读取;
3. 通过导线连接编程器读取固件;
接下来我将描述在尝试这些方式时遇到的一些问题和得出的结论。
1)通过UART调试接口获取shell
拆卸设备外壳,在PCB板上找到UART接口后,通过USB-TTL串口进行通信,飞线连接RX、TX、GND,这一步网上有很多教程,这里就不再赘述了。因为不知道波特率是多少,尝试了多个波特率,最后设置为波特率为57600不会发生乱码,得到路由器启动信息如下:
从以上信息可以看到U-boot在启动时可选择4个选项,但并没有调试相关选项,并且在实际接入过程中,无法发送中断信号中断下来进行选择,所以这个方法无效。对于这种情况有一些猜测:
1. 该系统并不提供调试接口;
2. 是否有一种只有开发人员知道的进入调试的方法。
排除获取调试接口这一选项后,可以通过其他方式对固件进行分析:
1. 改刷U-Boot,优点是可以获取调试shell,缺点是风险大,容易把设备弄坏;
2. 提取设备固件进行分析,优点是风险小,缺点是需要大量逆向工作。
2)拆下芯片读取
观察整个PCB板,只存在一个flash芯片,型号为QH16B-104H1P。由于一些设备的原因,并且还是想让这个路由器设备尽量保持原样,所以没有选择拆下芯片,不到万不得已我不考虑这种方法。
3)通过导线连接编程器读取固件
排除上面两种方法后选择通过导线连接编程器读取固件,这种方式风险比较小。我通过芯片上面的标识,用半导小芯APP查询到芯片资料如下:
可以看到这是一个8 Pin 的 NOR Flash存储器,这种flash可以直接在存储器上执行代码,不必把代码读取到RAM中。另外还有一种NAND的Flash,通常这两种flash配合使用。这里有了引脚信息之后开始考虑如何通过飞线读取固件,有两种方式:
1. 通过编程器读取;
2. 通过树莓派读取;
首先尝试了第一种方式,因为我主力机是Mac,没有USB 2.0的接口,这里存在一个问题,编程器不支持USB 3.0,导致无法读出flash数据。然而USB 2.0的转接头不太好买,所以我换了Windows台式机读取。(图中用的这种夹子连接引脚,这里其实我手上是有编程夹的,只是为了试试这个夹子,这种夹子没有整体的那种牢固,容易脱落,还卖得比较贵,可能更适用于其他场景吧)
在断电状态下,通过编程器读取出来的固件不包含任何的VxWorks信息,binwalk解析如下:
一般来说在固件系统中应该有一块比其他内容都要大的文件,也就是内核文件,但这里没有。那么这时候就有了一些猜测,断电状态难道没办法读取到系统内核文件吗?是否需要U-Boot启动才能读取到内核文件?
对于这些猜想,我对于通电情况做了一个测试。
这里要注意一下,由于编程夹有8个引脚,接到编程器上难免会接到或者触碰到VCC供电,可能会造成短路引发一些不可预测的后果。为了保险起见,这里通电就不用编程器读取了,换用树莓派来读取固件,不接VCC。当然这种通电读取的方式肯定有一些的风险存在,可能会烧掉树莓派。
树莓派有很多引脚,需要按照以下的标识来接入芯片:
对于8 Pin的芯片,接下面的引脚就够了,排除VCC:
根据芯片的资料接入:
通过网线连接树莓派,配置主机与树莓派同一网段,ssh连上树莓派,确定SPI接口后,使用flashrom工具来提取固件。提取过程如下:
这一次提取出的固件就包含了VxWorks的内核文件:
把第一块大文件提取出来,然后用binwalk解析,就看到了VxWorks系统信息。如下:
到这一步就算是把固件系统从设备中提取出来了,接下来对固件进行分析。
2.2 初步分析
有了固件之后,先尝试使用 `binwalk -e` 直接提取固件内容。这一步会提取出来非常多的文件,原因是因为binwalk对于这种RTOS,或者是加密的固件系统提取效果并不理想,存在很多杂乱的信息,下图就是一个例子:
可以从文本中看出来一些HTML标签的特征,暂且认为它是一个HTML文件。但在开头一行解压出来的内容却是一些不可见字符,说明binwalk提取是不够准确的,也可能是固件进行了某种加密。用binwalk查看固件熵值如下图:
从熵值得知了一些信息,看起来这个固件至少在开头是没有进行加密的,也就是在U-Boot位置是可以分析的,这算是一个入手点,可以通过分析U-Boot加载过程来分析固件系统。中间位置暂时无法判断是经过加密还是压缩过的,需要进一步分析。
在binwalk解析结果的最大的一块内容一般是内核文件:
可以手动剥离这一部分,然后手动lzma解压出来:
解压之后再用binwalk查看,很明显就是一个VxWorks系统了,但遗憾的是并没有发现有相关的符号表信息,可能是一个去符号的固件:
binwalk解析出固件为VxWorks 5.5.1 MIPS Little Endian,把这个VxWorks文件放到IDA进行解析,现在并不知道固件加载地址,不需要输入加载地址,因为现在的主要目的还不是逆向它,我们得先搞清楚这个固件是不是有加密导致的binwalk解析不准确。
在初步分析固件的时候,最容易入手的一个点就是查找字符串,然后根据字符串引用找到调用函数。庆幸的是我们找到了如下字符串:
很明显这是一些关于请求的字符串,说明这个VxWorks文件并没有其他加密,只是套了一层lzma压缩而已。这里尝试了一下查找字符串引用是不成功的,因为我还没有对加载地址进行分析。另外,尝试了对bzero、usrInit、bfill等VxWorks初始化函数名称进行搜索无果,再次证明这是一个没有任何符号的固件。
通过以上结论,即然所有文件都是lzma文件进行压缩,那么就可以对lzma文件格式进行分析,手动提取出其中的某个lzma文件,然后手动解压看看还有没有乱码的情况。
这里需要知道lzma压缩格式的一些关键知识。lzma文件头前13字节为文件描述,包括魔术字、文件夹大小、未压缩大小,分别占用byte、dword、qword。固件中部分内容如下:
第一、二、三部分组成header,第四部分为压缩的内容content,直到以四个“00”字符结束。第一部分properties由lc、lp、pb通过位运算得到0x5A,用lzmainfo命令可以查看他们的具体值,第二部分为文件夹大小dictionary size。第三部分为未压缩大小uncompressed size。我们以此规律通过十六进制编辑器找到了VxWorks压缩时的起始地址和终止地址,分别为0x15200和0x11c240,总大小为0x107040,跟刚才binwalk解析后我们手动剥离的大小一样。
结束位置其实出现了一些偏差,很明显上面有个MINIFS字符串,往上是0xFF填充,往下紧邻另一部分lzma头部信息,那么就不该是这四个“0”字符结束,而是在0xFF填充之前结束,这个其实解析的时候手动修正偏差即可。
现在尝试提取出0x11C240开始,结束位置为0x122de4的lzma文件,然后手动解压出来,得到如下内容:
在开头位置还是有不可见字符,说明不管是我们手动提取还是binwalk解析提取,出来的结果都是相同的。
其实在手动解析lzma的时候,发现了一个比较有意思的事情:在上一部分的lzma内容中,最后的几个字节存储着下一部分lzma的大小,简单理解就是这些lzma文件是连接在一起的。如下:
上图中,第三处为lzma头部标识,lzma文件从这里开始,0x5A是由lc、lp和pb三个标志位计算得出的。第四处为小端序未压缩大小,它的长度是qword,而第一处是大端序未压缩大小,长度为dword,第一处和第四处的值相同。第二处则表示上一个文件结束,紧接着就是lzma压缩文件的内容。
在分析过程中有一些MINIFS字符串,在固件中全局搜索,发现有四处MINIFS,并且他们之上都是以0xFF进行填充:
这里不妨大胆猜测,MINIFS作为管理多个lzma的总文件夹,把这些lzma文件连接在一起,在用到这些文件的时候再解压使用,也就是说,有极大的可能这个固件中存储的所有文件都是被lzma压缩的状态。这种方式其实很好理解,主要还是为了节约嵌入式设备存储空间,但难免牺牲一些速度。
2.3 MINIFS文件格式
我这里看到MINIFS的文件格式和网上查阅的资料不一样,网上说的是MINIFS字符串为开始,加上0x18字节后,为文件路径表,每个表占用88字节。每个表开头有一个dword的偏移值,紧接着就是明文的文件路径直到字符串结束,以下是资料给出的示例:
而我的是这样的:
这一部分内容是什么无从得知,猜测是VxWorks系统对其进行了加密,接下来就需要对VxWorks进行逆向。
2.4 加载地址分析
分析VxWorks系统首先需要知道固件加载地址。其实这种RTOS系统它们虽然是把文件系统放入到整个内核中,给我们逆向分析带来不便,但这也带来一个问题,这种RTOS系统无法使用类似Linux中PIE的保护机制,也就是说,固件的加载地址是固定的,一旦确定加载地址,那么所有的函数都可以进行解析。
现有的加载地址分析方法有多种:
1. 通过bzero、usrInit、bfill等带符号的函数信息,得到sp寄存器赋值,即固件加载地址;
2. 通过字符串引用地址,猜测固件加载地址;
3. 通过分析固件系统得到VxWorks加载地址。
由于没有函数符号信息,所以先排除掉第一种方法。
第二种方法也比较麻烦,所以这里直接采取第三种方式。这种方式是猜测疑似uimage header的数据段,也就是固件全局中搜索字符串"MyFirmware"再往上找到段起始位置,其中有两处地址指向了0x80001000,这个地址也和很多同类路由器设备的固件加载地址相同。在固件文件中,0x15000处,加0x18个字节,因此我们可以尝试使用该地址作为固件加载地址进行分析:
ghidra设置好加载基址后可以自动分析出8000多个函数。而IDA只得到4200多个函数,有相当多的内容无法自动识别,此时在IDA中手动创建或脚本创建函数,最后得到8000多个函数,但是这些函数并没有函数名称。
由于binwalk解压出来文件中有许多乱码,对乱码进行分析:
上图为文件部分内容,通过观察得到如下结论:
* 红色部分可能为32位加密的文件名称或路径信息,具体还需要再分析
* 绿色部分为当前文件大小,也就是这个文件占用多少字节
* 粉红色部分为文件起始地址,此值加上文件大小就是文件结束位置
另外还有一种情况:
上图可以看到许多0d 0a,这个是Windows下换行符,通常二者搭配使用。还有ef bb bf,这个是Unicode BOM签名,具体可百度。其余内容和图一的含义相同。
接下来对VxWorks函数进行逆向分析,确定VxWorks是否对MINIFS进行了加密,顺便识别一些基本的函数。
2.5 VxWorks逆向分析
通过之前UART串口读出的信息
然后搜索字符串引用得到如下结果:
上图的printf原本是没有符号的,是我通过一些printf特征手动命名的。从图中调用的printf得知,参数a2是一个文件路径字符串,那么sub_80014d28函数就肯定是加载模块的函数了,里面有文件解密函数,逻辑比较复杂,但可以肯定的是它调用了lzma解压函数:
到目前为止这个解密过程还没有完全逆向分析出来,但我通过网上泄露的VxWorks 5.5.1 的源码,恢复了strcpy等函数的符号。泄露的源码中,strcpy函数定义如下:
从源码来看,这是一种非常简便的strcpy实现方法,其中宏定义的EOS我在泄露源码中并未找到相关定义,并且其他的一些宏定义也无法找到,看来是缺少了一些头文件。好在直接在谷歌或者百度搜索EOS会发现一些有用的信息,经过搜索之后发现这个EOS的定义其实就是一个“0”字符,同NULL的功能。
接下来是strcpy在VxWorks文件中,IDA反编译出来的结果展示。MIPS汇编如下:
伪代码如下:
通过对比源码之后对IDA中函数进行重新命名即可。通过此方法还可以找到strncpy函数,如下图:
strncpy具有strcpy的一部分特征,也就是在赋值这一部分代码是相同的,也就是下图中红色框内和strcpy逻辑相同:
伪代码也和源码相同:
其实分析出上面的信息之后,就已经可以开始漏洞挖掘了,但如果说要研究透彻VxWorks系统,还是需要对符号进行恢复。对于无符号VxWorks系统恢复符号主要有以下几种方式:
1. 开源工具lscan使用Hex Rays提供的sig文件识别去符号静态编译的库函数,Hex Rays使用了FLIRT技术,其原理是通过不同编译器生成的汇编代码数据库文件对函数进行识别,但目前无法完成自动化生成sig签名,无法识别sig中未包含的函数。虽然Hex Rays提供了大量的sig文件,但并未包含任何风河公司的sig函数签名,还需要自己进行实现;
2. Rizzo脚本插件,使用启发式函数识别,比FLIRT识别更多的函数,需要带有符号的signature文件。关键点就是找到带有符号的signature文件比较难找;
3. IDA的flair插件;
4. 阿里公开的finger函数识别插件,是阿里生成的签名库,可搭配lscan使用。
5. 通过bindiff有符号的和无符号的VxWorks文件恢复函数名。
总结下来就是:在知道程序使用的库函数时,可以先编译相关的库,再使用FLIRT、Rizzo、bindiff进行识别。 在不知道程序使用的库函数时,可以使用lscan先识别可能的库,再加载识别。也可以使用Finger或lumina进行识别。这也是对于VxWorks研究比较耗费精力的步骤之一,到目前为止我们也在对自动化符号恢复方式进行研究。
1. 对于VxWorks的文件系统提取不够精准,需要对文件名称恢复,以及提取文件内容的完整性进行研究;
2. 对于自动化内核函数名称恢复,需要实现准确无误的函数名称恢复,这也是研究难点之一;
3. 对于VxWorks调试方法的研究,需要实现固件系统模拟和调试功能。
本文以一个真实的固件设备对VxWorks的研究记录做了整理,包括系统加载地址的分析识别、固件拆分和解压、函数名称恢复等研究内容做了讨论,由于笔者也是靠自己摸索得到的一些结论,有些内容无法找到确切的文献,也没有通过大量的固件样本进行实验,因此不能保证结论的准确性,本文仅作为讨论交流,提供一些思路,若有错误的地方请斧正。
还没有评论,来说两句吧...