js经验分享 JavaScript反调试技巧

在此之前,我一直都在研究JavaScript相关的反调试技巧。但是当我在网上搜索相关资料时,我发现网上并没有多少关于这方面的文章,而且就算有也是非常不完整的那种。所以在这篇文章中,我打算跟大家总结一下关于JavaScript反调试技巧方面的内容。值得一提的是,其中有些方法已经被网络犯罪分子广泛应用到恶意软件之中了。

对于JavaScript来说,你只需要花一点时间进行调试和分析,你就能够了解到JavaScript代码段的功能逻辑。而我们所要讨论的内容,可以给那些想要分析你JavaScript代码的人增加一定的难度。不过我们的技术跟代码混淆无关,我们主要针对的是如何给代码主动调试增加困难。

本文所要介绍的技术方法大致如下:

1. 检测未知的执行环境(我们的代码只想在浏览器中被执行);

2. 检测调试工具(例如DevTools);

3. 代码完整性控制;

4. 流完整性控制;

5. 反模拟;

简而言之,如果我们检测到了“不正常”的情况,程序的运行流程将会改变,并跳转到伪造的代码块,并“隐藏”真正的功能代码。

一、函数重定义

这是一种最基本也是最常用的代码反调试技术了。在JavaScript中,我们可以对用于收集信息的函数进行重定义。比如说,console.log()函数可以用来收集函数和变量等信息,并将其显示在控制台中。如果我们重新定义了这个函数,我们就可以修改它的行为,并隐藏特定信息或显示伪造的信息。

我们可以直接在DevTools中运行这个函数来了解其功能:

console.log("HelloWorld");
var fake = function() {};
window['console']['log']= fake;
console.log("Youcan't see me!");

运行后我们将会看到:

VM48:1 Hello World

你会发现第二条信息并没有显示,因为我们重新定义了这个函数,即“禁用”了它原本的功能。但是我们也可以让它显示伪造的信息。比如说这样:

console.log("Normalfunction");
//First we save a reference to the original console.log function
var original = window['console']['log'];
//Next we create our fake function
//Basicly we check the argument and if match we call original function with otherparam.
// If there is no match pass the argument to the original function
var fake = function(argument) {
  if (argument === "Ka0labs") {
    original("Spoofed!");
  } else {
    original(argument);
  }
}
// We redefine now console.log as our fake function
window['console']['log']= fake;
//Then we call console.log with any argument
console.log("Thisis unaltered");
//Now we should see other text in console different to "Ka0labs"
console.log("Ka0labs");
//Aaaand everything still OK
console.log("Byebye!");

如果一切正常的话:

Normal function
VM117:11 This is unaltered
VM117:9 Spoofed!
VM117:11 Bye bye!

实际上,为了控制代码的执行方式,我们还能够以更加聪明的方式来修改函数的功能。比如说,我们可以基于上述代码来构建一个代码段,并重定义eval函数。我们可以把JavaScript代码传递给eval函数,接下来代码将会被计算并执行。如果我们重定义了这个函数,我们就可以运行不同的代码了:

//Just a normal eval
eval("console.log('1337')");
//Now we repat the process...
var original = eval;
var fake = function(argument) {
  // If the code to be evaluated contains1337...
  if (argument.indexOf("1337") !==-1) {
    // ... we just execute a different code
    original("for (i = 0; i < 10;i++) { console.log(i);}");
  }
  else {
    original(argument);
  }
}
eval= fake;
eval("console.log('Weshould see this...')");
//Now we should see the execution of a for loop instead of what is expected
eval("console.log('Too1337 for you!')");

运行结果如下:

1337
VM146:1We should see this…
VM147:10
VM147:11
VM147:12
VM147:13
VM147:14
VM147:15
VM147:16
VM147:17
VM147:18
VM147:19

正如之前所说的那样,虽然这种方法非常巧妙,但这也是一种非常基础和常见的方法,所以比较容易被检测到。

二、断点

为了帮助我们了解代码的功能,JavaScript调试工具(例如DevTools)都可以通过设置断点的方式阻止脚本代码执行,而断点也是代码调试中最基本的了。

如果你研究过调试器或者x86架构,你可能会比较熟悉0xCC指令。在JavaScript中,我们有一个名叫debugger的类似指令。当我们在代码中声明了debugger函数后,脚本代码将会在debugger指令这里停止运行。比如说:

console.log("Seeme!");
debugger;
console.log("Seeme!");

很多商业产品会在代码中定义一个无限循环的debugger指令,不过某些浏览器会屏蔽这种代码,而有些则不会。这种方法的主要目的就是让那些想要调试你代码的人感到厌烦,因为无限循环意味着代码会不断地弹出窗口来询问你是否要继续运行脚本代码:

