写在前言在此之前,我一直在研究JavaScript相关的反调试技巧。但是在网上搜索相关资料的时候发现网上关于这方面的文章并不多,即使有也很不全。所以在这篇文章中,我打算和大家总结一下JavaScript反调试技术。值得一提的是,其中一些方法已经被网络犯罪分子在恶意软件中广泛使用。对于JavaScript,你只需要花一点时间调试和分析,就可以理解JavaScript代码段的功能逻辑。而我们将要讨论的内容会给那些想要分析你的JavaScript代码的人增加一定的难度。但是,我们的技术与代码混淆无关。我们的主要关注点是如何让主动调试代码变得困难。本文要介绍的技术方法大致如下:1.检测未知执行环境(我们的代码只想在浏览器中执行);2.检测调试工具(如DevTools);3.代码完整性控制;4.流量完整性控制;5、反仿真;简而言之,如果我们检测到一个“异常”的情况,程序的运行流程就会发生变化,跳转到一个伪造的代码块,“隐藏”真正的功能代码。1.函数重定义这是最基本也是最常用的代码反调试技术。在JavaScript中,我们可以重新定义用于收集信息的函数。例如,console.log()函数可以用来收集函数、变量等信息,并显示在控制台中。如果我们重新定义这个函数,我们可以修改它的行为并隐藏特定信息或显示虚假信息。我们可以在DevTools中直接运行这个函数来了解它的功能:'看我!");运行后我们会看到:VM48:1HelloWorld你会发现没有显示第二条信息,因为我们重新定义了这个功能,也就是“禁用”了它原来的功能。但我们也可以让它显示虚假信息。比像这样说:console.log("Normalfunction");//首先我们将引用保存到原始console.logfunctionvaroriginal=window['console']['log'];//接下来我们创建我们的fakefunction//基本上我们checktheargumentandifmatchwecalloriginalfunctionwithotherparam。==="Ka0labs"){original("Spoofed!");}else{original(argument);}}//Weredefinenowconsole.logasourfakefunctionwindow['console']['log']=fake;//然后wecallconsole.logwithanyargumentconsole.log("Thisisunaltered");//现在我们应该在consoledifferentto"Ka0labs"中看到其他文本另外,为了控制代码的执行方式,我们还可以通过更智能的方式修改函数的功能。例如,我们可以根据上面的代码构建一个代码片段,重新定义eval函数。我们可以将JavaScript代码传递给eval函数,代码将被评估和执行。如果我们重新定义这个函数,我们可以运行不同的代码://Justanormalevaleval("console.log('1337')");//Nowwerepattheprocess...varoriginal=eval;varfake=function(argument){//如果要被评估的代码包含1337...if(argument.indexOf("1337")!==-1){//...wejustexecuteadifferentcodeoriginal("for(i=0;i<10;i++){console.log(i);}");}else{original(argument);}}eval=fake;eval("console.log('Weshouldseethis...')");//Nowweshouldseetheexecutionofaforloopinsteadofwhatisexpectedeval("console.log('Too1337foryou!')");运行结果如下:1337VM146:1Weshouldseethis...VM147:10VM147:11VM147:12VM147:13VM147:14VM147:15VM147:16VM147:17VM147:18VM147:19前面说过了,这个方法虽然很巧妙,但是也是一个很基础很常用的方法,所以比较容易检测。2、断点为了帮助我们理解代码的功能,JavaScript调试工具(如DevTools)可以通过设置断点来阻止脚本代码的执行,而断点也是代码调试中最基本的。如果您使用过调试器或x86架构,您可能熟悉0xCC指令。在JavaScript中,我们有一个类似的指令,称为调试器。当我们在代码中声明调试器函数时,脚本代码会在调试器指令处停止运行。例如:console.log("Seeme!");调试器;console.log("看我!");许多商业产品会定义在代码中循环的调试器指令,但有些浏览器会阻止这种代码,有些则不会。这个方法的主要目的是惹恼那些想调试你代码的人,因为***循环意味着代码会不断弹出来询问你是否要继续运行脚本代码:setTimeout(function(){while(true){eval("debugger")3.时间差这是一种基于时间的反调试技术,借鉴了传统的反逆向技术,当脚本在DevTools等工具环境中执行时,运行速度会会很慢(时间),所以我们可以通过运行时间来判断脚本当前是否正在调试。比如我们可以测量代码中两个设定点之间的运行时间,然后以此值作为参考。如果runningtimeexceeds该值表示脚本当前正在调试器中运行,demo代码如下:setInterval(function(){varstartTime=performance.now(),check,diff;for(check=0;check<1000;check++){console.log(check);console.clear();}diff=performance.now()-startTime;if(diff>200){alert("检测到调试器!");}},500);四、DevTools检测(Chrome)this这种技术使用了div元素中的id属性。当div元素被发送到控制台时(例如,console.log(div)),浏览器会自动尝试获取其中的元素id。如果代码调用了console.log则调用了getter方法,说明控制台当前正在运行。一个简单的概念验证代码如下:letdiv=document.createElement('div');letloop=setInterval(()=>{console.log(div);console.clear();});Object.defineProperty(div,"id",{get:()=>{clearInterval(loop);alert("DevToolsdetected!");}});五、隐式流程完整性控制当我们尝试对代码进行反混淆时,我们会先尝试重命名一些函数或变量,但是在JavaScript中我们可以检测一个函数名是否被修改,或者我们可以直接通过获取其原始名称或调用序列堆栈跟踪。arguments.callee.caller可以帮助我们创建堆栈跟踪来存储以前执行的函数。演示代码如下:returnstack}functiontest1(){console.log(getCallStack());}functiontest2(){test1();}functiontest3(){test2();}functiontest4(){test3();}test4();注意:源代码越模糊,这种技术就越有效。6.代理对象代理对象是目前JavaScript中最有用的工具。这个对象可以帮助我们理解代码中的其他对象,包括在特定环境下修改它的行为和触发对象活动。比如我们可以创建一个对象,跟踪每一次document.createElemen的调用,然后记录下相关信息:;returntarget.apply(thisArg,args)}}document.createElement=newProxy(document.createElement,handler)//Createourproxyobjectwithourhookreadytointerceptdocument.createElement('div');接下来我们可以在控制台记录相关的参数和信息:VM64:3InterceptedacalltocreateElementwithargs:div我们可以利用这些信息通过拦截一些具体的函数来调试代码,但是这篇文章的主要目的是介绍反调试技术,那么如何我们是否检测“对手”是否使用代理对象?其实这是一个“猫捉老鼠”的游戏,比如我们可以使用相同的代码段,然后尝试调用toString方法并捕获异常://Calla"virgin"createElement:try{document.createElement.toString();}catch(e){console.log("Isawyourproxy!");}信息如下:"functioncreateElement(){[nativecode]}"但是当我们使用代理时://Thenapplythehookconsthandler={apply:function(target,thisArg,args){console.log("InterceptedacalltocreateElementwithargs:"+args);returntarget.apply(这个参数,args)}}document.createElement=newProxy(document.createElement,handler);//Callournot-so-virgin-after-that-partycreateElementtry{document.createElement.toString();}catch(e){console.log("Isawyourproxy!");}是的,我们确实可以检测到代理:VM391:13Isawyourproxy!我们还可以添加toString方法:consthandler={apply:function(target,thisArg,args){console.log("InterceptedacalltocreateElementwithargs:"+args);returntarget.apply(thisArg,args)}}document.createElement=newProxy(document.createElement,handler);document.createElement=Function.prototype.toString.bind(document.createElement);//AddtoString//Callournot-so-virgin-after-that-partycreateElementtry{document.createElement.toString();}catch(e){console.log("Isawyourproxy!");}Nowwecan'tdetect:"functioncreateElement(){[nativecode]}"正如我所说,这是一场“猫捉老鼠”的游戏Leaveacomment在下面的评论部分,或给我发推文(@TheXC3LL)。
