Safari信息泄露漏洞分析

前言

Javascript中的数组和数组对象一直都是编程人员优化的主要目标,一般来说,数组只会包含一些基本类型数据,比如说32位整数或字符等等。因此,每个引擎都会对这些对象进行某些优化,并提升不同元素类型的访问速度和密集型表示。

在JavaScriptCore中,JavaScript引擎是在WebKit中实现的,其中每一个存储在对象中的元素都代表着一个IndexingType值,一个8位整数代表一套Flag组合,具体的参数定义可以在IndexingType.h中找到。接下来,引擎会检测一个对象中indexing的类型,然后决定使用哪一条快速路径,其中最重要的一种indexing类型就是ArrayWithUndecided,它表示的是所有元素均为未定义(undefined),而且没有存储任何实际的值。在这种情况下,引擎为了提升性能,会让这些元素保持未初始化。

分析

下面,我们一起看一看旧版本中实现Array.prototype.concat的代码(ArrayPrototype.cpp):

EncodedJSValueJSC_HOST_CALL arrayProtoPrivateFuncConcatMemcpy(ExecState* exec){    ...     unsigned resultSize =checkedResultSize.unsafeGet();    IndexingType firstType =firstArray->indexingType();    IndexingType secondType =secondArray->indexingType();    IndexingType type =firstArray->mergeIndexingTypeForCopying(secondType); // [[ 1 ]]    if (type == NonArray ||!firstArray->canFastCopy(vm, secondArray) || resultSize >=MIN_SPARSE_ARRAY_INDEX) {        ...    }     JSGlobalObject* lexicalGlobalObject =exec->lexicalGlobalObject();    Structure* resultStructure =lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(type);    if(UNLIKELY(hasAnyArrayStorage(resultStructure->indexingType())))        return JSValue::encode(jsNull());    ASSERT(!lexicalGlobalObject->isHavingABadTime());    ObjectInitializationScopeinitializationScope(vm);    JSArray* result =JSArray::tryCreateUninitializedRestricted(initializationScope, resultStructure,resultSize);    if (UNLIKELY(!result)) {        throwOutOfMemoryError(exec, scope);        return encodedJSValue();    }     if (type == ArrayWithDouble) {        [[ 2 ]]        double* buffer =result->butterfly()->contiguousDouble().data();        memcpy(buffer,firstButterfly->contiguousDouble().data(), sizeof(JSValue) *firstArraySize);        memcpy(buffer + firstArraySize,secondButterfly->contiguousDouble().data(), sizeof(JSValue) *secondArraySize);    } else if (type != ArrayWithUndecided) { ...

这个函数主要用来判断结果数组[[1]]的indexing类型,我们可以看到,如果indexing类型为ArrayWithDouble,它将会选择[[2]]作为快速路径。接下来,我们看一看:

mergeIndexingTypeForCopying的实现代码,这个函数主要负责在Array.prototype.concat被调用时,判断结果数组的indexing类型:

inlineIndexingType JSArray::mergeIndexingTypeForCopying(IndexingType other){    IndexingType type = indexingType();    if (!(type & IsArray && other& IsArray))        return NonArray;     if (hasAnyArrayStorage(type) ||hasAnyArrayStorage(other))        return NonArray;     if (type == ArrayWithUndecided)        return other; [[ 3 ]] ...

我们可以看到在这种情况下,有一个输入数组的indexing类型为ArrayWithUndecided,结果indexing类型将会是另一个数组的indexing类型。因此,如果我们我们用一个indexing类型为ArrayWithUndecided的数组和另一个indexing类型为ArrayWithDouble的数组去调用Array.prototype.concat方法的话,我们将会按照快速路径[[2]]运行,并将两个数组进行拼接。

这段代码并不能保证这两个“butterfly”(JavaScript引擎攻击技术里的一种概念,详情请参考【这篇文章】)在代码调用memcpy之前能够正确初始化。这也就意味着,如果我们能够找到一条允许我们创建一个未初始化数组并将其传递给Array.prototype.concat的代码路径,那我们就能够在堆内存中拥有一个包含了未初始化值的数组对象了,而且它的indexing类型还不是ArrayWithUndecided。从某种程度上来说,这个安全问题跟lokihardt在2017年报告的一个旧漏洞有些相似,只不过利用方式不同。

在创建这种数组对象时,可以利用NewArrayWithSize DFG JIT的操作码来实现,在对FTLLowerDFGToB3.cpp中FTL所实现的allocateJSArray操作码进行分析之后,我们可以看到这个数组将会包含未初始化的值。引擎根本不需要对数组进行初始化,因为这个数组的indexing类型为ArrayWithUndecided。

ArrayValuesallocateJSArray(LValue publicLength, LValue vectorLength, LValue structure,LValue indexingType, bool shouldInitializeElements = true, boolshouldLargeArraySizeCreateArrayStorage = true){    [ ... ]    initializeArrayElements(       indexingType,       shouldInitializeElements ?m_out.int32Zero : publicLength, vectorLength,       butterfly); ...voidinitializeArrayElements(LValue indexingType, LValue begin, LValue end, LValuebutterfly){     if (begin == end)        return;     if (indexingType->hasInt32()) {        IndexingType rawIndexingType =static_cast<IndexingType>(indexingType->asInt32());        if (hasUndecided(rawIndexingType))            return;  // [[ 4 ]]

语句new Array(n)在被FTL JIT编译时将会触发[[4]],然后返回一个indexing类型为ArrayWithUndecided的数组,其中就包含未初始化的元素。

漏洞利用

清楚了之前所介绍的漏洞原理之后,想必触发这个漏洞也并非难事:我们可以不断重复调用一个使用new Array()方法来创建数组的函数,然后调用concat方法将这个数组和一个只包含double类型数据的数组进行拼接。在调用够足够次数之后,FTL编译器将会对其进行编译。

这份【漏洞利用代码】可以利用这个漏洞来泄漏一个目标对象的内存地址,实现机制是通过我们所创建的对象进行内存喷射,在触发这个漏洞之后,我们就能够从代码所返回的数组中找到目标对象的地址了。

总结

这个漏洞目前已经在iOS 12和macOS Mojave的最新版本(Safari)中修复了,该漏洞的CVE编号为CVE-2018-4358。

* 参考来源:phoenhex,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

原文发布于微信公众号 - FreeBuf(freebuf)

原文发表时间:2018-11-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java技术分享圈

Java的数据库连接工具类的编写

1114
来自专栏欧阳大哥的轮子

深入解构objc_msgSend函数的实现

熟悉OC语言的Runtime(运行时)机制以及对象方法调用机制的开发者都知道,所有OC方法调用在编译时都会转化为对C函数objc_msgSend的调用。

1072
来自专栏编程

30个基本的Python技巧和窍门程序员

1.就地交换两个数字。 Python提供了一种直观的方式来分配和交换一行。请参考下面的例子。 x,y = 10,20print(x,y) x,y = y,xpr...

2137
来自专栏chenssy

一题带你彻底理解 sleep() 和 wait()

这段话令人感到迷惑,一个对象不是只有一个锁吗?只有获得这个对象的锁才能对它进行操作,若这个对象的锁被一个线程先获得,那就其他线程就需要等待。那多次加锁什么意思,...

1461
来自专栏DOTNET

asp.net web api 异常捕获

1 向客户端发送错误消息 使用throw new HttpResponseException()向客户端抛出错误信息。 HttpResponseExceptio...

50312
来自专栏大内老A

关于CLR内存管理一些深层次的讨论[上篇]

半年之前,PM让我在部门内部进行一次关于“内存泄露”的专题分享,我为此准备了一份PPT。今天无意中将其翻出来,觉得里面提到的关于CLR下关于内存管理部分的内存还...

1908
来自专栏散尽浮华

Python介绍

Python概述 Python创始人是吉多.范罗苏姆。在1989年万圣节期间为打发时间而开发的。 目前Python在TIOBE排行榜第五位置 ? Python可...

32310
来自专栏JAVA技术站

JFinal一行代码搞定增删改,要的就是快 原

首先需要重写getModel方法,但是这有个问题,重写之后的方法和getModel的参数有点冲突,所以选择不重写,另外写个方法占且叫getBaseModel吧,...

691
来自专栏测试开发架构之路

堆和栈的区别

一、预备知识—程序的内存分配          一个由C/C++编译的程序占用的内存分为以下几个部分     1、栈区(stack)— 由编译器自动分配释放,存...

2858
来自专栏JavaEdge

开发人员必备Redis知识点基础命令键命令string命令hash结构listset结构sorted set

3116

扫码关注云+社区