无限debugger通杀方案——定制chromium

发布时间:2023年12月18日

无限debugger作为一种反爬手段因其使用门槛低应用广泛,能在很多网站上看见其身影。
逆向分析网站的运行逻辑,无限debugger常常是我们要迈过去的第一道坎,而无限debugger的难易程度不一,简单的重构下函数即可绕过,难的如瑞数使用了eval函数对一大段的js代码进行了隐藏,难以找到debugger命令的具体位置,加上其代码总在变,绕过要费不少时间。
逆向的网站一多,时间成本就很高,期望有通用工具一劳永逸解决所有网站的无限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

在这里插入图片描述

Chromium Mac 官方文档

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

安装depot_tools

$ 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

修改V8源码

chromium使用v8作为js引擎,因为我们要修改js命令,所以只需要修改v8的部分即可。
我chromium的目录是 /Volumes/PSSD/chromium

JavaScript 源代码在 V8 引擎中的解析过程主要包括以下步骤:

  1. 词法分析:在这个阶段,源代码被分解成一系列的词素(lexeme),例如变量名、关键字(如 if、else、debugger )、运算符等,每个词素生成并输出一个词法单元(token)。这个过程也被称为扫描(Scanning)。
  2. 语法分析:V8 会根据 JavaScript 的语法规则将上一步得到的 tokens 组织成一个抽象语法树(AST)。如果源代码中有语法错误,例如缺少括号或关键字拼写错误,这个阶段会抛出错误。这个阶段也被称为解析(Parsing)。
  3. 生成字节码:解析完成后,V8 的解释器 Ignition 会将 AST 转化为字节码。

因为我们要对命令进行修改,可以考虑在词法分析的阶段进行,词法分析的代码位于 /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个文件即可

改完代码编译chromium

$ 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,内存还好
在这里插入图片描述

在这里插入图片描述

运行chromium

/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挪到应用程序目录下

文章来源:https://blog.csdn.net/qq_43572758/article/details/134399490
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。