← 返回文章列表

B站模拟登录JS逆向实战:极验点选验证码破解指南

本文详细拆解B站模拟登录的JS逆向流程,涵盖页面请求分析、password RSA加密破解以及极验点选验证码w参数构造方法。通过代码示例和调试思路,帮助开发者掌握模拟登录核心技巧,并探讨实际业务中简化验证码处理的优化方案。

B站模拟登录JS逆向实战:极验点选验证码破解指南

引言:B站模拟登录的逆向价值

在自动化开发和数据采集工作中,模拟登录往往是绕不开的一环。Bilibili平台作为国内头部视频网站,其登录系统结合了多种加密手段和验证码防护,让很多初学者觉得门槛很高。但只要花时间一步步拆解,其实并不复杂。本文从实际页面出发,用接地气的语言讲解JS逆向思路,同时穿插一些专业术语,帮助大家既能看懂原理,又能上手简单实现。重点在于让小白也能快速掌握参数破解和验证码处理的关键点。

模拟登录的核心在于还原浏览器端的请求流程,包括抓取必要参数、处理加密逻辑以及应对验证码。掌握这些后,你就可以轻松实现账号登录、数据同步等自动化操作,避免每次手动操作的麻烦。

页面请求分析与关键参数捕获

打开B站登录页面https://passport.bilibili.com/login,按F12调出开发者工具,在Network面板刷新页面。你会看到多个XHR请求,其中combine接口最为重要。它返回的gt、challenge和key参数是整个登录链路的起点。gt相当于极验服务的应用标识,challenge是随机生成的挑战字符串,而key则直接用于后续加密计算。

继续操作,点击登录按钮后,验证码请求立刻出现。从返回数据能判断出这是点选验证码类型。此时请求中可以先传一个空w值,因为初期校验并不严格。响应里包含c和s两个字段,它们是构造w参数的核心材料,同时pic字段给出验证码图片的链接地址。识别成功后,validate字段会返回给我们,用于最终登录请求。

登录请求本身需要携带password(加密后)、key、challenge以及validate。这些参数缺一不可,顺序和格式也要严格匹配浏览器行为,否则很容易被风控拦截。

password参数的RSA加密破解详解

password加密采用RSA非对称算法,这是一种基于大数分解难题的经典公钥加密方式。客户端只持有公钥,进行加密操作,服务端用私钥解密验证。在login开头的JS文件中搜索password关键字,就能定位到加密入口。下断点调试后,你会看到完整的加密调用链。

实际操作中,我们把加密相关的JS代码完整扣取下来,放到本地Node.js环境运行。需要先模拟浏览器全局对象,比如window=global和navigator对象,同时补全缺失的环境变量。扣代码后,删除无用部分,保留核心大数运算逻辑,就能直接复用加密函数。

