"死灰复燃"的恶意脚本行为分析
本文承接上文,没看过的同学可以先大致了解一下背景。现在X度文库已经注意到了自家文档被恶意脚本复制传播的问题,设计了一些反制策略,比如将文档渲染为 canvas。但是,恶意脚本的迭代速度也没落下,且其实现成本十分低廉。下面我截取了一段恶意脚本的关键实现代码,借此分析恶意脚本是如何对抗x度文库的防复制策略的。
document.createElement = new Proxy(document.createElement, {
apply: function (target, thisArg, argumentsList) {
var element = Reflect.apply(target, thisArg, argumentsList);
if (argumentsList[0] === "canvas") {
var tmpData_1 = {
canvas: element,
data: [],
};
element.getContext("2d").fillText = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
tmpData_1.data.push(args);
originObject.context2DPrototype.fillText.apply(this, args);
};
canvasDataGroup.push(tmpData_1);
}
return element;
},
});
复制代码
这段代码有几个关键点,一是利用Proxy
劫持document.createElement
,二是 Hook 每个canvas
实例的关键绘制函数,即fillText
,从而拿到格式化后的明文信息。恶意脚本的具体行为可以参照下面这个图。
基于 WASM 的反制
在上篇文章中已经说明了为什么要选择 WASM 来提高前端的安全性,这里不再赘述。在 WASM 中只要操作 dom 或 bom 就一定会被恶意脚本以同样的方式劫持并 Hook,因此解密后的明文信息不能通过浏览器提供的任何渲染接口来绘制。基于此,我想到了直接把文字当成一个个像素点绘制到一张图片中,这需要在 WASM 中提供处理图片信息的能力。所幸,已经有开源库完成了这部分工作,如 image 和 imageproc。由于这些开源库与底层文件系统与图形系统不进行耦合(这点很重要,因为 WASM 提供的是一个沙箱环境),所以它们都可以很好在 WASM 环境上运行。
具体流程图变化不大,只是修改了绘制明文的方式。
下面我们具体看看引入 WASM 后如何反制恶意脚本。
恶意脚本最关键的第五步已经失效了,因为在 WASM 层与 dom 的交互,仅仅是将处理后的图片通过 putImageData 来绘制到 canvas 上,即使对 putImageData 进行 Hook 也仅仅只能拿到图片数据,而图片数据要倒推回明文信息,那只能够通过 OCR 来进行解析。具体实现代码已贴到附录。
实现效果
具体实现效果可以看下图:
总结
通过分析恶意脚本的行为,我们发现恶意脚本想要劫持和 Hook 那些 dom 和 bom 的方法和对象是十分简单的,不管x度文库做了多大努力,恶意脚本都可以轻松的以同样的思路来破解。我认为,问题的本质就在于 js 代码的可读性较强(即使经过了打包和混淆),而且 js 很容易被注入和动态调试,而 wasm 经过编译后其可读性已经很差了而且也没法注入和动态调试(仅为个人认知,欢迎讨论)。所以通过把关键函数乃至渲染放到 WASM 中能大大提高破解成本。当然如果最后祭出了 OCR 大法,在浏览器层面是没法解决的(有本事来客户端碰一碰)。
附录
- 具体实现源码 - github.com/ascodelife/…
参考文章
- canvas fillText 接口 - developer.mozilla.org/zh-CN/docs/…
- Proxy - developer.mozilla.org/zh-CN/docs/…
- WebAssembly 介绍 - developer.mozilla.org/zh-CN/docs/…
- wasm-bindgen 文档 - rustwasm.github.io/wasm-bindge…
- canvas putImageData 接口 - developer.mozilla.org/zh-CN/docs/…
- rust image 开源库 - github.com/image-rs/im…
- rust imageproc 开源库 - github.com/image-rs/im…