紧接着前文:https://bbs.pediy.com/thread-267128-1.htm#1687388
这里将前文的poc.js命名为poc1.js,因为这篇文章会有其他该poc的简单变种。
poc1.js为:
通过前面对v8优化poc1.js后生成指令的逆向分析,我们知道要了解这个漏洞的原因可以分解为以下两个问题:
第一:为什么v8对poc1.js进行优化后生成的指令,对x计算会出现错误,使得随后可以获得长度为1的有效数组对象var cor = new Array(x)。(前面文章,我们通过逆向分析v8对poc1.js进行优化后生成的指令的过程,是在x = Math.abs(x);这一条代码语句对应的指令执行中发现了x计算错误)。
第二:为什么在v8对poc1.js进行优化后,数组对象cor.shift()操作后会直接写入0xFFFFFFFE到代表其数组对象长度的内存位置。
写在前面:
1:v8优化的漏洞利用的核心就是获得可以越界读写的数组。
2:v8优化的漏洞不能直接通过二进制逆向分析寻找漏洞的真正原因,是因为最后能调试的指令只是优化的结果生成的指令,而不是优化的本身,优化的本身是如何生成这些优化指令。所幸google提供了turbofan工具来分析v8优化的各个过程。
3:v8运行poc1.js加上参数 --trace-turbo 会生成一个.json文件的,这个.json文件会记录优化的各个阶段,我们使用turbofan工具时打开该.json文件时,看到的图表只会显示某个优化过程的部分节点。
我们可以先找到显示的要分析的目标节点附近节点,再一步步追踪到我们需要关注的节点(如图 1.1.1和1.1.2操作)。用这操作可以用turbofan分析每个优化阶段的详细过程。
图 1.1.1
图 1.1.2
第一部分,模拟正确JIT优化过程:
既然是车祸原因分析嘛,就先来个简单车祸现场模拟。
如果对计算机和v8符号数的处理机制,以及x64汇编指令有足够的了解的话,可以直接定位错误的位置,这一步可以省略应该,但本人对这些知识不太熟悉,因此用自己的思路解决问题。
这里主要的方式是通过对构造相似的js代码,模拟v8正确优化poc1.js的情况下生成指令,然后对该指令进行逆向分析。
1.1:v8优化错误的指令定位:
车祸分析,最开始的是确定在那个路口出现问题。
在前面动态调试之中,我们是因为在v8在对poc1.js优化后的,其语句x = Math.abs(x); 对应的指令执行时,返回了其参数自身,从而发现其是将参数处理产生错误的。
图1.1.3
如图1.1.3所示的ecx=0x80000001。
这里是我们发现错误的位置,但这不代表这就是错误的开始位置。
这里列举2个可以想到的原因:
a):v8在poc1.js优化后,执行x = Math.abs(x)这一句代码对应的指令之前的部分就已经把他作为无符号数处理了,也就是这句代码的前一句,x = (_arr[0] ^ 0) + 1这句生成的对应的指令就已经是错误的,在往前就没有有代码了。
b):v8在poc1.js优化后,生成的x = Math.abs(x) 这一句代码对应的指令是错误的。
1.2:模拟v8正确的优化:
接下来是对错误指令位置的验证:
这里采用的参照的方法
猜测poc1.js错误的优化和使用0这个自然数有关, 因此可以尝试将常数0用一个Uint32Array变量来存储,让(_arr[0] ^ 0) +1这句代码得到v8正确优化。(也可以尝试别的办法)
因此这里简单修改poc1.js构造poc2.js,获得正确优化情况下的指令:
v8对poc2.js优化后 x = (_arr[0] ^ _arr_0[0]) + 1;这一句代码对应的指令如下图所示:
图1.2.1
图1.2.2
如图1.2.2所示,v8对poc2.js进行优化以后,代码x = (_arr[0] ^ _arr_0[0]) + 1;这一句代码生成的对应的指令为:
xor ecx, dword ptr ds:[rdi]
movsxd rcx, ecx
add rcx,1
这里我们发现v8(可能是x64架构的别的软件也这么处理)将有符号32位数转移到64位寄存器中,正确的处理指令为movsxd。
处理结果为:FFFFFFFF80000000,带有符号扩展,然后再add rcx,1,这样是得到的正确的结果,我们推测这个是v8期望的优化处理结果。
而在v8在优化poc1.js后,生成的x = (_arr[0] ^ 0) + 1; 这句js对应的指令如下:
图1.2.3
如图1.2.3所示:
生成的指令为
mov ecx, dword ptr ds:[rcx]
add rcx, 1
这里直接把_arr[0] ^ 0优化为一个值,然后传递值是采用了mov这个无符号扩展指令,也没有其他的任何符号处理措施,就进行将32位数ecx扩展为64位rcx进行接下来的add rcx,1操作。
图1.2.4
在这里我们发现车祸真正开始出现问题的路口了。
v8优化poc1.js后,生成的x = (_arr[0] ^ 0) + 1;这句代码对应生成的指令是将的_arr[0]^0优化为固定的结果放入内存中,但取出来进行接下来的操作时出现了问题。这里在传递过程中没有对符号位进行处理就直接进入接下来add rcx, 1运算,导致结果错误出现错误。
换句话说在poc1.js的优化中,v8应该采用movsxd这样带有符号扩展的传值指令,而不是使用无符号扩展的mov指令,这是导致我们看到的,在绝对值操作之后产生错误数值的根本原因。
但是为什么会这样呢,为什么v8优化poc1.js时对x = (_arr[0] ^ 0) + 1;这句代码的_arr[0]^0结果从32位扩展为64位时,选择错误的mov指令,而不是使用正确的movsxd来传递数据呢?这像是探索出现车祸背后的交通规则设计的缺陷。
单靠逆向优化后的指令是无法知晓这个问题的答案的。这里就要就要借助google提供的turbofan,对优化的重要阶段进行分析;
第二部分:对poc1.js优化过程的turbofan进行分析:
这里对poc1.js进行简单的修改,tubofan分析变得简单一点,这里将这新文件命名为poc3.js。
2.1:TFTyper阶段分析
2.1.1:在我这环境中用turbofan查看v8生成的.json文件是顺着70:Branch[None, NoSafetyCheck]节点往上看,可以找到v8优化后的poc1.js中x = (_arr[0] ^ 0) + 1; 这句代码对应的所有节点。(不同环境可能会不同)
图2.1.1
接着我们从return节点往上找
图2.1.2
2.1.2:图2.1.2 中的162: StoreField[+12]该节点为重要节点,是cor.shift()这句代码优化的重要部分,StoreField[+12]也就是偏移12的位置存放数值,根据对这图表的分析可以知道这节点代表的是往cor数组对象的+12的位置存放数据,而这个内存位置代表的正是数组的长度。
我们需要的是知道是后面阶段是如何优化的,最后为什么会直接写进0xFFFFFFFE这个数值。
图2.1.3
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-5-13 10:08
被苏啊树编辑
,原因: