首页
社区
课程
招聘
[翻译]Pegasus三叉戟漏洞原理分析及Poc(CVE-2016-4656)第二部分
2016-9-8 15:34 8649

[翻译]Pegasus三叉戟漏洞原理分析及Poc(CVE-2016-4656)第二部分

2016-9-8 15:34
8649
Timeline:
2016-8-30 Pegasus间谍套件内部原理及流程剖析:
http://bbs.pediy.com/showthread.php?t=212483

2016-9-3 Pegasus三叉戟漏洞原理分析及Poc(CVE-2016-4656)
http://bbs.pediy.com/showthread.php?t=212555

2016-9-8 Pegasus三叉戟漏洞原理分析及Poc(CVE-2016-4656)第二部分
http://bbs.pediy.com/showthread.php?t=212647

2016-9-8 CVE-2016-4656分析与调试附POC利用源码(感谢dirge大神)
http://bbs.pediy.com/showthread.php?t=212638


Pegasus三叉戟漏洞原理分析及Poc(CVE-2016-4656)第二部分

前言

在我们上周发布第一篇iOS内核漏洞分析之后(注释1)(注释2),我们又发现了新的问题:

1,我们发现在今年的五月,为了修补一个被发现的UAF漏洞,OSUnseriallzeBinary()函数已经被patch过一次了。具体详情可以见这份报告(注释3),这个漏洞的编号为CVE-2016-1828,由Brandon Azad于2016年1月11日提交。

2,我们上一篇文章中发现可以发动UAF的这段代码貌似在iOS 9和OS X 10.11之前并不存在。而据报道Pegasus是可以通吃iOS的旧版本的,所以很可能我们发现的这个漏洞并不是Pegasus所使用的这个漏洞。(真任性,随随便便就公布了一个苹果未发现的漏洞...不愧是树人)

旧的OSUnseriallzeBinary()

在iOS 9 和OS X 10.11之前的版本中 OSUnseriallzeBinary()这个函数是长酱紫的:
393 if (dict)
394 {
395         if (sym)
396         {
397                 DEBG("%s = %s\n", sym->getCStringNoCopy(), o->getMetaClass()->getClassName());
398                 if (o != dict) ok = dict->setObject(sym, o);
399                 o->release();
400                 sym->release();
401                 sym = 0;
402         }
403         else
404         {
405                 sym = OSDynamicCast(OSSymbol, o);
406                 ok = (sym != 0);
407         }
408 }


从上文代码可以看出,在之前的代码中,字典键值必须是OSSymbol对象,并且OSString代码路径还没被添加上去。这就意味着我们分析的那个UAF漏洞在这个版本上是无效的。如果您看的更加仔细一点还可以 发现传入setObject()的参数只有两个,而不是三个,这是因为上面的这段code是在苹果修补CVE-2016-1828之前的版本。

第一个发动UAF的方法

根据这段代码,想要发动UAF的话,第一个方法是使用CVE-2016-1828中的方法:

1,第398行会把字典的k1键值赋值给对象哦o1
2,这会增加o1和k1的应用计数增加1(达到2)
3,第399行o1会被释放,引用计数减少1
4,第400行k1(引用)会被释放,引用计数减少1
5,这个时候一切都没有问题,因为字典中还是抵达对象的引用维持着
6,接下来o2反序列化并会被添加到字典中,在重用k1的时候,setObject()方法会在字典中用o2代替o1,这时候o1的引用计数就减到0了。
7,o1就会在内存中被释放掉了,如果反序列器还要创建一个到o1的引用,就是UAF了。

第二个发动UAF的方法

这个方法看上去才更像是Pegasus使用的方法(CVE-2016-4656)

1,第398行,如果我们试图插入的对象o是dict自身的链接的话,setObject()不会被调用
2,如果setObject()不被调用,那么o和sym都不会被增加
3,399行会减少o的引用计数至一个大于或等于1的值,因为o本身就是dict的引用
4,400行很有可能会把sym的引用计数降到0,如果这个符号是个...举个例子,奇怪的随机字符串(fuzz)
5,这时OSSymbol对象sym已经不存在了
6,任何指向sym的引用都可以变成UAF

从上文可以看出,iOS 9和OS X 10.11之前的版本已经出现了这两个代码位置非常接近的漏洞,如果算上我们上一篇文章指出的地方,这短短二十行代码就有三处UAF漏洞!

如何修补