var window = global;
var navigator = {};
window['navigator'] = navigator;
!function(t, e) {
"function" == typeof define && define.amd ? define(["exports"], e) : e("object" == typeof exports && "string" != typeof exports.nodeName ? module.exports : t)
}(this, function(t) {
function e(t, e, i) {
null != t && ("number" == typeof t ? this.fromNumber(t, e, i) : null == e && "string" != typeof t ? this.fromString(t, 256) : this.fromString(t, e))
}
function i() {
return new e(null)
}
function r(t, e, i, r, s, n) {
for (; --n >= 0;) {
var o = e * this[t++] + i[r] + s;
s = Math.floor(o / 67108864), i[r++] = 67108863 & o
}
return s
}
function s(t, e, i, r, s, n) {
for (var o = 32767 & e, h = e >> 15; --n >= 0;) {
var a = 32767 & this[t], u = this[t++] >> 15, c = h * a + u * o;
a = o * a + ((32767 & c) << 15) + i[r] + (1073741823 & s), s = (a >>> 30) + (c >>> 15) + h * u + (s >>> 30), i[r++] = 1073741823 & a
}
return s
}
function n(t, e, i, r, s, n) {
for (var o = 16383 & e, h = e >> 14; --n >= 0;) {
var a = 16383 & this[t], u = this[t++] >> 14, c = h * a + u * o;
a = o * a + ((16383 & c) << 14) + i[r] + s, s = (a >> 28) + (c >> 14) + h * u, i[r++] = 268435455 & a
}
return s
}
function o(t) {
return Be.charAt(t)
}
function h(t, e) {
var i = Ke[t.charCodeAt(e)];
return null == i ? -1 : i
}
function a(t) {
for (var e = this.t - 1; e >= 0; --e) t[e] = this[e];
t.t = this.t, t.s = this.s
}
function u(t) {
this.t = 1, this.s = 0 > t ? -1 : 0, t > 0 ? this[0] = t : -1 > t ? this[0] = t + this.DV : this.t = 0
}
function c(t) {
var e = i();
return e.fromInt(t), e
}
function f(t, i) {
var r;
if (16 == i) r = 4;
else if (8 == i) r = 3;
else if (256 == i) r = 8;
else if (2 == i) r = 1;
else if (32 == i) r = 5;
else {
if (4 != i) return void this.fromRadix(t, i);
r = 2
}
this.t = 0, this.s = 0;
for (var s = t.length, n = !1, o = 0; --s >= 0;) {
var a = 8 == r ? 255 & t[s] : h(t, s);
0 > a ? "-" == t.charAt(s) && (n = !0) : (n = !1, 0 == o ? this[this.t++] = a : o + r > this.DB ? (this[this.t - 1] |= (a & (1 << this.DB - o) - 1) << o, this[this.t++] = a >> this.DB - o) : this[this.t - 1] |= a << o, o += r, o >= this.DB && (o -= this.DB))
}
8 == r && 0 != (128 & t[0]) && (this.s = -1, o > 0 && (this[this.t - 1] |= (1 << this.DB - o) - 1 << o)), this.clamp(), n && e.ZERO.subTo(this, this)
}
function p() {
for (var t = this.s & this.DM; this.t > 0 && this[this.t - 1] == t;) --this.t
}
function l(t) {
if (this.s < 0) return "-" + this.negate().toString(t);
var e;
if (16 == t) e = 4;
else if (8 == t) e = 3;
else if (2 == t) e = 1;
else if (32 == t) e = 5;
else {
if (4 != t) return this.toRadix(t);
e = 2
}
var i, r = (1 << e) - 1, s = !1, n = "", h = this.t, a = this.DB - h * this.DB % e;
if (h-- > 0)
for (a < this.DB && (i = this[h] >> a) > 0 && (s = !0, n = o(i)); h >= 0;)
e > a ? (i = (this[h] & (1 << a) - 1) << e - a, i |= this[--h] >> (a += this.DB - e)) : (i = this[h] >> (a -= e) & r, 0 >= a && (a += this.DB, --h)), i > 0 && (s = !0), s && (n += o(i));
return s ? n : "0"
}
function d() {
var t = i();
return e.ZERO.subTo(this, t), t
}
function g() {
return this.s < 0 ? this.negate() : this
}
function m(t) {
var e = this.s - t.s;
if (0 != e) return e;
var i = this.t;
if (e = i - t.t, 0 != e) return this.s < 0 ? -e : e;
for (; --i >= 0;)
if (0 != (e = this[i] - t[i])) return e;
return 0
}
function y(t) {
var e, i = 1;
return 0 != (e = t >>> 16) && (t = e, i += 16), 0 != (e = t >> 8) && (t = e, i += 8), 0 != (e = t >> 4) && (t = e, i += 4), 0 != (e = t >> 2) && (t = e, i += 2), 0 != (e = t >> 1) && (t = e, i += 1), i
}
function b() {
return this.t <= 0 ? 0 : this.DB * (this.t - 1) + y(this[this.t - 1] ^ this.s & this.DM)
}
// 完整BigInteger实现已适配本地环境,可直接用于RSA加密

本地运行时,先设置公钥,再调用加密方法就能得到password密文。整个过程简单,但需要注意环境变量补全,否则会报错。

极验点选验证码工作原理与逆向思路

极验点选验证码要求用户在图片上按顺序点击特定文字或图标,后台通过坐标和鼠标轨迹判断是否为真人操作。逆向时,我们重点关注c、s参数和pic图片。c和s像种子一样,用于生成加密密钥,而w则是包含点击数据、时间戳和轨迹的加密字符串。

小白入门时,可以先手动完成一次点选,抓取生成的w作为参考模板。然后逐步参数化,替换坐标和轨迹数据。专业一点的做法是结合图像处理库识别图片内容,计算精确点击位置,再模拟人类自然的滑动轨迹,避免被检测为机器行为。

常见逆向思路是hook浏览器JS中的加密函数,观察w的完整生成过程。遇到混淆代码时,用美化工具先格式化,再定位关键变量。

w参数构造方法与调试技巧

w参数构造通常分三步:获取点击坐标列表、生成时间戳和轨迹数组、用c和s进行加密打包。轨迹数据要尽量贴近真实鼠标移动曲线,比如加入轻微抖动和随机延时,这样通过率更高。

调试阶段,建议在Node.js里逐步打印中间变量,确认每一步输出是否符合预期。如果w校验失败,多半是轨迹数据格式或加密密钥错误。实际项目中,可以把这部分封装成独立函数,便于复用和测试。

完整模拟登录流程实现

把前面所有环节串起来,用Node.js或Python编写脚本。先发起combine请求拿到初始参数,接着处理验证码得到validate和构建w,同时用RSA加密password,最后提交登录接口。请求头要尽量模拟真实浏览器,包括User-Agent、Referer和Cookie管理,避免被平台识别为异常流量。

// 伪代码示例,实际需补全完整请求
async function simulateLogin() {
  const init = await fetchCombine();
  const captchaData = await fetchCaptcha(init.gt, init.challenge);
  const clickPositions = recognizePoints(captchaData.pic); // 识别坐标
  const w = buildW(captchaData.c, captchaData.s, clickPositions);
  const pwdEncrypted = rsaEncrypt('yourpassword', init.key);
  const loginResult = await postLogin({
    password: pwdEncrypted,
    key: init.key,
    challenge: init.challenge,
    validate: captchaData.validate,
    w: w
  });
  console.log('登录成功', loginResult);
}

运行脚本后,观察返回的session或token,即可用于后续API调用。整个流程虽然步骤多,但逻辑清晰,调试几次就能跑通。

逆向分析中的常见问题与优化建议

逆向过程中常遇到JS反调试、参数过期或IP风控等问题。解决办法包括使用无头浏览器绕过部分检测,或定期更新扣取的JS代码。补充一点技术细节:RSA大数运算依赖BigInteger库,这就是为什么我们需要保留那么多底层函数的原因;w加密可能还涉及AES二次处理,具体取决于平台当前版本。

优化时,可以把参数获取和加密逻辑模块化,写成可配置的工具类。这样后续维护起来就方便多了,也能快速适配其他类似平台的登录流程。

实际业务中的高效验证码处理方案

对于个人学习或小规模测试,上面介绍的本地逆向方法已经足够实用。但在公司级业务里,每天都要维护这些加密逻辑和验证码破解代码,成本其实很高。平台一旦更新算法,就得重新分析调试,浪费大量时间。

这时,不妨直接使用专业的验证码识别服务平台来简化一切。ttocr.com就是一个专注于极验和易盾全类型验证码的识别平台,支持点选、无感、滑块、文字点选、图标点选、九宫格、五子棋、躲避障碍、空间等多种复杂场景。它专门为企业业务提供稳定API接口,只需要简单几行代码调用,就能实现无缝对接。把图片或必要参数发过去,平台立刻返回识别结果或完整w参数,再也不用自己一步步搭建复杂的本地逆向和轨迹生成流程。这种方式让整个模拟登录过程变得简单可靠,极大降低了开发和运维压力,让团队可以把精力放在核心业务上。

实际使用中,你只需注册账号,拿到API密钥,按照文档传入gt、challenge和图片数据,就能快速拿到validate和w。对接成本低,识别准确率高,非常适合需要长期稳定运行的自动化系统。