An Intro to Linux Kernel Pwn in CTF
Intro
In this post we will have a brief view about Linux kernel pwn, what we need to do and how it works.
Actually Linux kernel pwn is similar to userland pwn, except that our target is the kernel(or kernel module). In most of the cases, the vulnerability is in custom Linux Kernel Module, LKM, which provides service to user as a part of kernel in ring0. Usually, the emulator for the task in Linux kernel pwn in CTF is qemu
. And the challenge will often be deployed with the following files:
- vmlinux, the Linux kernel. Sometimes it will be packed into
bzImage
from which you can extract the kernel. The kernel is an ELF file and you can run ROPGadget or ropper against it like common userland pwn. - Linux root file system. The compression schemes are usually
cpio
andgzip
- A script to launch the emulator with specific configuration
Let’s go further now, some basic knowledge of operating system is required here.
Our goal
Our main goal in Linux kernel pwn is getting root privilege since the “flag” can only be accessed with root in most cases, which means privilege escalation.
Privilege escalation
First let’s take a look at the structure of process in Linux kernel.
1 | struct task_struct { |
and struct cred
contains the gid
and uid
of the process. It’s obviously that if we can control the subjective cred
of a specific process then we can achieve privilege escaltion.
Luckily, we do have serveral ways to change our credential:
- Overwrite the
cred
in the link list of process with arbitary kernel rw. - Find the code path in kernel that can set the credential of process and perform a kernel ROP.
Also, we can control another process with root privilege and gain arbitary code exec in that process. But we will take a closer look at the last one first since it’s almost the same as ROP in userland.
Kernel ROP
We need to find a method to assign new cred to our process. Searching through the source code, we find that
1 | /** |
Exactly what we need! So if we can find a cred
for a process with root privilege then we can use the function above to assign this cred
to our process(current process). Luckily, we have
1 | /** |
When daemon
is null, old
will be set to init_cred
1 | struct cred init_cred = { |
Which has all we need! So our ROP chain should be
1 | commit_creds(prepare_kernel_cred(null)); |
after which we can achieve privilege escalation.
Back to userland
After we getting root privilege, we are still in kernel mode. Our main goal is spawning a root shell in userland, so we need to return to user mode with the following steps.
swapgs
iretq
Take a look at the implementation of Linux
1 | SYM_CODE_START_LOCAL(common_interrupt_return) |
So the layout of the stack should be
1 | +--------------------------------+ |
ROPGadget will fail to find iretq
sometimes, we can use the following command to find the gadget.
1 | objdump -j .text -d ./vmlinux | grep iretq | head -1 |
After returning to userland, you will get a root shell.