吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4649|回复: 95
上一主题 下一主题
收起左侧

[Web逆向] 拦截 Chrome DevTools Protocol 实现忽略与自定义 debugger

    [复制链接]
跳转到指定楼层
楼主
LoveCode 发表于 2024-9-23 23:56 回帖奖励
本帖最后由 LoveCode 于 2024-9-29 00:36 编辑

这次不是斗法、是锻造法器啦。

使用本文的方案,可以在 Edge、Chrome 浏览器上忽略 debugger 语句,同时可以定义属于自己的 debugger —— 而且不影响代码的可移植性哟

这是本文的结构:

  1. 本方案的缺点 —— 至关重要,必看内容!
  2. 项目的使用方式 —— 我知道你很急,所以放在了前面
  3. 网站检测调试的原理与绕过 —— 安心地打开开发者工具
  4. 简述本文方案的原理

[toc]

本方案的缺点

使用本方案需要以下步骤:

  • 打开浏览器的远程调试功能。
  • 安装一个浏览器插件,由项目提供,有以下辅助作用:
    • 快速打开单独的开发者工具,并不是按 F12 键打开的那个版本哟。
    • 自动插入与取消自定义的 debugger 语句。
    • 与标签页关联 —— 标签页关闭时自动关闭开发者工具等等。
  • 需要点击插件打开新的开发者工具使用,不能使用按 F12 键显示的那个版本。
  • 启动一个服务器 cdp server,由项目提供,它是最核心的部分。

上述操作可以在需要过 debugger 时再快速启用,如果能接受以上繁杂的步骤,可以继续阅读下文了


2024-09-29 补充:现在发现此方案有一个最大的缺点,具体见评论区这篇回答:在 debugger 语句停留的时长

可预见的解决方式如下:
发现 debugger 语句时,自动地在它的位置上下一个 Never pause here 的断点,退出调试时由 cdp server 自动清理这些断点。
我考虑了很久,是否有必要这样做,结合实际情况,或许这个缺点可以忽略不计?

项目的使用方式

本文以 Chrome 浏览器为例进行展示,Edge 浏览器的配置也是一样的。

基本配置

step 1

访问项目,获取源代码:Hosinoharu/SkipJSDebugger。在 Releases 中的文件根据后文说明按需求下载。

step 2

修改浏览器的启动参数为如下情形,然后打开浏览器。

// chrome.exe --remote-debugging-port=9222 --remote-allow-origins=localhost

// --remote-debugging-port 启动远程调试端口,暂时定为 9222,后续可以手动修改
// --remote-allow-origins 远程调试时允许连接的来源,建议设置为 localhost。使用 * 运行任何来源

项目中提供了一个 powershell 脚本,位于 scripts\debug_browser.ps1,可以等到需要的时候再快速启动浏览器。不过使用之前需要填入本机浏览器的位置。

# 找到脚本的这个位置,修改这两个变量的值为本机的浏览器的路径
$Edge = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
$Chrome = "E:\Google\Chrome\Application\chrome.exe"

基本使用如下:

# 首先修改 powershell 执行脚本的权限
Set-ExecutionPolicy RemoteSigned

# 然后按需执行下面任意一条命令打开浏览器,注意!执行 ps 脚本时需要加上路径哟
.\debug_browser.ps1             # 默认打开 Chrome 浏览器,远程调试端口为 9222
.\debug_browser.ps1 -Port 8888  # 默认打开 Chrome 浏览器,远程调试端口为 8888

.\debug_browser.ps1 Edge               # 打开 Edge 浏览器,远程调试端口为 9222
.\debug_browser.ps1 Edge -Port 8888    # 打开 Edge 浏览器,远程调试端口为 8888

step 3

手动添加插件 OpenDevtoolsPage,其位于项目根目录。在浏览器地址栏输入 chrome://extensions/ 打开界面,然后如下进行添加:

step 4

运行 cdp server,位于项目的 cdp_server 目录中,共有两个版本。

  • Python 实现。最初的版本,使用此版本时,每次打开浏览器开发者工具时它都会发动时间法则,硬控我一秒,所以后续使用 Go 重写。如果没有 Go 环境,可以用这个版本。
  • Go 实现。可以自己从源代码编译,也可以在 Releases 中下载编译好的版本 —— 仅限于 windows 10

