Linux Kernel Exploitation - Environment

I would like to give an introduction in modern kernel exploitation on linux. This will consists of several blogposts about the following topics:

This post describes how to build an debugging environment. It explains how to create an initramfs file, how to build the kernel and how to use qemu with newly build kernel and initramfs file.

requirements

The following tools will be needed:

initramfs

Initial RAM filesystem (initramfs) is the successor of initial RAM disk (initrd) and is a small environment which can be used to load various kernel modules and sets up necessary things, e.g. mounts encrypted filesystems, and handing over control to init. This filesystem can be used to get a small environment in order to access the kernel, which ensures that only the necessary things are loaded and reduces the boot time.

initramfs can be created in serveral ways:

This post put the focus on the from scratch approach.

First of all, a version of busybox needs to be downloaded and statically compiled. During the time of writing v1.36.1 is the current version of busybox.

wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2

Since the current version is from 2023, it cannot be build without modifications. Therefore, it will be build by using a docker environment:

tar xf busybox-1.36.1.tar.bz2
docker run -it -v $(pwd)/busybox-1.36.1:/busybox ubuntu:22.04

Inside the docker container the necessary dependencies and tools need to be installed.

apt-get update && apt-get install -y make gcc ncurses-dev bzip2 perl

The following commands show how to compile it. The installation path is set to ./busybox_rootfs.

cd /busybox
make defconfig
make menuconfig
sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
make
make CONFIG_PREFIX=./busybox_rootfs install 

Note In order to make make menuconfig work on a current system, line 15 in scripts/kconfig/lxdialog/Makefile has to be commented out, due to changes in ncurses. Furthermore, the line CONFIG_TC=y has be be replaced with CONFIG_TC=n.

The next step is to create the filesystem structure:

# create filesystem structure
mkdir -p initramfs/{bin,dev,etc,home,mnt,proc,sys,usr}

# copy busybox build files 
cp -r busybox-1.36.1/busybox_rootfs/* initramfs/

I prefer to create a directory for kernel modules which should be loaded automatically.

mkdir initramfs/modules

An init script needs to be created. That will be executed during start.

touch initramfs/init
chmod +x initramfs/init

The init script should mount the necessary filesystems like proc and sys, load the kernel modules and start a shell.

#!/bin/sh
# mount filesystems
mount -t proc none /proc
mount -t sysfs none /sys

### load the kernel modules
for i in `find modules -iname "*.ko"`
do
	insmod /modules/$i
done
mdev -s     # make device files available

# start shell with root privileges
# exec /bin/sh

# start sh with uid 1000 and without root privileges
setuidgid 1000 sh

The last step is to pack the initramfs directory to a cpio archive.

cd initramfs
find . -print0 | cpio --null -ov --format=newc > ../initramfs.cpio 
gzip ../initramfs.cpio

linux kernel

Several ways exist how to get a linux kernel. One possibility is to take the kernel from an linux distribution, e.g. Ubuntu. The distributions provides mostly sources and compiled binaries of their kernel. Each distribution normally takes the vanilla kernel from kernel.org and add features and patches on top. For example, the Ubuntu kernel source code can be cloned from their git repository. The whole repository can be cloned and the tag of the wanted version can be checkout, or only the respective branch can be cloned. The following snippet shows how to download the kernel source code for the focal release.

git clone git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/focal
cd focal
git checkout Ubuntu-hwe-5.13-5.13.0-28.31_20.04.1

Another possibility is to compile a version of the vanilla kernel. Several versions (longterm, mainline, stable) of the kernel can be download as a tarball directly from the website. However, in order to get any version of the kernel, it is recommended to clone the kernel git repository and checkout the right version (e.g. 5.13.0).

git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
cd linux-stable
git checkout v5.13.19

The next step is to create a valid configuration (.config) for the kernel. That can easily be done by using the configuration that is stored in /boot.

cp /boot/config-$(uname -r) .config

Another possiblity is to create a new one, or modify the copied one.

make menuconfig

In order to compile the kernel with debug symbols, the option CONFIG_DEBUG_INFO should be enabled. If the debug symbols should be placed in a separate file, the option CONFIG_DEBUG_INFO_SPLIT needs to be enabled as well.

If an older version of the kernel shall be compiled, it is recommended to use a docker container and build the kernel in the docker container.

docker run -it -v $(pwd):/kernel ubuntu:22.0
apt update
apt install -y zstd cpio python3 git fakeroot build-essential \
    ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison

The kernel can now be compiled by using make and install it afterwards to a custom directory.

cd /kernel
make 
# or
# make -j 10 # in order to use more CPUs

mkdir -p ./build/modules
make INSTALL_PATH=./build install
make INSTALL_MOD_PATH=./build/modules modules_install

All necessary modules need to be copied to the modules folder of the initramfs. The content needs to be packed again afterwards.

If custom kernel modules also need to be compiled, that can be accomplished in the same container by adding the directory of the kernel module as volume as well.

# replace modulepath with the path to the module
docker run -it -v $(pwd):/kernel <modulepath>:/module ubuntu:22.0
cd /module
make -C /kernel/ M=$PWD

qemu and gdb

QEMU with its KVM extension is a hypervisor that can be used to execute and debug the kernel. The kernel should be configured with CONFIG_GDB_SCRIPTS enabled.

All necessary arguments can be set via commandline parameter. Additionally, security features can be opt-in/opt-out.

qemu-system-x86_64 -kernel vmlinuz-5.13.19 \
    -nographic \
    -initrd initramfs.cpio.gz \
    -m 512 \
    -cpu kvm64 \
    -s \
    -append "console=ttyS0 nokaslr nopti nosmep nosmap quiet panic=1"

The argument -s enables the gdb stub which provides remote debugging capabilities on port 1234. gdb can be used to connect to that port attach to it.

gdb vmlinux-5.13.19
(gdb) target remote :1234

If the kernel is compiled with debug symbols, all symbols are available in the debugging session. If a custom module should be debugged, the debug symbols have to be loaded.

First of all, the addresses of the .text, .data and .bss section are needed:

cat /sys/module/<modulename>/sections/.text
0xffffffffc0002000
cat /sys/module/<modulename>/sections/.data
0xffffffffc0004000
# if available
# cat /sys/module/<modulename>/sections/.bss 

The symbols can be loaded afterwards:

(gdb) add-symbol-file path/to/the/module 0xffffffffc0002000 -s .data 0xffffffffc0004000 # -s .bss 0xffffffffxxxxxxxx

resources

https://www.kernel.org/doc/html/v4.14/dev-tools/gdb-kernel-debugging.html https://wiki.ubuntu.com/Kernel/SourceCode