在此以前,我一直都在研究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,快速升職加薪,走上人生巔峯。