五月份苹果修补CVE-2016-1828的方式就是在调用setObject()的地方加了一个参数:

if (o != dict) ok = dict->setObject(sym, o, true);


这样的话,第一个情况中如果想要覆盖一个已经设定好的词典键值的话,程序就会报错,所以就堵上了Brandon Azad报告的这个漏洞。不幸的是苹果就乐不思蜀,止步不前,固步自封了,不再重新审计一下OSUnseriallzeBinary()这个函数,要不然如果是老师傅肯定可以发现这里还有一些来自objsArray的直接引用已经释放的对象并由此导致的UAF。这样就揭露给大家仅仅给setObject()加第三个参数”true”并不是完美的修复方案。

然而苹果并没有做更多的安全审核,没人发现上文的其他两处释放代码可能导致UAF漏洞,仅仅20行代码发现了一个还有两个UAF,可以用来突破内核获取最高权限。现在Pegasus被曝光了,苹果把此事提高到了最高议程来解决20行中的其他漏洞。因为他们已经废弃了给CVE-2016-1828的补丁,并且重构了函数不再有调用release()的地方(除了有个清理error的地方和临时变量)。所有的来自objsArray对象在函数快结束前都被释放了。这样在反序列化过程中所有的引用计数都不会降到0.

可以在这里看下我们反汇编OSSerializeBinary()的代码(见注释4),或者可以看下两套代码的区别:

--- OSSerializeBinary.cpp       2016-05-09 22:28:11.000000000 +0200
+++ OSSerializeBinaryPatched.cpp        2016-09-05 16:19:03.000000000 +0200
@@ -237,19 +237,21 @@

 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

-#define setAtIndex(v, idx, o)                                                  \
+#define setAtIndex(v, idx, o, max)                                             \
        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 (v##Capacity < max) {        \
+                       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;                                     \
+               } else ok = false;                                              \
        }                                                                       \
        if (ok) v##Array[idx] = o;

@@ -338,13 +340,12 @@
                    case kOSSerializeObject:
                                if (len >= objsIdx) break;
                                o = objsArray[len];
-                               o->retain();
                                isRef = true;
                                break;

                    case kOSSerializeNumber:
                                bufferPos += sizeof(long long);
-                               if (bufferPos > bufferSize) break;
+                               if (bufferPos > bufferSize || ((len != 32) && (len != 64) && (len != 16) && (len != 8))) break;
                        value = next[1];
                        value <<= 32;
                        value |= next[0];
@@ -354,7 +355,7 @@

                    case kOSSerializeSymbol:
                                bufferPos += (wordLen * sizeof(uint32_t));
-                               if (bufferPos > bufferSize)           break;
+                               if (bufferPos > bufferSize || len < 2)           break;
                                if (0 != ((const char *)next)[len-1]) break;
                        o = (OSObject *) OSSymbol::withCString((const char *) next);
                        next += wordLen;
@@ -386,8 +387,11 @@

                if (!isRef)
                {
-                       setAtIndex(objs, objsIdx, o);
-                       if (!ok) break;
+                       setAtIndex(objs, objsIdx, o, 0x1000000);
+                       if (!ok) {
+                               o->release();
+                               break;
+                       }
                        objsIdx++;
                }

@@ -395,33 +399,35 @@
                {
                        if (sym)
                        {
-                               DEBG("%s = %s\n", sym->getCStringNoCopy(), o->getMetaClass()->getClassName());
-                               if (o != dict) ok = dict->setObject(sym, o, true);
-                               o->release();
-                               sym->release();
-                               sym = 0;
+                               OSSymbol *sym2 = OSDynamicCast(OSSymbol, sym);
+                               if (!sym2 && (str = OSDynamicCast(OSString, sym)))
+                               {
+                                       sym2 = (OSSymbol *) OSSymbol::withString(str);
+                                       ok = (sym2 != 0);
+                                       if (!sym2) break;
+                               }
+
+                               if (o != dict) ok = dict->setObject(sym2, o);
+                               if (sym2 && sym2 != sym) {
+                                       sym2->release();
+                               }
                        }
                        else
                        {
-                               sym = OSDynamicCast(OSSymbol, o);
-                               if (!sym && (str = OSDynamicCast(OSString, o)))
-                               {
-                                   sym = (OSSymbol *) OSSymbol::withString(str);
-                                   o->release();
-                                   o = 0;
-                               }
-                               ok = (sym != 0);
+                               sym = o;
                        }
                }
                else if (array)
                {
                        ok = array->setObject(o);
-                   o->release();
                }
                else if (set)
                {
-                  ok = set->setObject(o);
-                  o->release();
+                       ok = set->setObject(o);
+               }
+               else if (result)
+               {
+                       ok = false;
                }
                else
                {
@@ -436,7 +442,7 @@
                        if (!end)
                        {
                                stackIdx++;
-                               setAtIndex(stack, stackIdx, parent);
+                               setAtIndex(stack, stackIdx, parent, 0x10000);
                                if (!ok) break;
                        }
                        DEBG("++stack[%d] %p\n", stackIdx, parent);
@@ -462,15 +468,19 @@
                        }
                }
        }
