js逆向-某某牛数据请求参数加密和返回数据解密

声明:本篇文章仅用于知识分享

实战网址:烯牛数据 · 新一代科创数据引擎

请求参数加密

  1. 访问网址,往下翻翻,可以看到触发了如下的数据包,请求参数进行了加密。

    image

  2. 全局搜索list_industries_by_sort地址,有四处,都位于同一个文件中。

    image

    随便点一个看看,可以看到有payload关键字。

    image

    打断点,刷新界面触发断点,一步一步往下调试,可以看到payload传给了j.a.fetch()函数。

    image

  3. 定位j.a.fetch()函数。

    image

    打断点,跳转进来。可以看到明显给payloadsig赋值的语句。

    image

    最关键的就是如下这行代码。
var f = Object(u.c)(Object(u.d)(JSON.stringify(s.payload))), p = Object(u.e)(f);

涉及到一个变量和三个函数,一个一个看。
(1)变量s.payload:直接输出看即可。
 

image

(2)函数Object(u.c):控制台输出,定位。
 

image

(3)函数Object(u.d):控制台输出,定位。
 

image

(4)函数Object(u.e):控制台输出,定位。
 

image

上面三个函数的代码可以不用看,直接调用js代码就可以了。
4. 目前的js代码如下。

// 定义请求的 payload
const s = {
    "payload": {
        "sort": 1,
        "start": 20,
        "limit": 20
    }
};

// Base64 编码表
const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// 加密密钥
const _p = "your_secret_key"; // 需要替换为实际密钥

/**
 * Base64 编码函数
 * @param {string} e - 需要进行 Base64 编码的字符串
 * @returns {string} - Base64 编码后的字符串
 */
function base64Encode(e) {
    if (e == null) return null;

    let c = "";
    let t, n, r, o, i, a, u;
    let l = 0;

    while (l < e.length) {
        o = (t = e.charCodeAt(l++)) >> 2;
        i = ((3 & t) << 4) | ((n = e.charCodeAt(l++)) >> 4);
        a = ((15 & n) << 2) | ((r = e.charCodeAt(l++)) >> 6);
        u = 63 & r;

        if (isNaN(n)) {
            a = u = 64;
        } else if (isNaN(r)) {
            u = 64;
        }

        c += _keyStr.charAt(o) + _keyStr.charAt(i) + _keyStr.charAt(a) + _keyStr.charAt(u);
    }
    
    return c;
}

/**
 * 异或加密函数
 * @param {string} e - 需要加密的字符串
 * @returns {string} - 加密后的字符串
 */
function xorEncrypt(e) {
    if ((e = _u_e(e)) == null) return null;

    let t = "";
    for (let n = 0; n < e.length; n++) {
        let r = _p.charCodeAt(n % _p.length);
        t += String.fromCharCode(e.charCodeAt(n) ^ r);
    }
    
    return t;
}

/**
 * 计算签名
 * @param {string} e - 需要计算 MD5 的字符串
 * @returns {string} - 计算后的 MD5 并转换为大写
 */
function generateSignature(e) {
    return md5(e + _p).toUpperCase();
}

// 执行加密流程
const payloadString = JSON.stringify(s.payload); // 转换为 JSON 字符串
const encryptedData = base64Encode(xorEncrypt(payloadString)); // 先 XOR 加密,再 Base64 编码
const signature = generateSignature(encryptedData); // 计算签名

// 输出加密后的数据和签名
console.log("加密后的数据:", encryptedData);
console.log("签名:", signature);

运行提示_u_e is not defined,去找一下补到代码中。
 

image

运行提示_p is not defined,去找_p
 

image

运行提示md5 is not defined,这里需要导入crypto-js库,可以通过npm install crypto-js命令下载。

const CryptoJS = require("crypto-js");

/**
 * 计算字符串的 MD5 签名
 * @param {string} data - 需要计算签名的字符串
 * @param {string} secretKey - 用于加密的密钥
 * @returns {string} - 计算出的 MD5 签名(大写)
 */
function generateSignature(data, secretKey) {
    if (!data || !secretKey) {
        throw new Error("数据和密钥不能为空!");
    }
    
    return CryptoJS.MD5(data + secretKey).toString().toUpperCase();
}

// 示例调用
const _p = "your_secret_key"; // 请替换为实际密钥
const testData = "example_data";
const signature = generateSignature(testData, _p);

console.log("签名:", signature);

运行提示CryptoJS.MD5(...).toUpperCase is not a function,是因为在转换为大写之前需要先用toString函数转为字符串。
完整代码如下:

// query_parameter_encrypt.js
const CryptoJS = require("crypto-js");

// 请求参数
const s = {
    "payload": {
        "sort": 1,
        "start": 0,
        "limit": 20
    }
};

// Base64 编码表
const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

