
Descifrar y extraer imágenes de firmware de FortiOS 8.0.0.
Descifra y extrae imágenes de firmware de FortiOS 8.0.0.
Este script extiende Forticrack de Bishop Fox para soportar imágenes de firmware 8.0.0. Además, el artículo de RandoriSec sobre el cifrado de firmware de FortiGate 7.4.7 fue muy útil para revertir el cifrado de FortiOS 8.0.0, ya que parece una iteración más reciente del de 7.4.7.
Funciona tanto para imágenes FGT como FFW.
Esto fue probado tanto en FGT como en FFW v8.0.0.F-build0167. Otras versiones podrían requerir revertir el kernel nuevamente para encontrar los segmentos exactos del kernel y las direcciones virtuales de la clave pública RSA y la clave XOR.
El script intenta autodetectar si el archivo .out corresponde a FGT o FFW según el nombre. También se puede especificar como un argumento opcional por si acaso.
$ python3 forticrack_v8.py
[x] Usage: python3 forticrack_v8.py <.out file> [FGT|FFW]
Demostración:

Directorio resultante:

Ya existen numerosos artículos y scripts sobre el descifrado de fortiOS (como los mencionados anteriormente). Sin embargo, ninguno de ellos aplica a FortiOS 8.0.0, ya que Fortinet ha modificado su cifrado una vez más.
Fortinet permite descargar imágenes de actualización tanto para FortiFirewall como para FortiGate en https://support.fortinet.com/ > Iniciar sesión > Soporte > Imágenes de VM. Estas imágenes son archivos .out, que es lo que este script espera como entrada. Cuando se ejecuta, realiza 4 operaciones principales:
.out (trabajo de Bishop Fox)El archivo de actualización .out está cifrado con un cifrado de bloque personalizado basado en XOR. Bishop Fox lo ingenierizó inversamente y publicó forticrack junto con un excelente artículo. Esta parte del script utiliza prácticamente el mismo código del forticrack original de Bishop Fox, que extrae la clave de 32 bytes correspondiente y descifra el archivo .out. Sugiero leer el artículo si quieres saber más al respecto.
El archivo descifrado es una imagen de firmware estándar de Fortinet. El script lo extrae usando binwalk, produciendo el siguiente sistema de archivos:
ext-root
├── boot
│ ├── cert.der
│ └── grub
│ ├── BOOTX64.EFI
│ ├── grub.cfg
│ └── grubx64.efi
├── boot.msg
├── datafs.tar.gz
├── datafs.tar.gz.bak
├── datafs.tar.gz.chk
├── datafs.tar.gz.chk.bak
├── extlinux.conf
├── filechecksum
├── flatkc
├── flatkc.chk
├── flatkc.sig
├── hash_bin.sha256
├── ldlinux.c32
├── ldlinux.sys
├── rootfs.gz
└── rootfs.gz.chk
Donde:
boot/ : Directorio con archivos del cargador de arranquedatafs.tar.gz : Sistema de archivos de datosflatkc : kernel Linux bzImagerootfs.gz : Sistema de archivos cifradoTodos los archivos interesantes para investigadores de vulnerabilidades, como /sbin/init, están cifrados dentro de rootfs.gz.
rootfs.gz (la parte novedosa)Esta es la parte que es nueva para la versión 8.0.0. Para resolverlo, se utilizó ampliamente Claude Code para revertir la lógica de descifrado correspondiente y obtener las direcciones virtuales codificadas dentro de la imagen del kernel, tomando como referencia el artículo de RandoriSec sobre FortiGate 7.4.7. Según mi experiencia, la ingeniería inversa asistida por IA realmente brilla cuando se analizan temas de criptografía, que era una tarea clásica difícil cuando la opción era la ingeniería inversa manual.
El archivo rootfs.gz está cifrado con un cifrado de flujo personalizado llamado FORT-RC4. La clave para descifrarlo está incrustada dentro de una firma RSA PKCS#1 adjunta al final del archivo. Para descifrar esta firma, se debe utilizar la clave pública RSA correspondiente, que se puede recuperar de la imagen del kernel.
Dado que flatkc es un bzImage, el ELF del kernel se puede extraer fácilmente localizando el payload gzip dentro y descomprimiéndolo. Dentro del ELF, en la dirección virtual 0xffffffff8179a1a0, hay 270 bytes de datos DER codificados con XOR que representan la clave pública RSA. La clave XOR de 32 bytes para descifrarlo se encuentra en 0xffffffff8179a2c0. El descifrado es simplemente decoded[i] = encoded[i] ^ xor_key[i & 0x1f], y el resultado se analiza como una estructura DER estándar PKCS#1 RSAPublicKey (una clave pública RSA-2048).
Con la clave pública RSA recuperada, el bloque de firma (últimos 256 bytes de rootfs.gz) se descifra calculando m = sig^e mod n. El resultado de 256 bytes es un mensaje con relleno PKCS#1 v1.5 Tipo 1 con el siguiente diseño:
m[0x00] = 0x00
m[0x01] = 0x01
m[0x02..0x9E] = 0xFF (157 padding bytes)
m[0x9F] = 0x00
m[0xA0..0xBF] = SHA256(rootfs.gz[:-256])
m[0xC0..0xDF] = (unused)
m[0xE0..0xFF] = RC4 key (32 bytes)
El hash SHA-256 se verifica contra el cuerpo de rootfs.gz como comprobación de cordura, y la clave RC4 de 32 bytes al final es lo que realmente se usa para descifrar el archivo.
En cuanto a FORT-RC4, fue completamente vibe-reversed por Claude. Aquí está cómo funciona:
FORT-RC4 tiene un KSA estándar pero un PRGA modificado: en lugar de producir un byte de flujo de clave por ronda desde una única búsqueda en la S-box, realiza dos búsquedas adicionales usando versiones mezcladas a nivel de bits de
iyj, aplica XOR con0xAAen un índice de mezcla, y combina dos valores de la S-box para producir el byte final. También hay una diferencia entre FGT y FFW: en FGT, tantoicomojse restablecen a 0 después del KSA antes de que comience el PRGA, mientras que en FFWjse mantiene del KSA. Esto es visible en el kernel de FGT en el offset+0x83dentro de la función de cifrado como la secuencia de bytes31 c0 31 d2(xor eax,eax; xor edx,edx), que está ausente en FFW. Por eso el script necesita conocer la variante.
Como se mencionó, el artículo de RandoriSec sobre 7.4.7 fue una referencia útil, pero el mecanismo de cifrado cambió lo suficiente como para que su enfoque no se aplicara directamente a 8.0.0, así que estas son las principales diferencias:
.init.data del kernel. En 8.0.0, esa sección es todo ceros, por lo que se utiliza un esquema XOR simple en su lugar.rootfs está cifrado con AES-CTR. En 8.0.0, es FORT-RC4 (un algoritmo de cifrado personalizado).rsa_parse_pub_key. El kernel de 8.0.0 está sin símbolos, por lo que las direcciones virtuales tuvieron que encontrarse revirtiendo la rutina de descifrado directamente.Las direcciones virtuales para el blob de la clave RSA son cercanas entre versiones (desplazadas por 0x3000), lo que también ayudó durante la ingeniería inversa.
La salida descifrada es un archivo gzip real. Al descomprimirlo, se obtiene un archivo CPIO, que es el formato estándar initrd de Linux y se puede extraer fácilmente con cpio -idmv.