CTF-pwn-虚拟化-qemu前置知识

文章目录

参考

https://xz.aliyun.com/t/6562?time__1311=n4%2bxnD0DRDBAi=GkDgiDlhjmYh2xuCllx7whD&alichlgref=https://www.bing.com/#toc-3

地址相关

每个qemu虚拟机都是宿主机上的一个进程,在进程中用mmap分配出大小为0x40000000字节的宿主机的虚拟内存来作为虚拟机的物理内存

GVA:guest virtual address(虚拟机中的虚拟地址)

GPA:guest physical address(虚拟机中的物理地址)

HVA:host virtual address(宿主机中的虚拟地址)

HPA: host physical address(宿主机中的物理地址)

GVA通过客户机的页表得到GPA,GPA实际是由宿主机进程mmap出来的空间内的偏移。再加上起始地址就是HVA(mmap分配出相应虚拟机申请大小的内存,用于给该虚拟机当作物理内存)

  1. 设备可以申请两类空间,memory mapped I/O(MMIO)和port mapped I/O(PMIO),并在配置空间中用Base Address Registers(BAR)来标记内存地址信息
c 复制代码
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>

#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN     ((1ull << 55) - 1)

int fd;

uint32_t page_offset(uint32_t addr)
{
    return addr & ((1 << PAGE_SHIFT) - 1);
    //虚拟地址addr在其所在页面内的偏移量
}

uint64_t gva_to_gfn(void *addr)
{
    uint64_t pme, gfn;
    size_t offset;
    offset = ((uintptr_t)addr >> 9) & ~7;//页表条目相对于文件开始的偏移量(GFN, Guest Physical Frame Number)
    lseek(fd, offset, SEEK_SET);//移动文件指针到计算出的偏移量处,准备读取PTE。
    read(fd, &pme, 8);  //从文件中读取8字节到pme变量即物理块起始地址
    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);
}

int main()
{
    uint8_t *ptr;
    uint64_t ptr_mem;
    // uint64_t ptr_malloc;
    fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }

    ptr = malloc(256);
    strcpy(ptr, "Where am I?");
    printf("%s\n", ptr);
    ptr_mem = gva_to_gpa(ptr);
    
    printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);
    // ptr_malloc = gva_to_gpa(&malloc);
    // printf("malloc physical address 0x%"PRIx64" \n",ptr_malloc);

    getchar();
    return 0;
}

gdb attach后vmmap可以查看到启动脚本中的-M指定的分配大小

交互相关

每个PCI设备有一个总线号、一个设备号、一个功能标识,存在PCI域,PCI域最多可以承载256条总线, 每条总线最多可以有32个设备,每个设备最多可以有8个功能

lspci 可以查看设备所在的域、总线号、设备号、功能号,不同版本lspci 显示的内容不一样

其中对于形如0000:00:03.0的数据,0000是域,00是总线号,03是设备号,0是功能号

resource中的pmio端口和mmio起始地址是固定的

mmio:将设置的寄存器映射到端口,访问端口来访问寄存器

pmio:将设置的寄存器映射到内存, 访问内存来访问寄存器

  • hexdump /sys/devices/pci0000:00/0000:00:03.0/config查看配置信息如deviceid和vendorid
  • 访问memory mapped I/O内存:打开获取fd后用mmap映射内存地址,直接操作内存
c 复制代码
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
  • 访问port mapped I/O内存:申请访问端口权限后用in、out访问
c 复制代码
iopl(3);// iopl(3) 表示打开对全部IO端口的访问权限
inb(port); //从端口读一个字节
outb(val,port); //往端口写一个字节val

设备会注册自己的MMIO和PMIO读写函数,当检测到访问其所在的内存或端口时调用注册的读写函数,实现对设备内存的读写功能

当对设备其所在的内存访问时,会调用注册的读mmio函数d3dev_mmio_read,读的位置是基地址+0x128,是4字节。被d3dev_mmio_read识别为了rsi为0x128(偏移),rdx为4


当对设备其所在的内存写时,会调用注册的写mmio函数d3dev_mmio_write

c 复制代码
#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>


unsigned char* mmio_mem;

void die(const char* msg)
{
    perror(msg);
    exit(-1);
}


void mmio_write(uint32_t addr, uint32_t value)
{
    *((uint32_t*)(mmio_mem + addr)) = value;
}

