网址:https://www.spolicy.com/
接口:https://www.spolicy.com/info_api/policyType/showPolicyType
过反调试
打开devtool,输入网址并回车,会发现被debugger住了,回溯堆栈看到混淆加Function.prototype.constructor构造无限debugger:
永不在此处暂停不管用,hook掉Function.prototype.constructor会发现很卡(有while-true)
我刚开始解决思路是用ast反混淆,但是解到一半,发现混淆代的码有好一部分都是用来废话:
果断删除发现依然正常请求数据,但是并不是所有混淆都能删除,像解密函数之类的需要保留,因为有些正经的请求代码也混淆了
因为这个代码会变,所有这里只给出一些案例(截止到写文章时的网站代码地址:https://www.spolicy.com/assets/index-4968aa6a.js):
保留函数:
解密包含的数组(不用删除):
主解密函数(需要保留):
数组重新排序(这是个立即执行函数,需要保留):
需要删除的:
而其他的例如(.*): [jzaMiQ]\$[\d] \d是因为有6个:
以及一个立即执行函数,但不是用来解密的:
都是需要删除的
注意
1. 删除后记得ctrl-s保存替换:
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已经生成:
回溯堆栈,再次下断点:
然后重新发包,发现断住,且数据是明文格式:
跟进去:
i里面有几个函数,都是用Promise执行的,需要挨个点进去看看:
第一个函数:
第二个函数:
剩下的就不看了,你可以猜一下数据在那生成的
小孩子才做选择,全部都下上断点然后回到i函数的位置,放开断点会发现断在了第一个函数的位置:
然后看值就行了,也可以交给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()
运行结果:

— 全文完 —
浙公网安备 33010602011771号