
FortiOS 8.0.0 펌웨어 이미지를 복호화 및 추출합니다.
FortiOS 8.0.0 펌웨어 이미지를 복호화하고 추출합니다.
이 스크립트는 Bishop Fox의 Forticrack을 확장하여 8.0.0 펌웨어 이미지를 지원합니다. 또한 FortiGate 7.4.7 펌웨어 암호화에 관한 RandoriSec의 글은 FortiOS 8.0.0(7.4.7의 새로운 반복처럼 보임)의 암호화를 리버싱하는 데 큰 도움이 되었습니다.
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은 https://support.fortinet.com/ > 로그인 > 지원 > VM 이미지에서 FortiFirewall 및 FortiGate용 업그레이드 이미지를 다운로드할 수 있도록 합니다. 이 이미지는 .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에서 새로운 부분입니다. 이를 알아내기 위해 Claude Code를 많이 사용하여 해당 복호화 로직을 리버싱하고 커널 이미지 내부의 하드코딩된 가상 주소를 찾았습니다. 참고 자료로 FortiGate 7.4.7에 관한 RandoriSec의 글을 사용했습니다. 제 경험상, AI 지원 리버싱은 암호화 관련 작업을 분석할 때 특히 빛을 발합니다. 이전에는 수동 리버싱만이 유일한 옵션이었던 고전적인 하드코어 작업이었습니다.
파일 rootfs.gz는 FORT-RC4라는 사용자 정의 스트림 암호로 암호화됩니다. 이를 복호화하는 키는 파일 끝에 추가된 PKCS#1 RSA 서명 내부에 포함되어 있습니다. 이 서명을 복호화하려면 해당 RSA 공개 키를 사용해야 하며, 이 키는 커널 이미지에서 복구할 수 있습니다.
flatkc는 bzImage이므로, 내부의 gzip 페이로드를 찾아 압축을 풀면 커널 ELF를 쉽게 추출할 수 있습니다. ELF 내부, 가상 주소 0xffffffff8179a1a0에는 RSA 공개 키를 나타내는 270바이트의 XOR 인코딩된 DER 데이터가 있습니다. 이를 디코딩하는 32바이트 XOR 키는 0xffffffff8179a2c0에 있습니다. 디코딩은 decoded[i] = encoded[i] ^ xor_key[i & 0x1f]이며, 결과는 표준 PKCS#1 RSAPublicKey DER 구조(RSA-2048 공개 키)로 구문 분석됩니다.
RSA 공개 키가 복구되면, 서명 블록(rootfs.gz의 마지막 256바이트)은 m = sig^e mod n을 계산하여 복호화됩니다. 256바이트 결과는 다음과 같은 레이아웃의 PKCS#1 v1.5 Type 1 패딩 메시지입니다.
m[0x00] = 0x00
m[0x01] = 0x01
m[0x02..0x9E] = 0xFF (157바이트 패딩)
m[0x9F] = 0x00
m[0xA0..0xBF] = SHA256(rootfs.gz[:-256])
m[0xC0..0xDF] = (사용되지 않음)
m[0xE0..0xFF] = RC4 키 (32바이트)
SHA-256 해시는 rootfs.gz 본문에 대해 검증되어 무결성을 확인하고, 끝에 있는 32바이트 RC4 키가 실제로 파일을 복호화하는 데 사용됩니다.
FORT-RC4에 관해서는 Claude에 의해 완전히 직감적으로 리버싱되었습니다. 작동 방식은 다음과 같습니다.
FORT-RC4는 표준 KSA를 가지지만 수정된 PRGA를 가지고 있습니다. 라운드당 하나의 S-box 조회에서 하나의 키스트림 바이트를 생성하는 대신,
i와j의 비트 혼합 버전을 사용하여 두 번의 추가 조회를 수행하고, 혼합 인덱스에0xAA를 XOR한 다음 두 S-box 값을 결합하여 최종 바이트를 생성합니다. 또한 FGT와 FFW 사이에는 차이가 있습니다. FGT에서는 PRGA가 시작되기 전에 KSA 후에i와j가 모두 0으로 재설정되는 반면, FFW에서는j가 KSA에서 이어집니다. 이는 암호 함수 내부 오프셋+0x83에서 FGT 커널에 바이트 시퀀스31 c0 31 d2(xor eax,eax; xor edx,edx)로 표시되며, FFW에는 없습니다. 이것이 스크립트가 변형을 알아야 하는 이유입니다.
언급한 대로 7.4.7에 관한 RandoriSec의 글은 유용한 참고 자료였지만, 암호화 메커니즘이 충분히 변경되어 그들의 접근 방식이 8.0.0에 직접 적용되지 않았습니다. 따라서 주요 차이점은 다음과 같습니다.
.init.data 커널 섹션의 시드를 사용하여 ChaCha20으로 난독화됩니다. 8.0.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로 쉽게 추출할 수 있습니다.