无限debugger作为一种反爬手段因其使用门槛低应用广泛,能在很多网站上看见其身影。
逆向分析网站的运行逻辑,无限debugger常常是我们要迈过去的第一道坎,而无限debugger的难易程度不一,简单的重构下函数即可绕过,难的如瑞数使用了eval函数对一大段的js代码进行了隐藏,难以找到debugger命令的具体位置,加上其代码总在变,绕过要费不少时间。
逆向的网站一多,时间成本就很高,期望有通用工具一劳永逸解决所有网站的无限debugger。
浏览器打开开发者工具的时候,执行debugger命令会使代码停止运行,
网站弄个定时器定时执行含有debugger命令的函数,当我们想要分析js代码而打开浏览器的开发者工具时就会不断停止运行,无法顺利调试代码。
不论代码如何混淆或是使用eval函数进行隐藏,最终都是执行debugger使程序停止运行,
我们可以从浏览器的源码出发将debugger进行修改,修改为null,使其名为“debugger”实为“null”,这样就不论debugger怎么执行对我们都不会有影响。
但是少了debugger,要使用debugger该怎么办?增加一个自定义的命令如debuggger,名为“debuggger”实为“debugger”。
macbook m1 pro(16G+512G)
APFS格式的移动固态硬盘,mac要APFS格式
系统:Sonoma 14.1.1 ,截止2023/11/21的最新版本,13.3编译会失败
各个平台都可以,我是mac就用mac示例了
其它平台参考官方文档里环境要求 https://chromium.googlesource.com/chromium/src/+/main/docs/get_the_code.md
m1需要创建arm64架构的python环境,需要时platform.machine()返回arm64才可以
参考 How to specify the architecture or platform for a new conda environment? (Apple Silicon)
CONDA_SUBDIR=osx-arm64 conda create --name py3115arm64 python=3.11.5 -c conda-forge
Checking out and building Chromium for Mac
https://chromium.googlesource.com/chromium/src/+/main/docs/mac_build_instructions.md
More details about Arm Macs
https://chromium.googlesource.com/chromium/src.git/+/main/docs/mac_arm64.md
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ export PATH="$PATH:/path/to/depot_tools"
需要科学上网才能下载
$ mkdir chromium && cd chromium
// --no-history 不获取历史记录下载会快些
fetch --no-history chromium
下载完23G
版本号:1cc8c02ab4a29727b7b6fdf1e505720285ed5b5c
chromium使用v8作为js引擎,因为我们要修改js命令,所以只需要修改v8的部分即可。
我chromium的目录是 /Volumes/PSSD/chromium
JavaScript 源代码在 V8 引擎中的解析过程主要包括以下步骤:
因为我们要对命令进行修改,可以考虑在词法分析的阶段进行,词法分析的代码位于 /Volumes/PSSD/chromium/src/v8/src/parsing
目录下。
修改逻辑是:当词法分析器在对词素和词法单元进行匹配的时候,进行判断修改,给词素debugger匹配null的词法单元,给词素debuggger匹配debugger的词法单元,修改如下:
1.修改 /Volumes/PSSD/chromium/src/v8/src/parsing/scanner-inl.h
V8_INLINE Token::Value Scanner::ScanIdentifierOrKeywordInner() {
DCHECK(IsIdentifierStart(c0_));
bool escaped = false;
bool can_be_keyword = true;
static_assert(arraysize(character_scan_flags) == kMaxAscii + 1);
if (V8_LIKELY(static_cast<uint32_t>(c0_) <= kMaxAscii)) {
if (V8_LIKELY(c0_ != '\\')) {
uint8_t scan_flags = character_scan_flags[c0_];
DCHECK(!TerminatesLiteral(scan_flags));
static_assert(static_cast<uint8_t>(ScanFlags::kCannotBeKeywordStart) ==
static_cast<uint8_t>(ScanFlags::kCannotBeKeyword) << 1);
scan_flags >>= 1;
// Make sure the shifting above doesn't set IdentifierNeedsSlowPath.
// Otherwise we'll fall into the slow path after scanning the identifier.
DCHECK(!IdentifierNeedsSlowPath(scan_flags));
AddLiteralChar(static_cast<char>(c0_));
AdvanceUntil([this, &scan_flags](base::uc32 c0) {
if (V8_UNLIKELY(static_cast<uint32_t>(c0) > kMaxAscii)) {
// A non-ascii character means we need to drop through to the slow
// path.
// TODO(leszeks): This would be most efficient as a goto to the slow
// path, check codegen and maybe use a bool instead.
scan_flags |=
static_cast<uint8_t>(ScanFlags::kIdentifierNeedsSlowPath);
return true;
}
uint8_t char_flags = character_scan_flags[c0];
scan_flags |= char_flags;
if (TerminatesLiteral(char_flags)) {
return true;
} else {
AddLiteralChar(static_cast<char>(c0));
return false;
}
});
if (V8_LIKELY(!IdentifierNeedsSlowPath(scan_flags))) {
if (!CanBeKeyword(scan_flags)) return Token::IDENTIFIER;
// Could be a keyword or identifier.
base::Vector<const uint8_t> chars =
next().literal_chars.one_byte_literal();
if (chars.length() == 9 && memcmp(chars.begin(), "debuggger", 9) == 0) {
std::string str = "debugger";
std::vector<uint8_t> mutableData(str.begin(), str.end());
base::Vector<const uint8_t> vectorData(mutableData.data(), mutableData.size());
return KeywordOrIdentifierToken(vectorData.begin(), vectorData.length());
}
if (chars.length() == 8 && memcmp(chars.begin(), "debugger", 8) == 0) {
std::string str = "null";
std::vector<uint8_t> mutableData(str.begin(), str.end());
base::Vector<const uint8_t> vectorData(mutableData.data(), mutableData.size());
return KeywordOrIdentifierToken(vectorData.begin(), vectorData.length());
}
return KeywordOrIdentifierToken(chars.begin(), chars.length());
}
can_be_keyword = CanBeKeyword(scan_flags);
} else {
// Special case for escapes at the start of an identifier.
escaped = true;
base::uc32 c = ScanIdentifierUnicodeEscape();
DCHECK(!IsIdentifierStart(Invalid()));
if (c == '\\' || !IsIdentifierStart(c)) {
return Token::ILLEGAL;
}
AddLiteralChar(c);
can_be_keyword = CharCanBeKeyword(c);
}
}
return ScanIdentifierOrKeywordInnerSlow(escaped, can_be_keyword);
}
2.修改 /Volumes/PSSD/chromium/src/v8/src/parsing/scanner.cc
Token::Value Scanner::ScanIdentifierOrKeywordInnerSlow(bool escaped,
bool can_be_keyword) {
while (true) {
if (c0_ == '\\') {
escaped = true;
base::uc32 c = ScanIdentifierUnicodeEscape();
// Only allow legal identifier part characters.
// TODO(verwaest): Make this true.
// DCHECK(!IsIdentifierPart('\'));
DCHECK(!IsIdentifierPart(Invalid()));
if (c == '\\' || !IsIdentifierPart(c)) {
return Token::ILLEGAL;
}
can_be_keyword = can_be_keyword && CharCanBeKeyword(c);
AddLiteralChar(c);
} else if (IsIdentifierPart(c0_) ||
(CombineSurrogatePair() && IsIdentifierPart(c0_))) {
can_be_keyword = can_be_keyword && CharCanBeKeyword(c0_);
AddLiteralCharAdvance();
} else {
break;
}
}
if (can_be_keyword && next().literal_chars.is_one_byte()) {
base::Vector<const uint8_t> chars = next().literal_chars.one_byte_literal();
Token::Value token =
KeywordOrIdentifierToken(chars.begin(), chars.length());
if (chars.length() == 9 && memcmp(chars.begin(), "debuggger", 9) == 0) {
std::string str = "debugger";
std::vector<uint8_t> mutableData(str.begin(), str.end());
base::Vector<const uint8_t> vectorData(mutableData.data(), mutableData.size());
token = KeywordOrIdentifierToken(vectorData.begin(), vectorData.length());
}
if (chars.length() == 8 && memcmp(chars.begin(), "debugger", 8) == 0) {
std::string str = "null";
std::vector<uint8_t> mutableData(str.begin(), str.end());
base::Vector<const uint8_t> vectorData(mutableData.data(), mutableData.size());
token = KeywordOrIdentifierToken(vectorData.begin(), vectorData.length());
}
if (base::IsInRange(token, Token::IDENTIFIER, Token::YIELD)) return token;
if (token == Token::FUTURE_STRICT_RESERVED_WORD) {
if (escaped) return Token::ESCAPED_STRICT_RESERVED_WORD;
return token;
}
if (!escaped) return token;
static_assert(Token::LET + 1 == Token::STATIC);
if (base::IsInRange(token, Token::LET, Token::STATIC)) {
return Token::ESCAPED_STRICT_RESERVED_WORD;
}
return Token::ESCAPED_KEYWORD;
}
return Token::IDENTIFIER;
}
3.修改 /Volumes/PSSD/chromium/src/v8/src/parsing/scanner.h
base::Vector<const uint8_t> next_literal_one_byte_string() const {
DCHECK(next().CanAccessLiteral());
base::Vector<const uint8_t> chars = next().literal_chars.one_byte_literal();
if (chars.length() == 9 && memcmp(chars.begin(), "debuggger", 9) == 0) {
std::string str = "debugger";
std::vector<uint8_t> mutableData(str.begin(), str.end());
base::Vector<const uint8_t> vectorData(mutableData.data(), mutableData.size());
return vectorData;
}
if (chars.length() == 8 && memcmp(chars.begin(), "debugger", 8) == 0) {
std::string str = "null";
std::vector<uint8_t> mutableData(str.begin(), str.end());
base::Vector<const uint8_t> vectorData(mutableData.data(), mutableData.size());
return vectorData;
}
return chars;
}
改这3个文件即可
$ cd /Volumes/PSSD/chromium/src/
$ gn gen out/fix_v8
可以修改/Volumes/PSSD/chromium/src/out/fix_v8/args.gn
,添加以下参数编译更快些
# Set build arguments here. See `gn help buildargs`.
is_debug = false
is_component_build = true # 若想编译后要打包成安装包的话,设置成false
symbol_level = 0
target_cpu = "arm64"
autoninja -C out/fix_v8 chrome
编译需要若干小时,过程比较吃cpu,内存还好
/Volumes/PSSD/chromium/src/out/fix_v8/Chromium.app/Contents/MacOS/Chromium
//Chromium会将用户配置目录设置为~/.config/chromium(在Mac上是~/Library/Application Support/Chromium)
如此便得到一个无视无限debugger的浏览器了,运行debugger没有反应,运行debuggger会暂停
hdiutil create -volname "Chromium" -srcfolder /Volumes/PSSD/chromium/src/out/fix_v8_release/Chromium.app -ov -format UDZO Chromium.dmg
安装时打开Chromium.dmg,把Chromium挪到应用程序目录下