Linux Kernel ROP - Ropping your way to # (Part 1)

by Vitaly Nikolenko

Posted on January 17, 2016 at 5:39PM

Kernel ROP

In-kernel ROP (Return Oriented Programming) is a useful technique that is often used to bypass restrictions associated with non-executable memory regions. For example, on default kernels1, it presents a practical approach for bypassing kernel and user address separation mitigations such as SMEP (Supervisor Mode Execution Protection) on recent Intel CPUs.

The goal of this tutorial is to demonstrate how a kernel ROP chain can be constructed to elevate user privileges. As the outcome, the following requirements need to be satisfied:

Execute a privilege escalation payload

Data residing in user space may be referenced (i.e., "fetching" data from user space is allowed)

Instructions residing in user space may not be executed

In typical ret2usr attacks, the kernel execution flow is redirected to a user-space address containing the privilege escalation payload:

void __attribute__((regparm(3))) payload() { commit_creds(prepare_kernel_cred(0); }

The above privilege escalation payload allocates a new credential struct (with uid = 0, gid = 0, etc.) and applies it to the calling process. We can construct a ROP chain that will perform the above operations without executing any instructions residing in user space, i.e., without setting the program counter to any user-space memory addresses. The end goal is to execute the entire privilege escalation payload in kernel space using a ROP chain. This is may not be required in practice, however. For example, in order to bypass SMEP, it is sufficient to flip the SMEP bit using a ROP chain and then a standard privilege escalation payload can be executed in user space.

The ROP chain based on the above payload should look similar to the following:

Using the x86_64 calling convention, the first argument to a function is passed in the %rdi register. Hence, the first instruction in the ROP chain pops the null value off the stack. This value is then passed as the first argument to prepare_kernel_cred() . A pointer to the new cred struct will be stored in %rax which can then be moved to %rdi again and passed as the first argument to commit_creds() . For now, we have deliberately skipped some details regarding returning to user space once the credentials are applied. We will discuss these details later in the "Fixating" section in Part 2 of this tutorial.

In this part, we will discuss how to find useful gadgets and construct a privilege escalation ROP chain. We will then describe the vulnerable driver code that is later used (in Part 2 of this tutorial) to demonstrate the ROP chain in practice.

Test System

For the rest of this tutorial, we will be using Ubuntu 12.04.5 LTS (x64) with the following stock kernel:

vnik@ubuntu:~$ uname -a Linux ubuntu 3.13.0-32-generic #57~precise1-Ubuntu SMP Tue Jul 15 03:51:20 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

If you would like to follow along and use the same kernel, all the addresses of ROP gadgets should be identical to ours.

Gadgets

Similar to user-space applications, ROP gadgets can be simply extracted from the kernel binary. However, we need to consider the following:

We need the ELF (vmlinux) image to extract gadgets from. If we are using the /boot/vmlinuz* image, it needs to be decompressed first, and A tool specifically designed for extracting ROP gadgets is preferred.

/boot/vmlinuz* is a compressed kernel image (various compression algorithms are used). It can be extracted using the extract-vmlinux script located in the kernel tree.

vnik@ubuntu:~$ sudo file /boot/vmlinuz-4.2.0-16-generic /boot/vmlinuz-4.2.0-16-generic: Linux kernel x86 boot executable bzImage, version 4.2.0-16-generic (buildd@lcy01-07) #19-Ubuntu SMP Thu Oct 8 15:, RO-rootFS, swap_dev 0x6, Normal VGA vnik@ubuntu:~$ sudo ./extract-vmlinux /boot/vmlinuz-3.13.0-32-generic > vmlinux vnik@ubuntu:~$ file vmlinux vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=0x32143d561875c4e5f3229003aca99c880e2bedb2, stripped

ROP techniques take advantage of code misalignment to identify new gadgets. This is possible due to x86 language density, i.e., the x86 instruction set is large enough (and instructions have different lengths), that almost any sequence of bytes can be interpreted as a valid instruction. For example, depending on the offset, the following instructions can be interpreted differently (note that the second instruction represents a useful stack pivot):

0f 94 c3; sete %bl 94 c3; xchg eax, esp; ret

Simply running objdump against the uncompressed kernel image and then grepping for gadgets, will only produce a small subset of all available gadgets (since we are working with aligned addresses only). It is worth mentioning that in a majority of cases, however, this is sufficient to find the required gadgets.

A more efficient approach is to use a tool specifically designed for identifying gadgets in ELF binaries. For example, ROPgadget can be used to identify all available gadgets:

vnik@ubuntu:~/ROPgadget$ ./ROPgadget.py --binary ./vmlinux > ~/ropgadget vnik@ubuntu:~/ROPgadget$ tail ~/ropgadget Gadgets information ============================================================ 0xffffffff810c108c : adc ah, ah ; add byte ptr [rax - 0x77], cl ; ret 0xffffffff81054c3a : adc ah, ah ; xor al, byte ptr [rax] ; pop rbp ; ret 0xffffffff815abb0a : adc ah, al ; lcall ptr [rbx + 0x41] ; pop rsp ; xor eax, eax ; pop rbp ; ret 0xffffffff81b0d595 : adc ah, al ; ljmp ptr [rcx + rax*4 - 9] ; call rax 0xffffffff8112fc05 : adc ah, bh ; add byte ptr [rax - 0x77], cl ; in eax, 0x5d ; ret 0xffffffff811965e9 : adc ah, bh ; lcall ptr [rbx + 0x41] ; pop rsp ; xor eax, eax ; pop rbp ; ret 0xffffffff81495bba : adc ah, bh ; mov esi, 0xc7c748ff ; loopne 0xffffffff81495c47 ; retf -0x177f 0xffffffff8158fb9a : adc ah, bl ; loopne 0xffffffff8158fba4 ; xor eax, eax ; pop rbp ; re

Note that the Intel syntax is used the ROPgadget tool. Now we can search for the ROP gadgets listed in our privilege escalaltion ROP chain. The first gadget we need is pop %rdi; ret :