FortiGate 飞塔固件自加解密逆向分析
字数 1029 2025-08-30 06:50:27
FortiGate飞塔固件自加解密逆向分析教程
1. 前言与背景
在IoT漏洞挖掘过程中,经常会遇到固件加密的问题,导致在没有硬件设备的情况下无法进行漏洞挖掘。本教程将详细分析FortiGate OS固件的自加密、自解密机制,通过逆向工程方法找到加密位置并实现解密。
2. 准备工作
2.1 环境搭建
- 安装必要的工具:
apt-get install qemu
apt install qemu-utils
- 加载FortiGate VMDK镜像:
modprobe nbd max_part=16
qemu-nbd --connect=/dev/nbd1 FortiGate7_4_2-disk1.vmdk
mount /dev/nbd1p1 /mnt
- 查看启动配置文件:
cat extlinux.conf
3. 固件加载流程分析
3.1 Linux内核rootfs加载机制
Linux内核通过populate_rootfs函数加载initramfs:
static int __init populate_rootfs(void) {
/* Load the built in initramfs */
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
if (err)
panic("%s", err);
/* If available load the bootloader supplied initrd */
if (initrd_start && !IS_ENABLED(CONFIG_INITRAMFS_FORCE)) {
err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
if (!err) {
free_initrd();
goto done;
} else {
clean_rootfs();
unpack_to_rootfs(__initramfs_start, __initramfs_size);
}
}
// ...
}
3.2 FortiOS实现分析
FortiOS基于Linux内核4.19.13(从7.4.3版本开始),通过逆向flatkc文件(使用vmlinux-to-elf转换为ELF格式)可以找到相关功能。
逆向后的关键函数:
__int64 populate_rootfs() {
v0 = _initramfs_size;
v1 = unpack_to_rootfs(_initramfs_start, _initramfs_size);
if (v1)
panic((unsigned int)&aS_18[1], v1, v2, v3, v4, v5);
if (initrd_start) {
v6 = unpack_to_rootfs(initrd_start, initrd_end - initrd_start);
if (!v6) {
free_initrd();
goto LABEL_10;
}
// ...
}
LABEL_10:
flush_delayed_fput();
load_default_modules();
return 0;
}
4. 签名验证机制
4.1 fgt_verify_initrd函数分析
该函数负责ramdisk的签名检查和解密:
__int64 fgt_verify_initrd() {
size = ::initrd_end - initrd_start;
if (::initrd_end - initrd_start <= 0x100u || (unsigned int)fgt_verifier_open(public_key)) {
err_code = -1;
goto LABEL_19;
}
// 计算SHA-256哈希(不包括最后256字节)
sha256_init(sha_context);
sha256_update_0(sha_context, initrd_start, (unsigned int)(size - 256));
sha256_final_0(sha_context, local_hash_1);
// 读取并验证签名数据
raw_data = mpi_read_raw_data(::initrd_end - 256LL, 256);
if (raw_data) {
if ((int)mpi_cmp_ui(raw_data, 0) < 0 ||
(int)mpi_cmp(signature_data, public_key[0]) >= 0) {
err_code = -22;
} else {
// RSA签名验证
mod_pow_result = mpi_alloc(0);
err_code = mpi_powm(mod_pow_result, signature_data, public_key[1], public_key[0]);
// 检查签名格式(PKCS#1 v1.5)
verify_result2 = verify_result | temp1 | *(BYTE *)(initrd_end - 256) ^ 1;
ptr = (BYTE *)(initrd_end - 255);
do {
verify_result2 |= (unsigned __int8)~*ptr++;
} while ((BYTE *)(initrd_end - 53) != ptr);
// 检查哈希匹配
for (n32 = 0; n32 != 32; ++n32)
err_code |= (*((BYTE *)(local_hash + n32)) ^ *(BYTE *)(initrd_end + n32 - 33));
}
}
::initrd_end -= 256LL; // 移除签名数据
fgt_verify_decrypt(); // 解密操作
return err_code;
}
4.2 签名验证流程
- 计算initrd内容的SHA-256哈希(不包括最后256字节)
- 读取initrd末尾的256字节签名数据
- 使用RSA公钥验证签名
- 检查签名格式是否符合PKCS#1 v1.5标准:
- 第一个字节必须为0x01的取反(0xFE)
- 接下来的201个字节必须全为0xFF
- 验证签名中的哈希值与计算得到的哈希值一致
5. 公钥解密机制
5.1 fgt_verifier_open函数
该函数初始化RSA公钥:
__int64 __fastcall fgt_verifier_open(__int64 *p_public_key) {
FFFFFFFF814967A8 = (int *)kmem_cache_alloc(qword_FFFFFFFF814967A8, 0x6000C0);
n_e = fgt_verifier_pub_key(FFFFFFFF814967A8);
n_e = rsa_parse_pub_key((__int64)v14, FFFFFFFF814967A8_1, 270);
e = mpi_read_raw_data(v14[1], (__int64)v14[9]); // 读取公钥e
n = mpi_read_raw_data(v14[0], (__int64)v14[8]); // 读取公钥n
public_key = {n, e, v11_chunk, v12_chunk};
return n_e;
}
5.2 fgt_verifier_pub_key函数
从固件中提取加密的公钥并使用ChaCha20解密:
unsigned __int64 __fastcall fgt_verifier_pub_key(int *initrd_start) {
// 生成32字节密钥
sha256_init(buf_);
sha256_update_0((__int64)buf_, &unk_FFFFFFFF817932E3, 29);
sha256_update_0((__int64)buf_, &EKY, 3);
sha256_final_0((__int64)buf_, (__int64)key);
// 生成8字节的IV
sha256_init(buf_);
sha256_update_0((__int64)buf_, byte_FFFFFFFF817932E1, 31);
sha256_update_0((__int64)buf_, &EKY, 1);
sha256_final_0((__int64)buf_, (__int64)iv);
// ChaCha20解密
crypto_chacha20_init(p_KEY, v6, iv);
chacha20_docrypt((__int64)p_KEY, initrd_start, initrd_start_, 0x10E);
}
5.3 ChaCha20实现
unsigned __int64 __fastcall chacha20_docrypt(__int64 KEY, int *initrd_start, int *initrd_start_2, __int64 size) {
if (initrd_start != initrd_start_2)
memcpy(initrd_start, initrd_start_2, (unsigned int)size);
if (size_1 <= 63) {
initrd_start_3 = initrd_start;
} else {
do {
chacha20_block((unsigned __int64 *)KEY, (__int64)enc_block);
initrd_start_4 = initrd_start_1;
do {
*((_QWORD *)initrd_start_4 - 1) ^= *(_QWORD *)(enc_block + 8 * v11);
} while (v11);
initrd_start_1 += 16;
} while (initrd_start_1 != initrd_start_3);
}
if (size_1) {
chacha20_block((unsigned __int64 *)KEY, (__int64)enc_block);
_crypto_xor(initrd_start_3, initrd_start_3, enc_block, size_1);
}
}
6. 解密工具实现
6.1 解密RSA公钥
/* gcc decrypt_rsapubkey.c chacha20.c -o decrypt_rsapubkey -lssl -lcrypto */
#include <stdio.h>
#include <openssl/evp.h>
#include "chacha20.h"
int main(int argc, char **argv) {
// 初始化
EVP_MD_CTX *mdctx;
unsigned char *md1 = NULL;
unsigned char *md2 = NULL;
// 生成密钥和IV
sha256_init(buf_);
sha256_update_0((__int64)buf_, &unk_FFFFFFFF817932E3, 29);
sha256_update_0((__int64)buf_, &EKY, 3);
sha256_final_0((__int64)buf_, (__int64)key);
// ChaCha20解密
struct chacha20_context ctx;
chacha20_init_context(&ctx, md1, md2);
chacha20_xor(&ctx, g_RSA_PubKey, 270);
printf("BER-encoded pub key:\n");
printhex(g_RSA_PubKey, 270);
}
6.2 解密rootfs
/* gcc decrypt_rootfs.c chacha20.c -o decrypt_rootfs -lssl -lcrypto */
#include <stdio.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include "chacha20.h"
int main(int argc, char **argv) {
// 读取输入文件
FILE *f = fopen(argv[1], "rb");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
fsize -= 256; // 跳过尾部签名
// 解密
struct chacha20_context ctx;
chacha20_init_context(&ctx, md1, md2);
chacha20_xor(&ctx, data, fsize);
// 检查是否为GZ文件
uint16_t magic = *(int16_t *)data;
if (magic != 0x8B1F) {
fprintf(stderr, "Failed to decrypt (not a GZ, magic=%X)\n", magic);
return 1;
}
// 写入输出文件
FILE *f_out = fopen(argv[2], "wb");
fwrite(data, fsize, 1, f_out);
fclose(f_out);
}
7. 自动化提取密钥
使用Python脚本自动提取固件中的密钥:
from miasm.analysis.binary import Container
from miasm.analysis.machine import Machine
from miasm.core.locationdb import LocationDB
def get_rootfs_key(target_binary):
fdesc = open(target_binary, 'rb')
loc_db = LocationDB()
cont = Container.from_stream(fdesc, loc_db)
machine = Machine(cont.arch)
# 根据架构设置寄存器
if cont.arch == "x86_64":
ret_val_reg = machine.mn.regs.RAX
arg_val_reg = machine.mn.regs.RSI
elif cont.arch == "aarch64l":
ret_val_reg = machine.mn.regs.X0
arg_val_reg = machine.mn.regs.X1
# 反汇编分析
mdis = machine.dis_engine(cont.bin_stream, loc_db=cont.loc_db)
addr = loc_db.get_name_offset("fgt_verifier_pub_key")
asmcfg = mdis.dis_multiblock(addr)
# 符号执行提取密钥
lifter = machine.lifter_model_call(mdis.loc_db)
ircfg = lifter.new_ircfg_from_asmcfg(asmcfg)
symb = SymbolicExecutionEngine(lifter)
all_seeds = []
while True:
irblock = ircfg.get_block(addr)
if irblock is None:
break
addr = symb.eval_updt_irblock(irblock, step=False)
if ret_val_reg in symb.symbols.symbols_id:
reg_expr = symb.symbols.symbols_id[ret_val_reg]
if reg_expr.is_function_call():
target = reg_expr.args[0]
target_func = loc_db.get_offset_location(target.arg)
target_func = list(loc_db.get_location_names(target_func))[0]
if target_func == "sha256_update":
all_seeds.append(symb.symbols.symbols_id[arg_val_reg].arg)
seed_addr = min(all_seeds)
seed_data = cont.executable.get_virt().get(seed_addr, seed_addr + 32)
return binascii.hexlify(seed_data).upper()
8. 总结
-
固件解密流程:
- 通过逆向分析找到固件自解密机制
- 提取并解密RSA公钥
- 绕过签名验证
- 解密rootfs内容
-
关键技术点:
- FortiOS使用修改的Linux内核加载机制
- 采用PKCS#1 v1.5格式的RSA签名验证
- 使用ChaCha20算法加密关键数据
- 通过SHA-256派生密钥和IV
-
应用场景:
- 无硬件设备时的固件漏洞挖掘
- 固件修改与重新打包
- 安全研究与分析
这种方法不仅适用于FortiGate固件,也可应用于其他采用类似自加密机制的IoT设备固件分析。