宏翼平台登录参数逆向

文章目录

声明

本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请私信我立即删除!

目标站点:https://file.sztbhz.com/

目标接口:https://file.sztbhz.com/Account/LoginResult

逆向目标:请求体中的 userNamepassWord 加密逻辑

这次目标看起来像一个很典型的登录参数逆向:账号密码不是明文提交,接口名也很直接,叫 /Account/LoginResult

这类任务的重点不是"把页面点开看看",而是搞清楚三件事:

  1. 参数在哪里被加工?
  2. 加工逻辑是不是依赖浏览器环境?
  3. 协议请求能不能脱离浏览器稳定跑?

最后的结论有点朴素,甚至朴素得让人想给前端算法倒杯茶:它不是 AES,不是 RSA,也不是一套 JSVMP 迷宫,而是一个固定 key 的 XOR 编码。

不过,简单不代表可以拍脑袋,下面来复盘一下。

一、先看请求

首页加载后,主要资源包括:

  • jquery-3.6.3.min.js
  • bootstrap
  • vue3
  • element-plus
  • vant
  • kmui.js
  • lucas-common-bundle.js
  • 微信扫码登录脚本 wxLogin.js

页面上同时存在微信扫码登录和账号密码登录。

我们关心账号密码登录区域:

html 复制代码
<input type="text" id="UserName" placeholder="用户名" />
<input type="Password" id="Password" placeholder="密码" />
<input type="button" onclick="login()" value="登录" />

看到 onclick="login()" 基本就能确定入口函数在页面内联脚本里。这个时候不要急着全局搜索 AESRSA,先找业务入口更高效。

搜索关键词:

text 复制代码
LoginResult
userName
passWord
login()

很快定位到核心提交代码:

javascript 复制代码
function login() {
    var secKey = 147;
    var userName = $("#UserName").val();
    var passWord = $("#Password").val();

    $.post("/Account/LoginResult", {
        userName: Encry(userName, secKey),
        passWord: Encry(passWord, secKey)
    }, function (data) {
        loginDone(data);
    })
}

这段代码给了两个关键信息:

  • secKey 是固定值 147
  • userNamepassWord 都调用同一个 Encry

这时已经能闻到"轻量编码"的味道了。

二、加密函数真身:XOR + hex

继续往上看,找 Encry 定义:

javascript 复制代码
function StringToByte(str) {
    var re = [], idx;
    for (var i = 0; i < str.length; i++) {
        idx = str.charCodeAt(i);
        if (idx & 0xff00) {
            re.push(idx >> 8);
            re.push(idx & 0xff);
        } else {
            re.push(idx);
        }
    }
    return re;
}

function Encry(str, key) {
    var data = StringToByte(str)
    var res = []
    for (var i = 0; i < data.length; i++) {
        var n = data[i] ^ key
        res.push(n.toString(16))
    }
    return res.join('')
}

算法流程非常直白:

  1. 遍历字符串。
  2. charCodeAt(i) 取 UTF-16 code unit。
  3. 如果 code unit 大于 0xff,拆成高字节和低字节。
  4. 每个字节和固定 key 147 做 XOR。
  5. 每个结果转十六进制字符串。
  6. 拼接成最终密文。

用一句话说:

text 复制代码
JS 字符 -> 字节数组 -> byte ^ 147 -> hex 拼接

这不是加密界的钢铁侠,顶多算穿了件雨衣。但对接口来说,只要服务端这么验,我们就必须完全复现。

三、不要忽略 JS 字符模型

这个算法最容易踩坑的地方不在 XOR,而在 charCodeAt

JavaScript 字符串按 UTF-16 code unit 处理。中文字符会被拆成两个字节,例如:

javascript 复制代码
StringToByte("测试")
// [109, 75, 139, 213]

所以 Python 不能简单用:

python 复制代码
"测试".encode("utf-8")

因为 UTF-8 下 "测试" 是:

text 复制代码
e6 b5 8b e8 af 95

