SpiderDemo题解系列——第3篇:调试拦截与非对称加密挑战 — JS逆向调试实战(第23题)
目录
- 一、分析和扣代码
- 二、请求测试
- 三、视频讲解
一、分析和扣代码
调试拦截原理参考文章:https://blog.csdn.net/xw1680/article/details/153742917
绕过参考文章:https://blog.csdn.net/xw1680/article/details/138547184
按照常规流程按下 F12 打开浏览器调试工具时,触发 debugger 断点,如下图所示:

简单观察一下这些堆栈是如何生成的 debugger,了解过其原理后你就会发现——它通常是通过 Function 构造函数动态创建的。接下来我们可以向上追溯调用栈,看看具体的生成位置:


发现这是通过 Function 构造函数直接传入字符串 "debugger" 生成的,这种方式触发的断点可以直接通过选择 "Never pause here 来跳过。回到断点位置后,按以下步骤操作即可:
此时又出现了如下所示的 debugger 断点:

学习过原理后我们就能判断,这种类型的 debugger 已经无法通过 Never pause here 绕过。虽然形式上依然是通过 Function 构造函数生成的,但这次传入的字符串是动态拼接的,将 debugger 与时间戳等变量结合使用。接下来,我们回到堆栈查看具体的调用位置:

我们先继续往下执行,观察接下来是否还会出现其他断点,然后再统一处理这些 debugger 断点:

这个断点学习过原理后也应该很熟悉——它是通过 eval 动态生成的,传入的字符串同样是动态拼接的,将 debugger 与时间戳结合使用。接下来,我们回溯堆栈查看具体调用位置:

它还有一层嵌套,我们继续往上查看:

可以发现,它正位于我们之前分析的 Function 堆栈的下方,属于同一条控制流。接下来我们就可以去仔细查看这一整块代码的来源以及调用方式,结果发现:

至此,我们已经追溯到了源头——原来是一个 setInterval 定时器,每隔 500 毫秒执行一次 debug 函数。解决方案可以有三种:
- 直接修改源代码:将
setInterval(debug, 0x1f4);注释掉 - Hook setInterval 函数:拦截定时器逻辑
- Hook Function 和 eval 函数:拦截动态生成的 debugger 语句
笔者尝试了方案 ①,会导致网页跳转到其他页面,推测有额外检测机制;相比之下,方案 ② 和 ③ 更可行。为了简化操作,这里选择 Hook setInterval,对应的 hook 脚本如下:
_setInterval = setInterval
setInterval = function (a,b) {if(a.toString().indexOf('debugger') == -1){return function(){}}else{_setInterval(a,b)}
}Function.prototype.toString = function () {return `function ${this.name}() { [native code] }`
}
使用 hook 脚本时,需要特别注意 hook 的时机,否则可能失效或引发错误。操作步骤如下:首先在页面中打上 script 执行断点:

按下 Ctrl + F5 强制刷新网页:

在控制台输入并执行 hook 脚本后,切换到 Sources 面板,先取消之前勾选的 script 脚本断点,然后点击下方按钮继续操作:

可以发现,此时我们已经正常进入网页,并且无限 debugger 已经被成功屏蔽,页面可以正常调试了。接下来切换到 Network 面板,点击页面上的页码,观察对应的数据包,会很容易发现请求参数和请求头中存在加密处理,如下所示:


