VXWorks固件NOE-711后门账号漏洞分析
by zgd
2019年1月
以Schneider厂商的Quantum系列系列PLC的NOE-711以太网模块的固件为例,学习和了解一下基于VxWorks操作系统的嵌入式设备固件的一些常用分析方法。
(I)web配置app默认账户和密码的获取
github下载到含固件的文件:
fw/fw.ini文件中包含固件版本信息:
wwwroot/classes/内的jar文件是Web配置端APP文件
使用JD-GUI反编译软件对jar文件逆向分析,并搜索PASSWORD,直接找到了web配置app默认的用户名和密码。
(II)固件分析
通过(I)对文件进行分析知道wwwroot/conf/exec/NOE77101.bin是NOE77101的固件。下面对固件进行分析。
(1)固件解压
使用binwalk解压工具进行固件解压,发现使用的是Zlib压缩算法格式。从0x385位置后为Zlib压缩格式。
通过binwalk的-e参数进行自动提取后,binwalk会把自动提取后的文件以偏移地址命名,并存储。
通过WinHex直接查看二进制文件,发现0x385(十进制901)位置内容如下图:
通过查找资料发现:Pattern字节为78 9C为Zlib算法压缩格式;
1F 8B为gzip算法压缩格式;
5D 00 00 80为LZMA算法压缩格式;
Binwalk也是根据这种方式来判断文件格式的。
(2)固件信息获取
使用binwalk对固件进行信息分析,可以得到固件的操作系统版本、符号表地址、指令集架构等关键信息。
使用strings+文件名,或binwalk -S +文件名的形式提取字符串的相关信息。
使用binwalk -A +文件名,提取固件的指令集架构信息。
可以得到如下重要信息:
操作系统版本号:VxWorks WIND kernel version "2.5"
符号表地址:0x301E74
指令集架构:PowerPC big endian instructions
由于固件特性,在普通逆向步骤上需要多三个部分:
修复代码函数位置(修复函数代码的方法有多种)
确定固件代码段基址(通过特殊指令的查找)
重构符号表(根据加载基址计算重构符号表)
由于嵌入式系统的固件需要加载到内存中的特定位置进行运行,这个特定的位置叫做固件加载地址(base address)。
为了对VxWorks系统固件进行逆向分析,首先需要知道固件在内存中的加载地址。加载地址的偏差会影响到一些绝对地址的引用,例如跳转函数表、字符串表的引用等。
(a)方法1:
使用固件加载基址提取脚本程序,得到固件加载基址,得到结果如此下图:
(b)方法2:
某些设备的固件会使用某种格式进行封装,比较常见的是使用ELF(executable and linkable format)格式进行封装。采用ELF格式封装后的文件头部有特定的数据位记录了该固件的加载地址,因此针对该情况我们可以直接使用greadelf等工具直接读取ELF文件头,从而直接获取到固件的加载地址,此外IDA也能直接识别ELF格式封装的VxWorks固件,无需额外处理即可进行自动化分析。
除了上诉的我们自己编写的加载基址的方法外,如果我们获取的是相应的vxwork,我们可以使用ubuntu自带的工具readelf,通过readelf -a vxworks工具直接获取加载基址地址,或通过github下载使用greadelf,通过greadelf -a vxworks工具直接获取加载基址地址。直接通过固件压缩包和固件本身是难以使用readelf命令找到的,如下图所示。具体原因不太清楚。
通过对vxworks直接进行分析就能够找到加载基址地址。下面是我在网上下载的一个版本的vxworks,这里的加载基址地址和我们研究的这个固件所在的加载基址不相同,因为版本是不一样的。
(c)方法三:
通过IDA Pro逆向固件二进制文件。
确定基址的思路是寻找一条相对寻址方式的lis指令。在IDA中使用ALT+T直接搜lis指令,CTRL+T进行向下(上)搜索,发现在0x00000A3C处的lis指令。
观察地址后面的@ha确定基址为0x10000,这也是固件常用基址
针对@h和@ha的问题,在IBM官网看到一篇文章https://www.ibm.com/developerworks/cn/linux/hardware/ppc/assembly/index.html 。
http://blog.chinaunix.net/uid-20663797-id-35772.html
(d)方法四:
通过IDA Pro逆向固件二进制文件。
或者可观察到:下图是默认加载后的IDA界面,仅仅分析出了极少数的函数。接下来就需要根据固件头部的这段代码来寻找加载地址的特征。
在固件头部有如下图所示的一段代码,在对r1和r3寄存器进行赋值后进行了跳转。
下图是PowerPC的寄存器用途说明,从图中可以看到R1寄存器是栈指针,而R3寄存器则是第一个参数。
回到我们之前看的固件头部代码处,这段代码相当于是先将栈地址设置为0x10000,将第一个参数(r3寄存器)设置为0x0,随后在栈上开辟0x10个字节的空间后跳转到当前地址+0x1cd94处执行。
根据VxWorks PowerPC内存布局图知初始化栈的地址同时也是固件的内存加载地址,因此r1寄存器指向的0x10000就是寻找的固件加载地址。
在分析出固件加载地址后就可以使用新的加载地址重新加载固件进行分析了。
(4)固件函数名修复
虽然IDA此时能够正确的识别函数及其调用关系,但依然无法自动识别出函数名,这对固件的分析工作造成了很大的阻碍。
此时可以查看固件在编译时是否编入了符号表,如固件编入了符号表那么就可以利用符号表中的内容来修复IDA中所显示的函数名。
通过使用binwalk可以帮助我们辅助分析VxWorks固件中是否编入了符号表,并识别出符号表在固件中的位置。如下图所示binwalk识别出的符号表地址在文件偏移0x301E74处。
VxWorks 5系列的符号表有他独特的格式,以16个字节为一组数据,前4个字节是0x00,之后是符号名字符串所在的内存地址,后4个字节是符号所在的内存地址,最后4个字节是符号的类型。
例如0x0027655C是函数名所在的内存地址,0x001FF058是函数的内存位置,0x0000500为函数名。
符号表开始位置:
符号表结束位置:
基于符号表的特征,能获取到固件中符号表的起始及结束位置。固件的符号表起始地址为0x301e64+0x10000,结束地址为0x3293a4+0x10000。
使用IDA的api来修复函数名,使用加载地址0x00重新加载固件后使用如下图所示的Python脚本即可进行修复。
函数修复脚本程序:
#-*- coding:utf-8 -*- from idaapi import * import time
symbol_interval = 16 #符号表间隔 load_address = 0x10000 #固件内存加载基址 symbol_table_start = 0x301e64 + load_address #符号表起始地址 symbol_table_end = 0x3293a4 + load_address #符号表结束地址 ea = symbol_table_start eaEnd = symbol_table_end
while ea < eaEnd: offset = 0 #4个字节为一组数据 #将函数名指针位置的数据转换为字符串 MakeStr(Dword(ea - offset), BADADDR) #将函数名赋值给变量sName sName = GetString(Dword(ea - offset), -1, ASCSTR_C) print sName if sName: #开始修复函数名 eaFunc = Dword(ea - offset +4) MakeName(eaFunc, sName) MakeCode(eaFunc) MakeFunction(eaFunc, BADADDR) ea += symbol_interval print "ok" |
修复后的结果:
(5)固件漏洞函数分析
一个固件分析的入手点就是查看例如loginUserAdd等关键函数的调用关系。loginUserAdd函数的用途是在登录表中添加一个用户,这个账号可以用于登录例如telnet及ftp等服务。
通过分析loginUserAdd函数的调用,可以看到在usrAppInit等函数中均调用了loginUserAdd函数。
查看usrAppInit函数,发现多个后门账户。