而前端逻辑要的是 UTF-16 code unit 拆出来的:

text 复制代码
6d 4b 8b d5

因此 Python 里要按 utf-16-be 处理,模拟 JS 的 charCodeAt

python 复制代码
def string_to_byte(value: str) -> list[int]:
    raw = value.encode("utf-16-be", "surrogatepass")
    result = []

    for index in range(0, len(raw), 2):
        code_unit = (raw[index] << 8) | raw[index + 1]
        if code_unit & 0xFF00:
            result.append(code_unit >> 8)
            result.append(code_unit & 0xFF)
        else:
            result.append(code_unit)

    return result

这一步是复现一致性的关键。否则 ASCII 账号能对上,一遇到中文用户名、特殊字符就翻车,排查时还容易误以为服务端在"玄学风控"。

四、样本对照:静态结论必须动态验证

静态代码看懂只是第一步。真正交付前,必须做样本对照。

浏览器侧执行结果:

text 复制代码
admin  -> f2f7fefafd
123456 -> a2a1a0a7a6a5
测试    -> fed81846
a测    -> f2fed8

Python 侧复现:

python 复制代码
SEC_KEY = 147

def encry(value: str, key: int = SEC_KEY) -> str:
    return "".join(format(item ^ key, "x") for item in string_to_byte(value))

对照结果一致:

text 复制代码
'admin'  [97, 100, 109, 105, 110]    f2f7fefafd
'123456' [49, 50, 51, 52, 53, 54]    a2a1a0a7a6a5
'测试'   [109, 75, 139, 213]          fed81846
'a测'    [97, 109, 75]                f2fed8

到这里,参数算法已经闭环。

五、真实请求体是什么样?

使用页面上下文直接触发 login(),并 hook jQuery.post,可以拿到真实提交数据。

测试账号:

text 复制代码
username = codex_probe_user
password = codex_probe_pass

浏览器真实请求体:

text 复制代码
userName=f0fcf7f6ebcce3e1fcf1f6cce6e0f6e1
passWord=f0fcf7f6ebcce3e1fcf1f6cce3f2e0e0

响应:

text 复制代码
账号或密码不正确

这个响应很重要。它说明请求已经到达业务层,而不是被网关、WAF、CSRF 校验或参数格式拦截。

爬虫工程里有个朴素判断:

text 复制代码
能拿到"账号或密码不正确",通常比拿到 200 HTML 更值得高兴。

因为前者是业务服务在说话,后者可能只是防护系统给你塞了一份"请先证明你像浏览器"的试卷。

六、普通 requests 为什么不行?

直接用 Python requests 访问时,GET 首页可以拿到 acw_tc

text 复制代码
acw_tc=...

但 POST 登录接口时,返回的不是业务文本,而是一段阿里云 WAF challenge HTML:

html 复制代码
<meta name="aliyun_waf_aa" ...>
<meta name="aliyun_waf_bb" ...>

这说明参数加密没问题,卡点在协议层指纹。常见差异包括:

  • TLS ClientHello 指纹
  • HTTP/2 行为
  • header 顺序和细节
  • 浏览器请求上下文
  • WAF cookie 校验链路

这里不要把问题误判成"加密还没还原"。如果同一 payload 在浏览器里返回 账号或密码不正确,而 requests 返回 WAF HTML,优先怀疑协议指纹。

最终使用 curl_cffi

python 复制代码
from curl_cffi import requests

session = requests.Session(impersonate="chrome")

它仍然是纯 Python 调用,不依赖浏览器,不执行页面 JS,只是在 TLS/HTTP 指纹上更接近真实 Chrome。

七、最终 Python 实现

核心代码如下:

python 复制代码
from typing import Dict, List

from curl_cffi import requests


BASE_URL = "https://file.sztbhz.com"
LOGIN_URL = f"{BASE_URL}/Account/LoginResult"
SEC_KEY = 147


