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

root

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

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.

pwndbg

You can also examine the rdi register as well.

rdi

In LK01 a kernel module named vuln is loaded.

You can see the list of loaded modules and the base address in /proc/modules.

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.

module_close

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.

offset

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.