魔改Chromium:debugger 失效与控制台防检测

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

现在不少网站会通过 debugger 断点与控制台侧信道来阻断调试或触发风控,例如瑞树的“无限 debugger”,以及 BOSS 的 console.table 检测控制台打开后跳转空白页等。这类检测依赖 V8/DevTools 的默认语义与 Inspector 上报路径,一旦命中就会直接影响分析与回放。

常见的魔改思路是删除 debugger 关键字或粗暴改写其行为,但这种处理容易留下新检测点。本文的方案选择在语法解析阶段介入:让 debugger; 语法依旧合法,但语义变为空语句,从而在不破坏脚本执行流的前提下消除断点副作用,并配合控制台静默与自用通道(vdebugger/vconsole)实现可控调试。

设计目标与约束

  • debugger 仍是关键字,确保 eval("typeof debugger") 等探测继续抛 SyntaxError
  • debugger; 语法合法但执行为空语句,不触发断点。
  • 新增 vdebugger; 语句,作为自控断点入口(仅内部使用)。
  • console.* 默认静默(不向 Inspector/DevTools 上报),降低“DevTools 打开”相关检测面。
  • 新增 vconsole.log 且不可枚举,作为自用的日志输出通道。

最小检测示例

1) 检测"控制台是否打开"(throw Error + getter)

经典手段:通过 throw 一个带有 getter 的 Error 对象。只有当 DevTools 打开并渲染错误对象(或页面代码主动读取 message)时,getter 才会触发;仅 HTML 中未捕获异常的默认上报通常不会触发。

<script>
window.hit = 0;
(() => {
  const err = new Error();
  err.__defineGetter__("message", () => (window.hit++, "msg"));
  setTimeout(() => { throw err; }, 0);
})();
setTimeout(() => {
  (document.body || document.documentElement).textContent =
      "hit=" + window.hit;
}, 50);
</script>

2) 检测"控制台是否打开"(console.table 时间差)

利用点:console.table 输出大对象时,DevTools 需要做序列化/渲染,会有明显的时间开销;控制台关闭时则几乎无开销。

const bigArray = new Array(10000).fill({ a: 1, b: 2, c: 3 });
const t0 = performance.now();
console.table(bigArray);
const t1 = performance.now();
// DevTools 打开时 t1 - t0 会明显变大(可能 >50ms);关闭时接近 0

改动范围速览

文件变更目的
v8/src/parsing/parser-base.hdebugger; no-op + vdebugger; 解析为 DebuggerStatement
v8/src/inspector/v8-console.h新增 createInspectorConsole 声明
v8/src/inspector/v8-console.cc实现 vconsole.log 注入与不可枚举绑定
v8/src/inspector/v8-inspector-impl.cc禁用默认 console delegate + 注入 vconsole

关键实现细节

1) debugger; 变成空操作

文件:v8/src/parsing/parser-base.h

核心思路是“语法依旧合法,语义变成空语句”。ParseDebuggerStatement() 直接返回 EmptyStatement(),运行时自然不会暂停:

修改前(片段)

// v8/src/parsing/parser-base.h
ParserBase<Impl>::ParseDebuggerStatement() {
  int pos = peek_position();
  Consume(Token::kDebugger);
  ExpectSemicolon();
  return factory()->NewDebuggerStatement(pos);
}

修改后

// v8/src/parsing/parser-base.h
ParserBase<Impl>::ParseDebuggerStatement() {
  int pos = peek_position();
  Consume(Token::kDebugger);
  ExpectSemicolon();
  USE(pos);
  return factory()->EmptyStatement();
}

2) 新增 vdebugger; 语句

文件:v8/src/parsing/parser-base.h

为了不破坏语义,这个识别只发生在“语句起始位置”,并且排除标签语句 vdebugger:。同时,必须满足“独立语句形态”(分号/换行/}/EOF)才触发:

修改前(片段)

// v8/src/parsing/parser-base.h
// 直接进入语句分发
switch (peek()) {
  case Token::kLeftBrace:
    return ParseBlock(labels);
  // ...
}

修改后

// v8/src/parsing/parser-base.h
if (V8_UNLIKELY(peek() == Token::kIdentifier) &&
    PeekContextualKeyword(
        ast_value_factory()->GetOneByteString("vdebugger"))) {
  Token::Value next = PeekAhead();
  if (next != Token::kColon) {
    const bool is_standalone =
        Token::IsAutoSemicolon(next) ||
        scanner()->HasLineTerminatorAfterNext();
    if (is_standalone) {
      int pos = peek_position();
      Consume(Token::kIdentifier);
      ExpectSemicolon();
      return factory()->NewDebuggerStatement(pos);
    }
  }
}

