SonicOS 固件解密 (2)
本文介绍一些 SonicWall SonicOS 固件解密的思路。
本 Blog 曾发布过一篇关于如何解密 SonicWall NSv 系统的文章,漫长的时间过去了,厂商也对该系统进行了数次升级迭代,现在我们来看看新版本中是否实现了更多安全措施。
本文提及的环境可以在网盘获取,相关工具可以在 Github 获取。
SonicOS 7.0
前篇文章分析的系统基于 SonicOS 6.0,SonicWall 于 2023 年发布了新的 GEN 7 系统,根据发布说明,该系统在多个方面进行了改进。我们以 7.0.1_5161 为例来看看新的系统是否修改了磁盘加密逻辑。
导入虚拟镜像后启动,等待系统完成安装过程,之后将磁盘挂载到 Linux 中分析,通过 lsblk 命令可以看到磁盘具有 7 个分区
1 | sdb |
其中 3、6、7、9 这 4 个分区依然是 LUKS 加密的,可以推测在 7.0 版本的系统中,还没有修改磁盘加密逻辑。
前篇文章是通过调试 luks 模块从内存中 dump 密钥实现磁盘解密,这种方法比较麻烦且容易出现错误,这次我们来分析一下具体的密钥生成算法是什么。
解密逻辑位于 luks.mod 中,关键函数 sub_0
。该模块应该属于 grub 项目的一部分,通过搜索找到源代码位于 luks.c 的 luks_recover_key
函数。对比源码发现,SonicWall 的开发者修改了 LUKS 相关逻辑,在 luks_recover_key
函数中会尝试从本地文件或者磁盘头部读取密钥信息,并自动对磁盘进行解密。
1 | __int64 __fastcall luks_recover_key(_QWORD *a1, __int64 a2, __int64 a3, __int64 *a4, __int64 a5) |
简要分析上面的代码,首先在 [1]
处,代码会调用 grub_disk_read
尝试从磁盘读取 592 字节的数据,在 [2]
处,调用 grub_puts
函数输出了 Loading...
字符串,在开机时可以看到这个字符串。
从 [3]
处开始,代码使用从磁盘读取的 592 字节数据进行一些复杂运算,最终会将计算结果保存在变量 v49 中。在 [5]
处会调用 grub_crypto_pbkdf2
解密磁盘,我们知道它的第二个参数应该是密钥,但在调用该函数之前都没有操作变量 a4 的逻辑。
继续向下分析代码发现在 [4]
处会将 a4 赋值为 v49,说明密钥确实和磁盘头部数据有关。那么将生成密钥的逻辑梳理出来形成程序,在本地就可以直接解密硬盘,无需复杂的调试过程。
具体实现请参考仓库代码。
SonicOS 7.1
目前 SonicOS 的最新版本为 7.1.2,和前面相同的思路挂载硬盘后发现分区信息有变化
1 | sdb |
前面 4 个似乎都是启动分区,sdb5 可以直接挂载,但其中只保存了系统当前正在运行的 .bin.sig
固件文件,且该文件是加密的,sdb6 的格式无法识别。
挂载 BOOT 分区,文件内容也和老版本不同,现在 GRUB 被打包成了 EFI/BOOT/bootx64.efi 文件,此外还有 SYSTEM.LIC 和 SYSTEM.SYS 两个文件,SYSTEM.LIC 是一个压缩包,解压可以得到 DATA:FW-crypt-release.key-877cebb9-f923-4245-9952-18a00ce5f77d
等数个类似密钥的文档,但格式都无法识别。
使用 binwalk 分析并解压 bootx64.efi 文件,得到一个叫做 soniccorex.bin 的 Linux 内核文件,它应该就是虚拟机 Linux 系统的内核。
解密磁盘这个行为有可能发生在内核或者 initramfs 中,按照以往的思路先定位到 populate_rootfs,该函数负责解压 initramfs 镜像。通过调试内核的方式在 populate_rootfs 下断点,从内存中可以提取 initramfs 压缩包。
解压后得到一个标准的 Linux 文件系统,其中包含一些关键文件,例如 onetime.key.enc
、sunup.cpio.gz.enc
等。分析 /init 这个初始化脚本,里面包含大量和解密相关的代码,这里列举部分关键代码,简要分析这个脚本的逻辑。
脚本主要执行以下几个操作
1 | early_setup |
从代码可以看到应该还存在一个叫做 “sunup” 的 initramfs,说明内核加载的 initramfs 中可能没有直接解密硬盘。
early_setup 函数执行一些初始化操作,setup_stage2 为关键函数,它会调用 unpack_stage2 函数。
1 | unpack_stage2() { |
这个函数包含几种不同的密钥加载方法,虚拟机环境中默认使用 SSSS 方法,因此代码又会调用 get_key_from_ssss 函数。
部分关键逻辑列举如下:
1 | efi_get_var() { |
简要总结解密 sunup 的逻辑
1 | 1. 从 /sys/firmware/efi/efivars 目录下读取 KEY:SUNUP 密钥 |
从逻辑上看系统应该是实现了一套 SecureBoot 流程,猜测在硬件设备上具有 TPM 等安全芯片,密钥信息被保存到芯片中。但在虚拟机环境下不具备 TPM 条件,所以只能将密钥保存在 BOOT 分区中。
按照以上逻辑解密 SUNUP,得到第二个 initramfs,在这个阶段代码就会解密磁盘。还是分析 /init 程序,它会拉起 /init.d 中的初始化脚本,其中有一个叫做 14-keys 的脚本负责初始化密钥。
1 | keys_install_key() { |
这个脚本程序会将 SYSTEM.LIC 中的密钥按照相同的解密逻辑解密,然后加载到内核密钥链中,供后面的程序使用。
最后在 opt/sonicwall/soniccore/scripts/disks 脚本中,会执行真正的解密磁盘操作。
1 |
|
解密逻辑大致为
1 | 1. 从内核密钥链加载 TINY:SYSTEM-HEADER 密钥,使用这个密钥解密 SYSTEM.SYS 文件 |
SYSTEM.SYS 实际上是一个 LUKS header 文件,LUKS 支持将 header 和加密数据分离,在需要时可以通过指定 –header 参数设置 header 文件解密硬盘。
所以到这里我们就理清了 SonicOS 7.1 版本的磁盘解密思路,按照该思路可以编写出本地解密脚本。
具体实现请参考仓库代码。
.sig 固件
官方为硬件设备提供的升级包都是 .sig 格式,熵值分析显示固件都是加密的。
虚拟机的升级包和 .sig 文件格式不同,想要分析格式可能需要硬件设备来调试。不过在查找系统镜像时,我发现 SonicWall 的另一款设备 SMA100 的升级包也是 .sig 格式,并且这款设备提供虚拟机镜像。
SMA100 已有前人详细研究过,这里不再赘述,解包得到系统文件之后,通过搜索和升级相关的信息可以定位到 upgradefirmware 这个程序,它负责接收用户上传的固件并检查是否合法。
1 | int __cdecl sub_804A58F(int a1, int a2, char *data, int size) |
简要概括流程:
1 | 1. 获取用户上传的文件,保存到本地路径 |
实际上是使用固定的 AES KEY 和 IV 对固件进行解密,参照流程可实现解密程序,具体请参考仓库代码。
参考链接
本文分析了 SonicOS 的磁盘和固件加密方法,实现了相应的解密脚本,希望能对感兴趣的研究者有一些帮助。
- Title: SonicOS 固件解密 (2)
- Author: Catalpa
- Created at : 2024-09-05 00:00:00
- Updated at : 2024-10-17 08:55:15
- Link: https://wzt.ac.cn/2024/09/05/sonicwall_dec2/
- License: This work is licensed under CC BY-SA 4.0.