// 加密密钥
const _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O";

/**
 * 处理字符串的 Unicode 转换
 * @param {string} e - 需要转换的字符串
 * @returns {string} - 转换后的字符串
 */
function unicodeEncode(e) {
    if (e == null) return null;

    e = e.replace(/\r\n/g, "\n");
    let t = "";

    for (let n = 0; n < e.length; n++) {
        let r = e.charCodeAt(n);
        if (r < 128) {
            t += String.fromCharCode(r);
        } else if (r > 127 && r < 2048) {
            t += String.fromCharCode((r >> 6) | 192);
            t += String.fromCharCode((r & 63) | 128);
        } else {
            t += String.fromCharCode((r >> 12) | 224);
            t += String.fromCharCode(((r >> 6) & 63) | 128);
            t += String.fromCharCode((r & 63) | 128);
        }
    }

    return t;
}

/**
 * Base64 编码
 * @param {string} e - 需要进行 Base64 编码的字符串
 * @returns {string} - Base64 编码后的字符串
 */
function base64Encode(e) {
    if (e == null) return null;

    let c = "";
    let t, n, r, o, i, a, u;
    let l = 0;

    while (l < e.length) {
        o = (t = e.charCodeAt(l++)) >> 2;
        i = ((t & 3) << 4) | ((n = e.charCodeAt(l++)) >> 4);
        a = ((n & 15) << 2) | ((r = e.charCodeAt(l++)) >> 6);
        u = r & 63;

        if (isNaN(n)) {
            a = u = 64;
        } else if (isNaN(r)) {
            u = 64;
        }

        c += _keyStr.charAt(o) + _keyStr.charAt(i) + _keyStr.charAt(a) + _keyStr.charAt(u);
    }

    return c;
}

/**
 * XOR 加密
 * @param {string} e - 需要加密的字符串
 * @returns {string} - 加密后的字符串
 */
function xorEncrypt(e) {
    if ((e = unicodeEncode(e)) == null) return null;

    let t = "";
    for (let n = 0; n < e.length; n++) {
        let r = _p.charCodeAt(n % _p.length);
        t += String.fromCharCode(e.charCodeAt(n) ^ r);
    }

    return t;
}

/**
 * 计算签名
 * @param {string} e - 需要计算 MD5 的字符串
 * @returns {string} - 计算后的 MD5 并转换为大写
 */
function generateSignature(e) {
    return CryptoJS.MD5(e + _p).toString().toUpperCase();
}

/**
 * 生成加密后的 payload 和签名
 * @param {Object} s - 需要加密的对象
 * @returns {Array} - [加密后的 payload, 签名]
 */
function generate(s) {
    const payload = base64Encode(xorEncrypt(JSON.stringify(s.payload)));
    const sign = generateSignature(payload);
    return [payload, sign];
}

// 输出加密后的数据和签名
console.log(generate(s));

运行结果如下。
 

image

成功,接下来就是编写python代码获取数据了。

import requests
import json
import execjs

# 目标请求的 URL
URL = "https://www.xiniudata.com/api2/service/x_service/person_industry_list/list_industries_by_sort"

# 请求头(Content-Type 必须设置,否则无法获取数据)
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
    "Content-Type": "application/json"
}

# 请求的参数
REQUEST_PAYLOAD = {
    "payload": {
        "sort": 1,
        "start": 0,
        "limit": 20
    }
}

# 读取 JavaScript 代码并编译
with open("query_parameter_encrypt.js", mode="r", encoding="utf-8") as file:
    js_code = file.read()
    js_context = execjs.compile(js_code)

# 生成加密的 payload 和签名
payload, sig = js_context.call("generate", REQUEST_PAYLOAD)

# 组织最终请求数据
DATA = {
    "payload": payload,
    "sig": sig,
    "v": 1
}

# 发送 POST 请求
response = requests.post(URL, headers=HEADERS, data=json.dumps(DATA))

# 解析并打印返回的 JSON 数据
try:
    response_data = response.json()
    print(json.dumps(response_data, indent=4, ensure_ascii=False))
except json.JSONDecodeError:
    print("返回数据格式错误:", response.text)

运行结果如下。

image

返回数据解密

  1. 一般来说对于返回数据的解密,常规操作是搜索interceptors,但是很可惜不适用于该网站。那就得想其他的办法。

    image

  2. 我们知道该数据包采用的是JSON格式数据,解密出来后肯定需要进行JSON解析,所以可以搜索JSON.parse关键字。

    image

    地方比较多,需要进行筛选。如果JSON.parse是该函数的第一行代码就铁定不是,因为是需要先解密再进行JSON解析的。点击几个,发现了一处跟加密逻辑很像的代码。(其实一般情况下加密逻辑和解密逻辑是写在一起的)

    image

  3. 打断点,一行一行调试。

    image

    可以看到s的值就是返回的数据,说明我们没找错地方。

    image

    y的值是明文了,所以解密逻辑肯定跟Object(u.a)Object(u.b)相关。
    找到这两个函数的定义。

    image

    image

  4. 接下来就跟加密逻辑一样了,把相关的js代码复制出来,把需要用到的变量和参数都补齐。
