魔改Firefox,实现无视Debugger断点反爬封面图

魔改Firefox,实现无视Debugger断点反爬

平时对一些网站开启F12大法,会遇到无限Debugger阻断,导致网页无法正常执行。
比较典型的就是樱花动漫(真真假假难辨,但是大部分都有反爬),就像这样:
01.png
这个问题也算是网站爬虫遇到的经典问题了,解决这个问题有两个方法:

  • 修改网页的代码,去掉所有的Debugger断点。这个方法比较麻烦,每次打开网站都需要修改JS。
  • 修改浏览器,无视Debugger断点。这个算是一劳永逸的方法了,让网站的Debugger断点反爬直接失效,这也是这次我打算走的路线。

定好了方法,接下来就开干吧!我打算拿Firefox进行开刀,因为Firefox是完全开源的浏览器,也比较贴合自由软件相关的理念。

拉取Firefox源代码

Firefox的源代码是公开的,可以在github.com/mozilla-firefox/firefox找到源代码。源代码会比较大,后期编译这类工作做完之后会占用到16GB,所以拉取代码的时候建议找个空间比较大的地方放置代码。
拉取代码后,可以切换分支进行修改,我个人建议是使用ESR的版本进行修改,使用git切换一下到ESR分支即可,我是在esr140分支进行修改的。

修改代码,无视Debugger

对于修改代码的工作,我是不可能上手修改的,直接丢给AI改了,提示词如下:

这是firefox 140 esr 的源代码,我需要你完成一个任务:让浏览器在开启开发者控制台的时候,无视 debugger 断点继续执行实现 反反爬 功能。
修改完成后,请进行编译验证。如果系统自带的python 环境因为权限无法更改的话,请使用conda创建合适的环境进行操作。
验收的标准是,当页面存在 debugger ,打开浏览器控制台,页面继续执行不会被打断。

我用的是华为云的CodeArts CLI,模型是GLM-5.2。不得不说,现在AI确实厉害,不一会就找到了相关的代码位置:

  • js/src/debugger/DebugAPI-inl.h
  • js/src/jit/VMFunctions.cpp

修改的代码量实际上并不算大,修改几行代码即可完成任务。

DebugAPI-inl.h 修改情况

函数: DebugAPI::onDebuggerStatement(第 154 行)
这是 debugger 语句的统一入口点,解释器和基线解释器均经此处理。
改动前:检查是否被调试器跟踪,是则触发钩子暂停执行

bool DebugAPI::onDebuggerStatement(JSContext* cx, AbstractFramePtr frame) {
  if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
    return slowPathOnDebuggerStatement(cx, frame);  // 触发 onDebuggerStatement 钩子 → 暂停
  }
  return true;
}

改动后:直接返回,不触发任何钩子

bool DebugAPI::onDebuggerStatement(JSContext* cx, AbstractFramePtr frame) {
  return true;
}

作用: 切断 debugger 语句到 DevTools 调试器的通知链。原代码中,
当 realm 被 DevTools 跟踪时(isDebuggee() 为 true),会调用
slowPathOnDebuggerStatementfireDebuggerStatement,触发已注册的
onDebuggerStatement 钩子,使执行暂停。改动后直接返回,钩子永不触发。

VMFunctions.cpp 修改情况

函数: GlobalHasLiveOnDebuggerStatement(第 1288 行)
这是 Warp JIT 代码中 debugger 语句的 bailout 判定函数
改动前:有调试器钩子时返回 true,触发 bailout 回退到解释器

bool GlobalHasLiveOnDebuggerStatement(JSContext* cx) {
  AutoUnsafeCallWithABI unsafe;
  return cx->realm()->isDebuggee() &&
         DebugAPI::hasDebuggerStatementHook(cx->global());
}

改动后:始终返回 false,JIT 代码中不 bailout

bool GlobalHasLiveOnDebuggerStatement(JSContext* cx) {
  AutoUnsafeCallWithABI unsafe;
  return false;
}

作用: JIT 代码执行到 debugger 语句时(CodeGenerator::visitDebugger),
原逻辑调用此函数判断是否需要 bailout 到解释器去触发钩子。改动后返回 false,
debugger 语句在 JIT 代码中变为 no-op,避免不必要的 bailout,兼顾正确性与性能。

配置Firefox编译环境

完整编译Firefox的时间比较久,我这边用E5-1680v4编译耗时大约34分钟。