同样地,我们可以通过搜索关键字快速定位到加密逻辑所在的位置:
return l.interceptors.request.use(function(e) {var n, t, r = e.url.match(/\/page\/(\d+)\//);if (r) {var c = parseInt(r[1]), a = new URLSearchParams(e.url.split("?")[1] || "").get("challenge_type") || "fsymmetry_challenge", o = Date.now(), s = "".concat(c, "_").concat(a, "_").concat(o);e.headers["X-Auth-Key"] = u(s),e.headers["X-Signature"] = (n = s,(t = new JSEncrypt).setPrivateKey("-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQC1vKwZUIv7pgpJUXXPpDlD4+VEon3a0ANOrNmqAESrcGfkmYzD\nCo2JeuYezhBGjBNjwVmSct/Y3BBOCRGT2bvtCJGdS12RMvHbFcdbwS/Adh48+rhL\niMNYXLm+7pI3e2k6TlScxKa7EeeZpVtew/Cv5z6ol0llNPp6BdqAlOa8DwIDAQAB\nAoGAS0GaWI9AsFAFEXBgoz/jkMf14DKTgEFEJVexeNLMnNuawhCNuBSOIMCaO2Zk\nWfpWaygdUeYs6M3UGKRruXhf92g/BRmJK5FzR0kWW4qw6WwlYob3TPc3c9MFOjmp\nVtWQ0VSeEPrnBNoQRccKl0dGBnToHGuV+KEuKx8oWZc/JM0CQQDH/cvlx0BKz2zN\n6PM8FidAvc+Wgon8YW81KJgC7iJIrK9FOpctOE3L1pdF7guOQNVGRqN4HCIgLfHE\ncqxWJKJtAkEA6KIkwHe/Q23uWH5GP8DHtVkLVfohTumYkpb0rk05EYQ0dsWSNzWH\nXDH/kD6ayNq+fscnS8g+59onzvfhJ0bq6wJBAKNFkDEHenWY4js481sauvEgBVnb\nOMvSv/emLHQ39cVfNbhPHRzN2rWPe/CbZtO8GmJFSS/FyBZ9a+P1uryZLAECQAaw\nApZ12s25b0yj9KkIhbU05hqGokZ+eKBeLpKELcvPHSL88wMbStTfqxUed5ymjStf\n1kVbcFOB9fsBLTvP0hkCQFCON0l1VjFli+vqfN0lypgIqCf85V6FZFN19creGCCd\n76pX/X2FIBbUSDN1z48SM5I/RKdCkTx7FY+509q2Mek=\n-----END RSA PRIVATE KEY-----"),t.sign(n, CryptoJS.SHA256, "sha256") || "");var i = function(e) {return CryptoJS.HmacSHA256(e, "dsa_secret_key_2025").toString()}(s), l = u(s + "_param"), f = e.url.includes("?") ? "&" : "?";e.url += "".concat(f, "data=").concat(encodeURIComponent(l), "&verify=").concat(i, "&t=").concat(o)}return e}
观察上方的核心代码后,可以将其改写为如下形式:
//需要u函数,JSEncrypt,CryptoJS,所以改写如下:
//你要从网页中将 jsencrypt.min.js Copy到本地,并且补一下window
// 如果改写不太懂的话 可以去看我录制的视频,里面有详细讲解,这里就不再赘述
window = globalThis;let CryptoJS = require('./CryptoJS')
let JSEncrypt = require('./jsencrypt.min.js')function u(e) {var n = new JSEncrypt;return n.setPublicKey("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1vKwZUIv7pgpJUXXPpDlD4+VE\non3a0ANOrNmqAESrcGfkmYzDCo2JeuYezhBGjBNjwVmSct/Y3BBOCRGT2bvtCJGd\nS12RMvHbFcdbwS/Adh48+rhLiMNYXLm+7pI3e2k6TlScxKa7EeeZpVtew/Cv5z6o\nl0llNPp6BdqAlOa8DwIDAQAB\n-----END PUBLIC KEY-----"),n.encrypt(e) || ""
}function encrypt(page, timestamp){let obj = {};let a = "fsymmetry_challenge";let s = "".concat(page, "_").concat(a, "_").concat(timestamp);obj["X-Auth-Key"] = u(s)let t = new JSEncrypt;t.setPrivateKey("-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQC1vKwZUIv7pgpJUXXPpDlD4+VEon3a0ANOrNmqAESrcGfkmYzD\nCo2JeuYezhBGjBNjwVmSct/Y3BBOCRGT2bvtCJGdS12RMvHbFcdbwS/Adh48+rhL\niMNYXLm+7pI3e2k6TlScxKa7EeeZpVtew/Cv5z6ol0llNPp6BdqAlOa8DwIDAQAB\nAoGAS0GaWI9AsFAFEXBgoz/jkMf14DKTgEFEJVexeNLMnNuawhCNuBSOIMCaO2Zk\nWfpWaygdUeYs6M3UGKRruXhf92g/BRmJK5FzR0kWW4qw6WwlYob3TPc3c9MFOjmp\nVtWQ0VSeEPrnBNoQRccKl0dGBnToHGuV+KEuKx8oWZc/JM0CQQDH/cvlx0BKz2zN\n6PM8FidAvc+Wgon8YW81KJgC7iJIrK9FOpctOE3L1pdF7guOQNVGRqN4HCIgLfHE\ncqxWJKJtAkEA6KIkwHe/Q23uWH5GP8DHtVkLVfohTumYkpb0rk05EYQ0dsWSNzWH\nXDH/kD6ayNq+fscnS8g+59onzvfhJ0bq6wJBAKNFkDEHenWY4js481sauvEgBVnb\nOMvSv/emLHQ39cVfNbhPHRzN2rWPe/CbZtO8GmJFSS/FyBZ9a+P1uryZLAECQAaw\nApZ12s25b0yj9KkIhbU05hqGokZ+eKBeLpKELcvPHSL88wMbStTfqxUed5ymjStf\n1kVbcFOB9fsBLTvP0hkCQFCON0l1VjFli+vqfN0lypgIqCf85V6FZFN19creGCCd\n76pX/X2FIBbUSDN1z48SM5I/RKdCkTx7FY+509q2Mek=\n-----END RSA PRIVATE KEY-----")obj["X-Signature"] = t.sign(s, CryptoJS.SHA256, "sha256")obj["verify"] = function(e) {return CryptoJS.HmacSHA256(e, "dsa_secret_key_2025").toString()}(s);obj["data"] = u(s + "_param");return obj
}
至此,JS 逆向的核心代码已经完成。接下来,我们就可以进行请求测试,验证生成的参数是否正确。
二、请求测试
完整代码实现:
# -*- coding: utf-8 -*-
"""
@File : t23.py
@Author : bb_bcxlc
@Date : 2025-10-22 21:28
@Blog : https://blog.csdn.net/xw1680
@Tool : PyCharm
@Desc :
"""
import timeimport requests
import execjsheaders = {"accept": "application/json, text/plain, */*","accept-language": "zh-CN,zh;q=0.9","user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36","x-auth-key": "","x-signature": ""
}
cookies = {"sessionid": "写你自己的sessionid"
}
url_template = "https://www.spiderdemo.cn/authentication/api/fsymmetry_challenge/page/{}/"
ctx = execjs.compile(open('./t23.js', 'r', encoding='utf-8').read())
sum_ = 0for i in range(1, 101):timestamp = int(time.time() * 1000)result = ctx.call("encrypt", i, timestamp)headers["x-auth-key"] = result["X-Auth-Key"]headers["x-signature"] = result["X-Signature"]data = result["data"]verify = result["verify"]params = {"challenge_type": "fsymmetry_challenge","data": data,"verify": verify,"t": f"{timestamp}"}response = requests.get(url_template.format(i), headers=headers, cookies=cookies, params=params)sum_ += sum(response.json().get("page_data"))print(response.text)print("100页的和为: ", sum_)
三、视频讲解
https://www.bilibili.com/video/BV1CJshzTEix/?spm_id_from=333.1387.homepage.video_card.click&vd_source=aea7edccf28831a3bbbb6e836a563988
感谢 spiderdemo 提供本次练习用的靶场环境。若需实操练习,请访问 (spiderdemo)。
