0x00 前言

前言的前言 笔者一直认为反调试不是银弹,它只能有限地提升利用成本,并没有本质上的改善

最近的一个项目里,不得不引入一些反调试的手段。因此,笔者对近些年来的 Chrome 反调试技术进行了回顾与测试。 在比较流行的反调试技术里,经常提到的有以下几类:

  1. 通过死循环 debugger 让用户无法正常调试
  2. 通过 HTML Element 的 id 属性检测 devtools 是否被开启
  3. 通过正则表达式的 toString() 方法检测 devtools 是否被开启

方法 1 依旧是简单有效的手段。由于过于简单,容易被发现,一般配合代码混淆以及 eval() 等动态代码执行特性实现。这种方式也容易违反 CSP 。如果没有结合动态代码执行,这个方式很容易被发现并且绕过。

方法 2、3 很不幸,在最新的 Chrome 中已经无法利用。关于 2 ,有个 Chromium 的 issue 记录了这个问题以及它的修复方式。关于 3, 笔者暂时没有没有找到 Chrome 的修复记录。

出于上述原因,笔者不得不找到更多的反调试实现。经过一番测试,找到了一个通过 sourcemap 检测 devtools 的方法。

0x01 SourceMap 检测 DevTools 的实现

关于 SourceMap 及其详细用途可参考 Mozilla 的介绍文档

为了加快下载于加载速度,现代网页的 JS 文件普遍被最小化(minified)处理过,去除了不必要的元素,简化了实现,甚至重命名一些变量/函数,提换成更简短的命名方式。这种抠到家的方式,对于开发人员的 debug 和 troubleshooting 是很不利,开发人员自己都读不懂这些 JS 文件。 因此,SourceMap 诞生了。

SourceMap 本质上是提供了一个原始 JS 文件和最小化 JS 文件的映射,使得读不懂的 JS 代码变量可以还原成原始代码。 现代浏览器是非常最求高效的,默认情况下并不会加载 SourceMap 文件。只有当 DevTools 打开的那一刻,Chrome 才会主动加载 SourceMap。 这也就为我们检测 DevTools 提供了可能性。

我们可以在每个 JS 文件的最末尾附加上下列语句:

yourOtherImplements();

//# sourceMappingURL=http://example.com/path/to/your/sourcemap.map

那么 Chrome 在打开 DevTools 的那一刻,服务端就可以感知到这个行为。

那么如何让客户端也感知到这个行为呢? 一个简单的方式是设置 Cookie ,然后客户端再轮询检测这个 Cookie 。 具体实现可参考detect.js,或者直接在本网页打开 DevTools 查看效果。

0x02 举一反三

这种方式实际上也不是特别灵活,需要提前在编译好的 JS 文件中添加或修改 SourceMap 的 URL 。 在实际使用的时候,我们其实可以通过动态添加 <script /> 标签的方式动态插入带 sourceMappingURL 的 JS 代码, 并且在检测到 DevTools 开启后,又自动移除掉该 <script />,提升隐蔽性。 然而这种方式比较容易影响到 CSP 。 可以通过 nonce 配合 unsafe-inline 的方式,保护网站的同时允许动态代码执行。(demo

在测试过程中发现一个有趣的现象,通过 sourceMappingURL 触发的请求,使用 Web Workers 是拦截不到, 说明 Chrome 有做环境上的隔离,保证安全性。然而, sourceMappingURL 设置的 Cookie 却会影响当前网页的 Context ,略微有点奇怪。

0x04 小结

通过 sourceMappingURLSet-Cookie 的方式,我们可以实现检测 DevTools 的开启,进而做一些反调试的工作。该方法在撰文时依旧有效。

然而,如前言所说,反调试不是银弹,切勿过度依赖。笔者更希望 Chrome 团队在不久的将来也将这个反调试手段给封堵了。