系统依赖环境准备

编译对工具链的要求如下:

工具最低版本
Clang15+
Rust1.82.0
Python3.12
NASM2.14+
Node.js18+
cbindgen0.27+

可以用下面的脚本批量检查一下pkg-config的依赖:

for lib in gtk+-3.0 nss nspr dbus-1 x11 xrender xrandr \
           glib-2.0 pango cairo freetype2 fontconfig \
           libpng hunspell vpx icu-uc icu-i18n; do
  pkg-config --exists $lib && echo "$lib: ✓" || echo "$lib: ✗ 缺失" 
done

如果缺依赖,可以用下面的命令安装:

pacman -S --needed \
  gtk3 gtk4 \
  nss nspr \
  dbus dbus-glib \
  libpulse alsa-lib \
  icu hunspell libvpx \
  libwebp libffi \
  freetype2 fontconfig \
  expat zlib xz \
  sdl2 libevent \
  nasm yasm \
  clang rustup \
  nodejs npm \
  python base-devel

构建环境配置

先进行环境初始化(如果是首次配置):
进入项目文件夹,如果 .build-env/ 尚未存在,按以下步骤初始化:

mkdir -p .build-env/rustup .build-env/cargo .build-env/conda/pkgs

配置环境设置脚本.build-env/setup.sh,内容如下:

#!/bin/bash
PROJECT_ROOT="/run/media/chocola/3T-DATA02/Projects/firefox"
export RUSTUP_HOME="$PROJECT_ROOT/.build-env/rustup"
export CARGO_HOME="$PROJECT_ROOT/.build-env/cargo"
export PATH="$PROJECT_ROOT/.build-env/cargo/bin:$PATH"
export MOZBUILD_STATE_PATH="$PROJECT_ROOT/.build-env/mozbuild-state"
export MACH_USE_SYSTEM_PYTHON=1
export PYTHON3="$PROJECT_ROOT/.build-env/conda/py312/bin/python3"
mkdir -p "$MOZBUILD_STATE_PATH"

请注意PROJECT_ROOT换成你实际项目所在的地址。
然后继续配置:

source .build-env/setup.sh
rustup default stable
cargo install cbindgen
export CONDA_PKGS_DIRS=$(pwd)/.build-env/conda/pkgs
conda create -y -p $(pwd)/.build-env/conda/py312 python=3.12

后续进行编译,每次新终端会话开始时,先 source 环境脚本:

source .build-env/setup.sh

后续所有 mach 命令使用 conda Python 执行:

./.build-env/conda/py312/bin/python3 ./mach <command>

编译Firefox

Firefox 编译需要设置配置文件再进行编译,和Linux内核有点像。

mozconfig 配置

完整浏览器构建的mozconfig内容:

# Full Firefox browser build (anti-anti-scraping)
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-full-build
ac_add_options --enable-application=browser
ac_add_options --enable-optimize
ac_add_options --disable-debug-symbols
ac_add_options --disable-tests
ac_add_options --without-wasm-sandboxed-libraries
ac_add_options --host=x86_64-unknown-linux-gnu
ac_add_options --target=x86_64-unknown-linux-gnu

各选项说明:

选项说明
MOZ_OBJDIR构建输出目录(与 SpiderMonkey 验证构建分开)
--enable-application=browser构建完整 Firefox 浏览器
--enable-optimize启用优化(release 模式)
--disable-debug-symbols不生成调试符号(减小体积)
--disable-tests不编译测试代码(加快编译)
--without-wasm-sandboxed-libraries禁用 RLBox WASM 沙箱(避免 wasi headers 依赖)
--host/--target修正 triplet(系统编译器报告 pc vendor,rustc 不认识)

完整浏览器编译

source .build-env/setup.sh

# 配置(首次或修改 mozconfig 后)
./.build-env/conda/py312/bin/python3 ./mach configure

# 编译
./.build-env/conda/py312/bin/python3 ./mach build

# 打包生成 .tar.xz
./.build-env/conda/py312/bin/python3 ./mach package

产物位置:

产物路径
可运行的 Firefoxobj-full-build/dist/firefox/firefox
安装包obj-full-build/dist/firefox-140.17.0.en-US.linux-x86_64.tar.xz

测试修改效果

