A new patch has been recently shipped in FreeBSD kernels to fix a vulnerability (cve-2019-5602) present in the cdrom device. In this post, we will introduce the bug and discuss its exploitation on pre/post-SMEP FreeBSD revisions.

The bug

A closer look at the commit 6bcf6e3 shows that when invoking the CDIOCREADSUBCHANNEL_SYSSPACE ioctl, data are copied with bcopy instead of the copyout primitive. This endows a local attacker belonging to the operator group with an arbitrary write primitive in the kernel memory.

The following code is sufficient to provoke a kernel panic. More precisely, the kernel tries to fill the data field (residing at address 0) with subchannel data. This is at least true on VMware virtualized environment where the scsi cdrom device emulator returns 4 NULL bytes that are filled by the kernel in the data field even in there is no media present. Please note that this may not be the case on physical FreeBSD hosts.

First, we will consider an environment where SMEP is not supported/enabled. In this case, the exploitation is trivial. One can simply nullify the upper bytes of an entry in the syscall table, map that address in userland, copy a shellcode there, and finally trigger code execution by invoking the corrupted syscall.

In order to get this to work, we need to determine the address of the syscall table entry. Namely, we need to resolve the address of the symbol sysent. Hopefully, FreeBSD provides a useful primitive to resolve kernel symbols: kldsym. Hereafter, we rely on a snippet of code from @CTurtE blog series on FreeBSD Kernel exploitation to resolve the needed symbols:

The exported sysent table holds elements of sysent structures:

As we can see, if we corrupt the upper bytes of the sy_call member, we can redirect system calls to code mapped in userland. In our case, we chose to corrupt the nosyssyscall (syscall N°0) which only purpose is to print out a message for non supported syscalls.

Finally, to elevate our privileges, we map the corrupted syscall address in userland and copy our shellcode there. Here again, we rely on code from @CTurt to gain root privileges. The idea is to retrieve a reference on the current running thread from the GS base, from which we derive a pointer to the ucred structure of the running process.

The full code of the exploit is given below:

Ok. That was the easy part. Now, how we can achieve code execution when SMEP is enabled?

Our strategy is to create several processes and write randomly the kernel memory with the hope to corrupt the uid of one of the forked processes. Our initial attempt was a total failure since in FreeBSD systems, unlike Linux, the structure holding user credentials (ucred) is shared among the processes.

Hopefully, we can trick the system so that it creates a fresh ucred structure for each forked process by calling setuid(getuid()).

Now to maximize our chance to corrupt the uid, we adopt the following strategy:

  • We fork several processes (i.e 0x1000).
  • Each process makes a call to setuid(getuid()) to force the creation of a new ucred structure. It is essential to make this call after the creation of all processes so that the ucred structures are sprayed contiuously in memory. As we can see in the figure below, we obtain a large memory area of ucred structures (aligned on a 0x100 boundary).
  • Once all ucred structures have been created, the parent process invokes periodically the vulnerable IOCTL starting from a base address determined from debugging session.
  • Each process checks in a loop if his uid has been altered.

This PoC has been successfully tested on the last release of FreeBSD. However, please note that this strategy is highly unreliable and will likely produce more panics than shells.

Via: synacktiv

Leave a Reply

Please Login to comment
Notify of