在绝大多数未Root且使用原厂(Stock)ROM的安卓手机上,当我们启用网络共享(Tethering,官方介绍请参见:https://support.google.com/nexus/answer/2812516?hl=en)功能前,首先需要与无线网络提供商进行服务开通状态检查(Provisioning Check),从而确保我们的数据流量计划是允许共享的。本文主要讲解了Tethr,这是一种在7.1.2版本前能够绕过安卓设备服务开通状态检查的方法。
在发现这一问题后,我向安卓开发团队报告了这一漏洞,并获得了CVE-2017-0554编号,目前在7.1.2版本之后已经对此问题进行了修复。关于该漏洞的具体细节,请参见:https://source.android.com/security/bulletin/2017-04-01#eop-in-telephony
背景介绍
安卓系统中,网络共享的这一功能是由设备的build.prop文件进行控制的,该文件通常位于/system/build.prop。在默认情况下,启动网络共享之前需要进行服务开通状态检查,但我们可以通过添加如下行来绕过这一项检查:
net.tethering.noprovisioning=true
没有Root过的设备并没有权限去编辑/system/build.prop文件,因此,ROM制造商通常会自行设置这个属性。举例来说,Google Nexus 6P就默认设置net.tethering.noprovisioning为true,绕过了该项检查。然而,这只是一个例外,并不普遍。Google Nexus 5X、Pixel和Pixel 2全部都会执行服务开通状态检查。
漏洞概述
当我们在安卓系统上启用网络共享时,操作系统将首先与运营商通信,进行服务开通状态检查,以确定用户的套餐计划是否允许共享。如果允许,则立即启用共享,否则会向用户显示相应的错误提示。
在没有插入SIM卡的情况下,不会执行该项检查,会直接允许共享。此外,如果在没有插入SIM卡的手机上先启用网络共享功能,然后再插入SIM卡,则共享会立即被关闭,在这一点上确保了验证的可靠性。
然而,如果我们在无线通信(Radio)连接的过程中启用网络共享,则不会进行服务开通状态检查,并且在无线通信连接建立之后,仍然会保持网络共享启用的状态。
由此,我从中发现了两个问题。第一个问题是:在原厂安卓系统上,用户自行安装的应用程序,具有重置蜂窝调制解调器的权限。第二个问题是,一旦蜂窝调制解调器完成重连接过程,将不会再进行服务开通状态检查。
上述这两个BUG,绕过了“net.tethering.noprovisioning”的服务开通状态项。不论做任何服务开通状态,都能允许安卓系统在“net.tethering.noprovisioning=true”的状态下运行,也就是能允许安卓系统绕过服务开通状态检查。
Tethr演示
在我们深入研究细节之前,请大家观看我们的演示视频。在视频中,我们展示了在安卓系统的用户界面中何时应该启用网络共享功能、如何执行的服务开通状态检查以及不允许网络共享的情况。然后,当我们尝试运行Tethr演示应用程序时,调制解调器会进行重置,此时手机暂时失去信号,随后网络共享成功启用。
演示视频:
该项目的源代码请参见:https://github.com/lanrat/tethr
编译后的apk文件请参见:https://github.com/lanrat/tethr/raw/master/build/Tethr.apk
漏洞1: 通过Java反射重置无线通信
在安卓系统中,可以通过Java反射(Java Reflection)来让应用程序调用文档中未记录(Undocumented)或隐藏的API。然而,这一调用并不是官方支持的,并且官方强烈不建议(参考:https://plus.google.com/+RetoMeier/posts/Cz5wQbdaNQB)这样操作。然而,它最终还是可以允许应用程序开发人员执行不受支持的任务,或者在这种情况下绕过权限检查。
在CellRefresh.java中,通过调用CellRefresh.Refresh()可以执行蜂窝调制解调器的重置。在大多数安卓版本上,CellRefresh都会重置蜂窝网络连接,但在安卓6及以上版本,使用的是如下反射:
getSystemService(Context.TELEPHONY_SERVICE).getITelephony().setCellInfoListRate();
getSystemService(Context.CONNECTIVITY_SERVICE).mService.setMobileDataEnabled();
较旧的安卓版本,则使用的是如下反射:
getSystemService(Context.CONNECTIVITY_SERVICE).mService.setRadio();
getSystemService(Context.TELEPHONY_SERVICE).getITelephony().disableDataConnectivity();
getSystemService(Context.TELEPHONY_SERVICE).getITelephony().enableDataConnectivity();
这一漏洞的修复方案是:使用这一方法之前,应该检查应用程序是否具有系统权限或特权。
漏洞2: 网络共享服务开通状态检查竞争条件漏洞
为了利用竞争条件(Race Condition)漏洞并绕过网络共享服务开通状态检查,在特定的时间,可以使用安卓中的PhoneStateListener和AccessibilityService来启用网络共享模式。
首先,如上所述,先对网络进行重置。在重置过程中,手机状态监听PhoneStateListener(TetherPhoneStateListener.java,https://github.com/lanrat/tethr/blob/master/src/main/java/com/vorsk/tethr/TetherPhoneStateListener.java)将会监听蜂窝网络何时断开,随后使用无障碍辅助功能AccessibilityService(TetherAccessibilityService.java,https://github.com/lanrat/tethr/blob/master/src/main/java/com/vorsk/tethr/TetherAccessibilityService.java)找到相应的UI开关,启动系统的网络共享功能。
针对这一漏洞利用方式,我们实际上不一定要使用AccessibilityService和PhoneStateListener。用户可以在恰当的时间手动开启网络共享功能,也可以实现相同的结果。但由于可以开启网络共享的这一时间非常短暂,如果借助AccessibilityService来自动完成,会更为简单。
针对这一漏洞,我们建议:除了在启用网络共享时要进行服务开通状态检查之外,还应该在每次无线通信重置后进行一次服务开通状态检查。
测试过程
通信运营商:1. Verizon;2. AT&T。
测试手机(使用原厂ROM、锁定Bootloader、OEM系统):
1. Nexus 5X(系统:Android 6.0.1);
2. Nexus 5X(系统:Android 7.0.0);
3. Nexus 5X(系统: Android 7.1.1);
4. Samsung Galaxy S7(系统:Android 6.0.1)。
未进行测试,但也同样可利用该漏洞的手机有:
1. Pixel (XL);
2. 其他非Nexus品牌但同样执行服务开通状态检查的设备。
特别要提出的是,由于Nexus 6P在其原厂build.prop中,已经将net.tethering.noprovisioning设定为True,因此不需要利用这个漏洞,直接就会绕过服务开通状态检查。
修复方案
在我将该漏洞情况提交给安卓开发团队后,Google对这一问题进行了修复,并针对安卓7.1.2版本发布了两个补丁。
其中,第一个补丁增加了对于setCellInfoListRate的权限检查,第二个补丁修复了网络共享服务开通状态检查的逻辑缺陷,并增加了重新校验的过程。
在修复完成后,Google还赠送给我一部Pixel XL手机,用于奖励发现并报告了上述漏洞。