下图本机的处理效果。提交到 Github Releases 的文件 SHA256 hashF96B7896A2ED14BC088DCB37B030F2C6A2B81E559FEE9DA632695B498C2E97EA

step 5

测试效果。先打开项目的 test/test_debugger.html 文件,然后按下述步骤测试:

然后关闭刚刚打开的开发者工具,刷新网页,继续下面的操作。

注:现在给插件换了个图标,所以和下图会有些区别。

如果以上过程顺利,那么现在可以忽略其它网站的 debugger 语句了。

关于插件打开的开发者工具

确保点击插件 OpenDevtoolsPage 打开新的开发者工具时,要关闭由 F12 键等方式打开的、原版自带的开发者工具,否则在显示上一定会出现问题 —— 因为出现了两个开发者工具调试网站。

  • 简单而言,只保留由插件打开的开发者工具
  • 如果懂编程概念,这种情况类似两个多线程读写同一个变量

如果点击插件打开开发者工具后,出现这样的信息,说明程序可能出错了,先检查上述的步骤,如果依然未能解决问题,可以在评论区说明。

补充说明

内嵌的开发者工具就是按 F12 键打开的那个版本,它的链接是 devtools://devtools/bundled/inspector.html,在浏览器中可以输入该链接进行访问。

本来是使用这个进行调试的,经过测试它会造成浏览器闪退(是浏览器突然就消失了,并不会弹出一个警告框告诉我浏览器已经崩溃),所以使用了远程调试的版本,即 localhost:port/devtools/inspector.html 这个版本。

这带来一个问题:得重新设置一下开发者工具,我指的是一些设置项、布局等需要重新设置一下。

自定义 debugger

默认情况下 —— 指的是:启动 cdp_server,并且用插件打开开发者工具,浏览器会忽略普通的 debugger 语句,且只会在 lovedebug() 函数的下一行断住 —— 其它的手动断点、条件断点均不受影响啦。

即默认情况下使用 lovedebug() 函数作为预定义的 debugger,下面展示如何使用它。

如果需要设置独属于自己的 debugger,比如取名为 hello_world,那么请按照以下步骤进行

第一步,修改 cdp server

如果使用 Python 实现的版本,修改 cdp_server/py/settings.py 的这里。

如果是使用 Go 实现,后续执行程序时传参: cdp_server.exe -debugger hello_world 即可,也可以自行修改源代码进行编译。另外,使用 cdp_server.exe -h 可以查看有哪些参数

第二步,修改 OpenDevtoolsPage 插件的配置。

自定义端口

总共有 2 个端口可以自定义。

一个是浏览器的远程调试端口,默认使用的 9222。如果要修改它,比如修改为 8888

首先要修改 Chrome 的启动参数 --remote-debugging-port=8888。如果使用上述的 PS 脚本时需要传参。

对于 cdp_server

  • 使用 Python 版本,在 cdp_server/py/settings.py 中修改变量 WEB_SOCKET_PORT 的值为 8888
  • 是 Go 版本,启动时传入参数: cdp_server.exe -port 8888

然后打开 OpenDevtoolsPage 插件的设置项做相应的修改。

一个是 cdp_server 的端口,默认使用的是 9221。如果要修改它,比如修改为 8887

对于 cdp_server

  • 使用 Python 版本,在 cdp_server/py/settings.py 中修改变量 CDP_SERVER_PORT 值为 8887
  • 是 Go 版本,启动时传入参数: cdp_server.exe -cdp 8887

然后打开 OpenDevtoolsPage 插件的设置项做相应的修改。

网站检测调试的原理与绕过

现在去除了 debugger 语句,网站还有一些检测控制台是否打开的方式,在项目中有一个 JS 文件,其位于  scripts\anti_dev_detector.js,它就是为了去除这些检测。

因为整个项目,额……略显鸡肋?所以我将它单独拿了出来,请将它放在油猴插件中运行。

