反调试吗?如何监听devtools的打开与关闭

由于公司产品定制了devtools,而我们的产品又是作用于任意网站的,对于一些网站(例如科大讯飞)是不能打开devtools或者说打开后立刻关闭,所以这篇文章总结一下有哪些实用的方法可以监听到devtools的打开。

快捷键方式阻止打开devtools

我们检测浏览器开发者工具的打开,首先我们需要知道有哪些方式可以打开浏览器开发者工具,然后如何避免这些方式的打开。

  • F12
  • Ctrl + Shift + I
  • Ctrl + Shift + C
  • Shift + F10, 可以打开右键菜单

这些情况很好处理就是监听用户键盘按下事件keydown,然后进行默认事件阻止就行。

js 复制代码
  document.addEventListener('keydown', (e) => {
    // 禁止f12,禁用ctrl+shift+i,c,禁用shift+f10
    const code = e.code
    const ctrl = e.ctrlKey
    const shift = e.shiftKey
    const isCSI = ctrl && shift && code === 'KeyI'
    const isF12 = code === 'F12'
    const isCSC = ctrl && shift && code === 'KeyC'
    const isSF10 = shift && code === 'F10'
    if ( isF12 || isCSI || isCSC || isSF10) {
      e.preventDefault();
    }
  })
  • 右键菜单
js 复制代码
  // 禁用菜单
  document.addEventListener('contextmenu', (e) => {
    e.preventDefault();
  })
  • 浏览器设置 - 更多工具 - 开发者工具 (这种就没办法阻止了)
  • 先打开一个正常页面,打开开发者工具,然后再访问禁止打开开发者工具的网站 (这种就没办法阻止了)

打开devtools,如何关闭

了解了如何打开开发者工具后,我们知道还有一些情况是我们开发无法阻止的,所以我们就需要等到用户打开开发者后,做一些操作来让开发者工具关闭掉。

这里最简单的一种做法就是监听当前可视区域的宽高,来判断是否打开了浏览器开发者工具。

js 复制代码
  window.addEventListener('resize', function () {
    // outerWidth 整个浏览器宽度   高度 103
    if (window.outerWidth - window.innerWidth > 0 || window.outerHeight - window.innerHeight > 130) {
        console.log("开发者工具已打开!");
        // TODO: 做一些操作,例如重定向,重写innerHTML,关闭页面等等
        location.href = "about:blank"
    }
  });

这种方式对于将开发者工具分离出一个窗口先打开一个正常页面,打开开发者工具,然后再访问禁止打开开发者工具的网站 这两种无法进行操作。这种方式也是devtools-detect的原理。

我们来看看disable-devtooldevtools-detector的实现原理吧。

目前有很多网站都使用dsiable-devtool来实现的。例如科大讯飞。

他主要是通过log, table来分别打印一下大对象数组,看打印相差的时间。其原理就是通过浏览器打印大对象数组的性能判断是否是打开了devtools。如果说我们打开浏览器开发者工具控制台,那么打印的内容将会展开照成内存大量占用,所以时间差就会变大。不打开浏览器开发者工具控制台将不会对对象展开。

js 复制代码
  function now() {
    return new Date().getTime();
  }
  // 创建大对象数组
  function createLargeObject() {
      const largeObject = {};
      for (let i = 0; i < 500; i++) {
          largeObject[`${i}`] = `${i}`;
      }
      return largeObject;
  }
  // 创建大对象数组
  function createLargeObjectArray() {
    const largeObject = createLargeObject();
    const largeObjectArray = [];
    for (let i = 0; i < 50; i++) {
        largeObjectArray.push(largeObject);
    }
    return largeObjectArray;
  }

  // 计算打印执行时间
  function calculateTime(func) {
    const start = now();
    func();
    return now() - start;
  }

  const largeObjectArray = createLargeObjectArray()
  let maxPrintTime = 0
  setInterval(() => {
    // table 打印时间
    const tablePrintTime = calculateTime(() => { console.table(largeObjectArray); });
    // 普通输出时间
    const printLogTime = calculateTime(() => { console.log(largeObjectArray)})
    maxPrintTime = Math.max(maxPrintTime, printLogTime)
    
    if (tablePrintTime === 0 || maxPrintTime === 0) {
      return
    }else {
      // 如果打印表格的时间是普通打印的10倍,那么就关闭
      if (tablePrintTime > maxPrintTime * 10) { // 如果当前表格打印时间大于指定时间,那么将表示打开了devtools
        console.log("时间对比", tablePrintTime, maxPrintTime)
        window.close()
      }
    }
  }, 500)

