此文档编写于:2025年5月
使用的 FortiGate 版本:7.6.3
主要参考文章:FortiGate Firmware Analysis (noways.io)
Recent modifications in FortiGate firmware crypto (randorisec.fr)
vm Images下载
官方下载地址:https://support.fortinet.com/support/#/downloads/vm
选项参考如下图:
提取rootfs.gz
与flatkc
具体操作可参考此文章的 "FortiGate-VM" 章节:https://www.noways.io/blogs/tech/fortigate-firmware-analysis
配置FortiGate-VM
将 "1.vm Images下载" 中下载下来的文件解压,并将 "FortiGate-VM64.ovf" 导入 vmware
将 “网络适配器” 全部修改为 "NAT"
登入虚拟机。其默认账户为"admin",密码为空(即按一下回车就行),登录后会提示输入密码,根据提示操作即可。
而后参考文章操作,对 FortiGate-VM 进行配置
config system interface
edit port1
set mode static
set ip 192.168.80.100 255.255.255.0 # 根据你的NAT网络配置进行修改
end
config router static
edit 1
set device port1
set gateway 192.168.80.2 # 根据你的NAT网络配置进行修改
end
若配置正确,即可在本机上访问到 web 界面
现在可以关闭 FortiGate-VM 了
挂载vmdk并提取rootfs.gz
在 FortiGate-VM 的文件夹中找到 "[your-vm-name]-disk1.vmdk",并移入虚拟机中
参考文章操作,挂载磁盘
└─$ sudo modprobe nbd max_part=16
└─$ sudo qemu-nbd -c /dev/nbd1 ./fgt-disk1.vmdk
└─$ sudo mount /dev/nbd1p1 ./fortigate
└─$ tree -a -L 1 ./fortigate
./fortigate
├── boot
├── boot.msg
├── capwap
├── cmdb
├── config
├── datafs.tar.gz
├── datafs.tar.gz.bak
├── datafs.tar.gz.chk
├── datafs.tar.gz.chk.bak
├── .db
├── .db.x
├── dhcp6s
├── dhcpddb.bak
├── dhcp_ipmac.dat.bak
├── etc
├── extlinux.conf
├── filechecksum
├── flatkc
├── flatkc.chk
├── flatkc.sig
├── hash_bin.sha256
├── ldlinux.c32
├── ldlinux.sys
├── lib
├── log
├── lost+found
├── rootfs.gz
├── rootfs.gz.chk
└── web-ui
11 directories, 19 files
最后使用cp
指令,将rootfs.gz
与flatkc
复制出来即可(方便后续操作,非必要)
在挂载磁盘时,如遇操作权限不够,sudo
即可(本人操作时只遇到这一个问题)
将flatkc
转为elf格式
如何使用其readme
写得也比较清楚了,不再赘述。
解密rootfs.gz
加密过程大体没变。加密过程的具体分析及解密脚本,参考这篇文章:https://blog.randorisec.fr/fortigate-rootfs-decryption/。这里我就丢一个加密整体流程吧:
但是文章里的解密脚本不能直接拿来用,有两处需要修改。
修改①
通过对flatkc.elf
中加密过程的分析,发现:用于生成chacha20的key和iv的偏移值需要修改
(位于参考文章中的"Signature decryption"章节)
##############################################
# 修改前
#def derivate_chacha20_params(seed):
# sha = sha256()
# sha.update(seed[5:])
# sha.update(seed[:5])
# key = sha.digest()
# sha = sha256()
# sha.update(seed[2:])
# sha.update(seed[:2])
# iv = sha.digest()[:16]
# return key, iv
##############################################
# 报错信息
#Traceback (most recent call last):
# File "...\decrypt_rootfs.py", line 193, in <module>
# decoded_key, _ = decoder.decode(
# ^^^^^^^^^^^^^^^
# ........................
#pyasn1.error.PyAsn1Error: <TagSet object, tags 0:0:13> not in asn1Spec: <RSAPublicKey schema object, tagSet=<TagSet object, tags 0:32:16>, subtypeSpec=<ConstraintsIntersection object>, componentType=<NamedTypes object, types <NamedType object, type modulus=<Integer schema object, tagSet <TagSet object, tags 0:0:2>>>, <NamedType object, type publicExponent=<Integer schema object, tagSet <TagSet object, tags 0:0:2>>>>, sizeSpec=<ConstraintsIntersection object>>
##############################################
# 修改后
def derivate_chacha20_params(seed):
sha = sha256()
sha.update(seed[6:]) #根据上文v46修改
sha.update(seed[:6])
key = sha.digest()
sha = sha256()
sha.update(seed[3:])
sha.update(seed[:3]) #根据上文iv修改
iv = sha.digest()[:16]
return key, iv
修改②
通过debug模式下的打印发现(自行添加了两处logging.debug
):crypto_ctx
结构体内参数顺序需要修改
##############################################
# 修改前
#class crypto_ctx(ctypes.Structure):
# _pack_ = 1
# _fields_ = [
# ("padding", ctypes.c_uint8 * 174),
# ("null", ctypes.c_uint8),
# ("u", crypto_ctx_ctr_u),
# ("aes_key", ctypes.c_uint8 * 32),
# ("rootfs_hash", ctypes.c_uint8 * 32),
# ]
##############################################
# 报错信息
#Traceback (most recent call last):
# File "...\decrypt_rootfs.py", line 209, in <module>
# assert sha.digest() == bytes(sig_struct.rootfs_hash), "rootfs corrupted?"
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#AssertionError: rootfs corrupted?
##############################################
# 修改后
class crypto_ctx(ctypes.Structure):
_pack_ = 1
_fields_ = [
("padding", ctypes.c_uint8 * 174),
("null", ctypes.c_uint8),
("rootfs_hash", ctypes.c_uint8 * 32), # rootfs_hash是第一个参数
("u", crypto_ctx_ctr_u),
("aes_key", ctypes.c_uint8 * 32),
]
解密
脚本环境直接看 git 仓库里的 "setup"
运行方法看 git 仓库里的 "Usage"
(.venv) $ python decrypt_rootfs.py ./flatkc.elf ./rootfs.gz ./output_rootfs_dec.gz
(.venv) $ python decrypt_rootfs.py ./flatkc.elf ./rootfs.gz ./output_rootfs_dec.gz --debug # debug模式
而后先使用gzip解压解密的文件,然后在使用cpio解压
└─$ file output_rootfs_dec.gz # 可要可不要,file一下安心
└─$ gzip -dc -S .dec < output_rootfs_dec.gz > rootfs.cpio
└─$ mkdir tmp; cd tmp; sudo cpio -idv < ../rootfs.cpio
└─$ ll
total 56676
-r--r--r-- 1 root root 41834768 May 13 21:41 bin.tar.xz
drwxr-xr-x 2 root root 4096 May 13 21:41 boot
drwxr-xr-x 3 root root 4096 May 13 21:41 data
drwxr-xr-x 2 root root 4096 May 13 21:41 data2
drwxr-xr-x 8 root root 20480 May 13 21:41 dev
lrwxrwxrwx 1 root root 8 May 13 21:41 etc -> data/etc
lrwxrwxrwx 1 root root 1 May 13 21:41 fortidev -> /
lrwxrwxrwx 1 root root 10 May 13 21:41 init -> /sbin/init
drwxr-xr-x 5 root root 4096 May 13 21:41 lib
lrwxrwxrwx 1 root root 4 May 13 21:41 lib64 -> /lib
-r--r--r-- 1 root root 15413732 May 13 21:41 migadmin.tar.xz
-r--r--r-- 1 root root 659488 May 13 21:41 node-scripts.tar.xz
drwxr-xr-x 2 root root 4096 May 13 21:41 proc
drwxr-xr-x 2 root root 4096 May 13 21:41 sbin
drwxr-xr-x 2 root root 4096 May 13 21:41 sys
drwxr-xr-x 2 root root 4096 May 13 21:41 tmp
drwxr-xr-x 3 root root 4096 May 13 21:41 usr
-r--r--r-- 1 root root 55172 May 13 21:41 usr.tar.xz
drwxr-xr-x 9 root root 4096 May 13 21:41 var
└─$ sudo xz --check=sha256 -d bin.tar.xz
└─$ tar -xf bin.tar