总有一天,每个人都需要对HP固件更新进行反向工程。在过去的几个月里,对于我们JSOF中的一些人来说,这一时刻已经到来。这是一个更大的安全研究项目的一部分,该项目将在接下来的几个月发布。我们需要能够对HP固件进行反向工程,并选择通过查看更新文件来执行此操作。我们编写的工具和文档可以将我们从.rfu格式的打印机更新文件一直带到Ghidra项目中正确映射的固件。
这篇文章是记录固件更新的不同结构和阶段的四部分博客系列的第一篇。该系列的下一部分将在我们撰写时逐周上传。
不同的研究人员和公司在不同的时间在许多地方对固件文件格式进行了部分记录,包括由Check Point Research进行的全面分析和外层编码的基本官方文档。在我们的研究中,我们遇到了缺乏最新和正确的信息。我们也没有找到任何工具来解包并将更新包的内容加载到内存映射中进行反向工程。我们最终编写了内部工具,并记录了基本的结构和编码。
当通读本文档时,很明显,高度分层的固件编码/打包是相当复杂和随机的。我们不确定这是某种形式的模糊安全,还是像古代废墟一样堆积如山的遗留实现。人们可以从石头的颜色和形状中看到,随着时间的推移,主导印刷商企业帝国的对立政治派别。
我们编写了两个主要工具来对打印机更新进行反向工程。第一个工具将固件更新包解包到我们拥有闪存映像的阶段。第二个获取闪存图像,并将其正确加载到Ghidra中。
第2部分:S记录解析S记录S记录格式及其对应的二进制记录,并分阶段更新。
第三部分:通过滑动窗口实现从NAND到RAM的固件结构,并将代码和数据加载到内存中。
第四部分:我们的HP固件更新解包工具和流程展示,一些研究过程和结束语
免责声明:所有这些信息对于我们使用的打印机和我们使用的固件版本都是正确的。我们使用HP OfficeJet Pro 8720,固件更新文件为:ojpro_8720_1919B_05102019.rfu。
HP固件以远程固件更新(RFU)格式打包。HP FTP服务器上提供了更新文件:ftp://ftp.hp.com/pub/networking/software/pfirmware/.。
如果有疑问,请使用Wireshark嗅探打印机的更新过程,以找到正确的下载链接和固件版本。
解包并解码更新包(扩展名为.rfu)以生成闪存映像。
每次启动打印机时,闪存映像都会加载到内存中。这一阶段是必不可少的,因为它使我们能够将不同的部分加载到内存中进行逆向工程。它还允许我们压缩磁盘上的部分数据和代码。
打印机作业语言(PJL)格式,它是描述打印作业的文档格式,是打印机命令语言(PCL)格式的扩展。
一种专有编码方案,由二进制到文本编码的二进制版本组成,类似于摩托罗拉的SREC格式。在解码此阶段之后,我们就有了写入NAND闪存的原始数据(如本例所示)。
一种专有固件描述格式,由带有节描述、结构和元数据的节表组成。
包含固件数据和代码的部分。许多部分使用几种支持的压缩方案中的一种进行压缩。
HP的RFU文件格式包含打印机作业语言(Printer Job Language,PJL)命令,这些命令像典型的打印作业一样部署到打印机(您只需打印它!)。HP开发了打印机作业语言(PJL),以允许在作业级别切换打印机语言(也称为个性)。支持PJL的应用程序可以使用PCL打印一个作业,并使用某种其他打印机语言(例如,PostScript)打印另一个作业。
%-12345X@PJL@PJL COMMENT MODEL=HP OfficeJet Pro8720@PJL COMMENT VERSION=WMP1CN1919BR@PJL COMMENT DATECODE=20190510@PJL升级大小=39640723-12345X@PJL COMMENT(NULL)@PJL ENTER Language=FWUPDATE E此设备不支持FWUPDATE!
首先要注意的是文件开头的特定序列:<;Esc&>%-12345X;(其中,*<;Esc>;表示ASCII中的转义代码,十六进制:1B,以下简称为:<;Esc>;)。序列称为通用退出语言(UEL)命令,它使打印机退出活动打印机语言,并将控制返回给PJL层(默认控制层)。此命令也会出现在文件的末尾。
从该页眉中,我们可以了解该固件所针对的打印机型号,以及固件版本和代码构建/发布日期。HP使用未记录的pjl命令升级及其选项值SIZE来指定RFU文件的大小(以字节为单位)。
ENTER命令用于选择用于打印后续数据的特定打印机语言。通常是PCL或PostScript,但在我们的示例中选择的语言是FWUPDATE。毫不奇怪,该语言用于固件更新过程,并且没有文档记录。理解该语言对于提取固件映像至关重要。在ENTER命令之后可以看到这是非标准打印机语言的另一个指示-有一个打印机重置命令(<;Esc>;E),后跟一条消息:此设备不支持FWUPDATE!。这应该由不支持此固件更新交付方法的打印机打印。
在检查RFU二进制文件时,可以看到横跨整个文件的<;esc>;*b模式。通过在线搜索一些信息,我们进入了PCL技术参考手册,更具体地说是关于光栅图形的章节(PCL5颜色技术参考手册的第6章)。
光栅图像是由点组成的图像(也称为位图图像)。每个点由一个位表示(0-不打印,1-打印点)。打印机能够使用光栅命令打印光栅图像,光栅命令是PCL的一部分。图像以点行的形式传送到打印机,每行代表光栅图像的一个条带。
下面是一个简单光栅图像的示例(为了可见性,0被替换为一个点):
打印机要打印光栅图像,应遵循特定的命令序列。这些命令定义光栅区域(高度和宽度)以及分辨率和可能的颜色信息。这是一个相对复杂的格式,并且有相当多的文档记录(请参阅进一步的阅读部分),因此我们不会详细介绍它的每个部分,只介绍用于解包固件的相关部分。
在较高级别上,对我们来说重要的命令序列如下所示,其中每一行都是一个命令:
源栅格高度源栅格宽度起始栅格图形Y偏移栅格压缩传输栅格数据.传输栅格数据Y偏移传输栅格数据.光栅压缩传输光栅数据.结束光栅图形。
光栅高度和光栅宽度命令定义图像的光栅区域。但是固件映像的高度和宽度是多少呢?乍一看,使用这些命令似乎很奇怪。在我们的示例中,高度未指定(隐含0),这意味着它将被忽略。宽度设置为16384,它定义光栅图像中行的长度。作为一种节省空间的技术,打印机会自动使用零字节填充任何部分行(即长度小于指定宽度的行),直到全长。
Y-Offset命令跳过整行,因此不需要发送一批全为零的行。此命令与固件解包无关。
传输光栅数据命令指定后跟使用光栅压缩命令指定的压缩方法压缩的二进制BLOB的长度。使用了几种压缩方法,下面将介绍其中一些方法。
现在我们已经对RFU文件结构有了基本的了解,我们需要剥离这些光栅图形和PCL层。对于此任务,我们需要了解PCL的基本命令语法,特别是光栅图形命令。
PCL命令是以ASCII格式指定的转义序列,至少由两个字符组成。第一个字符是转义字符<;Esc>;。无论接下来发生什么,都会被解释为打印机命令。
集团字符、参数字符、值字段和数据被认为是可选的。我们已经看到没有这些的PCL命令:打印机重置命令<;esc>;E,它紧跟在enter pjl命令之后。实际上,惠普在他们的PCL技术参考中忘记了在Z1之前的第一个#0字符;希望他们在读完这篇帖子后能解决这个问题。
这个命令就像它得到的一样糟糕(即复杂),所以让我们一起来解析它。其余的命令应该相对容易理解。
星号(‘*’)字符称为参数化字符。它是RFU文件中除打印机重置命令和UEL之外的所有PCL命令使用的命令。这个字的确切意思与我们的目的无关,只与它的句法作用有关。
最重要的角色是群体角色。它的意思也与我们的目的无关。
字符是指定光栅高度的参数字符。此字符的值字段应出现在该字符之前,但缺少该字段。因此,值0是隐含的。
在第一个字符之后,我们看到一个数值16384°(以十进制表示),后跟参数字符s。‘s’字符指定栅格宽度。
最后,我们看到一个大写字母A,它既表示此转义序列的结束,也表示“Start Raster Graphics”命令的结束。
总之,此组合转义序列指定光栅高度(0)、光栅宽度(16384)和开始光栅图形的标记。可以将其视为按顺序编写这些命令的快捷方式(请注意大写的T和S,它们现在充当终止字符):
为节省空间,通常会压缩命令TRANSPORT RASTER DATA的二进制有效负载。要发出正在使用哪种压缩的信号,可以使用Set Compression Method命令。命令语法为:
到目前为止,我们已经知道如何解析该命令。HP支持多种压缩方法,每种方法都由唯一的值编号标识:
HP PCL技术参考手册中记录了压缩方法(请参阅进一步阅读部分)。
在我们的RFU示例中,使用的压缩方法是Unencode(0)和TIFF(2)。前者微不足道,所以我们只描述后者。
标记图像文件格式编码是游程编码(RLE)和完全无编码的组合。每个模式字节序列前面都有一个控制字节。此控制字节标识我们是否使用RLE编码或根本不使用编码。
值在0到127之间的非负控制字节c表示后面的c+1个字节应该按字面解释。
负控制字节-c(-127到-1)表示后面的字节应该复制c+1次。
有两种方法可以将栅格数据按行或按平面传送到打印机。按行传输栅格数据的常规语法为A<;ESC>;*b#W,通过飞机传输的常规语法为A<;ESC>;*b#V。
值字段标识传输中的字节数(以压缩形式),可以是0到32767范围内的任何数字。
最初,这两个命令用于传递HP打印机的像素信息。这两种方法中使用较早的一种,用于提供单色栅格数据。该方法用于根据选择的调色板通过平面发送彩色像素。例如,如果我们使用RGB,那么第一个、第二个和第三个平面分别对应于红色、绿色和蓝色。
在我们的示例中,这些命令用于指定后面的字节数(以压缩形式)。需要注意的一点是,如果解压缩后的字节数小于光栅宽度,则命令按平面传输光栅数据(‘V’)为零填充,而按行传输光栅数据(‘W’)不为零填充。这一行为是FWUPDATE语言独有的,它是我们解包程序中细微错误的来源。
这是大量的信息,所以让我们用一个简单的例子来结束这篇文章。我们将使用FWUPDATE语言对字符串JSOFrulez!111111111进行编码:
<;Esc>;*rt32sA将高度设置为0,宽度设置为32,并发出光栅图形开始的信号。
<;Esc>;*b2m14V指定TIFF作为压缩方法,并指定应解压缩以下14个字节。由于解压缩后的数据长度为24(我们的字符串长度),因此应该由8个空字节进行零填充。
根据格式,<;esc>;*bw命令是必需的(但在我们的情况下不写任何内容),因为每个命令序列后面都必须跟有一个ww命令。
PCL-Dumper Github Repo,一个用于转储PCL打印机数据流的Java命令行工具和库。
Faxploit:将传真发送回黑暗时代,这是Check Point针对传真设备进行的一项研究。
我们要感谢Checkpoint的研究人员,感谢他们在这个主题上先前发表的有益的研究。
最后,我们要感谢电子前沿基金会(EFF)的时间、耐心和指导。