1deK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6*7K9s2g2S2L8X3I4S2L8W2)9J5k6i4A6Z5K9h3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8U0p5&6x3e0b7%4y4o6x3J5z5o6M7H3x3e0R3&6x3e0b7H3x3o6R3`.
改变 cdp 中 Console 的实现, 让 devtools 无法再通过 console、throw 检测
以 console.log
为例子, 当 JS 调用 console.log
时, V8 会通过外部引用机制调用注册的 C++ 函数, 具体实现:
而 console 所有要输出到控制台的内容都会通过 ConsoleHelper 类中的 reportCall
方法传输, 让我们来看看它的实现:
总结来说, 创建一个 V8ConsoleMessage 对象,用于封装控制台 API 调用的信息。然后如果不是清空操作,则将消息发送到宿主环境。让我们聚焦:
在 V8 的默认实现中,此方法是空方法或未实现。若运行时未主动处理,则无任何输出。需由运行时开发者通过继承 V8InspectorClient 并重写 consoleAPIMessage() 实现。
总结来说就是向 devtools 的控制台消息存储中添加一条新的控制台消息, 并将消息累积。让我们聚焦:
首先看看这个枚举对象 V8MessageOrigin::kConsole
:
kConsole
代表了 console 输出的 devtools 控制台消息, kException
表示 throw 出来的异常消息, kRevokedException
表示被撤销的异常, 在 devtools 中出错的 js 会有红线标记, 改对后, 需要撤销红线。
再来看看 session->consoleAgent()->messageAdded(message.get())
consoleAgent 的 messageAdded
, 在 V8 Inspector
那篇文章有提及, consoleAgent 是 devtools cdp 中控制台消息的代理, 负责向控制台输出消息。
这里判断了 devtools console 域是否打开, 打开的话就向其发送消息, 否则就是什么也不做。再跟进 reportMessage:
跟入 message->reportToFrontend
:
在跟入 frontend->messageAdded
, 来到了 out/Debug/gen/v8/src/inspector/protocol/Console.cpp
文件中的
看过 9ffK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6*7K9s2g2S2L8X3I4S2L8W2)9J5k6i4A6Z5K9h3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8U0p5&6x3e0b7%4y4o6x3J5z5o6M7H3x3e0R3&6x3e0b7H3x3o6R3`.
讲 V8 Inspector 这篇的小伙伴应该反应过来了, 这就是 cdp 中 Console.messageAdded
的实现, 将消息通过 cdp 协议发给 devtools。
回头再看 session->runtimeAgent()->messageAdded(message.get())
runtimeAgent 是 cdp 中的 runtime 域。和 consoleAgent 类似:
跳过上面这两个过程函数, 跟入 message->reportToFrontend
同样 frontend->exceptionThrown
frontend->exceptionRevoked
frontend->consoleAPICalled
是 runtime 域 cdp 协议的实现:
将各自的消息报告到 devtools。
分析到这, 有点小伙伴可能会疑惑在最开头说了 "console 所有要输出到控制台的内容都会通过 ConsoleHelper 类中的 reportCall
方法传输" , 那直接在 reportCall 一开始返回不就行了, 还扯这么多干嘛。事实上这样做只能解决 console 的检测, 无法过掉 throw 的检测(检测代码58aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6%4N6i4y4Z5k6h3&6H3k6i4u0K6L8$3&6Q4x3V1k6@1K9s2u0G2N6@1y4Z5k6h3y4C8e0%4m8W2L8R3`.`.
), 为了过掉 throw 的检测, 还要继续我们的旅程, 搜索 V8MessageOrigin::kException
, 定位到 v8/src/inspector/v8-console-message.cc
中 V8ConsoleMessage::createForException
函数, 创建一个异常消息, 查看它的引用, 来到了 v8/src/inspector/v8-inspector-impl.cc
中的 V8InspectorImpl::exceptionThrown
函数:
跟进 ensureConsoleMessageStorage(groupId)->addMessage(std::move(consoleMessage))
, 又回到了 V8ConsoleMessageStorage::addMessage
函数:
ok, 到此为止, 我们掌握 cdp 中向 devtools 控制台传输消息的大部分(还有一小部分是脚本段分离的报错消息, 和我们关注的 devtools 检测无关)。
让我们修改 V8ConsoleMessageStorage::addMessage
函数, 为它加一个默认参数:
四个检测:

