QemuPWN
学习下qemupwn,qemu的基础知识QOM模型,Timer相关可以看上一篇文章
一些前置知识,基本上是参考 qemu-pwn-基础知识 « 平凡路上 (ray-cp.github.io)
前置知识
地址转换
qemu虚拟机的地址转换,由于EPT的存在,实际是经过三层
GVA -> GPA -> HPA
GVA Guest Virtual Address
GPA Guest Physical Address
HPA Host Physical Address
我记得是由于GPA 到HPA,物理页刚好是映射到Qemu Process的一个基址开始的对应偏移上,所有这里图中显示的是四个阶段(可能有误)
qemu启动脚本中,-m 指定的就是物理机中qemu进程分配给虚拟机的内存,这是一大块内存,这块内存会被虚拟机视为其物理地址
1 | ./qemu-system-x86_64 \ |
首先把qemu中虚拟地址转换成物理地址,该地址就是qemu进程为其分配的内存的偏移,因此用qemu进程分配出来的内存基址加上这个偏移就是虚拟机的虚拟地址在宿主机上的地址
GVA -> GPA的代码为
1 |
|
PCI设备
lspci可以查看当前主机的所有PCI总线信息,以及所有连接的PCI设备
1 | ubuntu@ubuntu:~$ lspci |
xx:yy.z的格式为总线:设备:功能
PCI设备通过VendorID,DeviceID,Class Code区分
1 | ubuntu@ubuntu:~$ lspci -v -m -n -s 00:03.0 |
也可通过查看其config 文件来查看设备的配置空间,数据都可以匹配上,如前两个字节1234为vendor id:
1 | ubuntu@ubuntu:~$ hexdump /sys/devices/pci0000\:00/0000\:00\:03.0/config |
查看设备内存空间:
1 | ubuntu@ubuntu:~$ lspci -v -s 00:03.0 -x |
可以看到该设备有两个空间:BAR0为MMIO空间,地址为febf1000
,大小为256;BAR1为PMIO空间,端口地址为0xc050
,大小为8。
可以通过查看resource
文件来查看其相应的内存空间:
1 | ubuntu@ubuntu:~$ ls -la /sys/devices/pci0000\:00/0000\:00\:03.0/ |
resource
文件包含其它相应空间的数据,如resource0(MMIO空间)以及resource1(PMIO空间):
1 | ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource |
每行分别表示相应空间的起始地址(start-address)、结束地址(end-address)以及标识位(flags)。
Strng
入门级神题,感谢raycp大佬,因为分析的太多了,这里只贴一下链接
qemu pwn-Blizzard CTF 2017 Strng writeup « 平凡路上 (ray-cp.github.io)
babyqemu
题目分析
DMA类型的入门题
qemu pwn-hitb gesc 2017 babyqemu writeup « 平凡路上 (ray-cp.github.io)
文件解压出来,pc-bios是启动qemu的必需
核心文件是qemu-system-x86_64,这个就是在qemu源码中添加题目相关代码,然后编译出来的,对于qemupwn一般是添加一个新的驱动设备,所以新加的驱动设备会在里面
lauch.sh里面会给新设备的名字 -device xxx,虽然可以通过直接在main函数里加上设备初始化以隐藏设备名称,但是没必要,
这题符号没去,正常运行需要安装libcurl3
直接输入root后就能登陆
知道名字后,一般就是直接搜相关函数
这种没去符号的
能看到其设备类HitbState,但是在Local Types中能看到相关结构,不一定能在struct里面看到其具体结构
QOM模型中,设备实现必定会经过三个阶段函数
class_init -> instance_init -> realize
Class-init能看到设备号
realize里面,开了一个定时器hitb_dma_timer
另外开了一个新线程hitb_fact_thread,注册了一个MMIO因为了开了符号,所以看的比较清楚
Mmio_read比较简单,只是读取各种结构体的参数
几个关键操作就是
也就是write基本都是给dma结构体赋值
既然触发了定时器,那就看一下定时器
定时器函数主要做下面几个
对应一下代码
情况3,IDA F5看着很乱,直接看汇编比较明白
![loc 284120: [rdi+HitbState. dstl dword ptr [hitb+HitbState.dma.cnt] ; len sub lea call and rax, edx, ecx rbp, rdi, rsi, ecx 4øøøøh is write rdi+rax+HitbState. dma_buf [rdi+HitbState . dma . src] addr r•bp cpu_phy s i c a I _memory_rw rax, [hitb40880h] rdx, rax edx, 4 short loc 2840E8
Memory_rw(dma.src,dma_buf[dma.dst - 0x40000],dma.cnt,)
所以这里是模拟实现的DMA操作
利用mimo设置好src dst和cmd之后利用timer就可以完成拷贝
我们可以看到write时len 以及地址都没有限制,意味着我们对buf越界读写
向上越界是enc函数,enc在这里初始化
如果我们通过越界读泄露了enc,就能根据偏移算出程序基址,然后再根据偏移找到system plt的地址,最后根据再越界写把system写入到enc中,最后把参数/bin/sh当作参数调用system,
exp分析
分析一下exp
首先把驱动的resources文件映射出来,Qemu题操作MMIO都要这样做
申请一块用户空间存储泄露的地址
mlock是锁定内存,不让其被交换,泄露enc的地址
这里buf也就是0x1000,所以这里传的地址实际就是越界到enc的地址
根据do_read的逻辑填写数据,最后根据泄露的地址打简单的system
hfdev
虎符线上赛的一道,大概也算DMA吧
2022功能能够off_by_one可以实现多此调用定时器,实现溢出,任意地址读leak,改bk或者改timer结构体劫持
题目分析
逻辑逆向
参考的hfState结构体,建议复现还是自己逆一下
1 | +A60 hwaddr |
1 | //Port_read的功能 |
可以更多逻辑的需要调用qemu_bh_schedule
bh首先从物理地址hwaddr处读len字节到buf
然后回根据前8个字节switch
Case 0x20
一个物理地址写
Case 0x30
V1是buf的地址,所以这里会判断是否小于0x100
大概能得到两个新结构体成员
+A78 timer_cpy_len
+1190 timer_cpy_buf
并根据+1188决定起不起动定时器,
Case 0x10根据A8B会继续分为两个功能,第一个是0x2202
2022功能
从+E88处开始,这边是从收到的数据的A8F处开始,逐字节异或,
len是+A70和+A8D中的最大者,
那么A70从哪里来呢,看了一圈,初始化的时候A70是0,定时器中会赋值一次A70
然后最重要的是这里do_while写的存在一个off_by_one
再来看一看定时器功能
先把+1188设置为0,也就是定时器只会启动一次
做一个memcpy
memcpy之后A70里的值会加上A78的值,A78从上面看是拷贝的len,而A78是在前面case30处赋值的
调用逻辑就是先调用2202,置A70为0x200
再case30,触发定时器,timer_cpy_len最大是0x100,调用完之后A70就变成了0x300
再2022就是offbyone,E88 + 0x300 = 0x1188,刚好能覆盖到timer_go标志位,导致定时器函数无限调用,无限调用就意味着A70可以无限叠加,最后因为其从物理地址处读数据,物理地址和长度都可以由我们指定,我们只要在程序中把准备好的数据地址转换成物理地址给他传过去就行
最后来分析一下exp
exp分析
首先是init,init主要是分配一块内存,主要用来传数据,因为hfdev是从物理地址读数据,所以顺带转换一下地址
Iopl(3)是必须执行的,要进行port_read/write必须执行这个命令申请权限
通过port_write设置读取数据的地址和长度
读取数据
Send_pkg就是触发schedule
Send_data_in执行完之后,+A70处为0x200
再触发timer,此时+A70处为0x300
通过off_by_one设置timer_go标志为非0,
再次触发timer,此时+A70已经变0x310
readdata,越界读0x310个字节到用户层的dmabuf里
读出的dma_buf+0x308就是0xE88+0x308 = 0x1190,刚刚被设置成+A88 buf的地址
这个buf其实就是hfstate对象的地址,根据偏移关系得到timer-addr和bh-addr的地址
再次触发一次timer,又把+A70 拉长8个字节
修改buf的地址到bh的cb的地址,用send_data_out修改
此时buf被修改到了bh的cb的位置,最后利用部分的逻辑是修改bh的cb和opague来实现控制流劫持
exp调试
先读出几个结构体的偏移,timer和bh以及hfstate都在堆上
获取的heap addr应该是0x5555576dc040 + 0xA88
5555 576D CAC8
所以这里获取的地址都没问题
执行完这里,确实设置了bh->cb的地址
这里设置的是延迟读,这里设置完地址之后才会触发timer,然后把地址读过来
最后一步send_date_out的作用是修复timer指针,因为前面已经覆盖掉了
然后再调用timer修改bh到faker_bh,
最后一个schedule触发system
但是莫名其妙看不到flag,流程应该是没问题的,困惑?
传exp
因为这个qemu就没开ssh,所以传scp,可以重打包img文件,ubuntu修改img文件很简单
先用mount挂载上去,然后把exp拷贝进去,再umount
总结
什么时候能秒qemu题