Saarsec

saarsec

Schwenk and pwn

X-MAS CTF 2019 - PhotonOS

In this challenge, the goal was to exploit a custom made kernel called PhotonOS, which is publicly available at https://github.com/JustBeYou/PhotonOS/tree/xmas-challenge1. However, we went the easy and probably unintended way to get both flags.

Flag locations

Flag 1 is simply stored as a global variable:

char flag1[] = "X-MAS{FLAG 1 SHOULD BE HERE}";

void kernel_main()
{
    switch_on = 1;

    //welcome();
    login();
    prompt();
}

Flag 2 is encrypted using a xor encryption and stored alongside the key at address 0xdead0000

void kernel_init(multiboot *mboot_ptr, size_t init_stack)
{
    ...
    int fd = kopen("/mnt/initrd/f2.txt", O_RDONLY); // key
    kread(fd, (void*)0xdead0000, 100);
    kclose(fd);
    
    fd = kopen("/mnt/initrd/testfile.txt", O_RDONLY); // flag
    kread(fd, (void*)(0xdead0000 + 100), 100);
    kclose(fd);
 
    char *ptr1 = (char*)0xdead0000;
    char *ptr2 = (char*)(0xdead0000 + 100);
    char x;
    for (int i = 0; i < 100; i++) {
        x = ptr1[i] ^ ptr2[i];
        ptr2[i] = x;
    }

    char *ptr_start = (char*)initrd_start;
    char *ptr_end = (char*)initrd_end;
    for (char *i = ptr_start; i < ptr_end; i++) {
        *i = 0;
    }
    unmap(0xdead0000);

    wstr_color("\nDONE!", COLOR_GREEN);    
    jmp_to_usermode();
}

The vulnerability

There was no difference between userspace addresses and kernelspace addresses. Also there are plenty of bufferoverflows, e.g. the following one where we can overflow cmd:

char *getsk(char *str)
{
    int c = tty_read_char();
    int i = 0;
    while (c != LF && c != CR) {
        if (c != DEL) {
            str[i++] = c;
            tty_write_char(c);
        } else if (c == DEL && i > 0) {
            str[--i] = 0;
            tty_write_char(DEL);
        }
        c = tty_read_char();
    }
    str[i] = '\0';
    tty_write_char('\n');
    return str;
}

void prompt() {
	char cmd[4096];
	while (true) {
		memset(cmd, 0, 4096);
		if (custom_prompt) {
			printk("%s ", prompt_msg);
		} else {
			printk("%s@%s:%s# ", user, machine, get_cwd());
		}
		getsk(cmd);
		if (cmd[0] != 0) {
		    if(shell(cmd)) {
		        printk("Command '%s' not found.\n", cmd);
		    }
		}
	}
}

So the obvious idea would be a ROP. However, when setting up the challenge locally, we saw that GRUB2 is used and we can switch to the grub shell at boot.

Leaking the flags

The boot image consists of three files: /boot/initrd, /boot/photon.elf and /boot/grub/grub.cgf. The initrd contains information about the filesystem, so it contains the flag 2 which is stored in the filesystem as testfile.txt. This file is located at the end of the initrd and we can just use cat /boot/initrd in the grub shell to get the flag. Unfortunately, the flag 1 is stored somewhere in the middle of photon.elf, at offset 0x11000 to be more precise. Transmitting the screen via netcat is much slower than printing the characters to the screen. So, if printing a large file, we miss a lot of data in between. Therefore, we can not just use cat to leak this file. But, fortunaly, grub comes with hexdump, which allows us to print a file from a given offset.

Flag 1 Flag 1

While this challenge did not really improve my kernel exploitation skills due to my own lazyness, I learned a few new things about grub and how to make netcat send each character immediately instead of sending them line by line: stty -icanon -echo && nc 127.0.0.1 1337. And of course, I had a lot of fun solving this and other challenges of this year’s X-MAS CTF!