Let’s check the root directory.
Under root, you’ll find a shellscript named init.
#!/bin/sh
# devtmpfs does not get automounted for initramfs
/bin/mount -t devtmpfs devtmpfs /dev
# use the /dev/console device node from devtmpfs if possible to not
# confuse glibc's ttyname_r().
# This may fail (E.G. booted with console=), and errors from exec will
# terminate the shell, so use a subshell for the test
if (exec 0</dev/console) 2>/dev/null; then
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
fi
exec /sbin/init "$@"
/dev/console is used to expose the kernel’s console to userspace.
The shellscript uses the mount command with -t to specify the type as devtmpfs, the device as devtmpfs at /dev.
Here’s on explanation on what devtmpfs is.
At the last line it executes /sbin/init with a $@.
ptr-yudai says that /sbin/init executes /etc/init.d/rcS, which is another shellscript.
According to him, /etc/init.d/rcS executes files that start with a S in /etc/init.d/.
He then tells us to check out S99pawnyable.
Here’s the shellscript.
#!/bin/sh
##
## Setup
##
mdev -s
mount -t proc none /proc
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
stty -opost
echo 2 > /proc/sys/kernel/kptr_restrict
#echo 1 > /proc/sys/kernel/dmesg_restrict
##
## Install driver
##
insmod /root/vuln.ko
mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}0
##
## User shell
##
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
echo "[ Holstein v1 (LK01) - Pawnyable ]"
setsid cttyhack setuidgid 1337 sh
##
## Cleanup
##
umount /proc
poweroff -d 0 -f
He tells us to change the userid and group id to root(0) instead of 1337.
Additionally he insists to comment out echo 2 > /proc/sys/kernel/kptr_restrict, a kernel security feature.
Now we’ll need to run the shellscript and archive it back to a cpio file.
To do that go to the root directory where all the programs are and run this script.
#!/bin/sh
find . -print0 | sudo cpio -o --format=newc --null --owner=root > ../rootfs_updated.cpio
Running the script will create rootfs_updated.cpio in the parent directory.
Update run.sh so it boots up the updated rootfs_updated.cpio file.
#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 nopti nokaslr" \
-no-reboot \
-cpu qemu64 \
-smp 1 \
-monitor /dev/null \
-initrd rootfs_updated.cpio \
-net nic,model=virtio \
-net user

The next step is to attach gdb to qemu.
Add this to run.sh don’t forget to pass another \.
-gdb tcp::12345 -S
QEMU explains that you should to use the -s and -S flag when attaching gdb to qemu.
Since we already passed -gdb tcp::12345 we don’t need to pass an extra -s flag.
Okay, let’s open another terminal, and run gdb.
Then type the command below.
target remote localhost:12345
Once you’re connected to gdb you should get something like this.
pwndbg>
Another reminder is that if you pass the -S flag, the terminal where you run qemu won’t show any booting messages or whatsoever.
Don’t panic, because thats’ the expected behavior.
I used to use gef as my gdb plugin, but for some unknown reason gef has a hard time attaching to qemu.
So, I recommend installing pwndbg.
If you insist using gef try out bata24’s gef.
Now, change the assembly so it uses the intel syntax.
set arch i386:x86-64:intel
Now that we’ve configured the settings for kernel debugging.
Let’s actual debug.
The /proc/kallsyms holds the kernel exported symbol definitions.

kallsyms reminds me of the nm command used in the userland.
For more information read the man page for kall_syms.
Then search for commit_creds.
# cat /proc/kallsyms | grep commit_creds
ffffffff8106e390 T commit_creds
Set a break point on commit_creds and continue program execution.
pwndbg> break *0xffffffff8106e390
pwndbg> conti
Here’s a screenshot of pwndbg.

You can also examine the rdi register as well.

In LK01 a kernel module named vuln is loaded.
You can see the list of loaded modules and the base address in /proc/modules.

The vuln module is loaded at 0xffffffffc0000000.
By the way you can locate the source code and the .ko ELF binary under src.
According to ptr-yudai, if you upload the vuln.ko into IDA and search for the module_close
function he says that the relative offset is 0x20f.
As I don’t have IDA, I uploaded vuln.ko to ghidra.

Unlike what ptr-yudai said, I actually couldn’t find any metadata on 0x20f.
Maybe it’s an IDA exclusive feature?, not sure…
Anyways if you set a break point on the memory address of vuln + 0x20f and inspect the disassembly you can check the function prologue.

ptr-yudai says that the vuln module is mapped to /dev/holstein.
Finally, you can use the setpi and nexti instructions in the kernel just as the userspace.