Chrome 反调试之检测 DevTools
0x00 前言
前言的前言 笔者一直认为反调试不是银弹,它只能有限地提升利用成本,并没有本质上的改善
最近的一个项目里,不得不引入一些反调试的手段。因此,笔者对近些年来的 Chrome 反调试技术进行了回顾与测试。 在比较流行的反调试技术里,经常提到的有以下几类:
- 通过死循环
debugger
让用户无法正常调试 - 通过 HTML Element 的
id
属性检测 devtools 是否被开启 - 通过正则表达式的
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 小结
通过 sourceMappingURL
和 Set-Cookie
的方式,我们可以实现检测 DevTools
的开启,进而做一些反调试的工作。该方法在撰文时依旧有效。
然而,如前言所说,反调试不是银弹,切勿过度依赖。笔者更希望 Chrome 团队在不久的将来也将这个反调试手段给封堵了。