
FortiOS 8.0.0 ファームウェアイメージを復号・抽出します。
FortiOS 8.0.0 ファームウェアイメージを復号および抽出します。
このスクリプトは、Bishop Fox の Forticrack を拡張して 8.0.0 ファームウェアイメージに対応させたものです。また、FortiGate 7.4.7 ファームウェア暗号化に関する RandoriSec の記事 は、7.4.7 の暗号化の新しい反復版のように見えることから、FortiOS 8.0.0 の暗号化をリバースする際に非常に役立ちました。
FGT と FFW の両方のイメージで動作します。
これは FGT と FFW の v8.0.0.F-build0167 の両方でテストされています。他のビルドでは、RSA 公開鍵と XOR 鍵の正確なカーネルセグメントと仮想アドレスを見つけるために、カーネルのリバースエンジニアリングを再度行う必要があるかもしれません。
このスクリプトは、ファイル名に基づいて .out ファイルが FGT に対応するか FFW に対応するかを自動検出しようとします。念のため、オプション引数として指定することもできます。
$ python3 forticrack_v8.py
[x] Usage: python3 forticrack_v8.py <.out file> [FGT|FFW]
デモ:

生成されるディレクトリ:

FortiOS の復号に関する記事やスクリプトはすでに数多く存在します(前述のものなど)。しかし、Fortinet が暗号化を再度変更したため、そのどれも FortiOS 8.0.0 には適用できません。
Fortinet では、FortiFirewall と FortiGate の両方のアップグレードイメージを https://support.fortinet.com/ > Login > Support > VM Images からダウンロードできます。これらのイメージは .out ファイルであり、このスクリプトはこれを入力として想定しています。実行すると、次の 4 つの主要な操作を実行します。
.out ファイルの復号(Bishop Fox の成果).out アップグレードファイルは、カスタムの XOR ベースのブロック暗号で暗号化されています。Bishop Fox はこれをリバースエンジニアリングし、forticrack と優れた解説記事を公開しました。スクリプトのこの部分は、Bishop Fox のオリジナル forticrack と実質的に同じコードを使用しており、対応する 32 バイトの鍵を抽出して .out ファイルを復号します。詳細を知りたい場合は、その解説記事を読むことをお勧めします。
復号されたファイルは、標準的な Fortinet ファームウェアイメージです。スクリプトは binwalk を使用してこれを抽出し、次のファイルシステムを生成します:
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
ここで:
boot/ : ブートローダーファイルを含むディレクトリdatafs.tar.gz : データファイルシステムflatkc : Linux カーネル bzImagerootfs.gz : 暗号化されたファイルシステム脆弱性研究者にとって興味深いファイル(/sbin/init など)はすべて、rootfs.gz 内で暗号化されています。
rootfs.gz の復号(新しい部分)これがバージョン 8.0.0 で新しく追加された部分です。これを解明するために、RandoriSec の FortiGate 7.4.7 に関する記事を参考にしながら、Claude Code を多用して対応する復号ロジックをリバースし、カーネルイメージ内のハードコードされた仮想アドレスを取得しました。私の経験では、AI 支援によるリバースエンジニアリングは、暗号関連の解析において真価を発揮します。これは、手動リバースだけが唯一の選択肢だった時代には、古典的なハードコアな作業でした。
rootfs.gz ファイルは、FORT-RC4 と呼ばれるカスタムストリーム暗号で暗号化されています。これを復号する鍵は、ファイルの末尾に追加された PKCS#1 RSA 署名内に埋め込まれています。この署名を復号するには、対応する RSA 公開鍵を使用する必要があり、その公開鍵はカーネルイメージから復元できます。
flatkc は bzImage であるため、内部の gzip ペイロードを特定して展開することで、カーネル ELF を簡単に抽出できます。ELF 内の仮想アドレス 0xffffffff8179a1a0 には、RSA 公開鍵を表す XOR エンコードされた 270 バイトの DER データがあります。これをデコードするための 32 バイトの XOR 鍵は 0xffffffff8179a2c0 にあります。デコードは decoded[i] = encoded[i] ^ xor_key[i & 0x1f] だけで、結果は標準的な PKCS#1 RSAPublicKey DER 構造(RSA-2048 公開鍵)としてパースされます。
RSA 公開鍵が復元されたら、m = sig^e mod n を計算して署名ブロック(rootfs.gz の末尾 256 バイト)を復号します。256 バイトの結果は PKCS#1 v1.5 Type 1 パディング付きメッセージで、次のレイアウトになります:
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)
SHA-256 ハッシュは健全性チェックとして rootfs.gz の本文と照合され、末尾にある 32 バイトの RC4 鍵が実際にファイルの復号に使用されます。
FORT-RC4 に関しては、完全に Claude によって vibe リバース(直感によるリバースエンジニアリング)されました。その仕組みは次のとおりです:
FORT-RC4 は標準的な KSA を持ちますが、PRGA が変更されています。1 ラウンドごとに単一の S-box ルックアップから 1 バイトのキーストリームを生成する代わりに、
iとjのビット混合バージョンを使用して追加のルックアップを 2 回実行し、0xAAをミックスインデックスに XOR し、2 つの S-box 値を組み合わせて最終バイトを生成します。また、FGT と FFW の間には違いがあります。FGT では、PRGA が開始される前に KSA の後にiとjの両方が 0 にリセットされますが、FFW ではjが KSA から引き継がれます。これは、FGT カーネルの暗号関数内のオフセット+0x83で、バイト列31 c0 31 d2(xor eax,eax; xor edx,edx)として確認できますが、FFW には存在しません。これが、スクリプトがバリアントを把握する必要がある理由です。
前述のとおり、RandoriSec の 7.4.7 に関する記事は有用な参考資料でしたが、暗号化の仕組みは十分に変更されており、そのアプローチを 8.0.0 に直接適用することはできませんでした。主な違いは次のとおりです:
.init.data セクションのシードを使用した ChaCha20 で難読化されています。8.0.0 では、そのセクションはすべてゼロであるため、代わりに単純な XOR スキームが使用されています。rootfs は AES-CTR で暗号化されています。8.0.0 では、FORT-RC4(カスタム暗号化アルゴリズム)です。rsa_parse_pub_key へのクロスリファレンスをたどることで RSA 鍵を特定できます。8.0.0 のカーネルはストリップされているため、復号ルーチンを直接リバースして仮想アドレスを見つける必要がありました。RSA 鍵ブロブの仮想アドレスはバージョン間で近い値であり(0x3000 だけシフト)、これもリバース中に役立ちました。
復号された出力は本物の gzip ファイルです。これを展開すると CPIO アーカイブが得られます。これは標準的な Linux の initrd 形式であり、cpio -idmv で簡単に抽出できます。