uint32_t mmio_read(uint32_t addr)
{
    return *((uint32_t*)(mmio_mem + addr));
}


int main(int argc, char *argv[])
{

    // Open and map I/O memory for the strng device
    int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);//O_SYNC标志表示同步I/O操作,确保对文件的写操作在返回前完成
    if (mmio_fd == -1)
        die("mmio_fd open failed");

    mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    //PROT_READ | PROT_WRITE:指定映射区域的访问权限,这里允许读写。
    //MAP_SHARED:映射类型,共享映射意味着对映射区域的修改会反映到设备上,其他映射同一资源的进程也能看到修改。
    //mmio_fd:之前通过open获得的文件描述符。
    //0:文件偏移量,从资源的起始位置开始映射。
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");

    printf("mmio_mem @ %p\n", mmio_mem);

    mmio_read(0x128);
    mmio_write(0x128, 1337);
    mmio_read(0x128);

}

可以发现rsi会被设置成偏移

c 复制代码
#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>

void die(const char* msg)
{
    perror(msg);
    exit(-1);
}


uint32_t pmio_base=0xc040;

uint32_t pmio_write(uint32_t addr, uint32_t value)
{
    outl(value,addr);
}

uint32_t pmio_read(uint32_t addr)
{
    return (uint32_t)inl(addr);
}

int main(int argc, char *argv[])
{


    if (iopl(3) !=0 )
        die("I/O permission is not enough");
    pmio_read(pmio_base+4);
    pmio_write(pmio_base+4,1);
    pmio_read(pmio_base+4);

}
  • qemu中执行其中的程序时,当涉及到设备读写此时进行的操作会是qemu进程执行的,不是虚拟中的,就是对qemu进程上的虚拟地址相关进行操作,不是mmap出来作为宿主机的那部分(就和平常用户态一样了)
  • 注册的相关读写函数中的addr是偏移,mmio范围是0x800,pmio是0x20

配置相关

PCI设备有其配置空间来保存设备信息,头部最开始的数据为Device id和Vendor id

在初始化设备时会初始化四个比较重要的结构体:TypeInfo -> TypeImpl -> ObjectClass -> Object,每个 Object对应一个具体的device,其构造函数在qemu启动用-device参数加载设备时调用

设备读写操作函数的第一个参数是这个Object类的指针

调试

打包exp到文件系统

bash 复制代码
gcc -static exp.c -o   exp
find . | cpio -o --format=newc > ../rootfs.img

启动qemu

bash 复制代码
./launch.sh
bash 复制代码
touch .gdbinit
往里面写入断点
b*d3dev_pmio_write
b*d3dev_pmio_read
b*d3dev_mmio_write
b*d3dev_mmio_read
bash 复制代码
ps -ef | grep qemu
一般是最下面的那个进程
bash 复制代码
sudo gdb attach -p 进程号

在qemu中

bash 复制代码
./exp

此时gdb会断在断点上,然后开始调试

成功attach之后即可下断点调试,开启了pie保护,且没有符号表可以用pwndbg的$rebase()根据ida中的地址确定进程中的真实地址

待完善(以后做题用到啥再加吧)

相关推荐
Mr_Fmnwon13 小时前
【我的 PWN 学习手札】tcache stash unlink
pwn·ctf·heap·tcache
CH13hh1 个月前
常回家看看之house of kiwi
pwn·ctf··house
波克比QWQ1 个月前
Large Bin Attack 源码调试
笔记·pwn·堆入门·large bin
Xh1Xxhg2 个月前
【pwnable.kr】0x02-collision Writeup
pwn·二进制安全·pwnable.kr
波克比QWQ2 个月前
FSOP,glibc-2.23攻击IO_list_all
pwn·ctfer·堆入门·fosp
看星猩的柴狗2 个月前
2024 HNCTF PWN(hide_flag Rand_file_dockerfile Appetizers TTOCrv_)
pwn
C0Lin2 个月前
PHP pwn 学习 (2)
php·pwn
波克比QWQ3 个月前
unsortedbin attack
笔记·pwn·ctfer·堆入门·unsortedbin
想拿 0day 的脚步小子3 个月前
9.pwn 栈溢出
linux·安全·渗透·二进制·pwn