test\test_console.html 文件中整理了我搜索到的检测方式,下面阐述相关的检测逻辑与处理。

利用 Console API

如果是使用 Console API 进行检测,那么将其 Hook 并改为空函数即可。当然还有一些检测被 Hook 的方式,常见的是检测 .toString() 返回结果。

我建议不要使用以下方式进行 hook:

const raw_log = console.log;
console.log = function log() {}
// 省略其它的步骤,如增加 toString()、或者添加属性描述符等等

建议利用 Proxy API 进行 hook:

这样处理 .toString() 检测就方便了。

如下是测试:

报错式检测

这种检测方案来自 https://www.bilibili.com/video/BV1Gi421C7Rz,核心思想是添加属性描述符 getter,所以通过 Hook Object.defineProperty、Object.prototype.__defineGetter__ 等函数可以过掉。

test/teset_console.html 文件中已经加入了这种检测。

请注意,一些前端框架可能利用这种方式进行错误日志记录等等,所以如果网站功能出现异常,应该禁用脚本

其它检测方案

基于 debugger 语句的检测方案现在可以忽略不谈了,比如下面这种:

const a = new Date;
debugger;
const b = new Date;
if ((b - a) > 10) {
    alert("检测到啦")
}

还有检测 F12 按键、浏览器窗口的大小变化的检测方案也不用谈了,因为现在是点击插件打开的浏览器开发者工具。

其它的大都是利用 Console API,比如下面这种形式。

const re = /x/;
re.toString = function () {
    alert("检测到啦");
}
console.log(re);

还有这种形式的递归循环 + debugger,本质上还是利用的 debugger。

setInterval(function() {
  check()
}, 4000);

var check = function() {
  function doCheck(a) {
    // 无论进入哪个分支都会调用 debugger 语句
    if (("" + a/a)["length"] !== 1 || a % 20 === 0) {
      (function() {}
      ["constructor"]("debugger")())
    } else {
      (function() {}
      ["constructor"]("debugger")())
    }
    doCheck(++a)   // 这里递归调用
  }

  // 一直调用,直到堆栈溢出出错,然后进入 catch 块中
  try {
    doCheck(0)
  } catch (err) {}
};

check();

目前我所搜索到的、稳定的检测方案就这些了。

本方案原理

仅图示原理,并未从代码上详细解释。

关于 Chrome Devtools Protocol

Chrome Devtools Protocol 简称 CDP,是一种通信协议,通常使用 websocket 进行传输。

简单描述为如下:

如何理解呢?

  1. F12 键能打开开发者工具界面,将它单独分离出来。
  2. 继续按 Ctrl+Shift+I 还能打开它的开发者工具。
  3. 然后就能看到原来的那个开发者工具位于 devtools:// 下。
  4. 所以说,开发者工具其实是单独的个体哟,调试过程中它和浏览器引擎进行通信啦。

更详细内容请查看其它文章。

浏览器可以查看 CDP 协议的内容哟,具体可以见文档:

现在我们已经知道 CDP,当浏览器开启远程调试时(调试端口为上文提到的 9222,会开放对应页面的调试链接,通过搭建一个中转服务器(即 Proxy、代{过}{滤}理),可以拦截、并修改 cdp 协议内容。

浏览器插件的 debugger API 可以发送、查看 cdp 通信内容,无法进行拦截与修改。

所以需要额外搭建一个服务器进行拦截。

好了,下面是画图简述原理。

忽略 debugger 语句

简单来说就是这种的:

具体逻辑是这样的。

那么依据此就可以实现这样的逻辑:

自定义 debugger 语句

先看一个普通函数 hello 的调用情况。

那么思路就是这样的。

Debugger.paused 消息中是可以看到调用栈信息的,从而在 cdp server 进行判断与拦截。

End

最初有这个想法、并测试成功时我很激动;当我完成并测试更多网站时,我热血沸腾。直到我找一些开源项目做测试,如 disable-devtool、console-ban 等,看了一些 issue,还有一些网站中作者的留言,感叹各有各的烦恼 —— 小网站流量少,为爱发电被薅羊毛,时间不够钻研技术,用开源方案或花钱买服务,买的肯定不会是瑞数级别的,钱都拿去租服务器了,太简单的又……拿来当入门练习了。

免费评分

参与人数 40吾爱币 +41 热心值 +39 收起 理由
wangyongdesign + 1 + 1 谢谢@Thanks!
zsr849408332 + 1 + 1 谢谢@Thanks!
度娘灬魂手 + 2 + 1 热心回复!
guhuishou + 1 + 1 谢谢@Thanks!
liyitong + 2 + 1 浏览器的反反调试
BrightMrW + 1 我很赞同!
ioyr5995 + 1 + 1 我很赞同!
gouzi123 + 1 + 1 谢谢@Thanks!
yixi + 1 + 1 热心回复!
irfox + 1 + 1 谢谢@Thanks!
zzyzy + 1 + 1 谢谢@Thanks!
Leaf08 + 1 + 1 我很赞同!
Issacclark1 + 1 谢谢@Thanks!
021012 + 1 + 1 用心讨论,共获提升!
Chenda1 + 1 + 1 谢谢@Thanks!
akts + 1 + 1 用心讨论,共获提升!
zoomzoomblood + 1 我很赞同!
kingguo + 1 + 1 用心讨论,共获提升!
kof97zip + 1 + 1 谢谢@Thanks!
5omggx + 1 + 1 用心讨论,共获提升!
chengdragon + 1 + 1 感谢分享,很详细
domain_vip + 1 + 1 用心讨论,共获提升!
weidechan + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
gun008 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
asdnasiudn + 1 + 1 我很赞同!
外酥内嫩 + 1 + 1 我很赞同!
allspark + 1 + 1 用心讨论,共获提升!
xiaohuihui3 + 1 + 1 谢谢@Thanks!
woyucheng + 1 + 1 谢谢@Thanks!
yp17792351859 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
kesshei + 1 + 1 我很赞同!
Cleopatra + 1 + 1 我很赞同!
ehu4ever + 1 + 1 谢谢@Thanks!
wshq + 1 + 1 谢谢@Thanks!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
jgs + 1 + 1 谢谢@Thanks!
WeiZhiDeR8 + 1 + 1 谢谢@Thanks!
laozhang4201 + 1 + 1 热心回复!
海水很咸 + 1 + 1 谢谢@Thanks!
HongHu106 + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| LoveCode 发表于 2024-9-24 23:04 |楼主
wuaikirin 发表于 2024-9-24 18:52
目标网站检测cdp协议呢

感谢你的提醒,忘了找一些专门的机器人测试网站进行测试了……。

刚才我在 https://www.browserscan.net/zh/bot-detection 这个网站进行了 cdp 测试,发现是可以过掉的,它的原理依然是利用报错式检测,具体原理文章可以参考这里:How New Headless Chrome & the CDP Signal Are Impacting Bot Detection

经过此次测试也发现了反检测脚本的缺点:

  • 默认情况下把 Console API 所有的函数都 hook 为空函数了,但是有几个函数它是有返回值的,如 createTask 函数 —— 最开始找的是 MDN 文档,发现没这个东西………
  • 所以如果在上面这个网页中启动反检测脚本,会造成功能异常,现在反检测脚本已经更新到 github 上了。

感谢评论,让我发现了反检测脚本的缺点。

免费评分

参与人数 1吾爱币 +2 热心值 +1 收起 理由
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

推荐
 楼主| LoveCode 发表于 2024-9-27 22:13 |楼主
kayakz 发表于 2024-9-25 13:44
试了下确实能过无限debugger,不过有些devtools检测还是过不去,可以试试这个= =
https://1997.pro/devtoo ...

感谢提供的测试网站,让我发现了本方案最大的缺点:debugger 语句处停留的时间上,本方案相比较直接忽略 debugger 语句更长

下面用图说明这种缺点。


现在继续说明那个网站的测试:经过实践,该网站中如果在 debugger 处停留的时间超过 0.1ms 就会触发检测

事实上该网站即便彻底取消 debugger 也无法过掉检测,下面猜测一下原理 —— 通过 hook 相关 api 而做出的猜测,因为在代码混淆面前我还是一个新兵蛋子。

所以,如果仅忽略了 debugger 语句,在主线程中手动断点时也会导致上图中的 end 消息来得太晚,导致无法过掉检测。

这种检测方式是:先建立一个正常的流程,调试时就会打破这个流程,从而进行检测。

举一个例子,一些动漫中的组织会设置 “巡逻人员”,监控敌人动向,通常来说他们都是:发现可疑目标就向上报告。

为了避免敌人向内部渗透,他们又规定:每隔 5 分钟所有人依次报一个暗号,如果有人没有报号、或者暗号对不上,就说明敌人已经渗透进来了。

该网站的检测方式类似:如果 worker 发过来的消息太晚,说明是在调试

同时网站还会连续的创建 Worker,这确实很恶心。

了解原理之后,过掉的方案是:Hook Worker API,主要有两个作用:

  • 首先避免重复发出网络请求。网站利用 setInterval 重复创建 Worker,不过直接 hook setInterval 距离目标太远,所以 hook Worker
  • 其次模拟消息发送,当 worker 发过来一个 start 消息时,立即发送一个 end 消息,之后拦截掉姗姗来迟的、由 worker 发送的 end 消息。

使用下面的油猴脚本可以过掉检测,同时可以在网站进行调试了。

// ==UserScript==
// home.php?mod=space&uid=170990         New Userscript
// home.php?mod=space&uid=467642    http://tampermonkey.net/
// home.php?mod=space&uid=1248337      2024-09-27
// @description  try to take over the world!
// home.php?mod=space&uid=686208       You
// home.php?mod=space&uid=195849        https://1997.pro/devtools_debugger_check
// home.php?mod=space&uid=593100         https://www.google.com/s2/favicons?sz=64&domain=1997.pro
// home.php?mod=space&uid=609072        none
// @run-at       document-start
// ==/UserScript==

(() => {
    const log = console.log;
    const raw_worker = Worker;
    let is_checked = false;
    const target_url = "/themes/theme-yazong/assets/js/debugger_check.js";

    // 简单的 Hook,并没有考虑反 hook 检测
    Worker = function Worker(url, option) {
        log("worker:", url);
        let w;
        if (url === target_url) {
            // 确保只请求一次
            if (is_checked) { return; }

            w = new raw_worker(url, option);
            is_checked = true;

            // 重写添加事件的方法,hook 事件处理函数
            // 当第一次运行 target_url,它发送一个 start 消息时,立即回复一个 end 消息
            // 并且拦截它后续的的 end 消息
            const listener = w.addEventListener.bind(w);
            w.addEventListener = function (event_name, func) {
                let new_func = func;
                if (event_name === 'message') {
                    // hook 处理 message 的函数
                    new_func = function (e) {
                        if (e.data === "end") { return; }
                        if (e.data === "start") {
                            func({ data: "start" });
                            func({ data: "end" });
                        }
                    };
                }
                return listener(event_name, new_func);
            }

            // 然后重写 onmessage 事件
            Object.defineProperty(w, 'onmessage', {
                set(v) {
                    w.addEventListener("message", v);
                }
            });
        } else {
            w = new raw_worker(url, option);
        }
        return w;
    }
})();
沙发
zhaoaa 发表于 2024-9-24 01:15
3#
darkf 发表于 2024-9-24 02:49
很详细使用
4#
markhoo911 发表于 2024-9-24 07:26
感谢分享,真的挺不错
5#
wasm2023 发表于 2024-9-24 07:44
可否实现ast与其结合,然后批量下断点呢
6#
KeviseBY 发表于 2024-9-24 08:01
感谢分享,真的挺不错,向大佬学习
7#
anson1599 发表于 2024-9-24 08:13
学习了,花了点时间,从头到尾读了一遍帖子
8#
pjyanzi 发表于 2024-9-24 08:15

感谢分享,向大佬学习
9#
mango1022 发表于 2024-9-24 08:25
很实用,点赞
10#
1006706246 发表于 2024-9-24 08:26
看起来很厉害啊
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-10-7 14:40

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表