-       DEBG("ret %p\n", result);
-
-       if (objsCapacity)  kfree(objsArray,  objsCapacity  * sizeof(*objsArray));
-       if (stackCapacity) kfree(stackArray, stackCapacity * sizeof(*stackArray));

-       if (!ok && result)
+       if (!ok)
        {
-               result->release();
                result = 0;
        }
+       if (objsCapacity) {
+               uint32_t i;
+               for (i = (result?1:0); i < objsIndx; i++) {
+                       objsArray[i]->release();
+               }
+               kfree(objsArray,  objsCapacity  * sizeof(*objsArray));
+       }
+       if (stackCapacity) kfree(stackArray, stackCapacity * sizeof(*stackArray));
+
        return (result);
 }


Hack.Lu

我们被Hack.Lu邀请十月份去卢森堡聊一聊这个我们对于这个CVE-2016-4656的发现并且如何发动攻击

结论

过去的两周中很多专家都在夸奖苹果对于新出的Pegasus的漏洞反应神速,同时还能很好的捂住代码不泄露。所以大家理所当然地觉得Pegasus用的是全新的内核漏洞,苹果也肯定是刚刚才知道了这些个漏洞。直到现在我们把漏洞完整分析了才直到压根儿就不是这么回事。

CVE-2016-4656之所以存在,完全就是因为苹果在给CVE-2016-1828打补丁的时候,忘记做代码安全审计了。短短20行代码!三处UAF漏洞!人家报告一个,苹果就修补一个,release()的位置就隔着setObject()啊!只要稍微长点儿心苹果可以轻而易举的修补所有的这三个漏洞,我觉得苹果这回真是糗大了,Brandon Azad都把门指给你了,你都不看,你要是瞅一眼,就没CVE-2016-4656什么事儿了。

不幸的是这不是苹果第一次搞砸了,两年来我们已经不断发现苹果把安全问题搞砸过一次又一次,直接导致了我们在苹果“没朋友”并且他们还把我们的SysSecInfo安全软件给ban了,过程在这里。(注释5)

最后一点:重读CVE-2016-1828和CVE-2017-4656我们发现其实Pegasus所用的漏洞的发现和利用,比Brandon Azad发现的漏洞难上许多,所以我们有理由认为其实Pegasus的漏洞利用是在CVE-2016-1828发现的后面的,要不然Pegasus肯定会使用CVE-2016-1828的,因为这个漏洞更简单呀。所以很有可能是Pegasus集团的人,逆向了CVE-2016-1828,然后在这个地方又重新捡到了宝贝。这只是我们不负责任的猜测,各位看官看看就好。

注释1:http://bbs.pediy.com/showthread.php?t=212555
注释2:https://www.sektioneins.de/blog/16-09-02-pegasus-ios-kernel-vulnerability-explained.html
注释3:https://bazad.github.io/2016/05/mac-os-x-use-after-free/
注释4:https://www.sektioneins.de/files/OSSerializeBinary.cpp
注释5:https://www.sektioneins.de/blog/16-08-11-sysinfo-what-happened.html

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

收藏
点赞3
打赏
分享
最新回复 (5)
雪    币: 30
活跃值: (41)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
AliceForever 2016-9-8 16:55
3
0
我也mark一记!
雪    币: 341
活跃值: (133)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
地狱怪客 2 2016-9-8 18:12
4
0
好东西.
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
葬爱家族Lynn 2016-9-8 20:46
5
0
顶楼主!
雪    币: 134
活跃值: (11)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
XXxiaofeng 2016-9-9 08:53
6
0
8个空格的缩进...
雪    币: 3907
活跃值: (5742)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
roysue 3 2016-9-9 09:43
7
0
what do you mean ?
游客
登录 | 注册 方可回帖
返回