三亩地.
  • 首页
  • 学习日记
  • 项目实战
  • 学习方法
  • 代码技巧
  • 避坑指南
  • 调试经验
  • 实战教程
  • 编程思维
  • 资讯中心
  • 关于我们

资讯详情

深入了解每一个知识点

  • 首页
  • /
  • 资讯中心
  • /
  • 文章详情

js逆向-某政策数据平台

📅 2026/7/14 9:42:15 👁️ 阅读次数 📝 编程学习
js逆向-某政策数据平台

js逆向-某政策数据平台

网址: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",将值转为 Stringif (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 获取 AuthTokenconst 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/Bufferreturn 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, requestsclass 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 resultdef 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()

运行结果:

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

— 全文完 —

编程学习 技术分享 实战经验

相关新闻

从3:2到4:2压缩:华莱士树乘法器的延时优化之路

2026/7/14 9:18:00

匿名内部类的使用场景

2026/7/14 7:04:00

Royal TSX中文汉化终极指南:3步让专业远程管理工具说中文

2026/7/14 9:20:23

最新新闻

2026年深圳亚克力厂家推荐榜单:亚克力立牌/动漫立牌/人形立牌/双面立牌/鬼灭之刃与恋与深空动漫周边专业源头工厂 - 甄选服务推荐

2026/7/15 7:43:14

终极指南:如何在 NixOS 配置中启用和管理 compose2nix 转换模块

2026/7/15 7:43:19

2026乌鲁木齐诚信的托育中心机构口碑排行榜,价格透明避坑实测 - mypinpai

2026/7/15 7:43:14

杭州音响改装门店深度解析:声铠汽车音响如何打造沉浸式声场体验,长城音响改装/奔驰音响改装,音响改装官方门店推荐 - 品牌推荐师

2026/7/15 7:43:14

计算机网络-链路层:从帧封装到多路访问,构建可靠数据传输的基石

2026/7/15 7:43:14

3秒解锁百度网盘资源:智能提取码查询工具终极指南

2026/7/15 7:42:03

日新闻

工业负载控制方案:TPD2017FN与PIC32MZ的智能应用

2026/7/15 0:00:00

数据库实战:AVG() 函数进阶用法与性能优化(头歌云课)

2026/7/15 0:00:00

Arch ECS 入门指南:10分钟掌握C#高性能数据驱动架构

2026/7/15 0:00:00

周新闻

npm 与 cnpm 镜像源对比:4 种配置方案实测与安全性分析

2026/7/14 12:15:10

Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动

2026/7/14 12:15:11

IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结

2026/7/14 9:50:20

月新闻

[C++]内存管理:串顺序存储的内存回收

2026/7/14 12:15:11

足球口袋教练 HarmonyOS 离线应用实战(03/20):ArkUI 首页仪表盘搭建

2026/7/14 12:15:11

抖音内容监控助手:告别手动刷新,让优质内容主动找你

2026/7/14 12:15:11

分类目录

  • 学习日记
  • 项目实战
  • 学习方法
  • 代码技巧
  • 避坑指南
  • 调试经验

热门标签

JavaScript Python Java 前端开发 后端开发 算法 数据结构 项目实战

关于三亩地

三亩地是一个专注于编程学习的平台,以真实学习日记为载体,分享编程学习经验、项目实操技巧和高效学习方法。

快速链接

学习日记

项目实战

学习方法

资讯中心

联系方式

邮箱:contact@mfbz.cn

微信:sanmudi_code

QQ 群:123456789

© 2026 三亩地 编程学习日记 版权所有 | mfbz.cn