安全技术

利用漏洞解锁锤子T1/2手机的bootloader

关于bootloader锁

Smartisan是手机中为数不多倾心于工业设计和用户体验的。老罗跨界过猛,也难免导致其最初的想法和现实存在差距。bootloader到底锁还是不锁,甚至曾被一个T1用户弄上法庭来质问。

weibo

当然,能从认为加锁是对系统的不自信,到后来发现解锁是安全隐患,绝对是个进步(loser口中的打脸)。技术层面来说,究竟T系列手机的bootloader能不能解锁呢?答案是,能。或者说,本来不能,但由于bootloader里存在的两个漏洞,恰好可解。

分析bootloader

正像Smartisan OS本身,其ROM目录结构也是极简的。firmware-update目录下emmc_appsboot.mbn就是bootloader镜像。由于是ELF格式,不需要更多的处理,就能逆向出不错的代码结构。无论是T1还是T2,bootloader的代码差不多,下面的分析选择的是T2的2.6版的ROM。

和很多高通芯片的手机一样,T2的bootloader是基于高通开源的lk。所以参考源码,可以很快梳理出bootloader的执行流程。启动后,根据按键组合,决定是否进入recovery,如果继续留在bootloader模式,就会注册一系列fastboot command,循环等待用户输入,决定下一步动向,如图1。

code1

图1.注册fastboot command

显然,control_flag为0的话,cmd_table中只有前四条命令被注册,后续命令就都无法使用了。通过观察cmd_table(如图2),可以发现那些真正令人激动的函数(比如oem unlock)都在比较靠后的位置上。

code2

图2.fastboot可以注册的命令列表

在搞清楚control_flag这个全局标记到底何去何从之前,不如先探探这仅存四条命令的究竟。reboot,reboot-bootloader命令正像他们的名字一样无趣,flash看起来就很有故事了。

执行flash命令时,如果control_flag为0,那就只能写一个名为security的分区。而control_flag为1时,所有其他分区就都可以写了,如图3所示:

code3

图3.写分区时的判断

联想之前fastboot command注册的过程,control_flag为0时,绝大部分功能无效,且分区不可写,control_flag应该就是is_allow_unlock,即bootloader是否上锁的标记。系统启动时,is_allow_unlock默认置0。当flash了security分区后,is_allow_unlock会有一次赋值操作,并且一旦赋值为1,就会提示解锁成功,如图4所示:

code4

图4.对security分区的检测,判断是否可以解锁

分析到这里基本可以肯定,T2提供了解锁功能,关键是写入security分区的内容是否能够经得住考验。

解锁bootloader

verify_security()函数比较复杂,涉及很多密码学算法的演绎。好在它使用的是openssl的标准库函数,识别起来有章可循。security分区内容采用的是RSA+MD5签名校验。合理的猜测是,官方本来设计的解锁流程其他厂商类似,即用户提交手机的序列号等信息,然后通过unlock时输入厂商给的解锁码(根据序列号计算出来的签名信息),实现解锁。只不过这一次解锁码是通过写入security分区实现输入。

security[128](security分区第128字节)是RSA初始化函数选择的依据,security[129]作为序列号长度。然后factory[5](factory分区的第5字节)起始的序列号作为MD5的计算依据,得到的hash值和security[0-127]签名信息验证的结果做比,相同返回1,否则返回0。这几乎是每个签名验证的都在用的标准化流程,采用的算法成熟,且由openssl实现(难怪发布会几百万门票钱捐给了openssl),基本不会有瑕疵。由于bootloader只存放了公钥e,没有私钥d,手机用户自己是没办法构造出128字节的签名信息的。

不过,由于代码上一些不大不小的问题,我们恰好可以绕过这些限制,构造出和序列号无关的通用解锁码。首先在RSA初始化时,如图5和6,当security[128]为66和67以外的数值时,初始化函数被选择为sub_F924A90。

code5

