> For the complete documentation index, see [llms.txt](https://fyr3p4w.gitbook.io/fyr3p4w-blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://fyr3p4w.gitbook.io/fyr3p4w-blog/other-activities/creating-a-kernel-pwn-challenge-setup-and-deployment.md).

# Creating a Kernel Pwn Challenge : Setup and Deployment

For this article I will be heavily referencing these 2 blogs for their scripts and setup

{% embed url="<https://r1ru.github.io/posts/0/>" %}

{% embed url="<https://nofilqasim.info/Making%20a%20Kernel%20CTF%20(PUCon'24%20pwn%20CTF)/>" %}

## Building your own Linux Kernel

### Installing Buildroot

Head over to [Buildroot](https://buildroot.org/downloads/) and install a copy of it and extract it.&#x20;

### Configuration

Inside the directory, run

```
make qemu_x86_64_defconfig
```

To change the Buildroot configurations, run&#x20;

```
make menuconfig 
```

<figure><img src="/files/UCOjTobRVRavMcKuyOJh" alt=""><figcaption></figcaption></figure>

* Go to `Kernel > Kernel Version` and select the kernel version that you want. If the version that you want is not specified in here, you will need to select `Custom Version` . Then, go back to `Kernel Version` and set your custom version. Then, back to main menu and go to `Toolchain > Custom kernel header series` and set to your desired version.
* Go to `Filesystem images` to select the type of filesystem image you want (cpio or ext2/3/4)

Next, we will configure the Linux kernel, run

```
make linux-menuconfig
```

* Enable kernel-level debugging features by selecting `Kernel hacking > Kernel debugging`
* Enable kernel debug symbols at `Compile-time checks and compiler options -> Debug information -> Rely on the toolchain's implicit default DWARF version`
* Sometimes, you might want to patch the kernel to make it easier to exploit. You should have the headers at `/output/build/linux-headers-X`  where you can patch the code. I have not tested this yet so I will not cover this.

Then, configure some BusyBox user utilities

```
make busybox-menuconfig
```

* Select `Runtime utilities -> setuidgid` which we will use later
* Select `Shells -> cttyhack`

Finally, to finish building your linux kernel, run

```
make -j$(nproc)
```

If everything ran successfully, you should see your Linux Kernel (bzImage) and filesystem image (ext or cpio) in `<BUILDROOT>/output/images` and vmlinux with debugging symbols at \``<BUILDROOT>/output/build/linux-<VERSION>/` .

## Running your Kernel with Qemu

The scripts from <https://r1ru.github.io/posts/0/#how-to-run-the-linux-kernel-with-qemu> works fine. Mount the filesystem and create a `/init` file in mounted fs and add the contents&#x20;

<details>

<summary>init</summary>

```sh
 #!/bin/sh

 if (exec 0</dev/console) 2>/dev/null; then
     exec 0</dev/console
     exec 1>/dev/console
     exec 2>/dev/console
 fi

 mkdir /home
 echo 'root:x:0:0:root:/root:/bin/sh' > /etc/passwd
 echo 'root:x:0:' > /etc/group
 chmod 644 /etc/passwd
 chmod 644 /etc/group

 adduser ctf --disabled-password 2>/dev/null

 chown -R root:root /
 chmod 700 -R /root
 chown ctf:root /home/ctf
 chmod 777 /home/ctf
 chmod 755 /dev
 chmod u+s /bin/su

 mount -t proc -o nodev,noexec,nosuid proc /proc
 mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
 mount -t tmpfs -o "noexec,nosuid,size=10%,mode=0755" tmpfs /run

 ln -sf /proc/mounts /etc/mtab

 echo 1 > /proc/sys/kernel/kptr_restrict
 echo 1 > /proc/sys/kernel/dmesg_restrict
 echo 1 > /proc/sys/kernel/perf_event_paranoid

 setsid cttyhack setuidgid 1000 sh
```

</details>

Remember to make this file executable. Next, create a script to run it

<details>

<summary>run.sh</summary>

```sh
#!/bin/sh
qemu-system-x86_64 \
      -m 64M \
      -cpu qemu64 \
      -kernel bzImage \
      -drive file=rootfs.ext3,format=raw \
      -snapshot \
      -nographic \
      -monitor /dev/null \
      -no-reboot \
      -smp 1 \
      -append "root=/dev/sda rw init=/init console=ttyS0 nokaslr nopti loglevel=3 oops=panic panic=-1"
```

</details>

## Writing your own vulnerable Kernel module

This is the part where you write the code for your kernel module. I wont cover how to write this as there are plenty of resouces online. Once you are done writing and ready to compile, create a Makefile

<details>

<summary>Makefile</summary>

```
obj-m := vuln.o
KERNEL_DIR := ~/buildroot-2025.08/output/build/linux-6.16.5
CROSS_COMPILE := ~/buildroot-2025.08/output/host/bin/x86_64-buildroot-linux-gnu- 

all:
        CROSS_COMPILE=$(CROSS_COMPILE) $(MAKE) -C $(KERNEL_DIR) M=$(shell pwd) modules

clean:
        CROSS_COMPILE=$(CROSS_COMPILE) $(MAKE) -C $(KERNEL_DIR) M=$(shell pwd) clean
```

</details>

This is important to ensure you are compiling against the headers of the linux kernel that is being used. If all is successful, you should have your vuln.ko.

## Deployment to Server

Before deploying to server, mount your file system again and modify `/etc/shadow` . Look for the line `root::::::::` and change it to `root:*:::::::`. This will disable password login to root so players cant do a `su root`.&#x20;

Next, add your kernel module into the file system. Then, edit your `init` file to load the module at startup. Add the lines

```sh
insmod /root/vuln.ko
chmod 666 /dev/vuln
```

That `/dev/vuln` is if you registered a new device in ur module.&#x20;

<details>

<summary>Dockerfile</summary>

```docker
FROM ubuntu:latest

RUN apt-get update
RUN apt-get install -y qemu-system-x86 qemu-utils socat fuse2fs fuse wget

EXPOSE 5000

RUN useradd -d /home/ctf -m -p ctf -s /bin/bash ctf
RUN echo "ctf:ctf" | chpasswd
RUN ulimit -c 0

WORKDIR /home/ctf

COPY bzImage ./bzImage
COPY deploy.sh ./deploy.sh
COPY rootfs.ext3 ./rootfs.ext3
COPY rootfs.ext2 ./rootfs.ext2
COPY flag.txt ./flag.txt
RUN chmod +x *.sh

CMD socat tcp-l:5000,reuseaddr,fork EXEC:"./deploy.sh",pty,stderr
```

</details>

<details>

<summary>deploy.sh</summary>

```sh
#!/bin/sh

read -r -p "Enter the link to your exploit binary: " link

wget "$link" -O exploit
chmod 755 ./exploit

mkdir -p mnt
rm -rf mnt/*
fuse2fs -o nonempty rootfs.ext3 ./mnt
chown -R 1000:1000 ./mnt
rm ./mnt/exploit
mv ./exploit ./mnt/exploit
fusermount -u ./mnt 

qemu-system-x86_64 \
    -m 64M \
    -cpu qemu64 \
    -kernel bzImage \
    -drive file=rootfs.ext3,format=raw \
    -drive file=flag.txt,format=raw \
    -snapshot \
    -nographic \
    -monitor /dev/null \
    -no-reboot \
    -smp 1 \
    -append "root=/dev/sda rw init=/init console=ttyS0 nokaslr nopti loglevel=0 oops=panic panic=-1"
```

</details>

This method expects the players to be able to host their binary up to download. This can be easily done using ngrok.

## Final words

Thanks for reading! If you notice any misconfigurations or want to suggest improvements, you can drop me a message


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://fyr3p4w.gitbook.io/fyr3p4w-blog/other-activities/creating-a-kernel-pwn-challenge-setup-and-deployment.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