这样做的直接结果是:

  • vdebugger; 生效;
  • obj.vdebuggervdebugger()let vdebugger = 1 等不会被误伤;
  • vdebugger: 标签语句保持原义。

3) 关闭默认 console delegate

文件:v8/src/inspector/v8-inspector-impl.cc

默认 console 的上报通道会被关闭,从源头上让 console.* 变成“无输出”:

修改前(片段)

// v8/src/inspector/v8-inspector-impl.cc
V8InspectorImpl::V8InspectorImpl(v8::Isolate* isolate,
                                 V8InspectorClient* client)
    : m_isolate(isolate),
      m_client(client),
      m_debugger(new V8Debugger(isolate, this)),
      m_lastExceptionId(0),
      m_lastContextId(0),
      m_isolateId(generateUniqueId()) {
  v8::debug::SetInspector(m_isolate, this);
  v8::debug::SetConsoleDelegate(m_isolate, console());
}

修改后

// v8/src/inspector/v8-inspector-impl.cc
V8InspectorImpl::V8InspectorImpl(v8::Isolate* isolate,
                                 V8InspectorClient* client)
    : m_isolate(isolate),
      m_client(client),
      m_debugger(new V8Debugger(isolate, this)),
      m_lastExceptionId(0),
      m_lastContextId(0),
      m_isolateId(generateUniqueId()) {
  v8::debug::SetInspector(m_isolate, this);
  // Keep the default console silent; vconsole is injected separately.
}

4) 注入 vconsole.log(不可枚举)

文件:v8/src/inspector/v8-console.h/.cc

新增 createInspectorConsole(),返回仅含 log 的对象,并通过 DefineOwnProperty(..., v8::DontEnum) 保证不可枚举:

修改前:无 createInspectorConsole,也没有 vconsole 通道。

修改后

// v8/src/inspector/v8-console.cc
v8::Local<v8::Object> V8Console::createInspectorConsole(
    v8::Local<v8::Context> context) {
  v8::Isolate* isolate = context->GetIsolate();
  v8::Local<v8::Object> vconsole = v8::Object::New(isolate);
  v8::Local<v8::ArrayBuffer> data =
      v8::ArrayBuffer::New(isolate, sizeof(CommandLineAPIData));
  *static_cast<CommandLineAPIData*>(data->GetBackingStore()->Data()) =
      CommandLineAPIData(this, 0);
  createNonEnumerableBoundFunctionProperty(
      context, vconsole, data, "log",
      &V8Console::call<&V8Console::Log>);
  return vconsole;
}

5) 全局注入 vconsole

文件:v8/src/inspector/v8-inspector-impl.cc

contextCreated() 中挂到全局,并设置 DontEnum

修改前(片段)

// v8/src/inspector/v8-inspector-impl.cc
  DCHECK(contextById->find(contextId) == contextById->cend());
  (*contextById)[contextId].reset(context);

  forEachSession(
      info.contextGroupId, [&context](V8InspectorSessionImpl* session) {
        session->runtimeAgent()->addBindings(context);
        session->runtimeAgent()->reportExecutionContextCreated(context);
      });

修改后

// v8/src/inspector/v8-inspector-impl.cc
{
  v8::HandleScope handle_scope(m_isolate);
  v8::Context::Scope context_scope(info.context);
  v8::Local<v8::Object> vconsole =
      console()->createInspectorConsole(info.context);
  v8::Local<v8::String> name =
      toV8StringInternalized(m_isolate, "vconsole");
  v8::Maybe<bool> did_define = info.context->Global()->DefineOwnProperty(
      info.context, name, vconsole, v8::DontEnum);
  USE(did_define);
}

行为变化与验证

debuggervdebugger

  • debugger;:语法合法但不暂停。
  • vdebugger;:仅在“独立语句形态”触发断点。
  • 其他上下文的 vdebugger 仍是普通标识符。

consolevconsole

  • console.*:默认静默(不向 DevTools 输出);但仍可能触发格式化占位符的类型转换副作用(与 DevTools 是否打开无关)。
  • vconsole.log(...):可在 DevTools Console 中看到输出。
  • vconsolevconsole.log 均不可枚举,但可被反射 API 发现。

无限debugger 失效:
在这里插入图片描述

boss控制台检测失效:
在这里插入图片描述

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值