浏览器调试与常用 Hook

2026-03-22

Hook 相关内容参考视频 https://www.bilibili.com/video/BV1gQ4mzMEA4/open in new window

断点

根据不同情况,可选用不同断点

dom断点

通过 ajax 请求拿到数据后,渲染到页面中时可通过 dom 断点定位页面元素被修改的代码

Elements tap ->指定标签右键 -> Break on(发生中断的条件)

script断点

读取到脚本时暂停 Event Listener Breakpoints 事件监听器断点 -> Scripts 脚本

xhr断点

发送指定请求时的断点,可通过请求地址暂停到指定请求之前

源码断点

源码断点常用的是以下几种

  • 普通代码断点:蓝色

  • 条件断点:橙色。右键添加条件断点。运行到条件断点处,会执行设置的内容。当设置内容返回为true时中断

    • 编写条件,返回true时断下

      • 参数数量为 N 时断下 arguments.callee.length === N
      • window 上绑定变量用于控制是否启动断点后,启动断点 window.enableBreakpoints === true
    • 可以编写普通代码(无返回值代码,如Statement),则会执行而不中断(代码不返回true时,不会触发断点机制)

      • Eg1: 修改断点行的参数值

        var id = 1;
        var a = id;  // 此行加断点,编写 id = 999,则此时id被修改为999,a会被赋值id的当前值,即999
        
      • Eg2: 计时

        for (let i = 1; i < 9999999; i++) {  // 断点内容  console.time('calc time')
            window.getComputedStyle(document.body)
        }  // 断点内容  console.timeEnd('calc time')
        
  • 日志断点:粉色。自动在控制台输出断点所设置的内容(监控当前代码位置,某个变量的值,即使当前代码为使用该变量)。通常设置为字符串拼接变量,例如 "当前i的值为" + i

调用堆栈

在调试时,常看的调用堆栈信息分为 Network 数据包的 Initiator 启动器 和代码断点的 Call Stack 调用栈

两个地方堆栈显示都遵循了上方是栈顶的策略。即最下方的是先触发的,最上方的是最后调用的

禁控制台

开源项目 disable-devtoolopen in new window 会检测控制台情况并触发以下行为

  • 禁用 F12 与右键,减少控制台打开的方式
  • 检测到控制台打开后,调用 window.close() 关闭当前标签页
  • 检测到控制台打开后,跳转到其他安全页面
  • 检测到控制台打开后,反复使用 console.clear() 清空控制台输出
  • 检测到控制台打开后,反复使用 console.table() 控制台输出大量内容
  • 检测到控制台打开后,反复使用 window.history.go()window.history.back() 进行跳转
  • etc(其他行为可详见项目说明)

处理手段

  • hook window.close() 为空函数,防止自动关闭
  • hook window.onbeforeunload = () => {debugger;return false}, 在堆栈中定位跳转代码,移除跳转代码并 overrides
  • hook console.clear() 为空函数,防止清楚控制台输出
  • hook console.table()
  • hook window.historygoback 是空方法

过 debugger

debugger 干扰调试通过与定时器配合实现反复触发 debugger 但不影响主进程执行业务逻辑

常规的 debugger 可通过断点不暂停过掉。

出现一些debugger无法通过断点不暂停过掉的情况,如: 使用 Function 构造 debugger

// 通过自定义函数构造器,调用 Function 构造 debugger 并调用
func.constructor('debugger').call() 
// 通过匿名函数
(function(){}.constructor('debugger').call())

可在发生 debugger 时查看堆栈,定位触发 debugger 的代码(函数),在执行代码块前下断点。刷新后重写该代码,再放行

或通过 hook 方式过 debugger (修改内置可能会被检测)。以下是 hook 代码

  • 重写定时器
    _setInterval = setInterval
    setInterval = function(a, b){
        // 判断无 debugger 关键字,再正常设置定时器
        if (a.toSting().indexOf('debugger') === -1) {
            return _setInterval(a, b)
        }
    }
    
  • 重写构造器
    _constructor = Function.prototype.constructor
    Function.prototype.constructor = function() {
        if(arguments[0] === 'debugger') {
            // 什么都不做
        } else {
            return _constructor.apply(this, arguments)
        }
    }