介绍
设备固件的安全性分析是IoT安全审计中非常重要的部分。获取固件是分析中的众多挑战之一,你可以通过多种技术来做到这一点。拥有固件后,就可以对其进行更仔细的分析。获取设备固件的最简单方法是从供应商更新服务器(通常是FTP服务器)下载,服务器中存储了不同版本的固件,而获取下一个版本的技术已编码在固件中。为了防止这种情况,供应商已开始以加密形式在服务器上存储固件,因此,即使你获取了固件,在进行进一步分析之前仍需要对其进行解密。
最近,我在ZDI博客上看到了一篇非常有趣的文章,作者分享了不同的技术来解密固件,我强烈建议你阅读该文章。在这篇文章中,我将引导你详细分析DLINK路由器DIR-822 US变体的固件解密,我们将使用ZDI博客中介绍的技术,研究设备如何处理解密的细节以及逆向解密算法。
固件比对技术
在本节中,我会总结ZDI文章中的内容,如果你已阅读,则可以跳过本节。
让我们考虑一种情况,你具有已加密的固件版本,并且用于解密更新固件的算法位于设备固件中。当你上传固件时,它会解密并开始正常的更新过程。但是,可以说该解密例程不是固件初始出厂版本的一部分,并且在以后的某个时间点,供应商考虑引入这种安全机制,因此他们将新的加密/解密API编码到新的固件版本中,此版本可以称为过渡版本。由于过渡版本未加密,因此我们可以对解密算法进行逆向并手动解密固件。这正是我们在这篇文章中要做的。
二进制比对是一种技术,你可以采用同一软件的不同版本的两个二进制文件并使用比对工具来了解程序新版本中引入的新功能。我们将使用kdiff3在过渡版本和先前的版本之间进行文件系统比对,因为在过渡版本中具有加密/解密例程,而先前的版本则没有。
产品详情
在深入研究之前,让我们看下设备的一些细节。
产品ID DIR-822-US CPU架构 MIPS R3000 描述 具有高增益天线的无线AC1200双频路由器 支持链接 下载 发布历史
简要了解路由器固件的发布历史。下表显示了发布日期以及FTP服务器上的版本ID和文件名。
Release Date Version Code File name 08/02/2020 3.15B02 DIR-822_REVC_FIRMWARE_v3.11B01_ICJG_WW_BETA.zip 03/12/2019 3.15B02 DIR-822_REVC_FIRMWARE_v3.15B02.zip 11/07/2019 3.13B01 DIR-822_REVC_FIRMWARE_v3.13B01.zip 10/05/2019 3.12B04 DIR-822_REVC_FIRMWARE_v3.12B04.zip 22/04/2019 3.11B01 DIR-822_REVC_FIRMWARE_v3.11B01.zip 17/09/2018 FW303WWb04_i4sa_middle DIR822C1_FW303WWb04_i4sa_middle.zip 17/09/2018 3.10B06 DIR-822_REVC_FIRMWARE_v3.10B06.zip 14/09/2017 3.01B02 DIR-822-US_REVC_FIRMWARE_v3.01B02.zip 08/06/2018 3.02B05 DIR-822_REVC_FIRMWARE_v3.02B05.zip
于2018年9月17日发布的固件FW303WWb04_i4sa_middle似乎有些奇怪。进行文件解压缩并对每个固件二进制文件执行binwalk,然后观察每个文件的结果,可以发现,对于FW303WWb04_i4sa_middle以及之前版本的所有固件,binwalk至少都匹配一个lzma压缩和squashfs文件系统。版本FW303WWb04_i4sa_middle之后的任何固件都没有签名匹配。在DIR822C1_FW303WWb04_i4sa_middle.zip文件中找到了如下所示的发行说明pdf,其内容证实了我的猜测。
此固件更新是一个过渡版本,其中引入了加密/解密功能,因为你可以看到同一日期有两个固件更新,并且在发布说明中还提到固件v3.10必须从固件v303WWb04_middle过渡版本升级。看来我们有很好的线索。
可视化方法
如果在某些情况下,发行说明中未提及固件保护,则可以使用熵计算方法来确定固件是否已加密。简而言之,熵是对随机性的一种度量,它的值在0到1之间,值越高表示随机性越好。接近1的值被认为是高熵,反之亦然。压缩或加密的数据具有较高的熵值。
二进制图像的熵分布将为我们提供二进制文件增量偏移的熵值。此信息将有助于猜测二进制文件的哪部分被加密/压缩以及哪部分是代码。让我们看一下两个不同的二进制版本的熵分布:过渡加密的FW303WWb04_i4sa_middle和加密版本3.15B02。让我们看看它们之间有何不同。
如果你注意到图中的差异,则上图的熵几乎恒定在0.9以上,这意味着很有可能固件的不同部分对其进行了加密。在下图中,初始部分的熵很低,然后一直很高,然后又下降,然后再次上升,这种波动表明它混合了代码和加密/压缩数据。
对于未加密的固件,你经常会看到这种模式,该固件最初具有波动的熵,而在随后的部分中具有较高的熵数据。这可能意味着二进制文件的初始部分中有一些代码,其会在设备启动期间动态解压缩代码(从后面的部分开始)。
注意:加密和压缩在这里都被认为是造成高熵的原因,因为没有确切的方法可以根据熵值来判断其中哪一个是造成随机性的原因。
接下来,我们应该尝试找出新版本中进行了哪些更改,并尝试逆向该算法。
文件系统分析
现在我们知道了过渡版本,让我们通过在版本3.02B05和FW303WWb04_i4sa_middle之间进行文件系统比对来找出新版本中引入了哪些变化。我们将为此使用kdiff3。我们应该在文件名中查找包含诸如“固件”、“更新”、“升级”、“下载”或这些关键字的组合之类的更新。
加密二进制的动态分析
在进行文件比对搜索期间,你能找到许多与固件更新功能相关的有意思的文件,但问题的关键是在/etc/templates/hnap/StartFirmwareDownload.php文件中的第111行,你会看到以下代码片段。
// fw encimg setattr("/runtime/tmpdevdata/image_sign" ,"get","cat /etc/config/image_sign"); $image_sign = query("/runtime/tmpdevdata/image_sign"); fwrite("a", $ShellPath, "encimg -d -i ".$fw_path." -s ".$image_sign." > /dev/console \n"); del("/runtime/tmpdevdata");
从上面的代码中,我们可以推断出通过命令encimg -d -i -s 执行了encimg二进制文件。接下来,搜索encimg二进制文件在文件系统中的位置,它位于路径/usr/sbin/encimg中,其他未知变量是fw_path,我猜测它是固件路径,另一个变量是image_sign,我们可以追溯该变量的值是从/etc/config/image_sign中读取的,还有一个-d参数尚不清楚。
让我们进行动态分析以进一步理解该命令。运行简单的file命令表明它是一个ELF 32位MIPS MSB可执行文件。现在,我们可以使用MIPS体系结构的qemu用户空间模拟器来运行此二进制文件。转到固件的squashfs-root目录,运行以下命令:
$ qemu-mips -L ./usr/sbin/encimg Usage: encimg {OPTIONS} -h : show this message. -v : Verbose mode. -i {input image file} : input image file. -o {output image file} : output image file. -e : encode file. -d : decode file. -s : signature.
注意:在上面的命令中,我们为qemu-mips提供-L参数,该参数指定用于加载依赖项的根目录的路径。经常使用的另一种方法是Linux chroot 。
从上面的帮助消息中可以看出这是用于解密固件的二进制文件,-s参数称为签名,但是我认为它用于将从文件/etc/config/image_sign读取的解密密钥作为参数。该文件包含字符串_wrgac43s_dlink.2015dir822c1,我觉得它是解密密钥。另一个参数-i是输入文件,它将是新接收到的加密固件文件。现在,让我们尝试使用qemu模拟器解密固件。
qemu-mips -L . ./usr/sbin/encimg -d -i -s wrgac43s_dlink.2015_dir822c1
Qemu用户空间模拟器还具有另一个有意思且非常重要的功能,它可以记录二进制文件执行的系统调用。可以通过向qemu提供-strace参数来启用它,它将打印二进制文件执行的所有系统调用,我在上述二进制文件上进行了尝试,并提取了一些有趣的日志,如下所示。
# Aahh.... a crypto library been loaded by the binary, it# hints that it is using some sort of encryption algorithm possiblity AES# or some symentic encryption algorithm as we have seen there already# a password we provide to the binary.open("/lib/libcrypto.so.0.9.8", O_RDONLY) = 3... ... ...# opening the file (return fd 3)open("/home/payatu/test_1.bin", O_RDWR) = 3stat("/home/payatu/test_1.bin", 0x7fffea90) = 0write(1,0x7f689298,99)The file length of /project/dlink/AC1200/test_1.bin is 6865072 = 99read(3,0x7fffe***,4) = 4# mmap fd 3mmap(NULL,6865072,PROT_WRITE,MAP_SHARED,3,0) = 0x7ef51000 munmap(0x7ef51000,6865072) = 0# again truncate operation on fd 3ftruncate(3,6865044,0,0,0,0) = 0# good bye fd 3close(3) = 0 逆向加密方法
你可以在喜欢的反汇编/反编译工具中加载encimg二进制文件以进行更深入的分析。我发现了main函数(0×401244),该方法解析参数,然后调用具有加密/解密所有核心功能的build函数(0x400d24)。此外,二进制还导入了加密相关函数,其中一些函数是_AES_set_encrypt_key、_AES_set_decrypt_key和_AES_cbc_encrypt,这说明二进制文件使用AES加密算法,意味着用于加密和解密文件的密钥相同。做一些基础研究,你可以了解函数原型,这是这些函数的简短说明。
// Same prototype for AES_set_encrypt_keyAES_set_decrypt_key ( // user input key const unsigned char *userKey, // size of key const int bits, // encryption key struct which will be used by // encryption function AES_KEY *key)AES_cbc_encrypt ( // input buffer const unsigned char *in, // output buffer unsigned char *out, // buffer length size_t length, // key struct return by previous function const AES_KEY *key, // initializatin vector unsigned char *ivec, // is encryption or decryption const int enc)
让我们看一下build函数的反编译代码。
从上面的代码,我们可以看到二进制文件是使用mmap函数映射到内存的。然后基于参数调用AES_set_encrypt_key或者AES_set_decrypt_key来设置AES_KEY数据结构,并使用该结构通过AES_cbc_encrypt加密/解密有效载荷,最后使用munmap函数调用将数据写回到文件中。没什么特别的!
结果
一旦我们解密了固件,再来看一下解密后的固件的熵。
它看起来与我们之前看到的未加密固件非常相似。
列举攻击面
1、由于我们将二进制文件上传到操作系统服务(固件更新服务)以处理该文件,我们可以找到用于解密算法的文件解析错误,并通过某种内存损坏问题来破坏服务进程,从而使我们可以访问系统。
2、我们可以使用诸如firmware mod-kit之类的固件补丁工具来更改固件文件并重新打包,并使用相同的加密二进制文件对其进行加密并上传文件以进行更新。如果没有完整性检查,则补丁固件将更新而不会出现任何问题。尽管使用了加密,但是恶意固件更新仍然是一个问题,固件签名机制用于防御这种攻击。
注意:由于项目时间和范围的限制,我尚未测试上述攻击,但如果我是攻击者,我会尝试这些方法。
Firmware Auditor
使用不同的工具进行分析可能既耗时又容易出错,这反映了我们的日常工作流程,我们开发了一款工具来自动执行繁琐的固件分析工作,该产品称为Firmware Auditor,任何人都可以免费使用社区版。如你所见,我使用进行了上面的大多数分析。你可以在此 上找到有关该产品的更多详细功能,并通过相同的方式将反馈发送给我们。Firmware Auditor可用于:
1、熵图
2、探索Linux文件系统并下载所有内容(在本文案例中为enimg二进制文件、PHP文件)
3、build 函数的反编译代码
结论
我们掌握了不同的方法来确定固件是否已加密,如何使用固件比对方法来查找固件中使用的解密方法,以及如何使用它并复制该方法到另一个固件。我们还讨论了一些可能的攻击面。使用这种保护方案时,需要意识到这一点。