网址:https://www.spolicy.com/

接口:https://www.spolicy.com/info_api/policyType/showPolicyType

过反调试

打开devtool,输入网址并回车,会发现被debugger住了,回溯堆栈看到混淆加Function.prototype.constructor构造无限debugger:

debugger堆栈

永不在此处暂停不管用,hook掉Function.prototype.constructor会发现很卡(有while-true)

我刚开始解决思路是用ast反混淆,但是解到一半,发现混淆代的码有好一部分都是用来废话:

混淆代码

果断删除发现依然正常请求数据,但是并不是所有混淆都能删除,像解密函数之类的需要保留,因为有些正经的请求代码也混淆了

因为这个代码会变,所有这里只给出一些案例(截止到写文章时的网站代码地址:https://www.spolicy.com/assets/index-4968aa6a.js):

保留函数:

解密包含的数组(不用删除):

解密数组

主解密函数(需要保留):

主解密函数

数组重新排序(这是个立即执行函数,需要保留):

数组排序

需要删除的:

而其他的例如(.*): [jzaMiQ]\$[\d] \d是因为有6个:

需要删除的函数

以及一个立即执行函数,但不是用来解密的:

立即执行函数

都是需要删除的

注意

1. 删除后记得ctrl-s保存替换:

保存替换1 保存替换2

2. 需要压缩代码(取消格式化)

因为解密函数有格式化检测:

格式化检测
const parser = require('@babel/parser');
const generator = require('@babel/generator').default;
const fs = require('fs');
const path = require('path');

const input_path=path.join(__dirname,'index.js');
const input_code=fs.readFileSync(input_path, 'utf-8');
var ast = parser.parse(input_code, {
    sourceType: 'module'
});
const output = generator(ast, { compact: true }).code;
fs.writeFileSync(input_path, output, 'utf-8');

替换后,再次刷新发现没有卡住,且可以正常请求数据:

正常请求

但是发现请求体是一个字节类型数据:

字节数据

解密请求体

找到请求堆栈:

请求堆栈

随便点一个进去,并下一个断点,重新发包,发现data已经生成:

data生成1 data生成2

回溯堆栈,再次下断点:

回溯堆栈

然后重新发包,发现断住,且数据是明文格式:

明文数据

跟进去:

跟进函数

i里面有几个函数,都是用Promise执行的,需要挨个点进去看看:

第一个函数:

第一个函数

第二个函数:

第二个函数

剩下的就不看了,你可以猜一下数据在那生成的

小孩子才做选择,全部都下上断点然后回到i函数的位置,放开断点会发现断在了第一个函数的位置:

断在第一个函数

然后看值就行了,也可以交给ai分析逻辑:

AI分析

这是执行了protubuf,ai还原:

axiosInstance.interceptors.request.use(config => {
  const newConfig = { ...config };

  // ========== 第一部分:Protobuf 编码处理 ==========
  if (config.url && isUrlEncoded(config.url) && newConfig.headers) {
    // isUrlEncoded 判断该 URL 是否需要 protobuf 编码
    const protoTypeName = c[config.url]; // 从映射表获取 protobuf 类型名
    newConfig.headers["Content-Type"] = "application/octet-stream";
    const MessageType = root.lookupType(c[config.url]); // 获取 protobuf Message 类型
    const fields = QA.nested[protoTypeName].fields;      // 获取字段定义

    Object.keys(newConfig.data).forEach(key => {
      const fieldDef = fields[key];
      // 如果字段类型是 "string",将值转为 String
      if (fieldDef && fieldDef.type === "string") {
        newConfig.data[key] = String(newConfig.data[key]);
      }
    });

    // 用 protobuf 编码数据
    newConfig.data = MessageType.encode(newConfig.data).finish().slice();
  }

  // ========== 第二部分:Auth Token 处理 ==========
  const token = getAuthToken(); // 从 storage 获取 AuthToken
  const currentUrl = new URL(window.location.href);

  if (token && newConfig.headers) {
    // 如果当前页面 URL 不包含 "326e.custom.spolicy.cn"
    if (!currentUrl.href.includes("326e.custom.spolicy.cn")) {
      // 设置 Authorization 头,格式: RSA加密(token + "." + 校准时间戳)
      newConfig.headers.Authorization = S$1(token + "." + getTime());
    }
  }

  // ========== 第三部分:AbortController 信号 ==========
  newConfig.signal = controller.signal;
  return newConfig;
});

上面的第二部分用不到,请求头里面也没有,即便有也已经知道逻辑了

好了到这直接就出来了,protobuf直接用开源的就行了github上有(https://github.com/protocolbuffers/protobuf),也可以用npm 安装npm install -g protobufjs,这里全局安装是因为python代码需要调用

最终逻辑:

const protobuf = require("protobufjs");

// ========== 1. 准备:原代码中的变量 ==========
const QA = {
    "nested": {
        "PolicyInfoParam": {
            "fields": {
                "id": {
                    "type": "string",
                    "id": 1
                }
            }
        },
        "PolicyInfoByTypeIdParam": {
            "fields": {
                "policyType": {
                    "type": "string",
                    "id": 1
                },
                "centralId": {
                    "type": "string",
                    "id": 2
                },
                "province": {
                    "type": "string",
                    "id": 3
                },
                "city": {
                    "type": "string",
                    "id": 4
                },
                "downtown": {
                    "type": "string",
                    "id": 5
                },
                "garden": {
                    "type": "string",
                    "id": 6
                },
                "sort": {
                    "type": "uint32",
                    "id": 7
                },
                "pageNum": {
                    "type": "uint32",
                    "id": 8
                },
                "pageSize": {
                    "type": "uint32",
                    "id": 9
                },
                "homePageFlag": {
                    "type": "uint32",
                    "id": 10
                }
            }
        },
        "PolicyInfoByTagsIdParam": {
            "fields": {
                "id": {
                    "type": "uint32",
                    "id": 1
                },
                "customerTagId": {
                    "type": "uint32",
                    "id": 2
                },
                "keyword": {
                    "type": "string",
                    "id": 3
                },
                "pageNum": {
                    "type": "uint32",
                    "id": 4
                },
                "pageSize": {
                    "type": "uint32",
                    "id": 5
                },
                "homePageFlag": {
                    "type": "uint32",
                    "id": 6
                }
            }
        },
        "PolicyInfoByDeptIdParam": {
            "fields": {
                "department": {
                    "type": "uint32",
                    "id": 1
                },
                "customized": {
                    "type": "string",
                    "id": 2
                },
                "garden": {
                    "type": "string",
                    "id": 3
                },
                "pageNum": {
                    "type": "uint32",
                    "id": 4
                },
                "pageSize": {
                    "type": "uint32",
                    "id": 5
                }
            }
        },
        "PolicyInfoSearchParam": {
            "fields": {
                "word": {
                    "type": "string",
                    "id": 1
                },
                "department": {
                    "type": "string",
                    "id": 2
                },
                "policyType": {
                    "type": "string",
                    "id": 3
                },
                "industry": {
                    "type": "string",
                    "id": 4
                },
                "customerIndt": {
                    "type": "string",
                    "id": 5
                },
                "startTime": {
                    "type": "string",
                    "id": 6
                },
                "endTime": {
                    "type": "string",
                    "id": 7
                },
                "province": {
                    "type": "string",
                    "id": 8
                },
                "city": {
                    "type": "string",
                    "id": 9
                },
                "downtown": {
                    "type": "string",
                    "id": 10
                },
                "garden": {
                    "type": "string",
                    "id": 11
                },
                "wholews": {
                    "type": "uint32",
                    "id": 12
                },
                "type": {
                    "type": "uint32",
                    "id": 13
                },
                "sorttype": {
                    "type": "uint32",
                    "id": 14
                },
                "pageNum": {
                    "type": "uint32",
                    "id": 15
                },
                "pageSize": {
                    "type": "uint32",
                    "id": 16
                }
            }
        }
    }
};

// 原代码中的 URL -> protobuf 类型名映射
const urlToProto = {
    "policyType/showPolicyType": "PolicyInfoByTypeIdParam",
    "SpeciaTag/findPolicyInfoByTagsId": "PolicyInfoByTagsIdParam",
    "Department/showByDepartmentGen": "PolicyInfoByDeptIdParam",
    "policyInfo/getPolicyInfo": "PolicyInfoParam",
    "policyinfoSearchController/searchEsPolicyinfo": "PolicyInfoSearchParam"
};

// 原代码中的 root
const root = protobuf.Root.fromJSON(QA);

// ========== 2. 核心逻辑:复现 interceptor 中的编码流程 ==========
function encodeRequestData(url, data) {
    if (!urlToProto[url]) {
        console.log("该 URL 不需要 protobuf 编码,返回原数据");
        return data;
    }

    // 步骤 1: 获取 protobuf Message 类型
    const protoTypeName = urlToProto[url];
    const MessageType = root.lookupType(urlToProto[url]);
    const fields = QA.nested[protoTypeName].fields;

    // 步骤 2: 模拟原代码中的 string 类型转换
    Object.keys(data).forEach(key => {
        const fieldDef = fields[key];
        if (fieldDef && fieldDef.type === "string") {
            data[key] = String(data[key]); // 强制转 String
        }
    });

    // 步骤 3: protobuf 编码(与原代码完全一致)
    const buffer = MessageType.encode(data).finish().slice();
    // finish() 返回 Uint8Array,slice() 生成新的 ArrayBuffer/Buffer

    return buffer;
}


// ========== 3. 测试 ==========
const result = encodeRequestData("policyType/showPolicyType", { "policyType": 6, "province": "", "city": "", "downtown": "", "garden": "", "centralId": "", "sort": 0, "homePageFlag": 1, "pageNum": 1, "pageSize": 7 });
console.log(JSON.stringify(Array.from(result)));

最后需要提醒一下,这个网站不要请求过于频繁,不然就会出现以下状况(最好加上代理):

请求频繁

python 源码:

do_one_js_code以前的文章写过,可以翻看以前的文章

import json, requests


class Spolicy:
    def __init__(self):
        self.cookies = {
        }
        self.headers = {
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Content-Type': 'application/octet-stream',
            'Origin': 'https://www.spolicy.com',
            'Pragma': 'no-cache',
            'Referer': 'https://www.spolicy.com/',
            'Sec-Fetch-Dest': 'empty',
            'Sec-Fetch-Mode': 'cors',
            'Sec-Fetch-Site': 'same-origin',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0',
            'sec-ch-ua': '"Microsoft Edge";v="147", "Not.A/Brand";v="8", "Chromium";v="147"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
        }
        self.js_code = r"""const protobuf=require("protobufjs"),QA={nested:{PolicyInfoParam:{fields:{id:{type:"string",id:1}}},PolicyInfoByTypeIdParam:{fields:{policyType:{type:"string",id:1},centralId:{type:"string",id:2},province:{type:"string",id:3},city:{type:"string",id:4},downtown:{type:"string",id:5},garden:{type:"string",id:6},sort:{type:"uint32",id:7},pageNum:{type:"uint32",id:8},pageSize:{type:"uint32",id:9},homePageFlag:{type:"uint32",id:10}}},PolicyInfoByTagsIdParam:{fields:{id:{type:"uint32",id:1},customerTagId:{type:"uint32",id:2},keyword:{type:"string",id:3},pageNum:{type:"uint32",id:4},pageSize:{type:"uint32",id:5},homePageFlag:{type:"uint32",id:6}}},PolicyInfoByDeptIdParam:{fields:{department:{type:"uint32",id:1},customized:{type:"string",id:2},garden:{type:"string",id:3},pageNum:{type:"uint32",id:4},pageSize:{type:"uint32",id:5}}},PolicyInfoSearchParam:{fields:{word:{type:"string",id:1},department:{type:"string",id:2},policyType:{type:"string",id:3},industry:{type:"string",id:4},customerIndt:{type:"string",id:5},startTime:{type:"string",id:6},endTime:{type:"string",id:7},province:{type:"string",id:8},city:{type:"string",id:9},downtown:{type:"string",id:10},garden:{type:"string",id:11},wholews:{type:"uint32",id:12},type:{type:"uint32",id:13},sorttype:{type:"uint32",id:14},pageNum:{type:"uint32",id:15},pageSize:{type:"uint32",id:16}}}}},urlToProto={"policyType/showPolicyType":"PolicyInfoByTypeIdParam","SpeciaTag/findPolicyInfoByTagsId":"PolicyInfoByTagsIdParam","Department/showByDepartmentGen":"PolicyInfoByDeptIdParam","policyInfo/getPolicyInfo":"PolicyInfoParam","policyinfoSearchController/searchEsPolicyinfo":"PolicyInfoSearchParam"},root=protobuf.Root.fromJSON(QA);function encodeRequestData(t,e){if(!urlToProto[t])return console.log("该 URL 不需要 protobuf 编码,返回原数据"),e;var i=urlToProto[t],t=root.lookupType(urlToProto[t]);const o=QA.nested[i].fields;return Object.keys(e).forEach(t=>{var i=o[t];i&&"string"===i.type&&(e[t]=String(e[t]))}),t.encode(e).finish().slice()}"""

    def get_req_data(self, data_dict, url_tail):
        js_code_ = self.js_code + f"""const result = encodeRequestData("{url_tail}", {data_dict});
        console.log(JSON.stringify(Array.from(result)));"""
        result = ''.join(list(map(lambda x: chr(x), json.loads(do_one_js_code(js_code_)['stdout']))))
        return result

    def req(self):
        # 自行添加处理逻辑,此处作为案例
        data = self.get_req_data({"policyType": 6, "province": "", "city": "", "downtown": "", "garden": "", "centralId": "", "sort": 0, "homePageFlag": 1, "pageNum": 1, "pageSize": 7}, "policyType/showPolicyType")
        response = requests.post('https://www.spolicy.com/info_api/policyType/showPolicyType', cookies=self.cookies, headers=self.headers, data=data)
        print(response.json())


if __name__ == '__main__':
    policy = Spolicy()
    policy.req()

运行结果:

运行结果
免责声明:本文仅供技术交流和学习使用,请勿将文中所述技术用于任何非法用途。未经授权访问他人网站或系统属于违法行为,由此产生的一切后果由使用者自行承担。请遵守相关法律法规,尊重他人的知识产权和隐私权。

— 全文完 —