setTimeout(function(){while (true) {eval("debugger")

三、时间差异

这是一种从传统反逆向技术那里借鉴过来的基于时间的反调试技巧。当脚本在DevTools等工具环境下执行时,运行速度会非常慢(时间久),所以我们就可以根据运行时间来判断脚本当前是否正在被调试。比如说,我们可以通过测量代码中两个设置点之间的运行时间,然后用这个值作为参考,如果运行时间超过这个值,说明脚本当前在调试器中运行。

演示代码如下:

set Interval(function(){
 var startTime = performance.now(), check,diff;
 for (check = 0; check < 1000; check++){
  console.log(check);
  console.clear();
 }
 diff = performance.now() - startTime;
 if (diff > 200){
  alert("Debugger detected!");
 }
},500);

四、DevTools检测(Chrome)

这项技术利用的是div元素中的id属性,当div元素被发送至控制台(例如console.log(div))时,浏览器会自动尝试获取其中的元素id。如果代码在调用了console.log之后又调用了getter方法,说明控制台当前正在运行。

简单的概念验证代码如下:

let div = document.createElement('div');
let loop = setInterval(() => {
  console.log(div);
  console.clear();
});
Object.defineProperty(div,"id", {get: () => {
  clearInterval(loop);
  alert("Dev Tools detected!");
}});

五、隐式流完整性控制

当我们尝试对代码进行反混淆处理时,我们首先会尝试重命名某些函数或变量,但是在JavaScript中我们可以检测函数名是否被修改过,或者说我们可以直接通过堆栈跟踪来获取其原始名称或调用顺序。

arguments.callee.caller可以帮助我们创建一个堆栈跟踪来存储之前执行过的函数,演示代码如下:

function getCallStack() {
  var stack = "#", total = 0, fn =arguments.callee;
  while ( (fn = fn.caller) ) {
    stack = stack + "" +fn.name;
    total++
  }
  return stack
}
function test1() {
  console.log(getCallStack());
}
function test2() {
  test1();
}
function test3() {
  test2();
}
function test4() {
  test3();
}
test4();

注意:源代码的混淆程度越强,这个技术的效果就越好。

六、代理对象

代理对象是目前JavaScript中最有用的一个工具,这种对象可以帮助我们了解代码中的其他对象,包括修改其行为以及触发特定环境下的对象活动。比如说,我们可以创建一个嗲哩对象并跟踪每一次document.createElemen调用,然后记录下相关信息:

const handler = { // Our hook to keep the track
  apply: function (target, thisArg, args){
    console.log("Intercepted a call tocreateElement with args: " + args);
    return target.apply(thisArg, args)
  }
}

document.createElement= new Proxy(document.createElement, handler) // Create our proxy object withour hook ready to intercept
document.createElement('div');

接下来,我们可以在控制台中记录下相关参数和信息:

VM64:3 Intercepted a call to createElement with args: div

我们可以利用这些信息并通过拦截某些特定函数来调试代码,但是本文的主要目的是为了介绍反调试技术,那么我们如何检测“对方”是否使用了代理对象呢?其实这就是一场“猫抓老鼠”的游戏,比如说,我们可以使用相同的代码段,然后尝试调用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
consthandler = {
  apply: function (target, thisArg, args){
    console.log("Intercepted a call tocreateElement with args: " + args);
    return target.apply(thisArg, args)
  }
}
document.createElement= new Proxy(document.createElement, handler);

//Callour 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 tocreateElement 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
//Callour not-so-virgin-after-that-party createElement
try {
  document.createElement.toString();
}catch(e) {
  console.log("I saw your proxy!");
}

现在我们就没办法检测到了:

"function createElement() { [native code] }"

就像我说的,这就是一场“猫抓老鼠“的游戏。

总结

希望我所收集到的这些技巧可以对大家有所帮助,如果你有更好的技巧想跟大家分享,可以直接在文章下方的评论区留言,或者在Twitter上艾特我(@TheXC3LL)。

* 参考来源:x-c3ll,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

(0)

相关推荐

  • js经验分享 JavaScript反调试技巧

    在此之前,我一直都在研究JavaScript相关的反调试技巧.但是当我在网上搜索相关资料时,我发现网上并没有多少关于这方面的文章,而且就算有也是非常不完整的那种.所以在这篇文章中,我打算跟大家总结一下关于JavaScript反调试技巧方面的内容.值得一提的是,其中有些方法已经被网络犯罪分子广泛应用到恶意软件之中了. 对于JavaScript来说,你只需要花一点时间进行调试和分析,你就能够了解到JavaScript代码段的功能逻辑.而我们所要讨论的内容,可以给那些想要分析你JavaScript代码

  • JavaScript逆向调试技巧总结分享

    目录 前言 一.加密分析 二.调试技巧 1.日志分析 2.常见算法 2.1)MD5 2.2)Base64 2.3)进制处理 三.Chrome 调试技巧 总结 前言 前段时间尝试对某音的 PC 端进行了逆向,目前已经全部逆向出来了,在这里总结下一些调试技巧和总结. 本文不会涉及任何的详细代码,仅仅是作为技术来讨论. 一.加密分析 在这里以账户下的视频列表为例,可以看到,在 dy 中,加密的 JS 是 webmssdk.js,其中最主要的加密参数有以下两个 在 Postman 中进行测试,发现这两个

  • JavaScript必备的断点调试技巧总结(推荐)

    目录 为什么要使用 debugger Chrome debugger 基本用法 VS Code 调试 SPA 应用 Chrome 调试 Nodejs 使用 VS Code 调试 Nodejs Conditional Breakpoint 条件断点 总结 为什么要使用 debugger 这篇文章将介绍如何使用断点来进行 JavaScript 调试.在读这篇文章之前,需要问一个问题:为什么要使用断点来进行调试? 我们需要了解使用断点的必要性,否则下文介绍的所有断点调试方法都会是废话.console.

  • JavaScrip调试技巧之断点调试

    首先,在各个浏览器中,断点调试支持的最好的当然是Firefox,Firefox不仅可以使用Firebug调试页面js脚本,还可以用高级调试工具例如JavaScript Debugger (Venkman) 来调试Firefox扩展里的js.除此之外,Firefox还支持一些更为高级的断点调试.变量监视功能. 其他浏览器里,Opera.Chrome和Safari的调试功能也比较好用.Opera的DragonFly速度相对比较快,界面清爽,功能强大,但不如Safari等友好.相比来说,IE8的程序员

  • 实用Javascript调试技巧分享(小结)

    见过太多同学调试Javascript只会用简单的console.log甚至alert,看着真为他们捉鸡..因为大多数同学追求优雅而高效地写代码,却忽略了如何优雅而高效地调试代码,不得不说是有点"偏科"了.下面我就分享一些实用且聪明的调试技巧,希望能让大家调试自己代码的时候更加从容自信. 1. 不要使用alert 首先,alert只能打印出字符串,如果打印的对象不是String,则会调用toString()方法将该对象转成字符串(比如转成[object Object]这种),所以除非你打

  • JavaScript如何调试有哪些建议和技巧附五款有用的调试工具

    以下内容是关于javascript如何调试有哪些建议和技巧的相关知识,具体详情请看下文吧. 浏览器开发者工具 我个人最喜欢Chrome开发者工具.虽然Safari和Firefox无法达到Chrome那么高的标准,但它们也在逐渐改善.在Firefox中,可以将Firebug和Firefox开发者工具组合使用.如果Firefox小组在改进内置开发者工具方面继续表现优异的话,Firebug有一天可能会被淘汰. 先把个人偏好放在一边,你应该能够在目标浏览器中对任意代码进行试验和调试.你的目标浏览器可能包

  • 使用Chrome调试JavaScript的断点设置和调试技巧

    你是怎么调试 JavaScript 程序的?最原始的方法是用 alert() 在页面上打印内容,稍微改进一点的方法是用 console.log() 在 JavaScript 控制台上输出内容.嗯~,用这两种土办法确实解决了很多小型 JavaScript 脚本的调试问题.不过放着 Chrome 中功能越发强大的开发者工具不用实在太可惜了.本文主要介绍其中的 JavaScript断点设置和调试功能,也就是其中的 Sources Panel(以前叫 Scripts).如果你精通 Eclipse 中的各

  • 必备的JS调试技巧汇总

    前言:任何一个编程者都少不了要去调试代码,不管你是高手还是菜鸟,调试程序都是一项必不可少的工作.一般来说调试程序是在编写代码之后或测试期修改Bug 时进行的,往往在调试代码期间更加能够体现出编程者的水平高低以及分析问题的准确度.不少初学者在寻找错误原因时,总是不得要领,花费了大量时间却无法解决一些最终证明是相当简单的Bug.掌握各种调试技巧,必定能在工作中起到事半功倍的效果.譬如,快速定位问题.降低故障概率.帮助分析逻辑错误等等.而在互联网前端开发越来越重要的今天,如何在前端开发中降低开发成本,

  • JavaScript的兼容性与调试技巧

    关于JavaSctipt的兼容性,最懒的办法就是用jQuery的工具函数.尽量不要用那些什么ECMAScript之类的函数,因为很多浏览器都会报找不到函数的错误.下面列出一些在开发过程中碰到过的javascript问题. 1.参数列表多个逗号. $.ajax({})方法,非常熟悉了吧,但是在IE中有个小地方要注意,如果你在拼接参数列表的时候最后一个也加了逗号,那么毫无疑问,IE下全部JS失效. 调试时报如下错误: 缺少标识符.字符串或数字 data: { S_Id: Subject_Id, le

随机推荐