JS檢測(cè)瀏覽器開(kāi)發(fā)者工具是否打開(kāi)的方法詳解
在某些情況下我們需要檢測(cè)當(dāng)前用戶(hù)是否打開(kāi)了瀏覽器開(kāi)發(fā)者工具,比如前端爬蟲(chóng)檢測(cè),如果檢測(cè)到用戶(hù)打開(kāi)了控制臺(tái)就認(rèn)為是潛在的爬蟲(chóng)用戶(hù),再通過(guò)其它策略對(duì)其進(jìn)行處理。本篇文章主要講述幾種前端JS檢測(cè)開(kāi)發(fā)者工具是否打開(kāi)的方法。
一、重寫(xiě)toString()
對(duì)于一些瀏覽器,比如Chrome、FireFox,如果控制臺(tái)輸出的是對(duì)象,則保留對(duì)象的引用,每次打開(kāi)開(kāi)發(fā)者工具的時(shí)候都會(huì)重新調(diào)用一下對(duì)象的toString()方法將返回結(jié)果打印到控制臺(tái)(console tab)上。
所以只需要?jiǎng)?chuàng)建一個(gè)對(duì)象,重寫(xiě)它的toString()方法,然后在頁(yè)面初始化的時(shí)候就將其打印在控制臺(tái)上(這里假設(shè)控制臺(tái)還沒(méi)有打開(kāi)),當(dāng)用戶(hù)打開(kāi)控制臺(tái)時(shí)會(huì)再去調(diào)用一下這個(gè)對(duì)象的toString()方法,用戶(hù)打開(kāi)控制臺(tái)的行為就會(huì)被捕獲到。
下面是一個(gè)小小的例子,當(dāng)Chrome用戶(hù)的開(kāi)發(fā)者工具狀態(tài)從關(guān)閉向打開(kāi)轉(zhuǎn)移時(shí),這個(gè)動(dòng)作會(huì)被捕獲到并交由回調(diào)函數(shù)處理:
<html> <head> <title>console detect test</title> </head> <body> <script> /** * 控制臺(tái)打開(kāi)的時(shí)候回調(diào)方法 */ function consoleOpenCallback(){ alert("CONSOLE OPEN"); return ""; } /** * 立即運(yùn)行函數(shù),用來(lái)檢測(cè)控制臺(tái)是否打開(kāi) */ !function () { // 創(chuàng)建一個(gè)對(duì)象 let foo = /./; // 將其打印到控制臺(tái)上,實(shí)際上是一個(gè)指針 console.log(foo); // 要在第一次打印完之后再重寫(xiě)toString方法 foo.toString = consoleOpenCallback; }() </script> </body> </html>
效果:
當(dāng)?shù)谝淮卧诖隧?yè)面打開(kāi)控制臺(tái)時(shí)會(huì)觸發(fā)到檢測(cè),但是如果是在一個(gè)已經(jīng)打開(kāi)了控制臺(tái)的窗口中粘貼網(wǎng)址訪(fǎng)問(wèn)則不會(huì)觸發(fā),同理在此頁(yè)面上已經(jīng)打開(kāi)控制臺(tái)時(shí)刷新也不會(huì)觸發(fā)。
這種方式雖然比較取巧,但是并不具有通用性,并且只能捕獲到開(kāi)發(fā)者工具處于關(guān)閉狀態(tài)向打開(kāi)狀態(tài)轉(zhuǎn)移的過(guò)程,具有一定的局限性。
二、debugger
類(lèi)似于代碼里的斷點(diǎn),瀏覽器在打開(kāi)開(kāi)發(fā)者工具時(shí)(對(duì)應(yīng)于代碼調(diào)試時(shí)的debug模式)檢測(cè)到debugger標(biāo)簽(相當(dāng)于是程序中的斷點(diǎn))的時(shí)候會(huì)暫停程序的執(zhí)行:
此時(shí)需要點(diǎn)一下那個(gè)藍(lán)色的“Resume script execution”程序才會(huì)繼續(xù)執(zhí)行,這中間會(huì)有一定的時(shí)間差,通過(guò)判斷這個(gè)時(shí)間差大于一定的值就認(rèn)為是打開(kāi)了開(kāi)發(fā)者工具。這個(gè)方法并不會(huì)誤傷,當(dāng)沒(méi)有打開(kāi)開(kāi)發(fā)者工具時(shí)遇到debugger標(biāo)簽不會(huì)暫停,所以這種方法還是蠻好的,而且通用性比較廣。
下面是一個(gè)使用debugger標(biāo)簽檢測(cè)開(kāi)發(fā)者工具是否打開(kāi)的例子:
<html> <head></head> <body> <script> function consoleOpenCallback() { alert("CONSOLE OPEN"); } !function () { const handler = setInterval(() => { const before = new Date(); debugger; const after = new Date(); const cost = after.getTime() - before.getTime(); if (cost > 100) { consoleOpenCallback(); clearInterval(handler) } }, 1000) }(); </script> </body> </html>
效果:
但是上面的代碼有一個(gè)很?chē)?yán)重的bug,就是在執(zhí)行到debugger那一行的時(shí)候如果用戶(hù)發(fā)現(xiàn)了貓膩沒(méi)有點(diǎn)按resume script execution按鈕,而是直接退出頁(yè)面的話(huà),那么將不能檢測(cè)到本次的打開(kāi)開(kāi)發(fā)者工具行為,實(shí)際結(jié)果與預(yù)期不符,我認(rèn)為這是嚴(yán)重bug,就像電影里演的不小心踩到地雷及時(shí)察覺(jué)不抬腳就還有活命機(jī)會(huì),到了debugger標(biāo)簽我察覺(jué)到這是檢測(cè)控制臺(tái)是否打開(kāi)的代碼我退出然后使用其它手段繞過(guò)它,那我可能做了一個(gè)假功能。
有一個(gè)需要注意的地方就是使用此方法的時(shí)候當(dāng)卡在debugger標(biāo)簽的時(shí)候,用戶(hù)是能夠看到debugger標(biāo)簽附近的代碼的,如果是有經(jīng)驗(yàn)的用戶(hù)一眼就能看出里面的道道,所以要想辦法隱藏一下真實(shí)目的,比如將debugger標(biāo)簽隱藏,并且對(duì)代碼進(jìn)行混淆盡量增加閱讀難度,關(guān)于如何隱藏debugger標(biāo)簽前后的邏輯,可以參考這幾個(gè)網(wǎng)站:(當(dāng)前2018-7-4 23:12:17有效)
http://app2.sfda.gov.cn/datasearchp/gzcxSearch.do?formRender=cx&optionType=V1
使用此種方案的話(huà)可能有個(gè)需要注意的點(diǎn)就是debugger是有可能不會(huì)暫停的,比如Chrome瀏覽器的source面板可以選擇在debugger語(yǔ)句時(shí)不暫停:
如果這個(gè)按鈕被點(diǎn)亮,再測(cè)試上面的網(wǎng)頁(yè)就會(huì)發(fā)現(xiàn)很悲劇檢測(cè)代碼失效了,因?yàn)閐ebugger標(biāo)簽根本就沒(méi)有暫停。
其實(shí)debugger標(biāo)簽還有另一種妙用,比如用來(lái)反調(diào)試,可以設(shè)定一個(gè)每秒就觸發(fā)一個(gè)debugger,讓調(diào)試者疲于應(yīng)付debugger或者耗費(fèi)他額外的成本去覆蓋掉JS,上面給出的幾個(gè)網(wǎng)站就是這么做的。
下面是一個(gè)使用debugger標(biāo)簽反js調(diào)試的簡(jiǎn)單例子:
<html> <head> <title>Anti debug</title> </head> <body> <script> !function () { setInterval(() => { debugger; }, 1000); }(); </script> </body> </html>
效果:
一個(gè)實(shí)際的例子,這個(gè)網(wǎng)站:http://jxw.uou0.com/的js檢測(cè)腳本,而針對(duì)不同的情況它又會(huì)有不同的反調(diào)試策略。
注意要想復(fù)現(xiàn)需要粘貼視頻地址解析之后才會(huì)加載檢測(cè)腳本,比如可以嘗試解析這個(gè)視頻:http://film.qq.com/film/p/topic/thwjlxby/index.html。
當(dāng)未打開(kāi)開(kāi)發(fā)者工具進(jìn)行解析,然后打開(kāi)開(kāi)發(fā)者工具,則會(huì)使用這種檢測(cè)方式:
!function() { var timelimit = 50; var open = false; setInterval(function() { var starttime = new Date(); debugger ;if (new Date() - starttime > timelimit) { open = true; window.stop(); $("#loading").hide(); $("#a1").remove(); $("#error").show(); $("#error").html("\u7cfb\u7edf\u68c0\u6d4b\u975e\u6cd5\u8c03\u8bd5\u002c\u8bf7\u5237\u65b0\u91cd\u8bd5\u0021") } else { open = false } }, 500) }();
因?yàn)檫@個(gè)網(wǎng)站是做vip視頻免費(fèi)解析的,一旦檢測(cè)到有人打開(kāi)開(kāi)發(fā)者工具在調(diào)試,就將解析好的視頻移除掉,通過(guò)彈出一個(gè)提示框:
而當(dāng)已經(jīng)打開(kāi)開(kāi)發(fā)者工具再粘貼地址進(jìn)行視頻解析的話(huà),將會(huì)觸發(fā)無(wú)限debugger。
當(dāng)然,應(yīng)付上面腳本最簡(jiǎn)單的方法是把Chrome瀏覽器設(shè)定為Deactive breakpoint,上面的腳本就歇菜了,不過(guò)這樣的話(huà)自己也沒(méi)辦法調(diào)試了,用來(lái)反調(diào)試確實(shí)能夠惡心一下對(duì)面的家伙,比較好的方法是使用Fiddler修改網(wǎng)頁(yè)返回內(nèi)容過(guò)濾掉debugger標(biāo)簽可以完美破解此套路。
三、檢測(cè)窗口大小
檢測(cè)窗口大小比較簡(jiǎn)單,首先要明確兩個(gè)概念,窗口的outer大小和inner大?。?/p>
window.innerWidth / window.innerHeight :可視區(qū)域的寬高,window.innerWidth包含了縱向滾動(dòng)條的寬度,window.innerHeight包含了水平(橫向)滾動(dòng)條的寬度。
window.outerWidth / window.outerHeight:會(huì)在innerWidth和innerHeight的基礎(chǔ)上加上工具條的寬度。
關(guān)于檢測(cè)窗口大小,不再自己寫(xiě)例子,有人專(zhuān)門(mén)針對(duì)此寫(xiě)了個(gè)庫(kù):https://github.com/sindresorhus/devtools-detect,畢竟幾百個(gè)star,比我這個(gè)渣渣寫(xiě)的好多了,代碼比較簡(jiǎn)單,使用部分其github都有說(shuō)明,這里只對(duì)其核心代碼做個(gè)分析,此處貼出鄙人對(duì)此庫(kù)核心代碼的分析:
/* eslint-disable spaced-comment */ /*! devtools-detect Detect if DevTools is open https://github.com/sindresorhus/devtools-detect by Sindre Sorhus MIT License comment by CC11001100 */ (function () { 'use strict'; var devtools = { open: false, orientation: null }; // inner大小和outer大小超過(guò)threshold被認(rèn)為是打開(kāi)了開(kāi)發(fā)者工具 var threshold = 160; // 當(dāng)檢測(cè)到開(kāi)發(fā)者工具后發(fā)出一個(gè)事件,外部監(jiān)聽(tīng)此事件即可,設(shè)計(jì)得真好,很好的實(shí)現(xiàn)了解耦 var emitEvent = function (state, orientation) { window.dispatchEvent(new CustomEvent('devtoolschange', { detail: { open: state, orientation: orientation } })); }; // 每500毫秒檢測(cè)一次開(kāi)發(fā)者工具的狀態(tài),當(dāng)狀態(tài)改變時(shí)觸發(fā)事件 setInterval(function () { var widthThreshold = window.outerWidth - window.innerWidth > threshold; var heightThreshold = window.outerHeight - window.innerHeight > threshold; var orientation = widthThreshold ? 'vertical' : 'horizontal'; // 第一個(gè)條件判斷沒(méi)看明白,heightThreshold和widthThreshold不太可能同時(shí)為true,不論是其中任意一個(gè)false還是兩個(gè)都false取反之后都會(huì)為true,此表達(dá)式恒為true if (!(heightThreshold && widthThreshold) && // 針對(duì)Firebug插件做檢查 ((window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized) || widthThreshold || heightThreshold)) { // 開(kāi)發(fā)者工具打開(kāi),如果之前開(kāi)發(fā)者工具沒(méi)有打開(kāi),或者已經(jīng)打開(kāi)但是靠邊的方向變了才會(huì)發(fā)送事件 if (!devtools.open || devtools.orientation !== orientation) { emitEvent(true, orientation); } devtools.open = true; devtools.orientation = orientation; } else { // 開(kāi)發(fā)者工具沒(méi)有打開(kāi),如果之前處于打開(kāi)狀態(tài)則觸發(fā)事件報(bào)告狀態(tài) if (devtools.open) { emitEvent(false, null); } // 將標(biāo)志位恢復(fù)到未打開(kāi) devtools.open = false; devtools.orientation = null; } }, 500); if (typeof module !== 'undefined' && module.exports) { module.exports = devtools; } else { window.devtools = devtools; } })();
缺點(diǎn):
1. 使用window屬性檢查大小可能會(huì)有瀏覽器兼容性問(wèn)題,因?yàn)椴皇菍?zhuān)業(yè)前端只測(cè)試了Chrome和ff是沒(méi)有問(wèn)題的。
2. 此方案還是有漏洞的,就拿Chrome瀏覽器來(lái)說(shuō),開(kāi)發(fā)者工具窗口有四個(gè)選項(xiàng):?jiǎn)为?dú)窗口、靠左、靠下、靠右。
靠左、靠右、靠下都會(huì)占用當(dāng)前窗口的一些空間,這種情況會(huì)被檢測(cè)到,但是獨(dú)立窗口并不會(huì)占用打開(kāi)網(wǎng)頁(yè)窗口的空間,所以這種情況是檢測(cè)不到的,可去此頁(yè)面進(jìn)行驗(yàn)證:https://sindresorhus.com/devtools-detect/。
四、總結(jié)
本文介紹了幾種檢測(cè)方式,其各有利弊,下面是對(duì)其缺點(diǎn)的一個(gè)簡(jiǎn)單的總結(jié):
重寫(xiě)toString():只能捕獲到開(kāi)發(fā)者工具從關(guān)閉狀態(tài)向打開(kāi)狀態(tài)轉(zhuǎn)移的過(guò)程
debugger標(biāo)簽:當(dāng)勾選了Chrome瀏覽器的Deactive breakpoint ,debugger標(biāo)簽不會(huì)暫停,將捕獲不到
檢測(cè)窗口大?。寒?dāng)開(kāi)發(fā)者工具是以獨(dú)立窗口打開(kāi)的時(shí)候不能檢測(cè)到
相關(guān)文章
javascript的慣性運(yùn)動(dòng)實(shí)現(xiàn)代碼實(shí)例
這篇文章主要介紹了javascript的慣性運(yùn)動(dòng)實(shí)現(xiàn)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09UniApp使用vue.config.js進(jìn)行配置的詳細(xì)教程
這篇文章主要給大家介紹了關(guān)于UniApp使用vue.config.js進(jìn)行配置的詳細(xì)教程,uniapp是一套基于Vue語(yǔ)法的框架,同樣也支持Vue.config.js配置,一般常用的莫過(guò)于路徑的名稱(chēng),需要的朋友可以參考下2023-10-10整理關(guān)于Bootstrap模態(tài)彈出框的慕課筆記
這篇文章主要為大家整理了關(guān)于Bootstrap模態(tài)彈出框的慕課筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03JavaScript常見(jiàn)打開(kāi)鏈接的幾種方法小結(jié)
在頁(yè)面中的鏈接除了常規(guī)的方式以外,如果使用javascript,還有很多種方式,下面這篇文章主要給大家介紹了關(guān)于JavaScript常見(jiàn)打開(kāi)鏈接的幾種方法,需要的朋友可以參考下2024-01-01js獲取當(dāng)前頁(yè)面的url網(wǎng)址信息
這篇文章主要介紹了通過(guò)js如何獲取當(dāng)前頁(yè)面的url網(wǎng)址信息,需要的朋友可以參考下2014-06-06詳解使用webpack構(gòu)建多頁(yè)面應(yīng)用
這篇文章主要介紹了詳解使用webpack構(gòu)建多頁(yè)面應(yīng)用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12