本文是翻译文章,原文链接:https://googleprojectzero.blogspot.com/2019/08/the-fully-remote-attack-surface-of.html
原文名:The Fully Remote Attack Surface of the iPhone
虽然过去几年里有一些关于iPhone被攻击者使用的完全远程漏洞(fully remote vulnerabilities)的谣言和报道,但有关这些漏洞的技术细节以及它们发生的潜在攻击面的信息有限,我调查了iPhone的远程、无交互攻击面,并发现了几个严重的漏洞
当攻击者不需要任何物理或网络接近目标以便能够利用此漏洞时,漏洞被视为远程
。远程漏洞被描述为完全远程
,无交互
或零点击
,它们不需要来自目标的任何物理交互,并且实时工作。我专注于远程访问iPhone的攻击面,不需要任何用户交互并立即处理输入
iPhone有几个具有这些特点的攻击面,包括SMS,MMS,VVM,Email和Message
短信似乎是一个很好的切入点,因为我在过去研究过短信在Android上的攻击面。与Android不同,iPhone的本地代码处理SMS消息,这增加了出现内存破坏类漏洞的可能性。SMS数据包数据单元(PDU)
由二进制文件CommCenter
使用方法sms::Controller::parseRawBytes
进行解析,该方法创建包含消息详细信息的类sms::Model
,该实例最终被sms::Controller::processReceivedSms_sync
处理,它还处理一些其它内容并将消息发送到对应的其他进程。我分析了这两种方法,但是没有发现任何漏洞。
我还注意到CommCenter
包含一个可以通过XPC
触发的SMS模拟器,此工具处理SMS传送PDU,就像它们通过网络到达一样。模拟器缺少一些库,这些库可能存在于内部测试设备上。因此我自己编写了一个库,它实现了使模拟器工作所需的功能。此工具可在此处获得,使用该工具对短信进行fuzz并未发现任何漏洞。
MMS消息也由CommCenter
处理,大部分处理在MmsOperation::decodeMessage
方法中执行。我使用IDA审查了这个方法,并通过编写在iOS中调用此方法的app来fuzz它,但是也都没有发现任何漏洞
在查看sms::Controller::processReceivedSms_sync
方法时,我注意到此方法将许多特殊格式的SMS消息转发到其它进程,一个看起来很有趣的地方是Visual Voicemail(VVM)
,我之前在Android上已经回顾过,VVM
是一种允许以与电子邮件显示方式类似的可视格式查看语音邮件的功能
VVM通过从设备运营商维护的IMAP服务器获取语音邮件来工作,它的服务器URL和credentials(凭证)由运营商通过SMS提供给设备。iPhone使用与公开的格式不同的VVM SMS消息格式,因此我通过在接收SMS PDU的CommCenter中放置断点来确定传入VVM SMS的内容,以下是传入VVM消息的示例:
STATE?state=Active;server=vvm.att.com;port=143;pw=asdf;[email protected]
我把Android设备设置成发送原始PDU,并尝试用它发送该消息,发现日志显示已对服务器进行了其它查询。我尝试将默认服务器更改为我控制的服务器,经过多次尝试,我能够发送更改目标设备的VVM服务器的消息,但有以下限制:
对于大多数运营商来说,这足以让VVM IMAP成为可行的攻击面,我认为它很可能包含错误,因为VVM使用与iOS上电子邮件相同的IMAP库。IMAP服务器通常可以抵御来自不受信任的电子邮件客户端的攻击,因为恶意客户端通常会攻击服务器以尝试访问其他用户的电子邮件。然而,客户端连接到恶意服务器的情况要少得多,因为用户需要手动输入这些服务器,并且通常只会输入他们信任的服务器。
这意味着从服务器的角度来看,服务器到客户端的这一攻击面可能不是很有效,因为它不是一个真正的攻击面。VVM对此进行了更改,因为它允许设备在没有用户交互的情况下连接到恶意IMAP服务器。
我用IDA查看了IMAP库,但仍然没有发现任何问题。我接下来尝试fuzz, 我写了一个假的IMAP服务器,它返回了对每个请求的错误响应,并使用其中的SMS模拟器不断发送VVM SMS
消息,让设备请求服务器响应。 我用这个方式发现了一个漏洞CVE-2019-8613
。 此漏洞是由于错误处理NAMESPACE IMAP
命令而导致的NSString UAF
。 当IMAP服务器建立连接时,它首先向客户端发送LIST
命令以获取邮箱分隔符字符串,然后发送NAMESPACE
命令以获取邮箱前缀。在iOS IMAP实现中,如果服务器遇到错误,则free分隔符字符串,但调用NAMESPACE
命令的代码不会检查命令是否成功,因此即使已经free分隔符,它也会继续。
译者注:
CVE-2019-8613
http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-201905-529
查看IMAP实现,我注意到MIME
中有几个代码路径未被VVM
使用,但在处理消息时由电子邮件客户端使用。其中有一个具有明显且不寻常的问题。
方法[MFMimePart _contents:toOffset:resultOffset:downloadIfNecessary:asHTML:isComplete:]
处理传入的MIME
消息,并根据MIME类型
将它们发送到特定的解码器。不幸的是,它是通过将传入消息中的MIME类型字符串追加到字符串"decode"并调用结果方法来实现。这意味着可能会调用非预期的选择器,从而导致内存损坏。
我在iOS 11.3.1
版本中发现了这个漏洞,但由于调用的非预期的选择器的功能发生了变化,因此在iOS 12中显然无法利用,这些变化似乎与安全无关。但此问题仍然可能导致crash,并且已解决为CVE-2019-8626
。
译者注:
CVE-2019-8626
https://support.apple.com/zh-cn/HT210118
虽然电子邮件是iPhone的潜在的远程攻击面,但目前还不清楚它有多严重。首先,一些用户安装第三方客户端而不是使用本机电子邮件客户端,一些电子邮件提供商还会过滤传入的邮件并删除 触发漏洞所需的格式错误 的MIME组件。虽然上述错误适用于在本机电子邮件客户端上登录的Gmail,但目前尚不清楚此配置的常见程度,或者提供商过滤是否可能是导致类似错误的问题。
iMessage
是iOS和Mac设备上的本机消息传递客户端。它支持使用各种格式选项发送和接收消息,还支持扩展,允许设备发送和接收自定义消息类型。扩展可以由Apple或第三方编写。SamuelGroß和我分析了默认安装在iPhone上的iMessage及其扩展。
为了开始这个项目,Samuel编写了可以在Mac上发送和转储iMessage消息的工具。它们通过hook iMessage中的代码
来工作,该代码使用Frida
发送或接收消息,并且在转储的情况下将消息写入控制台,或者在发送的情况下将其替换为不同的消息。以下是使用这些工具转储的示例消息。
to: mailto:TARGET@gmail.com from: tel:+15556667777 { gid = "FAA29682-27A6-498D-8170-CC92F2077441"; gv = 8; p = ( "tel:+15556667777", "mailto:[email protected]" ); pv = 0; r = "68DF1E20-9ABB-4413-B86B-02E6E6EB9DCF"; t = "Hello World"; v = 1; }
它是一个包含多个字段的二进制plist。以下是其中有趣的部分:
t | Plain text message content |
---|---|
x | XML message content |
bid | “Balloon identifier” for plugin |
bp | Plugin data |
ati | Attribution info |
p | Participants |
我们注意到其中几个字段包含使用NSKeyedUnarchiver
类进行反序列化的二进制数据(字段也可以选择使用gzip压缩。为了解决这个问题,我们编写了一个程序,调用[NSData _FTOptionallyDecompressData]
在Mac命令行上解压缩它们)
具体来说,bp 字段在SpringBoard中被反序列化以用于通知,这使得反序列化成为一个完全远程的攻击面。SpringBoard
在iOS上也没有任何沙盒。此字段也由MobileSMS
反序列化,但这需要单击一次。ATI字段也在imagent
过程中解码,并且不需要用户交互,虽然它相对于bp
字段在解码时更严格。
NSKeyedArchiver
序列化以plist
格式对NSObject
实例进行编码。下面是包含`NSURL实例的序列化对象的示例部分。
<dict> <key>$class</key> <dict> <key>CF$UID</key> <integer>7</integer> </dict> <key>NS.base</key> <dict> <key>CF$UID</key> <integer>0</integer> </dict> <key>NS.relative</key> <dict> <key>CF$UID</key> <integer>6</integer> </dict> </dict> <string>http://www.google.com</string> <dict> <key>$classes</key> <array> <string>NSURL</string> <string>NSObject</string> </array> <key>$classname</key> <string>NSURL</string> </dict>
NS.base
字段和NS.relative
字段是将用于构造NSURL
实例的对象,该NS.relative
字段引用字符串http://www.google.com
,它代表了URL位置。下面的字典,带有$classes
和$classname
字段,描述了实例的类,它由第一个字典的$class
字段引用。反序列化此实例时,解码器将调用[NSURL initWithCoder:]
,其中包含将反序列化NS.base
和NS.relative
字段并使用它们初始化NSURL
的代码。
NSKeyedArchiver
可以学序列化或反序列化任何实现initWithCoder:
的Object-C
类,但它有一个名为NSSecureCoding
的安全功能,允许开发人员限制解码的内容。
首先,实现initWithCoder
的类,还必须实现requiresSecureCoding
,以便在启用NSSecureCoding
时启用反序列化。这可以防止开发人员在安全的上下文中意外暴露反序列化代码。其次,NSSecureCoding
要求所有反序列化调用提供可以反序列化的允许类的列表,并且不允许其他类。但值得注意的是,允许的类列表并不是 序列化期间可以调用initWithCoder:
方法的完整列表。例如,具有一些遗漏的[NSURL initWithCoder:]
的伪代码表示如下:
[NSURL initWithCoder:](NSURL *u, id decoder){ NSData* book = [decoder decodeObjectOfClass:[NSData class] forKey:@"NS.minimalBookmarkData"]; if(book) return [URLByResolvingBookmarkData:data]; NSString* base = [decoder decodeObjectOfClass:[NSString class] forKey:@"NS.base"]; NSString* relative = [decoder decodeObjectOfClass:[NSString class] forKey:@"NS.relative"]; return [NSURL initWithString:base relativeToURL:relative]; }
对于不是bookmark
的URL,该方法将需要为NS.relative
和NS.base
字段反序列化类NSString
的实例,并且在该反序列化中将允许NSString
类。同样,如果序列化数据包含NS.minimalBookmarkData
字段,它将反序列化NSData
的实例。因此,虽然限制了 反序列化的类 将返回的类,但它们并不将攻击面限制为该类。尽管如此,他们仍然会减少攻击面。
有几种方法可用于创建NSKeyedUnarchiver
实例或反序列化对象,并且默认情况下并非所有方法都启用NSSecureCoding
,默认情况下,以下方法启用它:
initForReadingFromData: unarchivedObjectOfClasses:fromData:error:
而下面的方法则不能:
initWithData: unarchiveObjectWithData:error initForReadingWithData:
这些方法的名字并没有清楚的表明是否启用了NSSecureCoding
,尤其是名称相同的initForReadingFromData:
和initForReadingWithData :
,我们首次尝试查找漏洞是在iMessage中寻找一个在没有NSSecureCoding
的情况下执行反序列化的地方。希望能够使用它来反序列化WebKit
实例,并找到一种方法来加载包含WebKit
漏洞的网页,因为发现了许多此类漏洞,并且可以它们的可利用性都很好。不幸的是,没有NSSecureCoding
的地方,我们没有发现任何反序列化。
接下来,我们研究了扩展。扩展是一个相当新的功能,因此我们希望找到扩展如何处理bp字段中的序列化数据的错误。有时可以在没有用户交互的情况下执行该处理。扩展程序可以支持预览,在这种情况下,SpringBoard
将在扩展程序中调用previewSummary
而无需用户交互。某些版本的iOS还通过调用initWithPluginPayload
来处理整个输入,没有用户交互,但它在每个版本上是不一致的。这发生在测试版本12.1.2
时,但不是更高版本。
我们在Digital Touch
扩展程序发现了一个漏洞CVE-2019-8624中,此扩展允许用户发送包含绘图和其他可视元素的消息。扩展允许使用自定义编码,只要它们发信号通知SpringBoard
不尝试解码bp字段,Digital Touch使用protobuf
解码其有效负载(payload)。它解码了几个字节数组,并且有一种情况是,在复制之前错误地检查字节数组的长度,从而导致越界读取。这个问题很可能无法被利用,但是产生bug的路径很有趣。
链接演示文稿扩展(The Link Presentation extension)显示在消息中发送链接时的链接预览。它的工作原理是在发件人设备上加载WebKit
中的链接,生成预览文本和图像,然后将其发送到目标设备。我们非常详细地分析了这个扩展,寻找在接收设备上生成WebKit
实例的方法,但没有找到。WebKit
处理似乎总是由发件人完成。
然后我们决定在initWithCoder
中查找bug ,SpringBoard
在生成消息预览时允许反序列化的类的方法。Ian Beer 发现了这种类型的几个问题
,允许权限提升。
issue如下:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1172
https://bugs.chromium.org/p/project-zero/issues/detail?id=1168
https://bugs.chromium.org/p/project-zero/issues/detail?id=1175
SpringBoard
解码bp字段时允许的类是:NSDictionary
,NSString
,NSData
,NSNumber
,NSURL
,NSUUID
和NSValue
。还允许这些类的具有任何继承级别的子类,NSKeyedUnarchiver
反序列化的情况也是如此。我们回顾了initWithCoder: SpringBoard
导入的这些类及其子类的实现。该分析发现了三个漏洞。
CVE-2019-8663是反序列化SGBigUTF8String
类的漏洞,该类是NSString
的子类。这个类的initWithCoder:
实现 反序列化一个字节数组,然后看作以null结尾的UTF-8字符串,即使它可能没有null。这可能导致创建一个包含越界内存的字符串。
CVE-2019-8661是[NSURL initWithCoder:]
中的漏洞,仅影响Mac。当反序列化URL时,通常会解码类NSString
的实例,但也可以对NSData
实例进行反序列化,然后将其视为bookmark
。仅在Mac上,此书签可能采用alis
,alis格式,在2012年已弃用。此格式由名为CarbonCore
的框架处理,该框架使用许多不安全的字符串处理函数来处理别名文件。该漏洞是由于对strcat
的不安全调用导致的堆内存破坏引起的。值得注意的是,iMessage
永远不会合法使用此书签功能。它存在是因为NSURL
反序列化在整个系统中是通用的,因此initWithCoder
实现必须支持所有输入可能性,甚至是在特定攻击面上 正常使用时 永远不会遇到的可能性。
CVE-2019-8646是一个在反序列化NSData, _NSDataFileBackedFuture
的子类的漏洞。此类允许创建包含文件内容的缓冲区,但在访问数据之前不会加载该文件。实现反序列化,以便从输入数据中反序列化缓冲区长度,文件名也是如此,并且从不检查反序列化长度是否与最终加载的文件的长度一致。这违反了NSData
类所做的基本保证,即length
属性将是字节的正确长度属性。这可能会导致各种问题,包括内存损坏,将在未来的博客文章中探讨。值得注意的是,_NSDataFileBackedFuture
类是一个隐藏类它不需要是public
或被导出以使其可用于反序列化。
在查看了所有initWithCoder
实现之后,我们开始想知道,如果允许类的子类没有实现initWithCoder
会发生什么。事实证明它遵循正常的继承规则,这意味着它将为其超类使用initWithCoder
实现,但随后将为该子类调用该类已重写的任何方法。事实证明,允许类(The allowed class)的许多子类是可能的,例如initWithCapacity:
是一种常用的方法来实现和调用。有些类具有阻止继承的检查,或者更常见的是需要直接继承(即,允许覆盖所有需要的方法的子类,而不依赖于某些超类实现的子类)。这是需要逐个类来审查的事情。我们通过将dyld_shared_cache
加载到IDA并运行检查Objective-C
元数据的脚本来确定可用的类。
我们发现的一个漏洞是CVE-2019-8647。反序列化_PFArray
类时发生此漏洞,该类扩展了NSArray
并实现了[NSArray initWithCoder:]
调用的[_PFArray initWithObjects:count:]
。此方法假定数组中的所有对象都有对它们的引用,这可能适用于此类的预期用途,但在反序列化期间不是这种情况。这意味着可以创建和使用包含已释放对象的数组。很可能这个类从来没有打算反序列化,并且在每当它包含的库被一个反序列化数组的进程导入时,方法initWithObjects:count:
的可行性没有被考虑周全,这是开发人员所不希望看到的。
我们在一个类中报告了一个类似的漏洞CVE-2019-8662,据我们所知,它没有导入到iMessage
中,但很可能被其他使用反序列化的应用程序导入。
关于
NSKeyedArchiver
序列化的另一个有趣问题是,如果序列化对象包含循环,会发生什么呢?
从根本上说,NSKeyedArchiver
格式是包含数字引用的plist
文件,因此对象可以引用自身,或者可以存在涉及多个对象的循环。查看IDA中的代码,对象的反序列化大致如下:
if(temp_dict[key]) return [temp_dict[key] copy]; if(obj_dict[key]) return [obj_dict[key] copy]; NSObject* a = [NSSomeClass alloc]; temp_dict[key] = a; //No references!! NSObject* obj = [a initWithCoder:]; temp_dict[key] = NIL; obj_dict[key] = obj; return obj;
因此,第一次反序列化对象时,会在其类上调用alloc
,然后alloc
返回的对象将存储在 不保留对该对象的引用的 临时字典 中,然后在分配的对象上调用initWithCoder
。完成该调用后,将从临时字典中删除已分配的对象,并将initWithCoder
返回的对象添加到永久对象字典中,该字典会添加对该对象的引用。
这个方案有几个问题。首先,不能保证initWithCoder
返回调用它的这个对象,事实上,文档特别指出这不能保证是这种情况。此外,initWithCoder
负责在未返回此对象的情况下释放该对象。
因此,理论上initWithCoder
实现可以释放alloc
返回的对象 ,然后反序列化可能是对同一对象的引用的字段,这将导致返回的引用是对释放的内存的无效引用。我们进行了分析,没找到任何initWithCoder
在SpringBoard
中存在此问题的实现,但它们可能存在于其他应用程序中,因为这不违反任何被记录的对initWithCoder
实现行为的限制。
另一个问题是,如果initWithCoder
的实现以反序列化自身收尾,然后使用该对象。但它缺可以在调用完成之前使用该对象。如果某些方法假定对象已完成或不会更改(因为它可能会随着initWithCoder
调用完成而继续更改),则会导致问题。
我们寻找涉及生命周期的漏洞并发现了两个这样的错误。第一个是CVE-2019-8641,我们尚未透露,因为它的修复并没有完全解决问题。
另一个问题是CVE-2019-8660。这种脆弱性是另一个子类的NSDictionary
,NSKnownKeysDictionary1
,这是另一个优化的字典类,需要预先提供密钥。在这种情况下,密钥作为类NSKnownKeysMappingStrategy1
实例提供,它将包含密钥的数组分别反序列化。在反序列化密钥后,检查反序列化的密钥数量是否与密钥数组长度一致,因此如果密钥是NSKnownKeysDictionary1
的另一个实例,则它可以在检查密钥数量之前使用NSKnownKeysMappingStrategy1
实例。这允许整数溢出导致[NSKnownKeysDictionary1 initWithCoder:]
中的内存损坏。
NSKeyedArchiver
序列化的本质使得保护非常困难。即使启用了NSSecureCoding
,NSKeyedArchiver
序列化也会无意中创建极大的攻击面。举个例子,如果启用了安全编码,下一个调用的攻击面是什么?
[NSKeyedUnarchiver unarchivedObjectOfClasses:@[NSURL] fromData:mydata error:NIL];
显然,它包括URL反序列化器,[NSURL initWithCoder:]
以及在调用的app中实现的任何子类反序列化器,例如[NSMyURLSubClass initWithCoder:]
但它还包括由app导入的库中的NSURL
的任何子类。例如,假设此应用程序导入UserNotifications
框架,在这种情况下,[UNSecurityScopedURL initWithCoder:]
,NSURL
的子类也将成为攻击面的一部分,即使该库被导入的原因并不是用于序列化
让我们再看一下前面讨论的[NSURL initWithCoder:]
:
[NSURL initWithCoder:](NSURL *u, id decoder){ NSData* book = [decoder decodeObjectOfClass:[NSData class] forKey:@"NS.minimalBookmarkData"]; if(book) return [URLByResolvingBookmarkData:data]; NSString* base = [decoder decodeObjectOfClass:[NSString class] forKey:@"NS.base"]; NSString* relative = [decoder decodeObjectOfClass:[NSString class] forKey:@"NS.relative"]; return [NSURL initWithString:base relativeToURL:relative]; }
它包含对decodeObjectOfClass:forKey:
的三次调用,分别解析类NSString
,NSData
和NSURL
的对象,所以[NSString initWithCoder:]
和[NSData initWithCoder:]
现在也是攻击面的一部分。在[NSURL的initWithCoder:]
实现中,也将把提供的NSData
对象看作一个bookmark并进行解析(如果它存在),所以这也是在攻击面。
攻击面现在也包括NSString
和NSData
的子类,假设此应用程序仅导入UserNotifications
框架以及通常必需的Foundation
和CoreFoundation
,以下反序列化函数现在位于攻击面中,因为它们是这两个类的子类:[_NSDispatchData initWithCoder:]
, [__NSLocalizedString initWithCoder:]
, [NSLocalizableString initWithCoder:]
and [UNLocalizedString initWithCoder:]
看看这些,有两种方法可以允许更多的类成为攻击面。[UNLocalizedString initWithCoder:]
反序列化NSArray
实例,同时[UNLocalizedString initWithCoder:]
解码类NSDictionary
、NSNumber
和NSDate
的对象。出于本示例的目的,我们不会研究这些类的子类,但很明显它们将允许更多的类,并且更多地增加攻击面,这些类的子类也是如此,等等。
这只是考虑子类的initWithCoder
方法,考虑到允许类的任何子类可能由于继承而成为反序列化的一部分,因此攻击面可能更大。例如[NSString initWithCoder:]
可以调用[NSString initWithString:]
或`[NSString initWithBytes: length: encoding:],具体取决于反序列化的字段,因此这两个子类方法都是攻击面的一部分。
在这种情况下,这包括[NSBigMutableString
initWithString:]
, [NSDebugString initWithString:]
,
[NSPlaceholderMutableString initWithBytes:length:encoding:]
和[NSPlaceholderString initWithBytes:length:encoding:]
,由于继承,所有其他允许的类在攻击面上都有类似的增加。
这是一个非常大的攻击面,用于解码URL(这可能只是一个字符串),它是一个随着应用程序的发展而呈指数级扩大的攻击面。例如,想象一下在此攻击面上导入一些额外库的影响,或者在允许列表中添加一些额外的类。同样重要的是,在序列化期间可以组合来自不打算一起使用的不同框架的类。例如,可以使用来自另一个框架的字符串子类来解码URL,该框架包含来自另一个框架的数据对象,并且生成的对象可以包含许多未预期或预期的类的属性。反序列化的广泛攻击面以及从许多框架反序列化对象时可用的多个自由度是我们在iMessage中发现如此多漏洞的原因。
我们分析了iPhone的远程攻击面,并审查了SMS,MMS,VVM,Email和iMessage,并且发布了几种可用于进一步测试这些攻击面的工具。我们报告了总共10个漏洞,所有这些漏洞都已修复。由于其广泛且难以枚举的攻击面,大多数漏洞都发生在iMessage
中。大多数此类攻击,表面不是正常使用的功能,对用户没有任何用处。Visual Voicemail
也有一个庞大且不直观的攻击面,可能导致上文提到的严重的漏洞。总的来说,我们发现的远程漏洞的数量和严重程度都很高。减少iPhone的远程攻击面可能会提高其安全性。