V8入门记录
前言
这篇文章拖的有点久,从去年11月份就开始搞,后面复习什么的就没继续了,虽然大体知识过了下,但是感觉没有精力去完善,就简单完结下吧。
正文
V8简介
V8引擎是一种JavaScript引擎的实现。
JavaScript引擎是执行JavaScript代码的程序或解释器。javaScript引擎可以实现为标准解释器或即时编译器,它以某种形式将JavaScript编译为字节码。
V8是被设计用来提高网页浏览器内部JavaScript执行的性能的。V8引入了JIT在运行时把js代码进行转换为机器码,而不产生字节码或任何中间代码,从而提高了性能。相应的代码执行过程大致为:源代码→抽象语法树→JIT→本地代码。
另外,在V8中当某些代码需要被执行时,才会进行编译。
V8充分多进程:主进程负责获取代码,编译生成机器码;有专门负责优化的进程;还有一个监控进程负责分析哪些代码执行比较慢,以便Crankshaft做优化;最后还有一个就是GC进程,负责内存垃圾回收。
V8具体优化方案(这里只列举,详细可查看最下方参考文章):
- 尽可能最大的内联
- 隐藏类的优化
- 内联缓存
- Compilation to machine code
- 垃圾回收机制
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项目相关脚本
- include 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 | gclient sync |
这样就将题目环境搭建好了。
漏洞分析
在分析漏洞前首先要搞懂patch的文件是什么。
可以明显看到对数组添加了oob方法:
1 | BUILTIN(ArrayOob){ |
即当参数个数为1时,返回array[length]处的值的浮点数形式;当参数个数为2时,将参数2以浮点数方式向array[length]处写入。
很明显,array[length]是数组溢出,而在js中,这个位置存放的是map类型,用于对该数组进行解析。
其中:
- map:定义了如何访问对象
- Prototype:对象的原型
- Emelmts地址:指向存放元素的地址
- Lengtrh:长度
因此修改map属性使其从整数变为浮点数等即可做到溢出,接着修改地址及长度即可做到任意写,然后结合wasm即可获取flag。具体步骤见参考文章4。