导语:在本文的上篇中,我们为读者介绍了与JavaScript反调试有关的一些技巧,其中包括函数重定义、断点、时间差异、DevTools检测和执行流程完整性的隐式控制等,接下来,我们将为读者介绍更多的反调试技巧。
在本文的上篇中,我们为读者介绍了与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] }"
0x07限制环境
正如前文所述,有时需要检测代码是否在正确的环境中执行。我们所说的“正确的环境”是:
·代码运行在浏览器中(不是模拟器、不是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项目中可以看到这类想法的影子。
0x08 WebGL
这里,我们不会介绍WebGL内部的反逆向或混淆技术,因为这方面的内容可以从网上找到。相反,本文所介绍的是如何使用WebGL处理数据并与JavaScript交互:如果有人试图“模拟”我们的JavaScript代码,他就需要为其模拟器提供WebGL支持。
我们可以实现一个简单的算法(例如多色分形)来创建基于各种“种子”的图像,然后在预定位置提取像素的值,并将其用作密钥来解码代码块。我想在将来条件成熟的时候再深入讨论这个话题,所以这里只是一个存根:P
小结
我希望上文中介绍的各种技巧能够对读者有所帮助。如果读者了解其他技巧,或者发现文中的错误和待改进之处,请随时在我的twitter @TheXC3LL上发表评论。
最后,对@cgvwzq提供的帮助致以深深的谢意:)
Byt3z!