VxWorks以其良好的可靠性和卓越的实时性被广泛地应用在通信、军事、航空、航天等高精尖技术及实时性要求极高的领域中,如卫星通讯、军事演习、弹道制导、飞机导航等。
美国的F-16、F/A-18战斗机、B-2隐形轰炸机和爱国者导弹,火星探测器如1997年7月登陆的火星探测器,2008年5月登陆的凤凰号、2012年8月登陆的好奇号都使用到了VxWorks。
遇到好几次Vxworks固件了,真是每次被拷打每次都有新的一点感悟,从不知所措到稍微知道怎么做了。符号表缺失其实可以根据字符串的提示、静态编译的库函数的辨别去硬逆个大概,甚至有些残存符号表可以仔细找找恢复上去。
但如果是遇到某些路由函数,就可能没有办法——没办法去看路由到的文件,有时候就不能很好地分析整个数据流。
因为,binwalk一把梭vxworks固件出来的画风往往是这样的,命名非常的混乱,但如果实际打开看看,内部内容其实是正常的。
仔细一想,vxworks系统启动的时候,肯定是有什么参照去恢复文件名和位置的,否则怎么做路由呢?所以就开始了接下来的vxworks文件名恢复探究,以及,一键自动化。
寻找偏移表和文件命名
既然要恢复文件名,反过来就可以grep一下文件名去找这个表。
以上图第二个例子为例:
/userRpm/WzdAccessCtrlTargetAddRpm.htm
grep -r "WzdAccessCtrlTargetAddRpm.htm"可以得到这几条结果:
可以看到有两个别的文件(实际上也是htm文件)定义了表单,提交数据到WzdAccessCtrlTargetAddRpm.htm。
只可惜我们连这两个htm叫什么名称都不知道,这就很难去分析数据流了。
说回正题,有两个文件引用了这个htm,分别是0x3FC0和0x53D60处分割出来的文件,以各自在固件中的偏移来命名(3FC0、53D60)。
实际上这两个文件是有来由的,3FC0其实就是uImage镜像。
补充说明一个事情:3FC0文件不是完整的uImage,被binwalk命名为3FC0.7z的文件实际上才是完整的uImage镜像文件。
而0x53D60的大小远远大于其它LZMA压缩区域,实际上,这正是vxworks的主程序bin文件,也是我们平常分析的重点文件。
所以比较合理的解释是,53D60有这个字符串的原因是在web服务时进行路由。
而3FC0(uImage)作为vxworks的启动镜像,其拥有这个字符串的唯一原因,是在恢复文件名和结构什么的,所以,如果有偏移表,那就在uImage内部。把3FC0.7z放入010editor看看:
首先,是最初的引导部分,这里放的都是程序代码:
搜索".htm"或者".png"之类的字符串,就能找到一个很整齐的表。
可以看到文件名的前面,好像有类似于偏移的记载,不过到目前还是猜测。
如果能找到一套正确的计算方法,能完全对上binwalk里面跑出来的镜像偏移的话,那就说明这确实可以用来恢复文件名。
至此,我们找到了最关键的表项,而具体的细节还需探究一下。
确认文件名与偏移量的对应关系
偏移表结构
一个容易踏入的误区是,刚开始以为文件名的前面是其对应大小和偏移,为此计算了十几个,发现不对劲,为什么每种文件总是有1-2个的偏移是别的文件类型,而其它都是对应类型的呢?
实际上,文件名的一串00 00 00 00后面,才是它的大小和偏移量:
确定文件系统偏移
一些固件,比如这个案例,会记载文件系统的偏移(没记载也可以有兼容性很好的方案,下一节细说)
实际上,表里的偏移量+文件系统的偏移,将会对应到binwalk解包的文件名,即,在固件中的偏移值,这就是vxworks文件名恢复的方法。
动手实验:偏移计算
在反复的测试当中,我发现vxworks的文件系统有很多都是文件系统偏移0xF080+xxx的组合。比如,以png来作为测试案例:首先按MIME type排序一下,因为偏移表本身也是按照类型来集中排布的,这样可以方便实验。
然后,以png为例进行偏移量的计算测试。可以在010editor等工具里面找到如下.png字样。
将其偏移逐一取出,并加上刚刚提到的0xF080,我们可以得到一些偏移量:
如图所示,所有的文件名都能找到对应偏移,那么,说明这种办法是切实可行的:比如binwalk解析出来的1FDA8,我们就可以知道它其实叫做loginbg.png,等等
(使用的excel语句是=DEC2HEX(HEX2DEC(B2) + HEX2DEC("F080")),可以帮助加快这个测试过程,不用每次都按计算器)。
紧接着来测试下jpg、gif,找到偏移表对应表项:
实验结果如下:
全对,看来该方法是正确的。
思考一键自动化VxWorks文件名恢复过程
接下来就是想自动化这个繁杂的过程,就是想能够像binwalk一样一键提取并恢复,而且为了以后反复使用,需要比较可靠的设计。
接下来介绍一些比较奇怪的设计的地方,力求更加兼容。
基本设计
首先,binwalk的各种文件标签识别还是很厉害的,那就必须使用binwalk的结果了。同时我们需要binwalk -Me一下,以提取所有文件。
路径最好和默认的不一样,避免用户在先前的vxworks探索中改动了文件名什么的,导致部分文件找不到对应名称。
在binwalk结果里,IMG0 (VxWorks) header前后,将会是很多信息的集中点,其前面是uImage偏移,后面是文件系统,所以会有专门的字符串匹配。
如何自动提取文件系统偏移
然后,我们需要读取IMG0 (VxWorks) header后面的那个文件系统偏移地址:
经过实验有些案例里面并非IMG0 (VxWorks) header正下方的偏移,而是下下方的偏移。
所以,为了提高脚本的兼容性,设计成了尝试IMG0 (VxWorks) header后三行的偏移量,应该是没有太大兼容问题的。
如何自动提取偏移表在uImage镜像位置
再之后,需要从IMG0 (VxWorks) header前面的那个区域,比如这里的0x3FC0,里面存放有我们需要的表。
如何全自动确定这个表的位置,以让我们不需要手动打开HxD和010editor去手动给程序这个参数?
但是问题就来了,如刚才所述,最初的引导部分,这里放的都是程序代码:
好消息是,在连续的启动引导代码以后,会有大片的00区域,思路是,其实可以进行检测,检测到多少个连续的00,就进入到搜索头部模式;如果检测到又有字符了,那基本上就是我们的IMG0头部了(对吗?哦布莱克斯)
对,也不对,有些固件是有多段引导代码的,具体表现例子为:
程序片段1+"00 00 00 00"*n+程序片段2+"00 00 00 00"*n+程序片段3+"00 00 00 00"*n+偏移表。
那比较简单地一次匹配多次出现的00字节,自然会不对,这样会让程序匹配到程序片段2。
比如水星系列mw313r,其该表项藏在3个程序片段之后,0x8b1f0处,这也让我发现需要去兼容这个问题。
最后,采取的方案是在使用extract_file_info()函数提取信息时,检测名称的合法性,如果发现提取不了文件名(连续失败到一定的阈值,说明不正常了),那就在提取文件名失败的地方重新开始搜索偏移表。
怎么正确识别文件名
正确识别文件名,即一个文件偏移记录块的开端,那么同时就可以解决偏移的提取问题。
仅限这些字符命名的文件:大小写字母、数字、下划线、短横线、斜杠,而且,必须有一个"."符号,因为vxworks目前遇到的,所有的文件都是有后缀的,即便是二进制文件。
而且,会按照4字节对齐的方式去取文件名。总之这套正则匹配使得精确程度提高了,比如,下面的owowowow...字符串,等等,就不会被匹配进去。
而万一有些文件里面如果有满足这种规则的坏字符串的话,还有最后两个办法:长度限制(满足这些规则以外,文件名长度还必须>=5)。
到最后的文件重命名环节,其需要确实对上了某个文件偏移才会被引用,否则会被丢弃。
还有就是,怎么知道文件名提取到了末尾呢?
其实就是,每次提取下一个文件名时,如果检测到连续0x100个非00字符,那就说明提取已经到尽头了,因为接下来就是下一个程序片段部分。
正确恢复文件名
正确恢复文件名实际上还需要考虑到一件事:一部分固件的文件名如下,是没有带路径的。
而另一些还是有的:
所以需要分情况处理:
一键自动化vxworks文件提取和文件名恢复代码实现
最后,附上开源代码https://github.com/0xba1100n/vxfile_extractor
效果图:
Vxfile ExtractorV2改进
受winmt师傅在评论区的建议启发,遂改进了相关的方法。
为了提高vxworks固件提取脚本的泛用性,采用以下方案来避免对任何硬编码字符串(IMG0 header等)的依赖,因为依赖binwalk上的字符串是不够稳妥的——vxworks可不止一种固件结构,而且存在文件偏移表的文件,也不一定会出现一大片可以匹配的00区域(比如wr842nv3),得想点更稳的方法。
1.寻找含偏移表文件
想来想去,还是决定用这样的混合方式(组合了:精确但仅适用于有http服务固件+不太精确但泛用)来匹配,为了精确和泛用性的平衡吧。
对于有网页的固件:借助web静态资源文件引用来确定文件偏移表位置
首先,我们的漏挖对象几乎都是有http页面的,而且,目前的rtos的web服务由于性能受限都是静态页面,受到SaTC前后端字符串匹配的启发,那就其实可以从文件引用的角度去得到确实存在且有其特殊性的文件名字符串。
这意味着,我们可以剔除那些,不包含这些100%正确字符串的固件了,并且认为它们是不具备文件偏移表的固件。
步骤是,会先从htm文件内部的"src=xxx"的资源引用语句入手,匹配前端文件引用到的东西,我们可以首先在binwalk刚解包的,名称混乱的区域内执行:
grep -r "src=" | grep -E ".(gif|jpg|js|css)"
然后,我们就得到了一些带路径的被引用静态资源文件名:
这时候需要明确一件事:如果真的存在一个足够丰富包含所有偏移的表,那么,其会大量包含这些文件名,只要多次匹配每个被引文件名的结果有重合的文件的话,那就一定是有文件偏移表在里面了,比如以下案例的两个binwalk解包文件,它们包含了所有的被引文件,里面大概率是有表的。
那么,使用这种方法来确定表项位置,准确率就会达到真正意义的99.9%了。
而且这个文件对应偏移应遵循“够用选小”原则,不仅是因为,这样一个偏移表是初始化时使用的,其偏移基地址一定是偏小的,靠近boot镜像的。
为什么会想到这样一个策略呢,因为实测有部分情况(如tplink的wdr7600),如果src引用的文件有很多个都没有对应的表项,但使用grep后缀的方法,还是能匹配到一些二进制文件有部分字样的话,这种就另说了,似乎没有文件偏移表。
这种实际上我暂时找不到恢复文件名的方法,其仅仅有一些残缺不全的文件名记载,更何况是完整的文件偏移记载了,巧妇难为无米之炊,如果有办法请务必告诉我。
对于无网页、有网页但没有静态资源引用固件:MIME类型排除+文件内部所含文件名字符串统计+计算"紧凑规整程度"
如果希望分析无网页的vxworks固件,需要想出另一种比较广泛的方法。
笔者没见过完全无网页的vxworks固件。。。欢迎提出新的建议或者新的让本项目无法解开的案例请提issue,想见识多点种类的vxwork固件。
这种方法也尽量不像之前那样去依赖binwalk结果IMG0 header的相对位置了,目前采取的方法比起之前更具兼容性。
如果用户觉得前一种方式的恢复效果不佳,也可以使用--fuzzmode来指定这种方法,或许会有奇效。
而且这个文件对应偏移同样是应遵循“够用选小”原则,不仅是因为,这样一个偏移表是初始化时使用的,其偏移基地址一定是偏小的,靠近boot镜像的。
首先脚本会获取解压文件夹中的所有文件的名称:
然后,脚本会排除这些MIME类型,面对这些类型的文件,脚本将不进行任何操作:
之后,在多次模糊匹配 方式被证明失败或者不够好以后,我想到了一个办法。
首先,明确一件事,文件偏移表正常情况下,会存在于最多.jpg,.png等等后缀文件名的文件处,所以我使用这样指令去显示每个文件内部的,拥有特定后缀的文件数目
strings_command = f"strings -t x -n 5 {file_path} | grep -E '\.jpg|\.png|\.js|\.cer|\.pem|\.bin'"
然而这种方法过于的理想化,实际上binwalk出来的文件,有时候并不是最多这样后缀的文件就是偏移表了,其有的时候是重复引用到了很多次某个文件,从而远超偏移表所在文件。
那么,更好的办法是什么呢,其实这个方法并不是没有依据,因为大多数情况可行,只是少数情况不可行。
所以我不仅使用文件内部文件名匹配数目的统计,更重要的是,strings有每个字符的偏移显示。
如果每个字符的偏移量,时而相隔0x30,时而相隔0x38,时而相隔0x28...那么其实还是挺整齐的。
反观如果这些字符并不是存在于文件偏移表,那么其实就很有可能时而相隔0x11,时而相隔0x41,时而又相隔0x191,总之,会体现出文件偏移的波动性,混乱。
这种紧凑和混乱,又该怎么体现呢?
如果严格地检查每个字符串的对齐0x10和0x8情况,其实这是武断的,因为strings字符串匹配的时候,前面的文件偏移会引入字符,导致偏移并不遵守该规则。
我想到了一个想法,如果每个字符的偏移是规整的,也就是近乎等差的。
假设文件偏移表是1,2,3,4,5这样,那么完全可以使用下列的方式来滤除那些不够好的带大量文件名的文件。
1-2+3-4+5=3,1-2+3-4+5-6=-3,这是比较合乎文件偏移表形式的。
而有些文件是2-6+7-12=-9,取绝对值比较,这样去统计,那么,哪个文件内部的文件名存放位置更合乎偏移表形式,就很显然了,"震荡"的更厉害的就个就是了。
但是,还不够,首先1-2+3-4+5=3,1-2+3-4+5-6=-3这种会逐渐增大,所以我们还需要以2为周期,除以i/2
(1-2+3-4+5)/3=1,(1-2+3-4+5-6)/3=-1
这样一来,紧凑规整程度计算函数就会在+-1这个范围震荡了,不会一直增加,后来实验了一下这使得匹配精确程度提升了。
紧凑规整程度计算函数 计算方式实现如下:
等等,这个函数也不够。我就遇到一种情况。
有些函数的匹配文件数偏小,比如内部只有2个文件名,但是包含文件偏移表的文件则有100个文件名。
那么,2个文件名会有很大偶然性获得优势,为了防止这种偶然性导致匹配文件错误,所以我们也要考虑上前文文件名匹配命中数目的影响,怎么组合这两种方法呢,在这里我使用了加权平均,把匹配的文件数乘0x1000以让它们的量级差距接近,然后把紧凑程度作为被除数,因为这个值越小越好(我是不是应该把这个命名为离散程度?不管了哈哈)。
而且也会排除被string+grep规则所命中文件名数小于等于5的文件,这样就可以进一步免除偶然性了。
如果固件内部是5个及以内的文件数目,想必是个可以手动分析的超迷你固件了吧,那我实在没办法,目前见过的固件基本上都是100多个文件的,最少也是10个以上文件。
最后的最后,如果万一有方案是相同紧凑程度的,还有方案:
sorted_result = dict(sorted(result.items(), key=lambda item: (item[1][2], -len(item[0])), reverse=True))
这样的规则考虑了文件路径长度,(这意味着3EA0.7z比11451.7z有优势),以及在文件路径长度相等时,按字符排序(这意味着3EA0.7z比4191,7z有优势)。
这样一来,我们就可以匹配到一个最靠近固件的初始化uimage部分的文件偏移表的所在文件。
2.寻找文件里面的文件偏移表的偏移(好拗口啊_(:з」∠)_)
后面发现在文件里找文件偏移表,如果是反复匹配0x100个null字节再搜索下一个非空字节来进行寻找,这并不可靠,因为不一定会出现一大片可以匹配的00区域(比如wr842nv3):
如图所示,这种就是程序.text内容和文件偏移表无缝贴合的,这就使得v1的 文件偏移表 在文件内部的 偏移 的提取方法失效。
目前我们已经获取到了一个偏移表文件,那么接下来可以首先对偏移表所在文件,匹配带有以下文件后缀的字符串位置,使用strings不仅可以进行匹配还能很方便地进行偏移显示。
◆图片文件
◆.jpg
◆.png
◆网页和样式文件
◆.js
◆.css
◆.htm
◆配置文件
◆.xml
◆证书和密钥文件
◆.cer
◆.pem
◆二进制文件
◆.bin
然后还会限制长度要至少是5,匹配命令和显示的字符串如下:
返回首行,即表项最有可能的偏移所在
strings -t x -n 5 40114.7z | grep -E ".jpg|.png|.js|.css|.htm|.cer|.pem|.bin" | head -n 1
这样我们就得到了最有可能的文件偏移表 在文件中的 偏移,比如这里的0x100aaa。
为了避免遗漏,这个得到的偏移数值还将被-0x50,然后使用之前提及的方案进行文件名匹配检测,这样即使有文件不是以上文件后缀且作为开头,从而被规则遗漏掉的,也将会被匹配到。
3.寻找文件系统基础偏移量
然后,也改进了文件系统的偏移量确定方法,不再是依赖字符串的位置,而是会进行从头到尾的计算尝试。
你可能觉得这种方式过于简单,可能在某些情况,某个并不正确的偏移+文件偏移表偏移恰好对应了真实文件。
然而,实际上之前提到过,如果连续10个文件偏移量是错误的,那就会退出匹配,这样一来,即使某个正确,那也不会导致文件的重命名一直进行下去(除非那种极小概率的偶然性了)。
以上就是v2版本的主要改动。
看雪ID:是气球呀
https://bbs.kanxue.com/user-home-869206.htm
*本文为看雪论坛精华文章,由 是气球呀 原创,转载请注明来自看雪社区