图5.根据security[128]指定的函数来初始化RSA密钥

code6

图6.RSA密钥初始化

跟进sub_F924A90后,可以看见图6所示的密钥填充,BN_bin2bn是openssl的库函数,用于将内存中存放的Big-Endian字符数组转化为Bignum类型,方便RSA的内部计算。私钥d填写的是伪数值,但p和q都填写的是真值。侧面说明写这段代码的人不太了解RSA,毕竟其安全性完全依赖于大数分解的NP难,而现在n的两个素数因子p和q都给了,虽然本意是加快计算速度,但私钥d也就因而可以从公钥e推出来了,d=e-1mod (p-1)(q-1),这就导致了第一个逻辑漏洞,用于伪造签名。

接下来,如图7,完成了RSA的初始化以后,会接着从factory分区读取数据:

code7
图7. 读取factory分区,得到序列号,然后计算MD5

究竟从factory分区读取多少字节是可控的,由security[129]决定。读取出来正常应该是一串字母开头后接一串数字的序列号,MD5后得到一串16字节的hash。最后利用RSA的公钥验证security[0-127]的128字节签名是否属于hash。

由于security[129]完全可控,就导致了第二个逻辑漏洞。如果该数指定为0,则MD5是针对一个空字符串进行计算的,计算结果总是d41d8cd98f00b204e9800998ecf8427e。所以无论是哪台手机,factory分区内容如何,签名验证将总是针对常量进行。只要构造该常量的签名写入security分区,就能够完成解锁。

为了减少padding,encoding等一系列开发可能造成的不确定性,在生成解锁码时,同样采用openssl的代码实现,示例如下:

 

#include <stdio.h>

#include <string.h>

#include <openssl/md5.h>

#include <openssl/crypto.h>

#include <openssl/rsa.h>

unsigned char m3_n[128] = {\

0xA4,0x0C, 0x69, 0x70, 0x25, 0x4F, 0x36, 0x49, 0x8E,\

0x83,0x4B, 0x74, 0x9A, 0x75, 0xC9, 0xF4, 0x7F, 0xE5,\

0x62,0xA8, 0xDE, 0x11, 0x13, 0x03, 0x57, 0x89, 0x31,\

0xCB,0x58, 0x84, 0xC8, 0x26, 0xBA, 0x2B, 0x60, 0xB5,\

0xB8, 0xA5, 0xD9, 0xBD, 0x27, 0x48, 0x3D,0x33, 0x38,\

0xA1,0x72, 0x62, 0x64, 0x87, 0x5E, 0x71, 0xF4, 0x1F,\

0xCB,0x68, 0x83, 0x92, 0xEA, 0x4B, 0xFF, 0x06, 0x38,\

0xAF,0xD5, 0x65, 0x55, 0x94, 0x04, 0x91, 0x88, 0xF7,\

0xA4,0x57, 0x72, 0x29, 0xFE, 0xEA, 0xB1, 0x27, 0x25,\

0xC1,0x12, 0x7D, 0x16, 0x6F, 0x13, 0xAF, 0xE2, 0x00,\

0x8D,0x5E, 0xA4, 0x0A, 0xB6, 0xF3, 0x71, 0x97, 0xC0,\

0xB0,0x60, 0xF5, 0x7C, 0x7F, 0xAA, 0xC4, 0x64, 0x20,\

0x3F,0x52, 0x0A, 0xA3, 0xC3, 0xEF, 0x18, 0xB6, 0x45,\

0x7D,0x72, 0x1E, 0xE2, 0x61, 0x0C, 0xD0, 0xD9, 0x1D,\

0xD0,0x5B\

};

unsigned char m3_e[1] = {3};

