This blog post is for documenting my own interpretation of kernel exploitation techniques. It contains progressive tutorials and steps setting up the environment. Take everything you read here with a grain of salt. I can’t promise that everything I say will be 100 % correct, and again this is just my own interpretation of the knowledge I’ve gathered from here and there. So, if you have some knowledge of user-land exploitation but have never done any ring-0 exploitation, you might find the information here helpful.
Setting up the environment
In these examples I’m using QEMU, I’m assuming you already have QEMU. If not, download it and install it. Also download pre-compiled x86 image, as this is going to be our debugging target kernel. If you prefer a newer kernel and different file-system, you can build either with Buildroot quite easy just don’t forget to include Debugging Symbols. To speed-up the emulation environment make sure to have KVM module installed as well (if possible). With KVM installed you can use the
-enable-kvm QEMU cmdline option, it will allow QEMU to utilize the hardware virtualization features and speed-up the VM. Now that you have QEMU and a working x86 image, you can use the following script to start the VM
-moption will allocate 1024 MB ram for the VM
-enable-kvmwill enable hardware virtualization features
-net user,hostfwd=tcp::10022-:22will map local port 10022 to VM’s port 22, so you can login to the VM by SSHing into localhost:10022
-nographicwill disable X
- If you decide to compile and use a new kernel you can use the
-append "root=/dev/sda1"to use a separate kernel file
- -S will pause boot until a debugger is attached and continue is passed
- -s will listen for debugger on forwarded port 1234. To use a different port, instead of using
Although we can start debugging right away, we can simplify our lives and install a kernel with debugging symbols and optionally we can download kernel sources.
Starting with the debugging symbols, for the pre-compiled image download the following linked deb, install it on the guest VM, reboot into the new kernel and copy
/usr/lib/debug/boot/vmlinux-3.2.0-4-686-pae to the host.
For the source files, we can download them via
apt-get on the guest and transfer on the host. Once unpacked on the host, you can use the
directory gdb command to include them into the search path of gdb.
The sources will be in
/usr/src/ usually in a tar archive. Move them to the host and extract them.
Now that we have the prepared gdb, kernel debugging symbols and sources we are good to do some debugging.
To add the sources:
Unfortunately PEDA doesn’t work with remote debugging but GEF seems to work.
Loading module’s symbols
Kernel modules are like hot pluggable functions to the kernel. We can load them with
modprobe then the module’s functions get exported to the kernel. So far we have loaded the kernel symbols into gdb but adding a new module will not load the module’s functions into our remote debugger. Luckily we can load them manually.
We have already compiled the module with debugging symbols not stripped out.
First we need to see the load addresses of the different sections.
Now download the module to the host system where you have gdb running and apply the following commands:
We can confirm that the symbols are working as intended (the same way we added the kernel sources we can add our module’s source files, so I will not be going over that…).
Building kernel modules
To build kernel modules you’ll see
make, as well as the kernel header files. You can obviously cross-compile, but I don’t think it’s worth the effort to build a quick module.
Make sure you have a Makefile. Here is a super simple template:
Once your build succeeds you should see a .ko file. You can then load it into the kernel with
insmod. to confirm that it loads successfully you can run
lsmod and make sure it’s listed. You can also check
dmesg to see any messages from the module itself.
insmodloads a module into the running kernel
modprobeloads module and any dependencies into the running kernel
rmmodunloads/removes a module from the kernel
lsmodlists the currently loaded modules
/devfilesystem is where devices registered by modules appear. One way of communicating with your module is via the device your module registers
- mmap_min_addr is a kernel exploitation mitigation mechanism that dictates the lowest memory address a user-land application can request via mmap. Default value is usually 65535 but the goal is to disallow requesting address 0. Additional way to disable/change it is to overwrite
- No ASLR in the kernel
/proc/kallsymslists all symbols and their load address
- Arguments to kernel functions are passed through registers sequentially in EAX, EDX, ECX and if there are more they are pushed to the stack
Starting with null pointer dereference, say that we have a vulnerability that overwrites a function pointer with 0 (NULL).
… TO BE CONTINUED (sorry about that)