V8入门记录

前言

这篇文章拖的有点久,从去年11月份就开始搞,后面复习什么的就没继续了,虽然大体知识过了下,但是感觉没有精力去完善,就简单完结下吧。

正文

V8简介

V8引擎是一种JavaScript引擎的实现。

JavaScript引擎是执行JavaScript代码的程序或解释器。javaScript引擎可以实现为标准解释器或即时编译器,它以某种形式将JavaScript编译为字节码。

V8是被设计用来提高网页浏览器内部JavaScript执行的性能的。V8引入了JIT在运行时把js代码进行转换为机器码,而不产生字节码或任何中间代码,从而提高了性能。相应的代码执行过程大致为:源代码→抽象语法树→JIT→本地代码。

另外,在V8中当某些代码需要被执行时,才会进行编译。

V8充分多进程:主进程负责获取代码,编译生成机器码;有专门负责优化的进程;还有一个监控进程负责分析哪些代码执行比较慢,以便Crankshaft做优化;最后还有一个就是GC进程,负责内存垃圾回收。

V8具体优化方案(这里只列举,详细可查看最下方参考文章):

  1. 尽可能最大的内联
  2. 隐藏类的优化
  3. 内联缓存
  4. Compilation to machine code
  5. 垃圾回收机制

v8项目结构如下:

  • V8
    • include V8接口
      • v8.h 用于包含V8的接口
      • v8-debug.h V8调试相关的接口
      • v8-profiler.h V8信息收集器的接口
      • v8-testing.h V8测试相关的接口
    • src V8内部实现
      • arm ARM后端,抽象语法树转成ARM指令的相关代码
      • ia32 IA32后端,抽象语法树转成IA32指令的相关代码
      • x64 X64后端,抽象语法树转成X64指令的相关代码
      • ast.h/cc 抽象语法树的实现
      • d8.h/cc V8的一个调试程序
      • full-codegen.h/cc 从抽象语法树生成本地代码
      • heap.h/cc V8使用的堆实现
      • extensions V8扩展机制
    • benchmarks JavaScript性能测试用例
    • build 编译V8项目相关脚本

环境搭建

使用技巧

%DebugPrint()

使用时需要添加启动参数--allow-natives-syntax,可用于打印对象的内存地址、属性、map等。

%SystemBreak()

即为断点,相当于int 3。

Print()

用于输出变量值。

readline()

用于输入。

gdb-v8-support.py

gdb-v8-support.py为v8自带的gdb调试命令,位于/tools/目录下,添加到gdbinit中即可使用。

job命令

用于可视化显示JavaScript对象的内存结构。(注意:只能在debug版本中使用,release版本会提示No symbol "_v8_internal_Print_Object" in current context.)

telescope

用于查看指定内存地址的数据。

polyfill

用于提供开发者们希望浏览器原生提供支持的功能。

例题

2019 *ctf oob

下载地址

环境搭建

首先下载源码:

1
fetch v8

回退到相应版本

1
git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598

接着在v8目录下打上patch:

1
git apply /path/oob.diff

接着进行编译:

1
2
3
4
5
6
7
gclient sync
#debug
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug
#release
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release

这样就将题目环境搭建好了。

漏洞分析

在分析漏洞前首先要搞懂patch的文件是什么。

可以明显看到对数组添加了oob方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
}
}

即当参数个数为1时,返回array[length]处的值的浮点数形式;当参数个数为2时,将参数2以浮点数方式向array[length]处写入。

很明显,array[length]是数组溢出,而在js中,这个位置存放的是map类型,用于对该数组进行解析。

数组地址对应内容

其中:

  • map定义了如何访问对象
  • Prototype对象的原型
  • Emelmts地址指向存放元素的地址
  • Lengtrh长度

因此修改map属性使其从整数变为浮点数等即可做到溢出,接着修改地址及长度即可做到任意写,然后结合wasm即可获取flag。具体步骤见参考文章4。

参考文章

JavaScript V8引擎

认识V8引擎

v8 exploit入门[PlaidCTF roll a d8]

chrome study by v8 oob