PS:本系列会先介绍一些前置知识,在前言中我会列出学习资料(参考rjk师傅仓库以及其他博客),文章后半部分会结合pwncollege进行练习。
文章目录
前言
[*]在学习本系列前,你应该对JS语法介绍 - JavaScript | MDN和二进制PWN堆栈漏洞原理和利用栈介绍 - CTF Wiki有基本认识。
[*]本系列将基于pwn.college中的V8 Exploitation以及rjk师傅V8仓库https://github.com/bjrjk/pwn-learning/tree/main/PwnCollege/V8Exploitation进行学习,建议先尝试自己做一下,有任何问题请通过评论或QQ进行讨论。[*]学完本篇文章后,你将掌握如何使用浮点数制作V8 shellcode。
一、V8漏洞挖掘价值
V8是Google用 C++ 编写的开源高性能 JavaScript 引擎,由于它广泛应用于Chrome浏览器和Node.js,所以使用Chrome内核或V8的内置浏览器都将受到V8漏洞的影响(如微信、支付宝和Electron应用等)。
二、初识JS引擎
1.JS引擎发展历史
众所周知,JavaScript是一种解释型脚本语言,早些年的JS执行工具只有解释执行,JS引擎会逐行读取源代码,解析每一行并直接执行。随着Web应用和动态内容的迅速增长,对JavaScript的执行需求急剧增加,传统的解释执行模式无法满足性能要求,所以在2000年代初引入了JIT(即时编译)技术。
2.V8引擎运行基本流程
V8引擎的五个基本步骤:
① 解析器(Parser):将JavaScript源代码转换为抽象语法树(AST)。
② 抽象语法树(AST):表示源代码的语法结构。
③ Ignition(解释器):将AST转换为字节码并执行,解释器逐行执行字节码。
④ TurboFan(编译器):对热点代码进行优化,将字节码编译为高度优化的机器码。
⑤ 执行:执行生成的机器码以提高代码的执行效率。
我这里画了一个流程图便于大家了解:
3.解析器(parser)
解析器是将源代码转换为更易于处理的结构(通常是AST)的程序部分。解析过程主要分为两个阶段:
① 词法分析(Lexical Analysis):
-
词法分析的主要任务是将源代码字符串分解成一系列称为“记号”(tokens)的基本元素。每个记号代表源代码中的一个基本组成部分,如关键字、标识符、运算符、数字等。
-
词法分析器会忽略空白字符和注释,聚焦于提取有效的代码元素。
② 语法分析(Syntax Analysis):
-
在这个阶段,解析器根据语言的语法规则将记号组合成更复杂的结构,通常是抽象语法树(AST)。
-
语法分析会检查记号的排列是否符合语言的语法规则,如果不符合,解析器会抛出语法错误。
4.抽象语法树(AST)
AST是一种树状数据结构,表示源代码的语法结构。每个节点代表代码的一个组成部分(如表达式、语句、操作符等),可以使用https://astexplorer.net/来查看AST结构。
5.解释器(Ignition)
在V8 JavaScript引擎中,Ignition是其解释器,它承担了将JavaScript代码转换为字节码并执行的任务。以下是Ignition的工作原理:
① 解析并生成字节码:
当V8接收到JavaScript源代码时,解析器首先将其解析为抽象语法树(AST),然后Ignition将AST转换为字节码。字节码是一种轻量级的中间表示,方便执行。
② 字节码的执行:
Ignition直接执行生成的字节码,它使用一种虚拟机机制逐行解释和执行这些字节码指令。
③ 动态优化:
Ignition会监测代码执行的频率,如果发现某段代码被频繁执行,它会标记为“热代码”。这种热代码之后可能会由V8的优化编译器(TurboFan)进一步优化,并转换为机器码以提高执行速度。
6.编译器(TurboFan)
① 优化编译:
当 Ignition 解释器执行字节码时,它会持续监测代码的执行情况。如果某段代码被多次调用,V8会将其标记为热代码。一旦代码被标记为热代码,TurboFan 编译器会接管,并对字节码进行优化编译。
② 生成本地机器码:
经过优化之后,TurboFan将生成本地机器码(native machine code)。这段机器码是直接针对CPU架构的,能够在硬件上高效执行。
③ 本地机器码执行:
V8引擎会将生成的本地机器码缓存起来,并在后续执行时直接调用这些机器码,而不是通过解释器逐行执行字节码。因为本地机器码已经是针对硬件的低级指令,执行效率极高。
三、V8漏洞种类
其实V8也是一种应用程序,所以也会有常见的二进制漏洞,根据漏洞占比主要可以分为以下三种类型:
① 越界访问(Out-of-Bounds, OOB):
访问数组或对象时,尝试读取或写入不在有效范围内的内存。比如data[]的长度为10,但在读取或写入data数组时,下标大于9或小于0,导致写入了data数组外的内存区域。
② 类型混淆(Type Confusion):
V8的类型混淆漏洞占了相当大的比例,这要归功于JavaScript是弱类型语言。在弱类型语言中,变量的类型是动态的,可以在运行时改变。例如,上边我们讲到编译器负责将热代码优化转换为机器码,当代码被优化时,类型信息的推断可能会不准确。如果优化器假设某个变量始终是特定类型,而实际上在某些情况下它是另一种类型,就可能会导致类型混淆漏洞。
③ 其他漏洞:
使用后释放(Use-After-Free, UAF),双重释放(Double Free),数据竞争(Data Race)等等。(这里简单了解一下即可,后边会有针对性讲解和练习)。
四、练习题
题目名称:PwnCollege V8 Exploitation Level 1
题目链接:pwn.college
知识点: 转换浮点数为机器码执行shellcode
1.PwnCollege的使用
打开题目链接后来到level1,这里提供了“Start”和“Practice”两种模式。Start模式是正式环境,在该环境中我们只有普通shell权限,要利用对应的漏洞才能获取flag;Practice模式是练习环境,允许我们提升到root权限,从而使用gdb等工具调试V8,不过在这里获取flag是没用的,打通后要在正式环境中利用才能获取到Real Flag。
这里使用VSCode或GUI Desktop交互都可以,看个人习惯。纯新手建议GUI Desktop。进入工作环境后,打开challenge文件夹,这里包含了题目所有文件。
2.题目文件的用法
题目提供了七个文件,catflag、d8、patch和run这四个文件是和解题有关的,通过./run /path/exp.js来执行getflag获取flag。脚本run调用d8(d8 是 V8 JavaScript 引擎的命令行工具,用于执行 JavaScript 代码)来执行我们的exp.js文件,利用漏洞getflag。
那么漏洞在哪呢?本系列的练习题都是人为添加的漏洞,所以我们通过分析patch文件,就可以知道增加删除了哪些代码,漏洞就在其中。
3.分析patch文件
打开patch文件,总体分析一下,发现主要逻辑在43~55行,主要是使用mmap函数分配一段内存,循环遍历elements数组,并将获取到元素的值转换为double类型,同时复制到mmap分配的内存中,最后执行这段内存中的代码。
最后run一下创建的double数组,即可触发ArrayRun。
4.利用思路
这道题主要帮助大家初步认识V8漏洞的一种利用手段,虽然真实V8中不会有这么明显的漏洞,但实际V8漏洞利用真的会用到double类型构造shellcode,所以这是非常好的入门利用练习。
本题利用思路就是先将shellcode(16进制)转换为double类型,当double数字被写入内存中时也就相当于写入了shellcode(因为两者二进制相同)。rjk师傅推荐了一个在线转换工具可以在学习初期使用Double (IEEE754 Double precision 64-bit) Converter感受转换过程,在下方Binary处填入对应的Hexadecimal,点击convert to decimal即可。
5.构造EXP
rjk师傅编写了类型转换脚本pwn-learning/PwnCollege/V8Exploitation/ShellCode at main · bjrjk/pwn-learning · GitHub,同时给出了第一题的构造脚本,当然还是希望读者自己从头写一下。
PS:有师傅疑惑64位如果参数较多,使用push+mov的方式会影响调用,这里提一下,{shellcraft.amd64.push(0)},{shellcraft.amd64.mov("rdx", "rsp")}也可以使用{shellcraft.amd64.setregs({'rdx': 0})}代替。
总结
恭喜你入门V8漏洞利用啦!感谢rjk师傅对我的指导,计划在两个月内完成10个练习,感兴趣的师傅可以点个关注。