Rapidly Building and Testing Different Kernels

Introduction

When doing any kind of development work, it’s important to take any opportunities you can to shorten your feedback loop. It’s invaluable to be able to make a small change and very quickly confirm that it behaves as desired. Unsurprisingly, this goes even for kernel hacking.

I don’t work on kernel stuff often, but I put this post together as a reference so that when I do, I’m a little bit more prepared. I’ve found this to be useful for all kinds of kernel hacking, including writing kernel modules, testing/writing kernel exploits, working on ebpf stuff, and even just trying out custom builds.

The bulk of this post will revolve around using eudyptula-boot, which was created in response to the eudyptula set of linux kernel exercises. If you’re already familiar with building the linux kernel, then you can pretty much skip out on this post and just go straight there! I really like this tool since it’s pretty light on resources; I do a lot of my work on a small DigitalOcean server and I haven’t run into any performance issues. This is in contrast with say, running a linux virtual machine locally which can take a ton of resources, especially when bringing up and down hosts to test out different kernel versions.

By the end of this post, you should be able to switch between arbitrary kernel versions quickly, flip individual exploit mitigations on and off, and be able to debug your kernel.

Getting the Tools

First, you’ll need to grab a copy of eudyptula-boot. It’s available on github. You will also need to install qemu. I’m using a Debian based distro targeting x86, so I can use apt and qemu-system-x86 and qemu-kvm.

$ sudo apt-get install qemu-system-x86 qemu-kvm
$ git clone https://github.com/vincentbernat/eudyptula-boot

Next, you’ll have to grab a copy of the linux kernel source code. I prefer using git since even though the metadata ends up taking a ton of disk space, but will let you move easily between any versions you need. This is particularly important when you need to test for compatibility, or you’re writing exploits for known vulnerabilities in specific versions of the kernel.

If you’re interested in just a single revision, you can just download it directly. If you find yourself doing this even more than once, it’s probably better to stick to git.

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git

Building and Configuring the Linux Kernel

Once you’ve set up a copy of the linux kernel source code locally, we can start building. Let’s check out a specific revision. For example, if we were testing an exploit for CVE-2017-5123, we could target4.13.0.

$ cd linux-stable

# grep for all the 4.13 tags, since there will be applicable minor revisions
$ git tag -l | grep 4.13

# pick any of them, `local_build` can be any name, it's local only
$ git checkout -b local_build v4.13

Now let’s build it. When you build a kernel, you generally have to specify a base configuration. eudyptula-boot comes with a minimal configuration, but I generally just copy the one from my live system. Feel free to consult make help to find the options most suitable for your environment, though!

$ ls /boot/ | grep config
config-4.15.0-55-generic

$ cp /boot/config-4.15.0-55-generic ./.config

Now with the base config, run make kvmconfig which will enable the particular settings for kvm guest kernel support.

If you want (you probably do) you can also set CONFIG_DEBUG_INFO and CONFIG_KASAN to build the kernel with debug symbols enabled and kernel address sanitizer built in. KASAN is great, particularly when doing kernel exploitation; there may be times where you successfully trigger a vulnerability, but without any visible effects. KASAN will basically yell at you when anything remotely funky happens, making sure you don’t miss it.

$ make kvmconfig

# edit the config file and set CONFIG_DEBUG_INFO=y + CONFIG_KASAN=y
$ vim ./.config

You should be ready to build now. At this point, you can make any modifications you’d like to the kernel (e.g. apply a patch, modify the vulnerable code, etc). Once you’re ready, run make. It’ll be a while, so maybe go for a walk or something…

Booting up the Kernel

Once the compilation is done, you can boot up a VM with the new kernel. Just point eudyptula-boot at your fresh bzImage and in a few seconds you should be greeted with a shell.

$ ./eudyptula-boot -k <your bzimage>

eudyptula-boot also provides a neat argument, -c/--cmdline which lets you pass parameters directly to the kernel. This can come in handy if for example you’re working on kernel exploitation and need to turn off certain mitigations. To disable SMEP/SMAP/KASLR, you could do the following:

$ ./eudyptula-boot -k <your bzimage> -c "nosmep nosmap nokaslr"

Keeping Track of your Kernels

If you do this multiple times for different versions, it can get messy keeping track of all your kernels and configurations. I wrote a little shell script that will find out what version of the kernel you just built (via git) and move everything into a kernels directory.

$ export vm_name=$(git describe --tags|sed s/v//)
$ mkdir -p kernels/$vm_name/
$ cp arch/x86/boot/bzImage kernels/$vm_name/
$ cp .config kernels/$vm_name/config-$vm_name
$ cp vmlinux kernels/$vm_name/

Debugging Your Kernel

You can still use gdb, but attaching to the kernel is a teensie bit different from attaching to any regular userland program. First you have to tell gdb to load your kernel so it has a copy of the program that’s being debugged (the kernel), and then you have to attach to it remotely. The console output should tell you where the pipe is located.

$ gdb kernels/$vm_name/vmlinux
# inside gdb:
$ target remote | socat /tmp/tmp.6ilUBaH77F/<pipe>.pipe -

From here, you’ve got basically a regular gdb console. You can inspect memory, set breakpoints, navigate stack traces, etc. You should be good to go. Happy hacking :~)!