2017年2月6日

利用漏洞解锁锤子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。