unsigned char m3_d[128] = {\

0x6d,0x5d,0x9b,0xa0,0x18,0xdf,0x79,0x86,0x5f,0x02,0x32,0x4d,0xbc,0x4e,0x86,0xa2,\

0xff,0xee,0x41,0xc5,0xe9,0x60,0xb7,0x57,0x8f,0xb0,0xcb,0xdc,0xe5,0xad,0xda,0xc4,\

0x7c,0x1c,0xeb,0x23,0xd0,0x6e,0x91,0x28,0xc4,0xda,0xd3,0x77,0x7b,0x16,0x4c,0x41,\

0x98,0x5a,0x3e,0xf6,0xa2,0xbf,0xdc,0xf0,0x57,0xb7,0x46,0xdd,0x54,0xae,0xd0,0x74,\

0x27,0xaa,0xad,0xf9,0xb9,0x33,0x8f,0x29,0x3b,0xf2,0xee,0x97,0x03,0x0b,0x5c,0xfc,\

0x92,0x95,0x6f,0x05,0xcd,0xbf,0x1c,0x77,0x16,0xce,0xd9,0x13,0xfb,0xf2,0x8f,0x74,\

0x09,0xca,0x78,0xf0,0xc7,0x4a,0xc2,0xc5,0xed,0x58,0xc1,0xfa,0xa1,0x6f,0x64,0x26,\

0x73,0x75,0x73,0x97,0x21,0xb4,0x01,0x13,0xad,0xd7,0xd5,0xbc,0x22,0x75,0x00,0xcb,\

};

int main(int argc, char*argv[]) {

MD5_CTX md5ctx;

unsigned chardigest[MD5_DIGEST_LENGTH];

unsigned charsigret[128];

unsigned int siglen;

unsigned chartestdata;

MD5_Init(&md5ctx);

MD5_Update(&md5ctx, &testdata, 0);

MD5_Final(digest, &md5ctx);

RSA *rsa =RSA_new();

rsa->n =BN_bin2bn(m3_n, 128, rsa->n);

rsa->e =BN_bin2bn(m3_e, 1, rsa->e);

rsa->d =BN_bin2bn(m3_d, 128, rsa->d);

RSA_sign(4,digest, 16, sigret, &siglen, rsa);

FILE *fp =fopen(“security.img”,”wb”);

fwrite(sigret, siglen, 1, fp);

fwrite(“\x40\x00”, 2, 1, fp);

fclose(fp);

return 0;

}

刷入security.img后,手机就可以解锁了。虽然上述分析是基于T2的ROM,T1也完全适用。如图8所示,T1刷入security.img同样可以解锁。

unlock
图8. T1刷入security.img后解锁

t2_unlock

图9.T2刷入security.img后解锁

And Then Some

2014年老罗在微博上提过关于bootloader方面的打算,“官方会提供 boot loader,方便你刷机,只是刷机后会失保”,所以初代ROM里的确如我们所见保留了解锁bootloader的功能。2016年有人因为提供解锁而状告Smartisan,老罗胜诉后说道“我在微博上说过做bootloader,但技术部门因安全考虑否决了,我代表我自己道歉。”,所以肯定是取消了该功能。尽管官方从来没有发布过任何解锁的方法,底层代码倒是可以清晰反映出这段经历。

对于T1和T2,2.6.7是最后一个可以解锁的ROM版本号,2.6.8开始,fastboot command列表被改写为图10所示内容,大部分指令被阉:

code8

code9

图10. 2.6.8后的版本中fastboot已经没有什么实质功能了

所以如果要解锁3.x的Smartisan OS,可以下载2.6.7的ROM完成降级,毕竟旧版本的ROM同样带有签名,使用recovery时允许刷入手机。更新到旧版的bootloader后,再用fastboot flash security security.img进行解锁。解锁后,每次升级用第三方无签名验证的recovery,更新除bootloader以外的模块即可。这样即便最新系统暂时没有公开的内核漏洞,也能root。

一般的Android手机,只要有签名认证的老版本bootloader里有漏洞,在系统没有开启限制(比如SW_ID)时,总可以通过降级,解锁,然后升级回新系统,刷入supersu的方式root。