def string_to_byte(value: str) -> List[int]:
    raw = value.encode("utf-16-be", "surrogatepass")
    result: List[int] = []

    for index in range(0, len(raw), 2):
        code_unit = (raw[index] << 8) | raw[index + 1]
        if code_unit & 0xFF00:
            result.append(code_unit >> 8)
            result.append(code_unit & 0xFF)
        else:
            result.append(code_unit)

    return result


def encry(value: str, key: int = SEC_KEY) -> str:
    return "".join(format(item ^ key, "x") for item in string_to_byte(value))


def build_login_payload(username: str, password: str) -> Dict[str, str]:
    return {
        "userName": encry(username),
        "passWord": encry(password),
    }


def login(username: str, password: str, timeout: int = 20) -> str:
    session = requests.Session(impersonate="chrome")
    session.headers.update(
        {
            "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/148.0.0.0 Safari/537.36"
            ),
        }
    )

    session.get(BASE_URL + "/", timeout=timeout)

    headers = {
        "Accept": "*/*",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "Origin": BASE_URL,
        "Referer": BASE_URL + "/",
        "X-Requested-With": "XMLHttpRequest",
    }
    response = session.post(
        LOGIN_URL,
        data=build_login_payload(username, password),
        headers=headers,
        timeout=timeout,
    )
    response.raise_for_status()
    return response.text

调用方式:

python 复制代码
import main

payload = main.build_login_payload("admin", "123456")
print(payload)

result = main.login("admin", "123456")
print(result)

输出 payload:

python 复制代码
{
    "userName": "f2f7fefafd",
    "passWord": "a2a1a0a7a6a5"
}

对比浏览器一致,ojbk。

八、总结

这次逆向不复杂,但非常适合作为爬虫工程里的标准小案例:

  • 入口清晰:login()
  • 参数清晰:userNamepassWord
  • 算法清晰:StringToByte + XOR 147 + hex
  • 坑点明确:JS 字符模型和 WAF 协议指纹
  • 结果可验证:伪账号返回业务层 账号或密码不正确

如果把逆向过程比作查案,这次不是密室杀人,而是门口贴了张纸条:

text 复制代码
钥匙在花盆底下,记得用 UTF-16 拿。

真正的工程价值不在于算法多炫,而在于把每一层都拆清楚:前端怎么生成参数、接口怎么接收、服务端怎么响应、防护层在哪里插手。这样写出来的爬虫代码才不是"一次性脚本",而是能维护、能解释、能排障的工程组件。

相关推荐
Amo Xiang7 小时前
申万宏源证券新闻中心 —— AES/ECB 响应解密(摩斯电码派生密钥)
js逆向·python爬虫·逆向工程·aes加密·响应解密
冰履踏青云13 小时前
某音x-tt-session-dtrait 算法逆向复盘
js逆向·session-dtrait
如烟花的信页1 天前
*花顺cookie逆向分析
javascript·爬虫·python·js逆向
Amo Xiang2 天前
SpiderDemo 第1题:请求头检测挑战 —— Disable cache 缓存头与请求特征差异
js逆向·爬虫逆向·spiderdemo·tls指纹·请求头检测·disable cache
Amo Xiang2 天前
全国新书网 —— AES/CBC 双向加解密
js逆向·python爬虫·cryptojs·前端加密·aes加解密·pycryptodome
如烟花的信页2 天前
加速乐cookie逆向分析
javascript·爬虫·python·js逆向
Amo Xiang2 天前
福建公共资源交易平台 —— MD5 签名 + AES 响应解密
js逆向·python爬虫·md5·cryptojs·前端加密·axios拦截器·aes解密
Amo Xiang3 天前
JS 逆向系统进阶路线:专栏总纲与文章导航
javascript·js逆向·前端加密·爬虫逆向·反爬虫
Amo Xiang3 天前
新华社客户端 —— 3DES 双向加解密
js逆向·python爬虫·cryptojs·3des·前端加密