-
-
[翻译]Firefox UAF漏洞利用——基于shared array buffers
-
发表于: 2017-8-1 22:53 5783
-
原文地址:https://phoenhex.re/2017-06-21/firefox-structuredclone-refleak
firefox-53b0下载地址:
firefox-53b0-source源码下载地址:
*******************************************************************************
* Firefox UAF漏洞利用——基于shared array buffers *
*******************************************************************************
这篇博客分析了操作shared array buffers过程中的一处指针解引用。通过和一处溢出检
测绕过结合,可以最终实现远程代码执行。这个问题由saelo发现,你可以在Firefox的Bug
zilla(https://bugzilla.mozilla.org/show_bug.cgi?id=1352681)中找到相关的报告。
这篇文档将分成如下几节:
* 背景介绍
* 漏洞分析
* 漏洞利用
* 总结
我们的exploit基于Linux系统上的Firefox Beta 53编写。需要指出的是,正式发行的Fire
fox版本不受这个漏洞影响,因为由于这个漏洞,shared array buffers已经在Firefox 52
之后的版本中被禁用了。完整的exploit可以在这里(https://github.com/phoenhex/file
s/tree/master/exploits/share-with-care)找到。
=============
1. 背景介绍
=============
要理解这个漏洞和它的利用手法需要一些前置知识,包括结构化克隆算法(structured cl
one algorithm,后文缩写为SCA)和shared array buffers, 本节会对它们做简要介绍。
--------------------------------
1.1 Structured Clone Algorithm
--------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~[引用]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
引用自Mozilla Develop Network官方文档:
结构化克隆算法SCA是HTML5规范为复杂javascript对象序列化而定义的新算法。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SCA被用于Spidermonkey引擎内部的序列化,以便在不同上下文之间传递对象。与json相反
,它可以解析循环引用。在浏览器中,postMessage函数使用了序列化和反序列化的功能。
postMessage函数主要在以下两个场景使用:
* 通过windows.postMessage()进行跨域/跨窗口的通信
* 与Web Worker通信,这是一种便捷的并行执行javascript代码的方式
与Web Worker通信的一个简单例子,如下所示:
var w = new Worker('worker_script.js');
var obj = { msg: "Hello world!" };
// send obj to Worker
w.postMessage(obj);
工作脚本“worker_script.js”可以注册一个onmessage监听器来接收主线程发送的obj。
this.onmessage = function(msg) {
var obj = msg;
// do something with obj now
}
跨窗口间的通信也采用类似的流程。这两个场景中,接收脚本在完全不同的全局上下文中
执行,因此无法访问发送方上下文的对象。因此,我们需要某种方法,在发送方和接收脚
本之间传输对象,以及在接收脚本的上下文中重建对象。为了实现这一点,SCA在发送方的
上下文中序列化obj,并在接收脚本的上下文中对其反序列化,从而创建对象的副本。
我们可以在源码路径“js/src/vm/StructuredClone.cpp”中找到SCA的代码。代码中定义了两个重要的类:JSStructuredCloneReader和JSStructuredCloneWriter。JSStructuredCloneReader的方法处理在接收线程的上下文中反序列化对象,而JSStructuredCloneWriter的方法处理发送线程上下文中对象的序列化。
处理对象序列化的关键函数是JSStructuredCloneWriter::startWrite(),关键逻辑如下所示:
bool
JSStructuredCloneWriter::startWrite(HandleValue v)
{
if (v.isString()) {
return writeString(SCTAG_STRING, v.toString());
} else if (v.isInt32()) {
return out.writePair(SCTAG_INT32, v.toInt32());
[...]
} else if (v.isObject()) {
[...]
} else if (JS_IsTypedArrayObject(obj)) {
return writeTypedArray(obj);
} else if (JS_IsDataViewObject(obj)) {
return writeDataView(obj);
} else if (JS_IsArrayBufferObject(obj)
&& JS_ArrayBufferHasData(obj)) {
return writeArrayBuffer(obj);
} else if (JS_IsSharedArrayBufferObject(obj)) {
return writeSharedArrayBuffer(obj); // [1]
} else if (cls == ESClass::Object) {
return traverseObject(obj);
[...]
}
return reportDataCloneError(JS_SCERR_UNSUPPORTED_TYPE);
}
这个函数会根据对象的类型执行不同的操作,如果是基本类型对象,就直接将它序列化,
否则调用相关函数以根据对象类型执行进一步的序列化操作。这些函数会确保对象的任何
属性或者数组的所有成员都被递归的序列化。我们感兴趣的地方是当对象是一个SharedArr
ayBufferObject时,这个函数会最终调用writeSharedArrayBuffer()函数(我们标注为[1]
的位置)。
函数的最后,如果传入的对象既不是基本类型也不是可序列化的对象,它将简单地抛出错误。反序列化的处理流程与上述过程类似,它将以序列化的对象为输入,创建新对象,并为其分配内存。
--------------------------
1.2 Shared Array Buffers
--------------------------
shared array buffer提供了一种创建共享内存的方式,可以在不同的上下文中被传递或访
问。它由继承自NativeObject类(NativeObject类是大多数Javascript对象的基类)的Sha
redArrayBufferObject C++类实现。SharedArrayBufferObject具有如下的抽象表示(如果
你查看源码,可能会发现源码中并没有这样明确的定义,为了读者能更好的理解下文的内
存布局,此处做了简化的介绍):
class SharedArrayBufferObject {
js::GCPtrObjectGroup group_;
GCPtrShape shape_; // used for storing property names
js::HeapSlot* slots_; // used to store named properties
js::HeapSlot* elements_; // used to store dense elements
js::SharedArrayRawBuffer* rawbuf; // pointer to the shared memory
}
rawbuf是一个指向SharedArrayRawBuffer对象的指针,该对象保存底层内存缓冲区。当通
过postMessage()发送时,SharedArrayBufferObject将被重新创建为接收工作者上下文
中的新对象。另一方面,SharedArrayRawBuffers在不同的上下文之间共享。 因此,单个S
haredArrayBufferObject的所有副本的rawbuf属性都指向相同的SharedArrayRawBuffer对
象。为了内存管理的目的,SharedArrayRawBuffer包含一个引用计数器refcount_:
class SharedArrayRawBuffer
{
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
uint32_t length
bool preparedForAsmJS;
[...]
}
引用计数器refcount_跟踪一共有多少个SharedArrayBufferObject指向它。在JSStructure
dCloneWriter::writeSharedArrayBuffer()函数内,序列化SharedArrayBufferObject时
,它会增加,在SharedArrayBufferObject的析构函数中它会递减:
bool
JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj)
{
if (!cloneDataPolicy.isSharedArrayBufferAllowed()) {
JS_ReportErrorNumberASCII(context(),
GetErrorMessage,
nullptr,
JSMSG_SC_NOT_CLONABLE,
"SharedArrayBuffer");
return false;
}
Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(),
&CheckedUnwrap(obj)->as<SharedArrayBufferObject>());
SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
// Avoids a race condition where the parent thread frees the buffer
// before the child has accepted the transferable.
rawbuf->addReference();
intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
return out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
static_cast<uint32_t>(sizeof(p))) &&
out.writeBytes(&p, sizeof(p));
}
haredArrayBufferObject::Finalize(FreeOp* fop, JSObject* obj)
MOZ_ASSERT(fop->maybeOffMainThread());
SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
// Detect the case of failure during SharedArrayBufferObject creation,
// which causes a SharedArrayRawBuffer to never be attached.
Value v = buf.getReservedSlot(RAWBUF_SLOT);
if (!v.isUndefined()) {
// refcount_ decremented here
buf.rawBufferObject()->dropReference();
buf.dropRawBuffer();
}
}
SharedArrayRawBuffer::dropReference()将在随后检查引用计数是否为零,如果满足条件就释放SharedArrayRawBuffer占用的内存。
=============
2. 漏洞分析
=============
我们发现了两处漏洞,它们单拿出来无法被利用,但组合在一起可以实现远程代码执行。
-------------------------------------
2.1 refcount_引用计数的整数溢出漏洞
-------------------------------------
上文提到的SharedArrayRawBuffer对象的属性refcount_,没有正确的进行溢出检查:
void
SharedArrayRawBuffer::addReference()
{
MOZ_ASSERT(this->refcount_ > 0);
++this->refcount_; // Atomic.
}
可以看到代码只是简单的增加refcount_的值,并没有校验是否发生溢出,当整数溢出时,
refcount_会变为0,将触发异常。回想一下,refcount_被定义为一个uint32_t整数,这意
味着上面的代码路径必须被触发232次以便溢出。这里的主要问题是每次调用postMessage
()将创建一个SharedArrayBufferObject的副本,并分配0x20字节的内存。Firefox的堆大
小的上限为4GB,上述的溢出将需要128GB,远远超过堆大小的上限,这使得这个整数溢出根本无法触发,更不可利用。
-------------------------
2.2 SCA中的引用计数泄漏
-------------------------
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]2017秋季赛第二题Writeup 3279
- [翻译]IoT设备固件分析教程之固件是怎么存储的 20910
- [原创]ARM汇编基础教程番外篇——配置实验环境 26931
- [翻译]ARM汇编基础教程——ARM指令集 12838
- [翻译]ARM汇编基础教程——数据类型和寄存器 11538