web想禁止调试,没有绝对安全的方式
妙用1 debugger 示例 用setInterval执行debugger,打开控制台就会暂停代码
1 2 3 4 5 6 7 8 (() => { function ban ( ) { setInterval (() => { debugger ; }, 50 ); } ban (); })();
妙用2 debugger 示例 效果:打开控制台就进入debugger,如果还像 妙用1 的破解之法解决,浏览器会被卡死
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (() => { let timeLimit = 50 ; setInterval (loop, 1 ); function loop ( ) { let startTime = new Date (); debugger ; if (new Date () - startTime > timeLimit) { window .stop (); let total = '' ; for (let i = 0 ; i < 1000000 ; i++) { total = total + i.toString (); history.pushState (0 , 0 , total); } } } })();
分析 下面这一段是导致浏览器卡死的罪魁祸首
1 2 3 4 5 let total = '' ;for (let i = 0 ; i < 1000000 ; i++) { total = total + i.toString (); history.pushState (0 , 0 , total); }
但是默认应该就会执行,为什么打开控制台才进入这一段呢?答案就在这一段
1 2 3 4 5 let startTime = new Date ();debugger ;if (new Date () - startTime > timeLimit) { }
只有进入debugger的情况下,new Date()才会比startTime(上一次的时间)大50ms
也可以这样操作:跳转至空页面
1 2 3 if (new Date () - startTime > timeLimit) { window .location .href = 'about:blank' ; }
还有很多方式,只要避免能调试当前页面即可
https://github.com/theajack/disable-devtool:**0.3.7 **
使用方式 1 2 3 4 5 <script disable-devtool-auto src='https://cdn.jsdelivr.net/npm/disable-devtool' stopIntervalTime="false" ></script>
配置含义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 interface IConfig { md5 ?: string ; url ?: string ; tkName ?: string ; ondevtoolopen?(type : DetectorType , next : Function ): void ; ondevtoolclose?(): void ; interval ?: number ; disableMenu ?: boolean ; stopIntervalTime ?: number ; clearIntervalWhenDevOpenTrigger ?: boolean ; detectors ?: Array <DetectorType >; clearLog ?: boolean ; disableSelect ?: boolean ; disableCopy ?: boolean ; disableCut ?: boolean ; disablePaste : boolean ; ignore ?: (string |RegExp )[] | null | (()=> boolean ); disableIframeParents ?: boolean ; timeOutUrl ?: string ; rewriteHTML : string ; } enum DetectorType { Unknown = -1 , RegToString = 0 , DefineId , Size , DateToString , FuncToString , Debugger , Performance , DebugLib , };
关键源码 禁用快捷键 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const KEY = {J : 74 , I : 73 , U : 85 , S : 83 , F12 : 123 };const isOpenDevToolKey = IS .macos ? ((e : KeyboardEvent , code : number ) => (e.metaKey && e.altKey && (code === KEY .I || code === KEY .J ))) : ((e : KeyboardEvent , code : number ) => (e.ctrlKey && e.shiftKey && (code === KEY .I || code === KEY .J ))); const isViewSourceCodeKey = IS .macos ? ((e : KeyboardEvent , code : number ) => (e.metaKey && e.altKey && code === KEY .U ) || (e.metaKey && code === KEY .S )) : ((e : KeyboardEvent , code : number ) => (e.ctrlKey && (code === KEY .S || code === KEY .U ))); target.addEventListener ('keydown' , (e ) => { e = e || target.event ; const keyCode = e.keyCode || e.which ;if ( keyCode === KEY .F12 || isOpenDevToolKey (e, keyCode) || isViewSourceCodeKey (e, keyCode) ) { return preventEvent (target, e); } }, true ); target.addEventListener ('contextmenu' , (e : Event & {pointerType: string } ) => { if (e.pointerType === 'touch' ) return ; return preventEvent (target, e); }); addPreventListener (target, 'selectstart' );addPreventListener (target, 'copy' );addPreventListener (target, 'cut' );addPreventListener (target, 'paste' );function addPreventListener (target : Window , name : string ) { target.addEventListener (name, (e : Event ) => { return preventEvent (target, e); }); }
检测方法 DataToString 利用 不打开控制不会执行console.log,执行了两次toString后视为打开了控制台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 init () { this .count = 0 ; this .date = new Date (); this .date .toString = () => { this .count ++; return '' ; }; } detect () { this .count = 0 ; log (this .date ); clearLog (); if (this .count >= 2 ) { this .onDevToolOpen (); } }
defineProperty console.log触发 getter 的特性来检测是否打开了开发者工具。当控制台试图渲染 this.div 对象时,触发了 id 属性的 getter,从而调用 this.onDevToolOpen() 来响应检测结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 init () { this .div = document .createElement ('div' ); (this .div as any ).__defineGetter__ ('id' , () => { this .onDevToolOpen (); }); Object .defineProperty (this .div , 'id' , { get : () => { this .onDevToolOpen (); }, }); } detect () { log (this .div ); }
Detector 利用Function的toString
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 init () { this .count = 0 ; this .func = () => {}; this .func .toString = () => { this .count ++; return '' ; }; } detect () { this .count = 0 ; log (this .func ); clearLog (); if (this .count >= 2 ) { this .onDevToolOpen (); } }
通过控制台打印大对象数组所花的大量时间来检测开发者工具是否被打开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 init () { this .maxPrintTime = 0 ; this .largeObjectArray = createLargeObjectArray (); } detect () { const tablePrintTime = calculateTime (() => {table (this .largeObjectArray );}); const logPrintTime = calculateTime (() => {log (this .largeObjectArray );}); this .maxPrintTime = Math .max (this .maxPrintTime , logPrintTime); clearLog (); if (tablePrintTime === 0 || this .maxPrintTime === 0 ) return false ; if (tablePrintTime > this .maxPrintTime * 10 ) { this .onDevToolOpen (); } }
debugger 原理同本文开头方式
1 2 3 4 5 6 7 detect () { const date = now (); (() => {debugger ;})(); if (now () - date > 100 ) { this .onDevToolOpen (); } }
resize 通过监测内外窗口尺寸(如outerWidth和innerWidth的差值)是否异常变化来检测开发者工具的打开状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 init () { this .checkWindowSizeUneven (); window .addEventListener ('resize' , () => { setTimeout (() => { this .checkWindowSizeUneven (); }, 100 ); }, true ); } private checkWindowSizeUneven () { const screenRatio = countScreenZoomRatio (); if (screenRatio === false ) { return true ; } const widthUneven = window .outerWidth - window .innerWidth * screenRatio > 200 ; const heightUneven = window .outerHeight - window .innerHeight * screenRatio > 300 ; if (widthUneven || heightUneven) { this .onDevToolOpen (); return false ; } clearDevToolOpenState (this .type ); return true ; } function countScreenZoomRatio ( ) { if (checkExist (window .devicePixelRatio )) { return window .devicePixelRatio ; } const screen = window .screen as any ; if (checkExist (screen)) { return false ; } if (screen.deviceXDPI && screen.logicalXDPI ) { return screen.deviceXDPI / screen.logicalXDPI ; } return false ; };
总结 其实就三种检测类型
resize
console.log
debugger
但产生了多种检测器,从而增大破解的难度
破解之法 破解之法1 仅需停用断点,即可破解
破解之法2 只要一律不在此处暂停(debugger),甚至还可以加断点调试网站
破解之法3 可以添加条件断点
破解之法4 先在一个空页面打开控制台,再进入页面,可以攻击只有debugger的网站
破解之法5 使用浏览器开发者工具替换修改js(Sources面板 –> Overrides),详情可看:在本地替换 Web 内容和 HTTP 响应标头
破解之法5 直接安装油猴脚本即可,或者自己写一个
参考 🔏别想调试我的前端页面代码🔒 ⚔️不让我在控制台上调试,哼,休想🛠️