在本文的上篇中, 我们为读者介绍了与 JavaScript 反调试有关的一些技巧, 其中包括函数重定义断点时间差异 DevTools 检测和执行流程完整性的隐式控制等, 接下来, 我们将为读者介绍更多的反调试技巧
0x06 代码完整性的隐式控制
在前面的 0x01 函数重定义部分, 我们提到可以在 JavaScript 中使用 toString()方法检索函数的代码正如我们所说的, 这对检查一个函数是否被重新定义是非常有用的, 事实上, 同样也可以使用这个思路来检测函数的代码是否被修改过
其中, 一种有效的方法是计算函数或代码块的哈希值, 并将其与预知的哈希表进行比较但是, 这种方法确实有点笨拙 更现实和有效的方法, 是之前使用的跟踪堆栈的策略 我们可以计算一段代码的哈希值, 并将其用作解密其他代码块的密钥
为了实现隐式完整性控制, 最巧妙的想法是 md5 碰撞这个想法是由 @cgvwzq 创造的, 就在去年夏天几杯啤酒下肚后, 一个天才的想法就这样诞生了简单来说, 我们可以创建这样的函数: 在函数内部检测自己的 md5 为了执行函数内部的检查, 我们需要使用碰撞 (我们想创建类似 function(){ if (md5(arguments.callee.toString() === '<md5>') code_function; } 这样的函数)
这种技术背后的思想与用于生成图像文件的概念相同, 这些图像文件显示的就是自己的 md5 校验和 这是一个经典的例子: 一个显示自己的 md5 校验和的 gif
关于如何创建这种类型的碰撞, 有大量的文章 (甚至在 PoC||GTFO 中还有实际例子) 可供参考, 但是, 我阅读的关于这方面的第一篇文章, 使用的是 PHP 语言您可以预先计算生成碰撞所需的块事实上, 这是由 @cgvwzq 创建的一个例子, 就是通过这种方式来检查函数内容的完整性
正如我们之前所说的, 这种技术想要发挥作用, 必须结合使用强大的混淆技术
0x07 代理对象
代理对象是 JavaScript 世界中引入的最有用的工具之一 这个对象可以用来窥探其他对象, 改变其行为(如 hook), 或者在某些情况下触发一个动作 例如, 如果我们想跟踪每个对 document.createElement 的调用并记录这些信息, 我们就可以创建一个代理对象:
- const handler = { // Our hook to keep the track
- apply: function (target, thisArg, args){
- console.log("Intercepted a call to createElement with args:" + args);
- return target.apply(thisArg, args)
- }
- }
- document.createElement = new Proxy(document.createElement, handler) // Create our proxy object with our hook ready to intercept
- document.createElement('div');
然后, 当调用 createElement 时, 我们会发现其参数将被显示到控制台中:
VM64:3 Intercepted a call to createElement with args: div
太棒了! 这样的话, 我们就可以通过拦截一些众所周知的函数 (làstrace/ltrace) 来调试代码了但正如在 0x01 函数重定义一节中看到的那样, 我们可以使用该方法来隐藏或伪造信息, 或者只是运行与我们所看到的代码不同的代码 (可以直接替换示例中 hook 内部的逻辑) 这种函数 hooking 技术的威力远胜于简单的重定义技术
但是, 在本文中, 我们关注的重点是提供一些反调试方面的思路和方法, 所以我们可以检测分析人员是否正在使用代理对象吗? 是的, 我们可以, 但这是一个猫捉老鼠的游戏例如, 使用相同的代码片段, 我们可以尝试调用 toString 方法来捕获异常:
- // Call a "virgin" createElement:
- try {
- document.createElement.toString();
- } catch(e){
- console.log("I saw your proxy!");
- }
如果一切正常, 则:
"function createElement() { [native code] }"
但是, 如果我们使用代理
- //Then apply the hook
- const handler = {
- apply: function (target, thisArg, args){
- console.log("Intercepted a call to createElement with args:" + args);
- return target.apply(thisArg, args)
- }
- }
- document.createElement = new Proxy(document.createElement, handler);
- //Call our not-so-virgin-after-that-party createElement
- try {
- document.createElement.toString();
- } catch(e) {
- console.log("I saw your proxy!");
- }
是的, 它的确可以检测到这个代理:
VM391:13 I saw your proxy!
如前所说, 这只是一个鼠猫游戏实际上, 我们可以添加 toString 方法:
- const handler = {
- apply: function (target, thisArg, args){
- console.log("Intercepted a call to createElement with args:" + args);
- return target.apply(thisArg, args)
- }
- }
- document.createElement = new Proxy(document.createElement, handler);
- document.createElement = Function.prototype.toString.bind(document.createElement); //Add toString
- //Call our not-so-virgin-after-that-party createElement
- try {
- document.createElement.toString();
- } catch(e) {
- console.log("I saw your proxy!");
- }
现在, 我们的检测将失败:
"function createElement() { [native code] }"
0x08 限制环境
正如前文所述, 有时需要检测代码是否在正确的环境中执行我们所说的正确的环境是:
. 代码运行在浏览器中(不是模拟器不是 NodeJS,)
. 代码运行在目标域 / 资源中(不是本地服务器)
例如, 我们可以通过进行一些简单的检查工作来验证代码是否在本地执行:
- // Pretty stupid idea found in commercial software
- if (location.hostname === "localhost" || location.hostname === "127.0.0.1" || location.hostname === "") {
- console.log("Don't run me here!")
- }
如果我们在本地 html 中运行上面的 JavaScript 代码段, 将看到以下消息:
VM28:3 Don't run me here!
按照这个思路, 另一个方法是检测用来打开文档的 handler(类似于 if(location.protocol =='file:'){}), 或者尝试通过 HTTP 请求检测其他资源 (图像 CSS 等) 是否可用 当然, 所有这些方法都非常容易绕过
一个更有趣的想法是避免代码在 NodeJS 中执行(或者就像我们在本文前面所做的那样: 修改执行流程, 使其进入伪造的路径) 虽然这很危险, 但已经有人在使用 NodeJS 解决 JavaScript 的各种挑战并绕过反暴力破解缓解措施
我们可以设法检测只存在于浏览器上下文中的对象:
- //Under NodeJS
- try {
- .. console.log(window);
- } catch(e){
- .. console.log("NodeJS detected!!!!");
- }
- NodeJS detected!!!!
反之亦然: 在 NodeJS 中, 有一些对象也是浏览器上下文所不具备的
- //Under the browser
- console.log(global)
- VM104:1 Uncaught ReferenceError: global is not defined
- at <anonymous>:1:13
- //Under NodeJS
- console.log(global)
- { console:
- Console {
- log: [Function: bound log],...
- ...
我们也可以搜索只存在于浏览器中的各种元数据 在 Panopticlick 项目中可以看到这类想法的影子
0x09 webGL
这里, 我们不会介绍 WebGL 内部的反逆向或混淆技术, 因为这方面的内容可以从网上找到相反, 本文所介绍的是如何使用 WebGL 处理数据并与 JavaScript 交互: 如果有人试图模拟我们的 JavaScript 代码, 他就需要为其模拟器提供 WebGL 支持
我们可以实现一个简单的算法 (例如多色分形) 来创建基于各种种子的图像, 然后在预定位置提取像素的值, 并将其用作密钥来解码代码块我想在将来条件成熟的时候再深入讨论这个话题, 所以这里只是一个存根: P
来源: https://jaq.alibaba.com/community/art/show?articleid=1516