声明
本文章所有内容仅供学习交流使用,不用于其他任何目的,其中的抓包内容、数据接口、敏感网址等均已做脱敏处理,严禁用于商业用途和非法用途,否则,由此产生的一切后果均与作者无关,若有侵权,请联系作者立即删除!
- 目标网址:aHR0cHM6Ly9mdXd1Lm5oc2EuZ292LmNuL25hdGlvbmFsSGFsbFN0LyMvc2VhcmNoL3BoYXJtYWNpZXM/Y29kZT0xNzQwMDAmbWVzc2FnZT1zZXJ2ZXJVcmwlMjBpcyUyMG51bGwmZ2JGbGFnPXRydWU=
- 目标数据接口:L3F1ZXJ5UnRhbFBoYWNCSW5mbw==
第一步:抓包分析,查看接口参数、返回值
- header中可以看到有多个疑似加密字段,不确定是否需要处理,暂且放置。
- 经过多次重放请求发现cookie值内容一样,所以cookie可以写死不用管。
- 请求参数中appCode是不变的,我们疑似需要处理的是encData、signData,优先处理encData
- 这样看请求参数是变化的且加密的,所以这部分内容是必须处理的,那么就可以先处理请求参数后再去验证header中的参数是否必须处理,如果请求参数处理完发现header写死也可以正常返回数据,那么皆大欢喜,如果还是无法正常返回则再尝试分析处理请求头。
- 返回结果中看到返回值也是密文,encData也是必处理项
第二步:定位请求参数
通过关键字搜索在几个疑似位置打上断点,刷新页面发现在一处断住
发现第一次断住不是我们目标接口,放行断点继续查看,第二次断住是我们的目标请求,并且找到了参数字段。分页参数是动态的,regnCode是地区编码可以根据需要传入固定值,queryDataSource固定值es,其他字段值都是空
向下调试发现在该处返回了密文,可以确定这里就是加密函数位置
往上翻翻明显看到这是webpack结构,我们的加密逻辑就在7d92这个模块里
进入n函数,也就是核心加载器函数o
把整个加载器文件抠到本地补上window环境,加上模块调用日志输出
尝试运行,发现少document环境
补上环境发现还少其他环境,那么我们分析加载器代码,是否存在初始化自执行模块来进行环境检测,下面发现自执行了o(o.s = 0),直接注释掉,再次运行,不再报环境错误。
把加载器挂在到全局,方便在外部调用,打印加载器测试
我们看到webpack在导出时把f函数和g函数重新命名为a、b导出,所以我们在外部使用时需要使用导出的模块函数名,f函数就是我们加密所在函数,所以我们使用window.loader("7d92").a作为加密函数。
最后在本地构建参数对象,测试后发现其他相关字段和header值都一同出来了
原来是f加密函数中做了所有的事情,后续关于请求部分我们就不用再分析header和signData了,现在请求部分已经完成,接下来我们同样的方式找到解密模块,最终发现就是g函数,也就是导出的b。最后在本地封装js函数和python代码实现完整流程:调用js获取加密参数 → 请求接口获取加密结果 → 调用js解密函数解出明文数据。
#.....扣下来的加载器部分js代码略
// ================== 初始化 ==================
const mod = window.loader("7d92")
const buildReq = mod.a // 构建请求
const decodeData = mod.b // 解密函数
// ================== 构建请求 ==================
function buildRequest(pageNum) {
const req = {
headers: {},
data: {
addr: "",
regnCode: "330100",
medinsName: "",
businessLvOutMedOtp: "",
pageNum: pageNum,
pageSize: 10,
queryDataSource: "es"
}
}
return buildReq(req)
}
// ================== 解密 ==================
function decode(encDataStr) {
const resp = JSON.parse(encDataStr); // 把完整 JSON 字符串解析成对象
return decodeData("SM4", resp);
}
// ================== CLI 入口 ==================
const mode = process.argv[2]
if (mode === "encrypt") {
const pageNum = parseInt(process.argv[3]) || 1
const result = buildRequest(pageNum)
console.log(JSON.stringify(result))
}
else if (mode === "decrypt") {
const encData = process.argv[3] || ""
const result = decode(encData)
console.log(JSON.stringify(result))
}
else {
console.error("Usage:")
console.error(" node crypto.js encrypt <pageNum>")
console.error(" node crypto.js decrypt <encData>")
process.exit(1)
}
import subprocess
import json
import logging
import requests
from typing import Dict, Any
# =========================
# 日志配置
# =========================
LOG_FORMAT = (
"[%(asctime)s] "
"[%(levelname)s] "
"[%(filename)s:%(lineno)d] "
"%(message)s"
)
logging.basicConfig(
level=logging.INFO,
format=LOG_FORMAT,
)
logger = logging.getLogger(__name__)
# =========================
# JS:生成加密参数
# =========================
def get_encrypted_params(page_num: int) -> Dict[str, Any]:
"""
调用 Node.js 获取接口加密参数
"""
logger.info("生成加密参数 | page=%s", page_num)
try:
result = subprocess.run(
["node", "loader.js", "encrypt", str(page_num)],
capture_output=True,
text=True,
encoding="utf-8",
timeout=10,
)
except subprocess.TimeoutExpired:
raise RuntimeError("JS 加密执行超时")
if result.returncode != 0:
logger.error("JS 加密失败: %s", result.stderr)
raise RuntimeError("JS 加密失败")
stdout = result.stdout.strip()
logger.info("JS encrypt 输出: %s", stdout)
try:
data = json.loads(stdout)
except json.JSONDecodeError:
raise RuntimeError("JS encrypt 输出不是合法 JSON")
if "headers" not in data or "data" not in data:
raise RuntimeError("JS encrypt 返回结构异常")
return data
# =========================
# JS:解密 encData
# =========================
def decrypt_enc_data(enc_data: str) -> Dict[str, Any]:
"""
调用 Node.js 解密接口返回的 encData
"""
logger.info("开始解密 encData | length=%s", len(enc_data))
try:
result = subprocess.run(
["node", "loader.js", "decrypt", enc_data],
capture_output=True,
text=True,
encoding="utf-8",
timeout=15,
)
except subprocess.TimeoutExpired:
raise RuntimeError("JS 解密执行超时")
if result.returncode != 0:
logger.error("JS 解密失败: %s", result.stderr)
raise RuntimeError("JS 解密失败")
stdout = result.stdout.strip()
logger.debug("JS decrypt 输出: %s", stdout)
try:
decrypted = json.loads(stdout)
except json.JSONDecodeError:
raise RuntimeError("JS decrypt 输出不是合法 JSON")
return decrypted
# =========================
# 请求接口
# =========================
def fetch_page_data(page_num: int) :
"""
请求接口获取加密响应
"""
encrypted = get_encrypted_params(page_num)
headers = {k: str(v) for k, v in encrypted["headers"].items()}
headers.update({
"Cache-Control": "no-cache",
"Pragma": "no-cache",
"channel": "web",
})
cookies = {
"amap_local": "330100",
}
body = encrypted["data"]
if isinstance(body, str):
body = json.loads(body)
url = "https://xxxxx"
logger.info("请求接口 | page=%s", page_num)
logger.info("请求 body: %s", body)
resp = requests.post(
url,
headers=headers,
cookies=cookies,
json=body,
timeout=30,
)
resp.raise_for_status()
return resp.text
# =========================
# 主流程
# =========================
def main():
"""
逐页执行:
加密 → 请求 → 解密 → 立即输出
"""
for page in range(1, 3):
logger.info("========== 开始处理第 %s 页 ==========", page)
try:
# 1. 请求接口
encrypted_resp = fetch_page_data(page)
logger.info("加密结果: %s", encrypted_resp)
# 2. 解密
decrypted_data = decrypt_enc_data(encrypted_resp)
# 3. 输出结果
result = {
"page": page,
"encrypted_response": encrypted_resp,
"decrypted_data": decrypted_data,
}
print(
json.dumps(
result,
ensure_ascii=False,
indent=2
)
)
logger.info("========== 第 %s 页处理完成 ==========", page)
except Exception as e:
logger.exception("第 %s 页处理失败", page)
print(
json.dumps(
{
"page": page,
"error": str(e)
},
ensure_ascii=False,
indent=2
)
)
if __name__ == "__main__":
main()
logger.info("全部任务执行完成")
最终完美输出结果
新人入门水平,大佬们多多交流指教。<抱拳>