0x00 Introduction

The Linux kernel Pwn is a kind of CTF challenge that requires player to write an exploit in C to exploit the vulnerability in kernel. Usually the vulnerability is in a Loadable Kernel Module. Our purpose is to raise the priviledge from normal user to root. In other word, we need to get the root shell in order to read the flag, and we already have the arbitrary code execution in the normal user priviledge level. Since we don’t want to buy a new computer for simply one Pwn challenge, in most case we will use qemu as the emulator. You can regard qemu as a virtual machine software just like VMware or VirtualBox that you might be more familiar with, but qemu is lighter and more appropriate for kernel Pwn like this.

0x01 Structure

Usually, a .tar or any other compression file will be given. The format of this file is not important, so we just decompress it to a folder.

There are several files given, I will use the core challenge in QWB 2018 as the example.

0x02 Brief look

bzImage: Linux OS image, the kernel codes of Linux are stored in this file.

core.cpio: file system of the Linux, the files such as /bin/* are stored in this file.

vmlinux: Linux OS image in ELF file, it essentially contains same information as bzImage, we can use this to extract ROP gadget. This file is not necessarily given, but we can still extract it from bzImage.

start.sh: the shell script that starts the Linux using qemu, which contains some qemu configurations

0x03 bzImage and vmlinux

We can extract vmlinux from bzImage using this script. We can also obtain the ROP gadget using ropper.

ropper --file vmlinux --nocolor > rop.txt

0x04 start.sh

Basically it looks like this

qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd  ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s  \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \

Here are some important configurations:

-m specifies the memory size. If the kernel cannot be started, try to increase the size of memory here.(e.g. the core challenge in QWB 2018)

-kernel and -initrd are clear as shown, specifying kernel and file system respectively.

kaslr in the -append means “kernel ASLR”, same concept as normal Pwn

-s means it will open a debug port for gdb to attach. We can simply use gdb vmlinux followed by target remote localhost:1234 inside gdb to start debugging. Actually the argument of gdb, vmlinux, can be ignored if you don’t want to load more debug information.

0x05 cpio file

This file is actually a gz file

file core.cpio
#core.cpio: gzip compressed data, last modified: Fri Jan 11 20:20:40 2019, max compression, from Unix

We can decompress it using following command:

mkdir core
cd core
cp ../core.cpio core.cpio.gz # copy the cpio file into the folder and change the suffix
gunzip ./core.cpio.gz 

Now we have a core.cpio file again, but this time the format is different and not gzip file anymore. You can understand this as another kind of compression file format.

file core.cpio 
#core.cpio: ASCII cpio archive (SVR4 with no CRC)

Then we can decompress it again. Therefore the .cpio file given is actually compressed twice, so we also need to decompress twice.

cpio -idm < ./core.cpio
ls -a
#.  ..  bin  core.cpio  core.ko  etc  gen_cpio.sh  init  lib  lib64  linuxrc  proc  root  sbin  sys  tmp  usr  vmlinux

Finally, we have the file system being decompressed, and it is obvious that the files being decompressed look like files in Linux root path.

You may ask, why do we need to decompress it? Well, the reason is that we need to edit and add files inside this file system.

Firstly, you may want to edit the file /init or /etc/init.d/rcS. This is the bash script that will be executed when the OS starts. We want to change some of its content.

We need to delete the automatic power-off, which makes our debug very inconvinient.

#poweroff -d 120 -f &

Then, we may also want to change this line

#setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh

Initially the shell that we can have is the non-root shell, and our purpose of exploitation is to raise the previledge to root. However, here we change the previledge of the initial shell to root directly, which seems to make no sense because why do we need to raise the previledge to root if we already have that in the beginning? Well, this is also to make the debug more convinient. When debugging, we may need to access /sys/module/core/sections/.text or /proc/kallsyms, which gives the address of the .text section of kernel object module or useful addresses of symbols including functions, and these are important to the debug. But, we can only access them with root shell, so this is the reason why we want the root shell when it starts, and we will pretend we don’t have a root shell and still try to get a root shell using our exploit. :)

Secondly, we may want to add our exploit into the file system, otherwise we will not have the exploit to run. :)

After modifying the file system, we may recompress the file system using the script given.

./gen_cpio.sh core.cpio

The content of the script is also not so hard. If there is no such script, we can add it manually.

#cat ./gen_cpio.sh
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1

It compresses everything into .cpio, and then into a .gz file. Finally we will have a core.cpio with gzip format in the current folder again.

file core.cpio 
#core.cpio: gzip compressed data, last modified: Fri Jan 11 22:13:17 2019, max compression, from Unix
mv ../core.cpio ../core.cpio.bak # make a backup for the original file
cp core.cpio ../core.cpio # substitute the `.cpio` file with our modified version

Starting the Linux with ./start.sh, we can see the Linux virtual machine has been run successfully. Then we can see our exploit is already in the file system of this Linux virtual machine, so we can run it and start debugging.

0x06 Debug

Then we are going to look how we can debug this thing. Firstly we need symbol information in the kernel object, if any.

The Kernel Object and Loadable Kernel Module are basically same thing, and I will use them interchangably.

#assume we are still inside the "core" directory that we decompressed above
ls
#bin  core.cpio  core.ko  etc  gen_cpio.sh  init  lib  lib64  linuxrc  proc  root  sbin  sys  tmp  usr  vmlinux
cp core.ko ../core.ko

What is core.ko? It is the vulnerable binary loadable kernel module that we are going to exploit in order to get root shell. It is loaded in the init file as shown below.

insmod /core.ko
#This command load the kernel object into the OS, 
#after this command, we can access the kernel object via `/proc/core`

Linux sees everything as file, so do Loadable Kernel Module being loaded. It implements many callback functions for user to call, such as read, write, open, ioctl. I will not detail the development part here, there are many tutorials online, just Google them for more information.

There might be some symbol information in .ko file, so we can use them for debug.

#in the Linux vitual machine, we read this file, which I have mentioned above
cat /sys/module/core/sections/.text
#[some address]

Then switch to gdb, we load the debug info by add-symbol-file ./core.ko [address obtained above]. Then we can set the breakpoint easily by typing b core_read in gdb. If there is no symbol in the ELF file of Kernel Object, we can only use the offset obtained from IDA to set the breakpoint like this b *(addr_of_text + offset).

If you start gdb without vmlinux as argument, you may need set architecture i386:x86-64:intel in gdb if it complains that “Remote ‘g’ packet reply is too long”.

0x07 Summary

That’s basically what you may want for the setup of Linux kernel Pwn. The setup is a bit more complex than normal Pwn, but it is not so hard either. Play around for several times, and you will become more familiar with it.