首页
社区
课程
招聘
[翻译]Firefox UAF漏洞利用——基于shared array buffers
发表于: 2017-8-1 22:53 5783

[翻译]Firefox UAF漏洞利用——基于shared array buffers

2017-8-1 22:53
5783

原文地址:https://phoenhex.re/2017-06-21/firefox-structuredclone-refleak

firefox-53b0下载地址:

https://download-installer.cdn.mozilla.net/pub/firefox/releases/53.0b1/linux-x86_64/en-US/firefox-53.0b1.tar.bz2

firefox-53b0-source源码下载地址:

http://ftp.mozilla.org/pub/firefox/candidates/53.0b1-candidates/build1/source/firefox-53.0b1.source.tar.xz


*******************************************************************************

*               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中的引用计数泄漏

-------------------------



[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//