具体可以看这两篇文章了console.log是否会照成内存泄漏

  • console 内存占用的误解 他主要是反驳上面一篇文章的说法,原因是打开控制台会展开对象(默认展开一层),所以也增加了内存占用。
js 复制代码
<!DOCTYPE html>
<html lang="en">
  <body>
    <button id="btn">点我</button>
    <div id="box"></div>
    <script>
      const btn = document.getElementById('btn');
      const box = document.getElementById('box');

      btn.addEventListener('click', function () {
        const MB = 1024 * 1024;
        log();

        function log() {
          const memory = performance.memory.totalJSHeapSize;
          const usagedMemory = Math.floor(memory / MB);
          box.insertAdjacentHTML('beforeend', `<span>${usagedMemory} </span>`);

          // const obj = { usagedMemory, str: 'g'.repeat(50 * MB) }; // 这种就会造成内存激增
          const obj = { usagedMemory, wrapper: {str: 'g'.repeat(50 _ MB)} }; // 这种就不会造成内存激增
          console.log(obj);

          setTimeout(() => log(), 50);
        }
      });
    </script>
  </body>
</html>

还有一种补充的方式就是通过debugger来判断是否打开devtools, 这种方式并不是打开开发者工具立刻就监听到,而是通过用户触发断点计算时间,间接判断。但是我们知道我们设置断点后,需要刷新浏览器后才会生效。chrome高版本(v121.0.6167.85)可以通过异步递归的方式来使打开开发者工具后立刻执行断点,而无需刷新浏览器。 这种方式也是看devtools-detector源码发现的。

js 复制代码
  function now() {
    return new Date().getTime();
  }

  const debuggerChecker = {
    name: 'debugger-checker',
    async isOpen() {
      const startTime = now();

      (function () {}).constructor('debugger')();

      return now() - startTime > 100;
    },
    async isEnable(){
      return true;
    },
  };

  async function _detectLoop() {
    let isOpen = false;
    let checkerName = '';
    checkerName = this._checkers[0].name;
    isOpen = await this._checkers[0].isOpen();

    isOpen && (function (isOpen, detail) {
      console.log('isOpen', isOpen, detail);
      // TODO: 这里执行关闭操作
      window.close()
    })(isOpen, {checkerName})

    setTimeout(_detectLoop, 500);
  }

  window._checkers = [debuggerChecker]
  window._isOpen = false
  _detectLoop()

这种方式我们也是可以通过一些方法避免的。

  • 重写console对象中的方法。
js 复制代码
Object.defineProperty(window, 'console', {
  value: {
      table: () => {},
      log: () => {},
      clear: () => {},
      debug: () => {},
      warn:() => {}
    }
});
  • 浏览器禁用js脚本
  • 重写source
  • 使用浏览器插件注入脚本。
  • ...

其实前端并没有提供一种api去监听devtools的打开与关闭,所以的方式都是奇淫巧技。都有一定的局限性,只能说尽可能的去监听。

注意以上是对chrome浏览器的分析,还有一些方式适用于其他浏览器。

参考

往期年度总结

往期文章

专栏文章

相关推荐
黄尚圈圈23 分钟前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水1 小时前
简洁之道 - React Hook Form
前端
正小安3 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光5 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   5 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   5 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web5 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常5 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇6 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器