// response_data_decrypt.js

// Base64 编码表
const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// 解密密钥
const _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O";

/**
 * Base64 解码
 * @param {string} e - 需要解码的 Base64 字符串
 * @returns {string} - 解码后的字符串
 */
function base64Decode(e) {
    let t, n, r, o, i, a, u = "";
    let c = 0;

    e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); // 过滤无效字符

    while (c < e.length) {
        t = (_keyStr.indexOf(e.charAt(c++)) << 2) | ((o = _keyStr.indexOf(e.charAt(c++))) >> 4);
        n = ((o & 15) << 4) | ((i = _keyStr.indexOf(e.charAt(c++))) >> 2);
        r = ((i & 3) << 6) | (a = _keyStr.indexOf(e.charAt(c++)));

        u += String.fromCharCode(t);
        if (i !== 64) u += String.fromCharCode(n);
        if (a !== 64) u += String.fromCharCode(r);
    }

    return u;
}

/**
 * 处理 Unicode 解码
 * @param {string} e - 需要转换的字符串
 * @returns {string} - 解析后的 Unicode 字符串
 */
function unicodeDecode(e) {
    let t = "", n = 0, r, o, i;

    while (n < e.length) {
        r = e.charCodeAt(n);

        if (r < 128) {
            t += String.fromCharCode(r);
            n++;
        } else if (r > 191 && r < 224) {
            o = e.charCodeAt(n + 1);
            t += String.fromCharCode(((r & 31) << 6) | (o & 63));
            n += 2;
        } else {
            o = e.charCodeAt(n + 1);
            i = e.charCodeAt(n + 2);
            t += String.fromCharCode(((r & 15) << 12) | ((o & 63) << 6) | (i & 63));
            n += 3;
        }
    }

    return t;
}

/**
 * XOR 解密
 * @param {string} e - 需要解密的字符串
 * @returns {string} - 解密后的字符串
 */
function xorDecrypt(e) {
    let t = "";

    for (let n = 0; n < e.length; n++) {
        let r = _p.charCodeAt(n % _p.length);
        t += String.fromCharCode(e.charCodeAt(n) ^ r);
    }

    return unicodeDecode(t);
}

/**
 * 解密主函数
 * @param {string} e - 需要解密的 Base64 加密数据
 * @returns {string} - 解密后的数据
 */
function decrypt(e) {
    return xorDecrypt(base64Decode(e));
}

// 示例调用
const encryptedData = "返回的加密数据"; // 需要替换为实际加密数据
console.log(decrypt(encryptedData));

运行结果如下。
 

image

成功。
5. 写python代码,将加密逻辑和解密逻辑进行融合。

import json
import requests
import execjs
import subprocess
from functools import partial

# 设置 subprocess 默认编码为 UTF-8
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")

# 目标 URL
URL = "https://www.xiniudata.com/api2/service/x_service/person_industry_list/list_industries_by_sort"

# 请求头(Content-Type 必须设置,否则无法获取数据)
HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
    ),
    "Content-Type": "application/json"
}

# 需要加密的请求参数
REQUEST_PAYLOAD = {
    "payload": {
        "sort": 1,
        "start": 0,
        "limit": 20
    }
}

def load_js_file(filename):
    """
    读取 JavaScript 文件内容并编译
    :param filename: JavaScript 文件路径
    :return: 编译后的 execjs 上下文
    """
    with open(filename, mode="r", encoding="utf-8") as file:
        js_code = file.read()
        return execjs.compile(js_code)

# 1. 加密请求参数
encrypt_js = load_js_file("query_parameter_encrypt.js")
payload, sig = encrypt_js.call("generate", REQUEST_PAYLOAD)

# 2. 组织请求数据
request_data = {
    "payload": payload,
    "sig": sig,
    "v": 1
}

# 3. 发送 POST 请求
response = requests.post(URL, headers=HEADERS, data=json.dumps(request_data))

# 4. 解析返回的数据
try:
    response_json = response.json()
    encrypted_response_data = response_json.get('d', '')

    # 5. 解密返回数据
    decrypt_js = load_js_file("response_data_decrypt.js")
    decrypted_data = decrypt_js.call("decrypt", encrypted_response_data)

    # 6. 输出解密后的数据
    print(decrypted_data)

except json.JSONDecodeError:
    print("返回数据格式错误:", response.text)

运行结果如下。
 

image

跟页面上显示的对应。
 

image

大功告成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值