全部通过:




附件放在 725K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6F1L8%4c8Z5K9h3&6Y4i4K6u0V1x3U0x3K6x3#2)9J5c8V1y4Z5M7X3!0E0K9i4g2E0e0h3!0V1
void
V8Console::Log(
const
v8::debug::ConsoleCallArguments& info,
const
v8::debug::ConsoleContext& consoleContext) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT(
"v8.inspector"
),
"V8Console::Log"
);
ConsoleHelper(info, consoleContext, m_inspector)
.reportCall(ConsoleAPIType::kLog);
}
void
V8Console::Log(
const
v8::debug::ConsoleCallArguments& info,
const
v8::debug::ConsoleContext& consoleContext) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT(
"v8.inspector"
),
"V8Console::Log"
);
ConsoleHelper(info, consoleContext, m_inspector)
.reportCall(ConsoleAPIType::kLog);
}
void
reportCall(ConsoleAPIType type,
v8::MemorySpan<
const
v8::Local<v8::Value>> arguments) {
if
(!groupId())
return
;
std::unique_ptr<V8StackTraceImpl> stackTrace;
switch
(type) {
case
ConsoleAPIType::kTrace:
stackTrace = m_inspector->debugger()->captureStackTrace(
true
);
break
;
case
ConsoleAPIType::kTimeEnd:
stackTrace = V8StackTraceImpl::capture(m_inspector->debugger(), 1);
break
;
default
:
stackTrace = m_inspector->debugger()->captureStackTrace(
false
);
break
;
}
std::unique_ptr<V8ConsoleMessage> message =
V8ConsoleMessage::createForConsoleAPI(
context(),
contextId(),
groupId(),
m_inspector,
m_inspector->client()->currentTimeMS(),
type,
arguments,
consoleContextToString(isolate(), m_consoleContext),
std::move(stackTrace)
);
consoleMessageStorage()->addMessage(std::move(message));
}
void
reportCall(ConsoleAPIType type,
v8::MemorySpan<
const
v8::Local<v8::Value>> arguments) {
if
(!groupId())
return
;
std::unique_ptr<V8StackTraceImpl> stackTrace;
switch
(type) {
case
ConsoleAPIType::kTrace:
stackTrace = m_inspector->debugger()->captureStackTrace(
true
);
break
;
case
ConsoleAPIType::kTimeEnd:
stackTrace = V8StackTraceImpl::capture(m_inspector->debugger(), 1);
break
;
default
:
stackTrace = m_inspector->debugger()->captureStackTrace(
false
);
break
;
}
std::unique_ptr<V8ConsoleMessage> message =
V8ConsoleMessage::createForConsoleAPI(
context(),
contextId(),
groupId(),
m_inspector,
m_inspector->client()->currentTimeMS(),
type,
arguments,
consoleContextToString(isolate(), m_consoleContext),
std::move(stackTrace)
);
consoleMessageStorage()->addMessage(std::move(message));
}
std::unique_ptr<V8ConsoleMessage> V8ConsoleMessage::createForConsoleAPI(
v8::Local<v8::Context> v8Context,
int
contextId,
int
groupId,
V8InspectorImpl* inspector,
double
timestamp, ConsoleAPIType type,
v8::MemorySpan<
const
v8::Local<v8::Value>> arguments,
const
String16& consoleContext,
std::unique_ptr<V8StackTraceImpl> stackTrace) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
std::unique_ptr<V8ConsoleMessage> message(
new
V8ConsoleMessage(V8MessageOrigin::kConsole, timestamp, String16()));
if
(stackTrace && !stackTrace->isEmpty()) {
message->m_url = toString16(stackTrace->topSourceURL());
message->m_lineNumber = stackTrace->topLineNumber();
message->m_columnNumber = stackTrace->topColumnNumber();
}
message->m_stackTrace = std::move(stackTrace);
message->m_consoleContext = consoleContext;
message->m_type = type;
message->m_contextId = contextId;
for
(v8::Local<v8::Value> arg : arguments) {
std::unique_ptr<v8::Global<v8::Value>> argument(
new
v8::Global<v8::Value>(isolate, arg));
argument->AnnotateStrongRetainer(kGlobalConsoleMessageHandleLabel);
message->m_arguments.push_back(std::move(argument));
message->m_v8Size += v8::debug::EstimatedValueSize(isolate, arg);
}
bool
sep =
false
;
for
(v8::Local<v8::Value> arg : arguments) {
if
(sep) {
message->m_message += String16(
" "
);
}
else
{
sep =
true
;
}
message->m_message += V8ValueStringBuilder::toString(arg, v8Context);
}
v8::Isolate::MessageErrorLevel clientLevel = v8::Isolate::kMessageInfo;
if
(type == ConsoleAPIType::kDebug || type == ConsoleAPIType::kCount ||
type == ConsoleAPIType::kTimeEnd) {
clientLevel = v8::Isolate::kMessageDebug;
}
else
if
(type == ConsoleAPIType::kError ||
type == ConsoleAPIType::kAssert) {
clientLevel = v8::Isolate::kMessageError;
}
else
if
(type == ConsoleAPIType::kWarning) {
clientLevel = v8::Isolate::kMessageWarning;
}
else
if
(type == ConsoleAPIType::kInfo) {
clientLevel = v8::Isolate::kMessageInfo;
}
else
if
(type == ConsoleAPIType::kLog) {
clientLevel = v8::Isolate::kMessageLog;
}
if
(type != ConsoleAPIType::kClear) {
inspector->client()->consoleAPIMessage(
groupId, clientLevel, toStringView(message->m_message),
toStringView(message->m_url), message->m_lineNumber,
message->m_columnNumber, message->m_stackTrace.get());
}
return
message;
}
std::unique_ptr<V8ConsoleMessage> V8ConsoleMessage::createForConsoleAPI(
v8::Local<v8::Context> v8Context,
int
contextId,
int
groupId,
V8InspectorImpl* inspector,
double
timestamp, ConsoleAPIType type,
v8::MemorySpan<
const
v8::Local<v8::Value>> arguments,
const
String16& consoleContext,
std::unique_ptr<V8StackTraceImpl> stackTrace) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
std::unique_ptr<V8ConsoleMessage> message(
new
V8ConsoleMessage(V8MessageOrigin::kConsole, timestamp, String16()));
if
(stackTrace && !stackTrace->isEmpty()) {
message->m_url = toString16(stackTrace->topSourceURL());
message->m_lineNumber = stackTrace->topLineNumber();
message->m_columnNumber = stackTrace->topColumnNumber();
}
message->m_stackTrace = std::move(stackTrace);
message->m_consoleContext = consoleContext;
message->m_type = type;
message->m_contextId = contextId;
for
(v8::Local<v8::Value> arg : arguments) {
std::unique_ptr<v8::Global<v8::Value>> argument(
new
v8::Global<v8::Value>(isolate, arg));
argument->AnnotateStrongRetainer(kGlobalConsoleMessageHandleLabel);
message->m_arguments.push_back(std::move(argument));
message->m_v8Size += v8::debug::EstimatedValueSize(isolate, arg);
}
bool
sep =
false
;
for
(v8::Local<v8::Value> arg : arguments) {
if
(sep) {
message->m_message += String16(
" "
);
}
else
{
sep =
true
;
}
message->m_message += V8ValueStringBuilder::toString(arg, v8Context);
}
v8::Isolate::MessageErrorLevel clientLevel = v8::Isolate::kMessageInfo;
if
(type == ConsoleAPIType::kDebug || type == ConsoleAPIType::kCount ||
type == ConsoleAPIType::kTimeEnd) {
clientLevel = v8::Isolate::kMessageDebug;
}
else
if
(type == ConsoleAPIType::kError ||
type == ConsoleAPIType::kAssert) {
clientLevel = v8::Isolate::kMessageError;
}
else
if
(type == ConsoleAPIType::kWarning) {
clientLevel = v8::Isolate::kMessageWarning;
}
else
if
(type == ConsoleAPIType::kInfo) {
clientLevel = v8::Isolate::kMessageInfo;
}
else
if
(type == ConsoleAPIType::kLog) {
clientLevel = v8::Isolate::kMessageLog;
}
if
(type != ConsoleAPIType::kClear) {
inspector->client()->consoleAPIMessage(
groupId, clientLevel, toStringView(message->m_message),
toStringView(message->m_url), message->m_lineNumber,
message->m_columnNumber, message->m_stackTrace.get());
}
return
message;
}
inspector->client()->consoleAPIMessage(
groupId, clientLevel, toStringView(message->m_message),
toStringView(message->m_url), message->m_lineNumber,
message->m_columnNumber, message->m_stackTrace.get());
inspector->client()->consoleAPIMessage(
groupId, clientLevel, toStringView(message->m_message),
toStringView(message->m_url), message->m_lineNumber,
message->m_columnNumber, message->m_stackTrace.get());
void
V8ConsoleMessageStorage::addMessage(
std::unique_ptr<V8ConsoleMessage> message) {
int
contextGroupId = m_contextGroupId;
V8InspectorImpl* inspector = m_inspector;
if
(message->type() == ConsoleAPIType::kClear) clear();
TraceV8ConsoleMessageEvent(message->origin(), message->type());
inspector->forEachSession(
contextGroupId, [&message](V8InspectorSessionImpl* session) {
if
(message->origin() == V8MessageOrigin::kConsole)
session->consoleAgent()->messageAdded(message.get());
session->runtimeAgent()->messageAdded(message.get());
});
if
(!inspector->hasConsoleMessageStorage(contextGroupId))
return
;
DCHECK(m_messages.size() <= maxConsoleMessageCount);
if
(m_messages.size() == maxConsoleMessageCount) {
m_estimatedSize -= m_messages.front()->estimatedSize();
m_messages.pop_front();
}
while
(m_estimatedSize + message->estimatedSize() > maxConsoleMessageV8Size &&
!m_messages.empty()) {
m_estimatedSize -= m_messages.front()->estimatedSize();
m_messages.pop_front();
}
m_messages.push_back(std::move(message));
m_estimatedSize += m_messages.back()->estimatedSize();
}
void
V8ConsoleMessageStorage::addMessage(
std::unique_ptr<V8ConsoleMessage> message) {
int
contextGroupId = m_contextGroupId;
V8InspectorImpl* inspector = m_inspector;
if
(message->type() == ConsoleAPIType::kClear) clear();
TraceV8ConsoleMessageEvent(message->origin(), message->type());
inspector->forEachSession(
contextGroupId, [&message](V8InspectorSessionImpl* session) {
if
(message->origin() == V8MessageOrigin::kConsole)
session->consoleAgent()->messageAdded(message.get());
session->runtimeAgent()->messageAdded(message.get());
});
if
(!inspector->hasConsoleMessageStorage(contextGroupId))
return
;
DCHECK(m_messages.size() <= maxConsoleMessageCount);
if
(m_messages.size() == maxConsoleMessageCount) {
m_estimatedSize -= m_messages.front()->estimatedSize();
m_messages.pop_front();
}
while
(m_estimatedSize + message->estimatedSize() > maxConsoleMessageV8Size &&
!m_messages.empty()) {
m_estimatedSize -= m_messages.front()->estimatedSize();
m_messages.pop_front();
}
m_messages.push_back(std::move(message));
m_estimatedSize += m_messages.back()->estimatedSize();
}
inspector->forEachSession(
contextGroupId, [&message](V8InspectorSessionImpl* session) {
if
(message->origin() == V8MessageOrigin::kConsole)
session->consoleAgent()->messageAdded(message.get());
session->runtimeAgent()->messageAdded(message.get());
});
inspector->forEachSession(
contextGroupId, [&message](V8InspectorSessionImpl* session) {
if
(message->origin() == V8MessageOrigin::kConsole)
session->consoleAgent()->messageAdded(message.get());
session->runtimeAgent()->messageAdded(message.get());
});
enum
class
V8MessageOrigin { kConsole, kException, kRevokedException };
enum
class
V8MessageOrigin { kConsole, kException, kRevokedException };
void
V8ConsoleAgentImpl::messageAdded(V8ConsoleMessage* message) {
if
(m_enabled) reportMessage(message,
true
);
}
void
V8ConsoleAgentImpl::messageAdded(V8ConsoleMessage* message) {
if
(m_enabled) reportMessage(message,
true
);
}
bool
V8ConsoleAgentImpl::reportMessage(V8ConsoleMessage* message,
bool
generatePreview) {
DCHECK_EQ(V8MessageOrigin::kConsole, message->origin());
message->reportToFrontend(&m_frontend);
m_frontend.flush();
return
m_session->inspector()->hasConsoleMessageStorage(
m_session->contextGroupId());
}
bool
V8ConsoleAgentImpl::reportMessage(V8ConsoleMessage* message,
bool
generatePreview) {
DCHECK_EQ(V8MessageOrigin::kConsole, message->origin());
message->reportToFrontend(&m_frontend);
m_frontend.flush();
return
m_session->inspector()->hasConsoleMessageStorage(
m_session->contextGroupId());
}
void
V8ConsoleMessage::reportToFrontend(
protocol::Console::Frontend* frontend)
const
{
DCHECK_EQ(V8MessageOrigin::kConsole, m_origin);
String16 level = protocol::Console::ConsoleMessage::LevelEnum::Log;
if
(m_type == ConsoleAPIType::kDebug || m_type == ConsoleAPIType::kCount ||
m_type == ConsoleAPIType::kTimeEnd)
level = protocol::Console::ConsoleMessage::LevelEnum::Debug;
else
if
(m_type == ConsoleAPIType::kError ||
m_type == ConsoleAPIType::kAssert)
level = protocol::Console::ConsoleMessage::LevelEnum::Error;
else
if
(m_type == ConsoleAPIType::kWarning)
level = protocol::Console::ConsoleMessage::LevelEnum::Warning;
else
if
(m_type == ConsoleAPIType::kInfo)
level = protocol::Console::ConsoleMessage::LevelEnum::Info;
std::unique_ptr<protocol::Console::ConsoleMessage> result =
protocol::Console::ConsoleMessage::create()
.setSource(protocol::Console::ConsoleMessage::SourceEnum::ConsoleApi)
.setLevel(level)
.setText(m_message)
.build();
if
(m_lineNumber) result->setLine(m_lineNumber);
if
(m_columnNumber) result->setColumn(m_columnNumber);
if
(!m_url.isEmpty()) result->setUrl(m_url);
frontend->messageAdded(std::move(result));
}
void
V8ConsoleMessage::reportToFrontend(
protocol::Console::Frontend* frontend)
const
{
DCHECK_EQ(V8MessageOrigin::kConsole, m_origin);
String16 level = protocol::Console::ConsoleMessage::LevelEnum::Log;
if
(m_type == ConsoleAPIType::kDebug || m_type == ConsoleAPIType::kCount ||
m_type == ConsoleAPIType::kTimeEnd)
level = protocol::Console::ConsoleMessage::LevelEnum::Debug;
else
if
(m_type == ConsoleAPIType::kError ||
m_type == ConsoleAPIType::kAssert)
level = protocol::Console::ConsoleMessage::LevelEnum::Error;
else
if
(m_type == ConsoleAPIType::kWarning)
level = protocol::Console::ConsoleMessage::LevelEnum::Warning;
else
if
(m_type == ConsoleAPIType::kInfo)
level = protocol::Console::ConsoleMessage::LevelEnum::Info;
std::unique_ptr<protocol::Console::ConsoleMessage> result =
protocol::Console::ConsoleMessage::create()
.setSource(protocol::Console::ConsoleMessage::SourceEnum::ConsoleApi)
.setLevel(level)
.setText(m_message)
.build();
if
(m_lineNumber) result->setLine(m_lineNumber);
if
(m_columnNumber) result->setColumn(m_columnNumber);
if
(!m_url.isEmpty()) result->setUrl(m_url);
frontend->messageAdded(std::move(result));
}
[培训]科锐逆向工程师培训第53期2025年7月8日开班!
最后于 2025-6-9 19:13
被nothing233编辑
,原因: