Page 212 - 《软件学报》2021年第8期
P. 212
2494 Journal of Software 软件学报 Vol.32, No.8, August 2021
的注入.举例来说,含有条件判断语句的 JavaScript 代码被 JIT 编译器编译后,同样能够产生所需要的 gadgets.如
图 12(a)所示,JavaScript 函数 js_gadget 中的条件语句将被编译为“test eax,eax;je 0xc380cd”.其中,JE 指令后的数
字表示 if 代码块内的指令长度,因此,通过设计相应的 if 代码块,攻击者 frame 可以在 JIT 代码中注入任意代
码.Maisuradze 等人 [21] 随后还开发了 DachShund 框架来帮助浏览器测试 JavaScript 中哪些情况会导致 gadgets
的注入.图 12(b)中展示了 DachShund 框架发现的 gadgets 注入场景,包括在 JavaScript 中调用 console 与 Math
等 API 时,参数中的大常数、三元语句以及 switch 语句中的大常数等.
浏览器未进行常数盲化的
通过编写特定长度的JavaScript
代码块来注入大常数 JavaScript语句,将导致这些大常
数被注入到编译后的指令中
JavaScript代码 JIT编译器编译后的二进制代码
(a) 利用条件语句的 gadaget 注入场景 [117] (b) DachShund 发现的部分 gadget 注入场景 [21]
Fig.12 Examples of injecting gadgets through JavaScript
图 12 通过 JavaScript 来注入 gadgets 的示例
5.2.2 防御方案
应对代码重用攻击的方案有两种思路,包括对浏览器进行额外的安全加固和对 JavaScript 代码的重写.
首先,对浏览器进行修改意味着可以确保现有的网站可以在不做任何改动的情况下运行并提高安全.
Athanasakis 等人 [20] 通过对 IE 浏览器的反汇编发现,IE 中实现了常数盲化(constant blinding)机制来阻止大
常数的注入.当 JIT 编译器发现 JavaScript 中含有大于两个字节的大常数时,将会首先生成一个随机数,然后将
JIT 代码用两条指令替代:第 1 条将随机数存储到指定寄存器中,第 2 条对寄存器作异或操作.异或操作的操作数
为随机数与原来大常数的异或值.经过常数盲化后,JIT 代码中将不存在大常数所直接对应的指令,但 JIT 代码的
逻辑未发生变化.特别的,考虑到两个字节的常数在 JIT 代码中大量存在与性能需求,浏览器不能对两个字节的
常数进行常数盲化.Athanasakis 等人 [20] 随后指出,利用 JavaScript 函数与两字节的常数同样能注入所需的
gadgets.因此,常数盲化并不能完全抵抗代码重入攻击.
地址随机化与控制流完整性也能被应用到浏览器中来抵抗代码重入攻击.Frassetto 等人 [116] 设计了能够抵
抗代码重入攻击的 JITGuard 框架.JITGuard 将 JIT 编译器放置在 SGX 机制所创建的 enclave 中,并且利用地址
随机化与地址双向映射将 JIT 编译器编译生成的 JIT 代码放置在随机的区域内.JITGuard 确保随机区域的地址
只有 enclave 内的 JIT 编译器才能获取.即使攻击者通过 JITSpraying 攻击注入了 gadgets,攻击者仍无法定位到
gadgets 的地址,因而代码重用攻击将无法成功.Niu 等人 [118] 将控制流完整性机制应用到 JIT 代码中,其提出的
RockJIT 机制能够抵抗代码重用攻击,但平均产生了 14.4%的运行时开销.
其次,对 frame 中 JavaScript 代码进行验证与重写也能应对代码重用攻击,并能保证浏览器本身的性能不会
因为安全加固而受到影响.Maisuradze 等人 [21] 在用户浏览器与 Web 服务器之间实现了一个网络代理,该代理对
接收到的 JavaScript 代码进行重写来消除大常数.图 13 给出了消除两种形式的大常数的示例.攻击者可通过对
变量的赋值来注入大常数,重写后的 JavaScript 脚本利用 JavaScriptAPI 中的 parseInt 函数,使得大常数 0x1234
作为字符串存在内存中.注意:除了明显的赋值语句外,类似于“1234”&567 的形式也会引入大常数.JIT 编译器在
解析右图的 JavaScript 代码时会进行预处理,即执行“1234”&567 语句并将结果 mov 到存储变量 i 的寄存器中.
网络代理可以利用 JavaScriptAPI 中的 toString 函数要求编译器将变量作为字符串存储.为保证 JavaScript 重写