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


qemu-system-i386 -hda debian_wheezy_i386_standard.qcow2 -m 1024 -enable-kvm -nographic -net user,hostfwd=tcp::10022-:22 -net nic -S -s
  • The -m option will allocate 1024 MB ram for the VM
  • The -enable-kvm will enable hardware virtualization features
  • The -net user,hostfwd=tcp::10022-:22 will map local port 10022 to VM’s port 22, so you can login to the VM by SSHing into localhost:10022
  • -nographic will disable X
  • If you decide to compile and use a new kernel you can use the -kernel and -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 -s use the -gdb tcp::<port>

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.

# On host
$ scp -P 10022 ./linux-image-3.2.0-4-686-pae-dbg_3.2.81-1_i386.deb root@localhost:~
# On guest
root@debian-i386:~# dpkg -i ./linux-image-3.2.0-4-686-pae-dbg_3.2.81-1_i386.deb
root@debian-i386:~# shutdown -r now
# On host
$ scp -P 10022 root@localhost:/usr/lib/debug/boot/vmlinux-3.2.0-4-686-pae .
$ file vmlinux-3.2.0-4-686-pae
vmlinux-3.2.0-4-686-pae: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=725ffd76ce66dcc2aa0cf9d914a28aceef5ead2e, not stripped

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.

# apt-get update
# apt-get install linux-source-3.2

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.

➜  kernel_dbg gdb
gdb-peda$ file vmlinux-3.2.0-4-686-pae
Reading symbols from vmlinux-3.2.0-4-686-pae...done.
gdb-peda$ source
gef➤  target remote :1234
Remote debugging using :1234
native_safe_halt () at /build/linux-M3n6wX/linux-3.2.81/arch/x86/include/asm/irqflags.h:50
50 /build/linux-M3n6wX/linux-3.2.81/arch/x86/include/asm/irqflags.h: No such file or directory.
gef➤  p prepare_kernel_cred
$1 = {struct cred *(struct task_struct *)} 0xc10529cc <prepare_kernel_cred>
gef➤  p commit_creds
$2 = {int (struct cred *)} 0xc1052761 <commit_creds>

To add the sources:

gef➤  show directories
Source directories searched: $cdir:$cwd
gef➤  directory linux-source-3.2/kernel/
Source directories searched: /home/user/kernel/linux-source-3.2/kernel:$cdir:$cwd
gef➤  directory linux-source-3.2/arch/x86/include/asm/
Source directories searched: /home/user/kernel/linux-source-3.2/arch/x86/include/asm:/home/user/kernel/linux-source-3.2/kernel:$cdir:$cwd
gef➤  list
45 }
47 static inline void native_safe_halt(void)
48 {
49    asm volatile("sti; hlt": : :"memory");
50 }
52 static inline void native_halt(void)
53 {
54    asm volatile("hlt": : :"memory");

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 insmod and 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.

$ file my_module.ko
my_module.ko: ELF 32-bit LSB  relocatable, Intel 80386, version 1 (SYSV), BuildID[sha1]=d15c70c2673ea5540685b48cb88ffb4f270fd8da, not stripped

First we need to see the load addresses of the different sections.

root@debian-i386:~# cat /sys/module/my_module/sections/.
./                         .gnu.linkonce.this_module  .rodata.str1.1
../                        .init.text                 .strtab
.data                     .symtab
.exit.text                 .rodata                    .text
root@debian-i386:~# cat /sys/module/my_module/sections/.text
root@debian-i386:~# cat /sys/module/my_module/sections/.data

Now download the module to the host system where you have gdb running and apply the following commands:

gef➤  add-symbol-file my_module.ko 0xf8357000 -s .data 0xf8359000
add symbol table from file "my_module.ko" at
   .text_addr = 0xf8357000
   .data_addr = 0xf8359000
Reading symbols from my_module.ko...done.

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…).

gef➤  p my_write
$4 = {ssize_t (struct file *, const char *, size_t, loff_t *)} 0xf8357000 <my_write>
gef➤  disas my_write
Dump of assembler code for function my_write:
   0xf8357000 <+0>:  push   0xf8358024
   0xf8357005 <+5>:  call   0xc12c33aa <printk>
   0xf835700a <+10>: xor    eax,eax
   0xf835700c <+12>: call   eax
   0xf835700e <+14>: pop    eax
   0xf835700f <+15>: xor    eax,eax
   0xf8357011 <+17>: ret
End of assembler dump.

Building kernel modules

To build kernel modules you’ll see gcc and 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.

apt-get install gcc
apt-get install make
apt-get install linux-headers-`uname -r`

Make sure you have a Makefile. Here is a super simple template:

obj-m += <module>.o

	KERNELSRC=/lib/modules/$(shell uname -r)/build

	make -C $(KERNELSRC) M=$(PWD) modules

	make -C $(KERNELSRC) M=$(PWD) clean

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.


  • insmod loads a module into the running kernel
  • modprobe loads module and any dependencies into the running kernel
  • rmmod unloads/removes a module from the kernel
  • lsmod lists the currently loaded modules
  • /dev filesystem 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 /proc/sys/vm/mmap_min_addr
  • No ASLR in the kernel
  • /proc/kallsyms lists 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

Null dereference

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)