mach portal漏洞利用的一些细节

前不久GP0的研究员Ian Beer公布了针对iOS 10.1.1的漏洞细节及利用代码,通过结合三个漏洞获取设备的root shell。之后意大利研究员@qwertyoruiopz在此基础上加入绕过KPP保护的漏洞利用并发布了完整的iOS10越狱

Ian Beer已经对漏洞的成因和利用做了相关描述,这里将不再阐述,而是介绍一些利用的细节以及可能的改进建议。

整个exploit chain包含了三个漏洞:

  • CVE-2016-7637 用于替换了launchd进程中往com.apple.iohideventsystem发消息的port
  • CVE-2016-7661 造成powerd崩溃重启,从而在接管com.apple.iohideventsystem后获取powerd的task port,进而获取host_priv
  • CVE-2016-7644 导致内核port的UAF,进一步获取kernel_task

替换launchd中的port

内核中的ipc_object对象对应到用户态下是一个name(int类型),每个进程的ipc_space_t中保存了name与object之间的映射关系。相关代码可以在ipc_entry.c中查看,ipc_entry_lookup函数将返回name对应的ipc_entry_t结构,其中保存了对应的object。name的高24位是table中的索引,而低8位是generation number(初始值是-1,增加步长是4,因此一共有64个值)

#define    MACH_PORT_INDEX(name)       ((name) >> 8)
#define    MACH_PORT_GEN(name)     (((name) & 0xff) << 24)
#define    MACH_PORT_MAKE(index, gen)  \
        (((index) << 8) | (gen) >> 24)

被释放的name会被标记到freelist的起始位置,当再创建的时候会有相同的索引号,但是generation number会增加4,因此当被重复释放和分配64次后会返回给用户态完全相同的name,从而可以完成劫持。

#define    IE_BITS_GEN_MASK    0xff000000  /* 8 bits for generation */
#define    IE_BITS_GEN(bits)   ((bits) & IE_BITS_GEN_MASK)
#define    IE_BITS_GEN_ONE     0x04000000  /* low bit of generation */
#define IE_BITS_NEW_GEN(old)   (((old) + IE_BITS_GEN_ONE) & IE_BITS_GEN_MASK)

简单的测试代码

    for (int i=0; i<65; i++)
    {
        mach_port_t port = 0;
        mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
        printf("port index:0x%x gen:0x%x\n", (port >> 8), (port & 0xff));
        mach_port_destroy(mach_task_self(), port);
    }

(更多…)

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

漏洞说明

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

(更多…)

Pegasus – 针对iOS设备的APT攻击分析

苹果在今天凌晨突然推送了iOS9.3.5更新,并且更新日志中提到修补了三个安全漏洞。随后Citizen Lab发布文章指出这三个0day被用于针对特殊目标远程植入后门,而Lookout则给出了对Pegasus的具体技术报告

远程植入的流程是首先引导用户访问指定页面,此时会触发webkit漏洞(CVE-2016-4657)获取代码执行权限,随后利用漏洞(CVE-2016-4655)泄露内核的加载基地址,最后触发漏洞(CVE-2016-4656)获取内核态的代码执行权限。在获取最高权限后,Pegasus还会进一步针对persistence处理,保证系统重启后后门仍然工作。

内核漏洞

通过攻击流程可以知道两个内核漏洞均是在浏览器内被触发的,同样在APP沙盒规则内也能利用该漏洞。盘古发布的9.3.3越狱同样也是利用了沙盒内的漏洞,苹果非常迅速的推送了9.3.4的更新。正如我们在今年Blackhat上讨论的,沙盒内直接攻击内核的漏洞将是苹果用户面临的重要风险,苹果的安全响应也在提速。

其中CVE-2016-4655漏洞是由于读取栈数据时缺乏边界检查,导致能够获取栈上额外的数据,而函数的返回地址一般会被保存在栈上,因此达到泄露内核地址的目的。

