在此以前,我一直都在研究JavaScript相关的反调试技巧。可是当我在网上搜索相关资料时,我发现网上并无多少关于这方面的文章,并且就算有也是很是不完整的那种。因此在这篇文章中,我打算跟你们总结一下关于JavaScript反调试技巧方面的内容。值得一提的是,其中有些方法已经被网络犯罪分子普遍应用到恶意软件之中了。css
对于JavaScript来讲,你只须要花一点时间进行调试和分析,你就可以了解到JavaScript代码段的功能逻辑。而咱们所要讨论的内容,能够给那些想要分析你JavaScript代码的人增长必定的难度。不过咱们的技术跟代码混淆无关,咱们主要针对的是如何给代码主动调试增长困难。html
本文所要介绍的技术方法大体以下:vue
1. 检测未知的执行环境(咱们的代码只想在浏览器中被执行);node
2. 检测调试工具(例如DevTools);webpack
3. 代码完整性控制;web
4. 流完整性控制;面试
5. 反模拟;浏览器
简而言之,若是咱们检测到了“不正常”的状况,程序的运行流程将会改变,并跳转到伪造的代码块,并“隐藏”真正的功能代码。bash
1、函数重定义网络
这是一种最基本也是最经常使用的代码反调试技术了。在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
正如以前所说的那样,虽然这种方法很是巧妙,但这也是一种很是基础和常见的方法,因此比较容易被检测到。
2、断点
为了帮助咱们了解代码的功能,JavaScript调试工具(例如DevTools)均可以经过设置断点的方式阻止脚本代码执行,而断点也是代码调试中最基本的了。
若是你研究过调试器或者x86架构,你可能会比较熟悉0xCC指令。在JavaScript中,咱们有一个名叫debugger的相似指令。当咱们在代码中声明了debugger函数后,脚本代码将会在debugger指令这里中止运行。好比说:
`console.log(``"Seeme!"``);`
`debugger;`
`console.log(``"Seeme!"``);`
复制代码
不少商业产品会在代码中定义一个无限循环的debugger指令,不过某些浏览器会屏蔽这种代码,而有些则不会。这种方法的主要目的就是让那些想要调试你代码的人感到厌烦,由于无限循环意味着代码会不断地弹出窗口来询问你是否要继续运行脚本代码:
setTimeout(function(){while (true) {eval("debugger")
3、时间差别
这是一种从传统反逆向技术那里借鉴过来的基于时间的反调试技巧。当脚本在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);`
复制代码
4、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!"``);`
`}});`
复制代码
5、隐式流完整性控制
当咱们尝试对代码进行反混淆处理时,咱们首先会尝试重命名某些函数或变量,可是在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();`
复制代码
注意:源代码的混淆程度越强,这个技术的效果就越好。
6、代理对象
代理对象是目前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] }"`
复制代码
就像我说的,这就是一场“猫抓老鼠“的游戏。本次给你们推荐一个交流圈,里面归纳移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。对web开发技术感兴趣的同窗,欢迎加入:582735936,无论你是小白仍是大牛我都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时天天更新视频资料。最后,祝你们早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。