目标地址:红人点集

模拟登录返回

正常与非正常登录响应

登录API及负载示例:

POST https://user.hrdjyun.com/wechat/phonePwdLogin HTTP/1.1
content-type: application/json

{
  "phoneNum": "18912345678",
  "pwd": "25f9e794323b453885f5181f1b624d0b",
  "t": 1669724122701,
  "tenant": 1,
  "sig": "6ae516c833cd58624d42f3351edef581"
}

由于逆向目的在于,破解并生成加密参数,以正常请求API,因此以下为正常响应示例:

{status: 3, message: "手机号还未注册", data: null}

非正常响应:

{status: 1, message: "参数错误", data: null}

XHR断点

直接开始使用XHR断点,根据栈查找指定的代码区域:
XHR

涉及到pwd参数加密代码:

Object(S["a"])(t.loginForm.password)

对比加密结果:

>Object(S["a"])('1')
<'c4ca4238a0b923820dcc509a6f75849b'

与在线工具生成一致,为MD5加密

在本地我们使用以下代码代替,免于扣代码:

const crypto = require('crypto-js')

function md5(text){
    return crypto.MD5(text).toString()
}

现在再来锁定sig(通过直接搜索事半功倍),找到以下代码:

var l = Object(g["a"])(P(S(e)));
            return Object.assign(e, {
                sig: l
            }),

通过控制台调用:

> Object(g["a"])('1')
< 'c4ca4238a0b923820dcc509a6f75849b'

可以发现依然是MD5加密

扣代码:

function P(n) {
    var e = []
      , t = "";
    for (var a in n)
        e.push(n[a]);
    for (var i = 0; i < e.length; i++)
        t += e[i] + "";
    return t += "JzyqgcoojMiQNuQoTlbR5EBT8TsqzJ",
    t
}

function S(n) {
    for (var e = Object.keys(n).sort(), t = {}, a = 0; a < e.length; a++)
        t[e[a]] = n[e[a]];
    return t
}

function md5(text){
    return crypto.MD5(text).toString()
}

function getSig(e){
    return md5(P(S(e)))
}

基本不需要处理...

所以包装一下的代码为:

const crypto = require('crypto-js')

function P(n) {
    var e = []
      , t = "";
    for (var a in n)
        e.push(n[a]);
    for (var i = 0; i < e.length; i++)
        t += e[i] + "";
    return t += "JzyqgcoojMiQNuQoTlbR5EBT8TsqzJ",
    t
}

function S(n) {
    for (var e = Object.keys(n).sort(), t = {}, a = 0; a < e.length; a++)
        t[e[a]] = n[e[a]];
    return t
}

function md5(text){
    return crypto.MD5(text).toString()
}

function getSig(e){
    return md5(P(S(e)))
}

function getForm(account,pswd){
    let form = {
        phoneNum: "18912345678",
        pwd: md5(pswd),
        t: new Date().getTime().toString(),
        tenant: 1
    }



    form.sig = getSig(form)

    return form
}

// 测试
console.log(getForm('18912345678','123456'))

后续测试

使用Python测试一下:

import os
import execjs
import requests
import json

os.environ["NODE_PATH"] = os.path.join('../' + "/node_modules")


def get_form(account: str, pswd: str) -> dict:
    with open('./hrdj.js') as f:
        js_code = f.read()

    js = execjs.compile(js_code)
    return js.call('getForm', account, pswd)


def main(account:str,pswd:str)->dict:
    api = 'https://user.hrdjyun.com/wechat/phonePwdLogin'
    header = {
        "Accept": "application/json, text/plain, */*",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Content-Length": "137",
        "Content-Type": "application/json",
        "Host": "user.hrdjyun.com",
        "Origin": "https://www.hh1024.com",
        "Pragma": "no-cache",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56"
    }

# 注意使用json.dumps()
    res = requests.post(url=api,data=json.dumps(get_form(account,pswd)),headers=header)

    res_json = res.json()

    return res_json

if __name__ == '__main__':
    print(main('18912345678','123456'))

输出结果:

{'status': 3, 'message': '手机号还未注册', 'data': None}

模拟验证成功

商品目录API加密参数

API:https://ucp.hrdjyun.com:60359/api/dy

商品目录

其中token是登录后返回,因此特别关注 sign

XHR断点

同理,直接尝试使用XHR断点定位
定位

下断点再请求一遍发现为此处,开始扣代码:

function E(n, e) {
    return k("param=" + JSON.stringify(n) + "&timestamp=" + e + "&tenant=1&salt=" + C)
}

function getSign(params){
    return E(params,new Date().getTime())
}

其中测试k(),发现其是标准SHA-256加密函数
SHA256加密

我们使用crypto.SHA256().toString()代替,最终代码为:

function k(text){
    return crypto.SHA256(text).toString()
}

function E(n, e) {
    return k("param=" + JSON.stringify(n) + "&timestamp=" + e + "&tenant=1&salt=" + C)
}

function getSign(params){
    return E(params,new Date().getTime())
}

这里还有一个C未解决,控制台输出为:

'kbn%&)@<?FGkfs8sdf4Vg1*+;`kf5ndl$'

相关代码:

 var C = z();
function z() {
    for (var n = Object(s["a"])().concat(Object(A["a"])(), Object(h["a"])(), Object(p["a"])(), Object(f["a"])(), Object(m["b"])()), e = "", t = 0; t < n.length; t++)
    e += String.fromCharCode(parseInt(n[t], 16));
    return e
}

但是,经过多次重新请求发现,C始终不变,所以暂时以固定值处理(如果后续无法爬取,再来处理),因此最终代码为:

function k(text){
    return crypto.SHA256(text).toString()
}

function E(n, e) {
    return k("param=" + JSON.stringify(n) + "&timestamp=" + e + "&tenant=1&salt=" + 'kbn%&)@<?FGkfs8sdf4Vg1*+;`kf5ndl$')
}

function getSign(params){
    return E(params,new Date().getTime())
}

请求一次,手动生成参数对比:
参数生成对比

参数一致