而CVE-2016-4656漏洞则是一个典型的UAF漏洞,通过精心构造数据可以在Free之后先分配对象来重新占用之后再触发Use,也可以进一步转换成double free。

Persistence

(更多…)

BlackHat USA 2016

BlackHat201622

盘古团队于2016年8月5日在美国拉斯维加斯举办的顶级安全峰会Blackhat USA 2016上分享了”Pangu 9 Internals”的议题,获得参会技术人员的广泛好评。

Slide下载: us-16-Pangu9-Internals

FBI vs Apple:FBI是幸运的

fbi-vs-apple

最近闹的沸沸扬扬的FBI vs Apple的事件,期间经历了FBI在法庭上要求苹果开发通用的破解锁屏密码的程序(并非媒体所传的后门), 苹果发布iOS 9.3,FBI要求查看iOS源代码到最后苹果威胁要在iCloud中用点对点加密代替现在的Master Key方案,最终该事件在前几天尘埃落定。据传言是在某个神秘选手的帮忙下,FBI终于解开了那台iPhone 5C手机。

最近的媒体传闻都是说苹果的锁屏密码多么难破解, 神秘选手技术多么厉害, 其实据我们分析, FBI这一次只是运气好, 碰到的是一台iPhone 5C, 如果这台设备是iPhone 5S的话, 那么很大可能还要通过法律手段。

苹果的锁屏密码到底有多么难破呢?为什么说这一次说FBI是幸运的?对于我们普通用户来说有什么影响?

请看我们对苹果数据加密机制以及锁屏密码保护机制的技术分析。

(更多…)

iOS 9.2/9.2.1修补的内核漏洞

苹果在iOS 9.2iOS 9.2.1中陆续修补了大量漏洞,其中Google Project Zero团队的Ian Beer报告了多个内核漏洞,并且在苹果修补后给出了漏洞细节

条件竞争漏洞

通过查看公告可以发现除了熟知的UAF类型漏洞外(例如Pangu9中使用的即是UAF漏洞),还包含了多个条件竞争类型的漏洞。通过分析漏洞的细节可以发现苹果在许多情况下都没有考虑用户态多线程调用导致的竞争问题,因此不排除在别的模块也有类似的漏洞(例如在未开源的内核扩展中)。

此次被修补的漏洞包含数个能被实际利用的漏洞,其中有一个漏洞能够绕过地址随机等保护机制完全攻破内核(可被用于越狱)。下面会简单分析两个漏洞的细节,讨论编写利用的一些思路。

double free in IOHIDEventQueue::start

通过查看542报告可以知道漏洞的主要原因在于IOFreeAligned释放dataQueue后没有置空。虽然大多数情况下随后的initWithEntries函数会对dataQueue重新赋值,但是如果initWithEntries失败的话,dataQueue并不会被赋值。如果再次调用start函数就会导致double free的问题。

void IOHIDEventQueue::start() 
{
...
        if (dataQueue) {
            IOFreeAligned(dataQueue, round_page_32(getQueueSize() + DATA_QUEUE_MEMORY_HEADER_SIZE));
        }

        if (_descriptor) {
            _descriptor->release();
            _descriptor = 0;
        }

        // init the queue again.  This will allocate the appropriate data.
        if ( !initWithEntries(_numEntries, _maxEntrySize) ) {
            goto START_END;
        }

(更多…)

POC2015 & RUXCON2015 盘古团队议题

盘古团队于2015年10月24日、25日在澳大利亚墨尔本举办的Ruxcon 2015会议上分享了”iOS 9 Security: The Story Beyond The White Paper”的议题,并于2015年11月5日、6日在韩国首尔举办的POC 2015会议上分享了”Hacking from iOS8 to iOS9″的议题。

poc01

poc02

POC2015 & RUXCON2015 议题PDF下载

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

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

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