调研时间:2026-09-17 目标:确认账号密码登录的技术细节 工作目录:
D:\传奇
| 关键问题 | 结论 | 难度 |
|---|---|---|
| 1. 签名算法 | HmacSHA1(不是 SHA256),key 在前端 JS 硬编码 k1ebbkxlojjdbnffnp3w2ykk3p625xnn |
✅ 低 |
| 2. 密码是否加密 | 明文传输(POST body 直接是 password: "你的密码") |
✅ 低 |
| 3. 登录接口 | POST https://user-sdkv2.dhsf.996db.cn/api/user/v5w/login |
✅ 低 |
| 4. Token 存储 | 返回的 data.token 存到 sessionStorage |
✅ 低 |
| 5. 业务请求鉴权 | 业务接口通过 query string 传 ?GET-X-Token=<token>(不是 header) |
✅ 低 |
| 6. 滑块触发条件 | jiyanStatus == 1 时才需要极验 GT4,条件触发,不是每次都要 |
✅ 中 |
整体评估:如果你只是做"账号密码登录 + session 复用",滑块只有在新设备/新 IP 才会触发。日常使用下,滑块几乎不构成门槛。
keySort 函数(前端源码 1637-1648 行):
keySort: function(val) {
var arr = [];
for (key in val) arr.push(key);
arr = arr.sort();
var signStr = "";
arr.forEach(function(item, index) {
signStr += item + "=" + (val[item] || val[item] == 0 ? val[item] : "")
+ (arr.length - 1 <= index ? "" : "&");
});
return signStr;
}
Hmacstr 函数(前端源码 1651-1656 行):
Hmacstr: function(e, key) {
var result = CryptoJS.HmacSHA1(e, key);
var sig = result.toString();
sig = encodeURI(sig);
return sig;
}
关键点:
k1ebbkxlojjdbnffnp3w2ykk3p625xnn(前端硬编码,可在任何客户端复用)sorted() 结果可能不同,需测试)Python 复现代码:
import hmac, hashlib, urllib.parse
KEY = "k1ebbkxlojjdbnffnp3w2ykk3p625xnn"
def key_sort(params: dict) -> str:
# JS 默认 sort() 是按 UTF-16 code unit 排序
keys = sorted(params.keys()) # Python str 排序和 JS Array.sort() 一致
return "&".join(f"{k}={params[k]}" for k in keys)
def hmac_str(sign_str: str, key: str) -> str:
sig = hmac.new(key.encode(), sign_str.encode(), hashlib.sha1).hexdigest()
return urllib.parse.quote(sig, safe="")
请求:
POST https://user-sdkv2.dhsf.996db.cn/api/user/v5w/login
Content-Type: application/json
Body(JSON,来自前端源码 956-981 行):
{
"username": "你的账号",
"password": "你的明文密码",
"game_id": "<URL参数>",
"vType": "csvKey",
"appid": "<URL参数>",
"promote_id": <URL参数,数字>,
"_device_info": "<encodeURIComponent后的JSON字符串>",
"lot_number": "<极验通过时附加>",
"captcha_output": "<极验通过时附加>",
"pass_token": "<极验通过时附加>",
"gen_time": "<极验通过时附加>",
"sign": "<HmacSHA1(keySort(...), key) 后的 URL-encoded hex>"
}
响应(成功):
{
"code": 200,
"msg": "成功",
"data": {
"token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"uid": "12345678",
"username": "你的账号",
"mobile": "138****1234",
"is_set_password": true,
"age_status": 1
}
}
前端用 query string 传 token,不是 header(前端源码 1223、1462、1542 行):
GET https://user-sdkv2.dhsf.996db.cn/api/user/identification?GET-X-Token=<token>
注:前端 axios 拦截器(472-487 行)全部被注释掉了,所以不会自动加 X-Token header。每个接口都要手动在 URL 上加 ?GET-X-Token=。
Session 有效性探测建议:
GET /api/user/getUserInfo?GET-X-Token=<token> 或类似的轻量接口code != 200 = 失效触发流程(前端源码 690-700 行):
// 进入页面就调
axios.post(registerUrl + "/api/user/appParam", params)
.then(res => {
this.captchaId = res.data.data.captcha_id;
this.jiyanStatus = res.data.data.jiyan_status; // 1=需要滑块,0=不需要
initGeetest4({captchaId, product: 'bind'}, callback);
});
关键点:
jiyanStatus 是动态下发的,不是写死的_device_info 字段(来自 Buryingpoint.js 的 Escalation 函数):
function Escalation(adkappid, sdkchannel, sdkId) {
let data = {
net_type, app_ver, appid, channel, sdk_ver,
device: {
type, model, id, id1, id2, id3, os,
width, height, lang, net_type
}
}
return encodeURIComponent(JSON.stringify(data))
}
注意:_device_info 中有些字段直接复用了 URL 参数(sdkappid、sdkchannel、sdkid),其他从浏览器环境取。这意味着:
width/height/os 等用 window.innerWidth 等浏览器 API_device_info 异常触发风控| 字段 | 来源 | 说明 |
|---|---|---|
lot_number |
极验 load 接口返回 | 任务流水号 |
captcha_output |
captcha.getValidate() 返回 |
极验 GT4 验证输出 |
pass_token |
captcha.getValidate() 返回 |
验证通过凭证 |
gen_time |
captcha.getValidate() 返回 |
验证时间戳 |
没有过滑块时这四个字段不传也能登录(如果 jiyanStatus=0)。
┌──────────────────────────────────────────────┐
│ Step 1:requests 模拟登录(仅做签名) │
│ - 拼 _device_info │
│ - keySort + HmacSHA1 签名 │
│ - POST /api/user/v5w/login │
│ - 不处理滑块(jiyanStatus=0 时就够了) │
├──────────────────────────────────────────────┤
│ Step 2:保存 token 到本地(加密) │
│ - 路径:%APPDATA%\chuanshi_login\session.enc│
│ - 用本地机器指纹作为加密 key │
│ - 存:token, uid, username, expire_estimate│
├──────────────────────────────────────────────┤
│ Step 3:每次使用: │
│ - 读本地 session │
│ - 调业务接口试 ?GET-X-Token=xxx │
│ - 有效 → 继续 │
│ - 失效 → 走 Step 1 重登 │
└──────────────────────────────────────────────┘
如果服务端风控变严,jiyanStatus 总是 1,再考虑:
| 风险 | 概率 | 影响 | 缓解 |
|---|---|---|---|
_device_info 异常触发风控 |
中 | 登录一直要滑块 | 第一次手动浏览器登录抓一份真实值,后续复用 |
| token 有效期短(天级别) | 中 | 频繁触发重登 | 加探测逻辑,自动重登 |
| 签名 key 被服务端改 | 低 | 脚本失效 | 程序启动时做一次接口校验 |
| 极验 GT4 算法升级 | 低 | 重登失败 | 走 Playwright 兜底 |
| 文件 | 用途 |
|---|---|
debug_logs/login_page.html |
登录页完整源码(已抓取到本地,1676 行) |
debug_logs/buryingpoint.js |
_device_info 的生成逻辑(已抓取) |
gt4.js / validateLogin.js / crypto.js / md5.js |
已分析过,逻辑清楚 |
login.py 跑通一次"不触发滑块"场景下的登录