1. 项目背景与目标
最近在研究某省级政府采购网站的数据采集时,遇到了一个典型的混淆加密案例。这个网站采用了RSA+sha1+md5的多重加密机制,对请求参数进行了严格校验。本文将详细拆解这个案例的逆向过程,分享如何定位加密参数、分析加密逻辑以及最终实现Python复现的全流程。
这类政府网站通常会有较强的反爬机制,但只要我们掌握正确的分析方法,依然可以找到突破口。通过本案例,你将学会如何处理混淆代码、定位关键加密函数以及用Python还原加密逻辑的实用技巧。
2. 加密参数定位与分析
2.1 接口参数初步探查
首先我们通过浏览器开发者工具观察接口请求,发现两个关键加密参数:
- nsssjss:看起来像是时间戳经过加密处理后的结果
- sign:典型的签名参数,用于接口鉴权
这两个参数在每次请求时都会变化,且其他接口并不携带,说明它们是这个特定接口的加密校验参数。我们的目标就是逆向出它们的生成逻辑。
提示:在分析加密参数时,首先要确认哪些是固定参数,哪些是动态生成的加密参数。可以通过多次请求对比参数值的变化规律。
2.2 加密参数定位方法
对于这类混淆过的前端代码,我通常采用以下步骤定位加密位置:
- 在开发者工具的Network面板中找到目标接口
- 右键接口选择"Search in all files"搜索参数名
- 在搜索结果中找到参数赋值的位置
- 通过调用栈分析加密函数的调用关系
在本案例中,通过搜索"nsssjss"很快就在一个混淆的JS文件中找到了它的生成位置。而sign参数则需要更深入的调用栈分析,最终发现是在请求拦截器中动态添加的。
3. nsssjss参数逆向分析
3.1 加密逻辑解析
通过调试分析,nsssjss参数的生成流程如下:
- 获取当前时间戳
- 对时间戳进行RSA加密
- 将加密结果转换为Base64编码
关键加密代码如下:
javascript复制function encryptTimestamp(timestamp) {
// RSA加密逻辑
var publicKey = generateRSAKey();
var encrypted = publicKey.encrypt(timestamp.toString());
return btoa(encrypted);
}
3.2 RSA密钥生成分析
这个案例的难点在于RSA公钥是动态生成的,而不是固定写在代码中。通过调试发现,公钥是通过以下步骤生成的:
- 调用一个混淆函数p()生成密钥对
- p()函数内部使用了两个大数组和数组打乱算法
- 最终生成的公钥格式为标准的PEM格式
在Python中复现时,我们需要先还原这个密钥生成过程。经过分析,发现它实际上是使用JSEncrypt库的标准密钥生成方式,只是代码被混淆了。
3.3 Python复现实现代码
python复制from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
import time
def generate_rsa_key():
# 还原JS中的密钥生成逻辑
key = RSA.generate(1024)
public_key = key.publickey().exportKey()
return public_key
def encrypt_nsssjss():
timestamp = int(time.time() * 1000)
public_key = generate_rsa_key()
rsa_key = RSA.importKey(public_key)
cipher = PKCS1_v1_5.new(rsa_key)
encrypted = cipher.encrypt(str(timestamp).encode())
return base64.b64encode(encrypted).decode()
4. sign参数逆向分析
4.1 签名生成流程
sign参数的生成比nsssjss更复杂,经过分析其流程如下:
- 拼接接口路径、时间戳和其他固定参数
- 对拼接字符串进行SHA1哈希
- 对哈希结果再进行MD5加密
- 最后转换为大写形式
关键代码逻辑:
javascript复制function generateSign(path, timestamp) {
var raw = path + timestamp + "fixed_salt";
var sha1 = p(raw); // p是混淆的SHA1函数
var md5 = x(sha1); // x是混淆的MD5函数
return md5.toUpperCase();
}
4.2 混淆函数分析
这里最大的挑战是p()和x()函数都被严重混淆了。通过调试分析,发现它们具有以下特点:
- 使用了大数组存储加密常量
- 有数组打乱函数对常量表进行混淆
- 最终实现的确实是标准的SHA1和MD5算法
对于这种情况,我们有三种处理方案:
- 完全还原混淆算法(最耗时但最准确)
- 调用JavaScript执行引擎直接运行原函数
- 用Python的标准库替代(如果确认是标准算法)
经过验证,确认这两个函数只是对标准算法进行了混淆包装,并没有修改算法本身,因此我们可以用Python的hashlib库直接实现。
4.3 Python复现实现代码
python复制import hashlib
def generate_sign(path, timestamp):
fixed_salt = "fixed_salt_value" # 需要根据实际情况替换
raw_str = path + str(timestamp) + fixed_salt
# SHA1哈希
sha1 = hashlib.sha1(raw_str.encode()).hexdigest()
# MD5加密
md5 = hashlib.md5(sha1.encode()).hexdigest()
return md5.upper()
5. 完整请求示例与调试
5.1 请求参数组装
现在我们已经可以生成两个加密参数,下面展示如何组装完整的请求:
python复制import requests
import time
def make_request():
url = "https://example.com/api/data"
path = "/api/data"
timestamp = int(time.time() * 1000)
headers = {
"User-Agent": "Mozilla/5.0",
"Content-Type": "application/json"
}
params = {
"page": 1,
"size": 10,
"nsssjss": encrypt_nsssjss(),
"sign": generate_sign(path, timestamp),
"_": timestamp
}
response = requests.get(url, headers=headers, params=params)
return response.json()
5.2 常见问题排查
在实际测试中可能会遇到以下问题:
-
签名无效:
- 检查salt值是否正确
- 确认参数拼接顺序是否与前端一致
- 验证时间戳格式(前端可能是毫秒或秒)
-
RSA加密失败:
- 确认密钥生成方式是否正确
- 检查加密前的数据格式
- 验证Base64编码方式
-
请求频率限制:
- 添加适当的请求间隔
- 使用代理IP池
- 模拟正常用户行为模式
注意:在实际逆向过程中,一定要控制请求频率,避免对目标服务器造成过大压力。
6. 混淆代码分析技巧总结
通过这个案例,我总结出以下分析混淆加密代码的经验:
-
定位关键函数:
- 搜索加密参数名
- 跟踪调用栈
- 观察网络请求初始化过程
-
分析混淆逻辑:
- 关注大数组和数组打乱函数
- 寻找自执行函数
- 注意解密函数的调用关系
-
验证加密结果:
- 使用已知输入输出对测试
- 分步骤验证中间结果
- 对比标准算法实现
-
Python复现策略:
- 优先使用标准库替代
- 复杂逻辑考虑调用JS引擎
- 关键参数可通过调试获取
在实际项目中,遇到混淆代码不要慌,耐心分析总能找到规律。这个案例中的加密方式虽然有一定复杂度,但使用的都是标准算法,只要掌握正确的分析方法,就能成功逆向。