Description

I have no idea what to put here

DISCLAIMER: Please test your solve locally before spawning a remote instance of the challenge!

Author’s Note

The challenge has lot’s of ways to be solved! It was intended for beginner to intermediate kpwn players. The path here explained is pretty straight forward and hopefully you will understand it.

If you want to share your exploit you can contact me on discord (handle down below!).

Challenge

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <err.h>

#define DEV "/dev/mem"

#define PAGE_SIZE 0x1000
#define GET_PAGE_BASE(address) address & ~(PAGE_SIZE-1)
#define GET_OFFSET(address) address & (PAGE_SIZE-1) 

__attribute__((constructor)) void init();
int get_long(char* str, unsigned long* value);
int physical_write(int dev, unsigned long address, unsigned long value);

__attribute__((constructor))
void init(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    setbuf(stdin, NULL);
}

int get_long(char* str, unsigned long* value){
    char* check;

    *value = strtoul(str, &check, 16);

    if(check == str)
        return 1;
    
    return 0;
}

int physical_write(int dev, unsigned long address, unsigned long value){
    int br;

    if(lseek(dev, address, SEEK_SET) == -1)
        return 1;

    br = write(dev, &value, sizeof(unsigned long));

    if(br == -1)
        return 1;
    
    return 0;
}

int main(int argc, char **argv){
    unsigned long address, value;
    int dev, check;

    dev = open(DEV, O_RDWR | O_SYNC);
    if(dev == -1)
        err(1, "could not open " DEV);

    if(argc != 3) {
        puts("./chall <address> <value>");
        return 1;
    }
    
    check = get_long(argv[1], &address);
    if(check || (address & (sizeof(unsigned long)-1)) != 0){
        puts("invalid address");
        return 1;
    }

    check = get_long(argv[2], &value);
    if(check){
        puts("invalid value");
        return 1;
    }

    if(physical_write(dev, address, value) != 0)
        err(1, "could not interact with " DEV);

    close(dev);
    return 0;
}

Challenge setup

The objective of the challenge was to gain root and read /flag.txt by interacting with the vulnerable program /usr/sbin/chall owned by root, only executable by other users and with the setuid flag set (look at /init for more information). The privileged binary lets the user write 8 bytes per run in a 8 byte aligned physical address by interacting with /dev/mem.

/dev/mem

This character device enables root users to read and write into memory using physical memory addresses. In this case CONFIG_STRICT_DEVMEM is disabled but CONFIG_HARDENED_USERCOPY is enabled, this means that you can primarily write in the kernel’s .data section. You cannot write into read only mappings because the 16th bit of CR0 (write protect) is enabled.

Exploitation overview

There are many different path to solve the challenge. I’ve personally found a path that unfortunately did not work in the end on the remote instances of the challenge (more information at the end) so in this write-up I will describe @Erge’s exploit (shout out to him!). The objective is to overwrite the exploit’s cred struct and gain root privileges. Be careful! CONFIG_STATIC_USERMODEHELPER is enabled so you cannot overwrite modprobe_path to privesc.

We can divide the exploit in a few simple steps:

  1. Find the physical base of the kernel (aka the start of the kernel’s .text area)
  2. Find the virtual base of the kernel. This will be useful to locate gadgets and the init_cred symbol that will be used in the next steps.
  3. Overwrite function pointers or vtables in kernel memory
  4. Gain arbitrary virtual memory read and write
  5. Traverse the linked list of task structures to overwrite the exploit’s cred struct