回归最本质的信息安全

;

JavaScript的反调试技术(下篇)

2018年2月27日发布

48,002
0
0

导语:在本文的上篇中,我们为读者介绍了与JavaScript反调试有关的一些技巧,其中包括函数重定义、断点、时间差异、DevTools检测和执行流程完整性的隐式控制等,接下来,我们将为读者介绍更多的反调试技巧。

在本文的上篇中,我们为读者介绍了与JavaScript反调试有关的一些技巧,其中包括函数重定义、断点、时间差异、DevTools检测和执行流程完整性的隐式控制等,接下来,我们将为读者介绍更多的反调试技巧。

0x06  代码完整性的隐式控制

在前面的“0x01函数重定义”部分,我们提到可以在JavaScript中使用toString()方法检索函数的代码。正如我们所说的,这对检查一个函数是否被重新定义是非常有用的,事实上,同样也可以使用这个思路来检测函数的代码是否被修改过。

其中,一种有效的方法是计算函数或代码块的哈希值,并将其与预知的哈希表进行比较。但是,这种方法确实有点笨拙。 更现实和有效的方法,是之前使用的跟踪堆栈的策略。 我们可以计算一段代码的哈希值,并将其用作解密其他代码块的密钥。

为了实现隐式完整性控制,最巧妙的想法是md5碰撞。这个想法是由@cgvwzq创造的,就在去年夏天几杯啤酒下肚后,一个天才的想法就这样诞生了。简单来说,我们可以创建这样的函数:在函数内部检测自己的md5。为了执行函数内部的检查,我们需要使用碰撞(我们想创建类似function(){ if (md5(arguments.callee.toString() === '<md5>') code_function; }这样的函数)。

这种技术背后的思想与用于生成图像文件的概念相同,这些图像文件显示的就是自己的md5校验和。 这是一个经典的例子:一个显示自己的md5校验和的gif。

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!

本文翻译自:https://x-c3ll.github.io/posts/javascript-antidebugging/ 如若转载,请注明原文地址: http://www.4hou.com/technology/10473.html

点赞 0
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

fanyeee

这个人很懒,什么也没留下

发私信

发表评论