可以使用下面的HTML代码保存为一个HTML文件测试无视Debugger的效果:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>反反爬测试 - debugger 语句验证</title>
<style>
body { font-family: monospace; padding: 20px; max-width: 800px; }
.pass { color: green; font-weight: bold; }
.fail { color: red; font-weight: bold; }
.box { border: 1px solid #ccc; padding: 15px; margin: 10px 0; border-radius: 5px; }
h1 { color: #333; }
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
</style>
</head>
<body>
<h1>Firefox 140 ESR 反反爬功能测试</h1>
<div class="box">
<h3>测试说明</h3>
<p>此页面包含多个 <code>debugger</code> 语句。在未修改的 Firefox 中,打开开发者工具(F12)后,页面执行会被 <code>debugger</code> 语句暂停。在已修改的 Firefox 中,<code>debugger</code> 语句被忽略,页面正常执行。</p>
<p><strong>测试方法:</strong> 按 F12 打开开发者工具,切换到"调试器"面板,然后点击下方"开始测试"按钮。</p>
</div>

<div class="box">
<h3>测试结果</h3>
<div id="results">
<p>点击"开始测试"按钮开始...</p>
</div>
</div>

<button id="startBtn" onclick="runTest()" style="font-size:16px;padding:10px 20px;">开始测试</button>

<script>
function runTest() {
    var results = document.getElementById('results');
    var output = [];
    var startTime = Date.now();

    // 测试1: 单个 debugger 语句
    debugger;
    output.push('<p class="pass">✓ 测试1通过: debugger 语句未暂停执行</p>');

    // 测试2: 循环中的多个 debugger 语句
    var sum = 0;
    for (var i = 0; i < 5; i++) {
        debugger;
        sum += i;
    }
    if (sum === 10) {
        output.push('<p class="pass">✓ 测试2通过: 循环中5个debugger语句全部被忽略 (sum=' + sum + ')</p>');
    } else {
        output.push('<p class="fail">✗ 测试2失败: sum=' + sum + ' (期望10)</p>');
    }

    // 测试3: 嵌套函数中的 debugger
    function inner() {
        debugger;
        return "inner-ok";
    }
    var r = inner();
    if (r === "inner-ok") {
        output.push('<p class="pass">✓ 测试3通过: 嵌套函数中debugger语句被忽略</p>');
    } else {
        output.push('<p class="fail">✗ 测试3失败: r=' + r + '</p>');
    }

    // 测试4: try-catch 中的 debugger
    try {
        debugger;
        output.push('<p class="pass">✓ 测试4通过: try块中debugger语句被忽略</p>');
    } catch(e) {
        output.push('<p class="fail">✗ 测试4失败: 异常 - ' + e + '</p>');
    }

    // 测试5: 定时器中的 debugger
    setTimeout(function() {
        debugger;
        var elapsed = Date.now() - startTime;
        document.getElementById('results').innerHTML += '<p class="pass">✓ 测试5通过: setTimeout中debugger语句被忽略 (耗时' + elapsed + 'ms)</p>';
    }, 100);

    var elapsed = Date.now() - startTime;
    output.push('<p>总耗时: ' + elapsed + 'ms</p>');
    output.push('<p class="pass"><strong>结论: 所有debugger语句均被忽略,页面正常执行。反反爬功能生效!</strong></p>');

    results.innerHTML = output.join('');
}
</script>
</body>
</html>

浏览器打开这个文件,然后打开开发者工具,点击开始测试,如果过程没有被打断说明无视Debugger成功。
再次测试一下樱花动漫的情况:
02.png
樱花动漫内心OS:

他说他改过了浏览器的源代码,啊。看来是——有bear来。这个,浏览器,不讲武德。来,骗!来,偷袭,我网站JS的,Debugger。这好吗?这不好。我劝!这个,浏览器,耗子尾汁,好好反思。以后不要再犯这样的聪明,小聪明啊。呃,网站,要以和为贵,要讲武德,不要搞,爬虫工具。谢谢朋友们!

当然,真要看番也不应该去樱花动漫,去找点种子站(例如 bangumi.moe,打不开是你的问题)把番剧下回来看比这玩意好多了。


我这边有编译好的 Firefox 140 ESR,想要的可以来拿:
firefox-140.17.0.en-US.linux-x86_64.tar.xz
另外,你可以去查看我Fork进行修改的GitHub仓库:github.com/Chocola-X/firefox

什么?你要Windows的?我现在电脑只用GNU/Linux,我编译Windows的版本干嘛?

评论区 (0)