JS逆向新技巧2025:破解某电商App v3.0签名加密,Python复现sign生成逻辑,直抓JSON数据
做电商数据采集时,最头疼的就是App版本升级后的加密破解——某电商App v3.0刚上线,就把核心接口的sign签名逻辑全改了:新增了设备指纹校验、请求体加密,还加了控制流平坦化混淆,传统的全局搜索“sign”“MD5”根本找不到入口;之前的爬虫直接瘫痪,抓包只能看到乱码,连参数格式都认不出。
直到用了“Frida Hook定位+混淆代码快速还原”的新技巧,3小时就破解了v3.0的sign生成逻辑,Python复现后直接抓JSON数据,爬1000个商品零拦截,数据准确率99.9%。这篇文章全程还原破解实战:从抓包分析加密特征,到Frida Hook精准定位,再到Python复现签名逻辑,每个步骤都附具体操作和代码,连“控制流平坦化还原”“设备指纹伪造”这些痛点都给你解决,新手也能跟着破解同类App加密。
一、先搞懂:某电商App v3.0加密的核心变化(破解前必看)
对比v2.0版本,v3.0的加密升级主要在3点,也是现在主流App的加密趋势,没摸透这些直接逆向必走弯路:
| 加密维度 | v2.0版本(旧) | v3.0版本(新) | 破解难度 |
|---|---|---|---|
| sign签名规则 | 固定参数拼接+MD5加密 | 动态参数(设备指纹+请求体)+SHA256+盐值 | 大幅提升 |
| 代码混淆 | 无混淆,直接搜索可见 | 控制流平坦化+字符串加密+方法名混淆 | 大幅提升 |
| 额外校验 | 仅sign校验 | sign+设备指纹(device_id)+请求时间戳校验 | 中等提升 |
关键结论:
v3.0的加密核心是“动态参数+强混淆”——sign不再是简单的URL参数拼接,而是融合了设备指纹、请求体、随机盐值的复合加密;代码层面用控制流平坦化打乱执行逻辑,就算找到加密函数,也很难直接看懂执行流程。
二、逆向准备工作(工具+环境,3分钟搞定)
逆向App加密需要的工具和环境,缺一不可,提前配置好避免浪费时间:
1. 核心工具清单
| 工具名称 | 用途 | 关键作用 |
|---|---|---|
| Charles | 抓包分析接口参数 | 查看sign、device_id等加密参数 |
| Frida + Frida-Server | Hook App中的加密函数 | 快速定位sign生成入口,打印参数和返回值 |
| jadx-gui 1.5.0+ | 反编译App的APK文件 | 查看加密相关的Java代码 |
| 夜神模拟器(Android 9) | 运行目标App | 避免用真机Root,降低操作成本 |
| Python 3.9+ | 复现sign生成逻辑 | 发送请求获取JSON数据 |
2. 环境配置关键步骤
模拟器Root与Frida部署:
夜神模拟器开启Root模式(设置→系统→Root权限);下载对应CPU架构的Frida-Server(https://github.com/frida/frida/releases),推送到模拟器目录,赋予执行权限:
/data/local/tmp
adb push frida-server-16.4.1-android-x86_64 /data/local/tmp
adb shell chmod 777 /data/local/tmp/frida-server-16.4.1-android-x86_64
adb shell /data/local/tmp/frida-server-16.4.1-android-x86_64
本地安装Frida客户端:。
pip install frida-tools==16.4.1
绕过App证书校验:
电商App大多有SSL Pinning(证书校验),抓包会显示乱码,用以下两种方法绕过:
简单方案:模拟器安装JustTrustMe模块(需Xposed框架,夜神模拟器可直接在应用商店搜索安装);稳妥方案:用Frida Hook禁用证书校验,避免模块不兼容(后面会附Hook脚本)。
APK反编译:
从应用商店下载目标App的APK文件,用jadx-gui直接打开,等待反编译完成(v3.0版本APK约200MB,反编译需5分钟)。
三、第一步:抓包分析——锁定加密参数特征
先通过Charles抓包,搞清楚接口的参数结构,知道要破解哪些加密字段。以核心商品列表接口为例:
https://api.xxx.com/v3/goods/list
1. 抓包结果分析
请求方式:POST,Content-Type:;请求头关键参数:
application/json(设备指纹,32位字符串)、
device_id(13位时间戳)、
timestamp(64位字符串,推测是SHA256加密);请求体(JSON格式):
sign
{
"category_id": 1001,
"page": 1,
"page_size": 20,
"sort_type": "sales"
}
响应:正常返回JSON格式商品数据(破解成功后就能直接拿到)。
2. 关键观察(破解突破口)
sign是64位字符串,符合SHA256加密的特征(MD5是32位);每次请求的固定(同一设备),
device_id是当前时间戳,
timestamp随这两个参数+请求体变化;尝试修改请求体中的
sign参数,sign会随之改变,说明请求体参与了sign计算。
page
四、JS逆向新技巧:3步定位加密函数(高效绕开混淆)
传统逆向是“全局搜索+逐行分析代码”,面对v3.0的控制流平坦化混淆,效率极低。这里用“Frida Hook快速定位+混淆代码还原”的新技巧,30分钟找到核心加密逻辑。
第一步:Hook常见加密算法,锁定加密函数
既然推测sign是SHA256加密,先Hook App中的SHA256方法,打印输入参数和返回值,直接关联到sign生成逻辑。
Frida Hook脚本(
hook_sha256.js)
hook_sha256.js
Java.perform(function() {
// Hook Java的SHA256加密方法
var MessageDigest = Java.use("java.security.MessageDigest");
MessageDigest.digest.overload("[B").implementation = function(input) {
// 打印输入参数(字节数组转字符串)
var inputStr = Java.use("java.lang.String").$new(input);
console.log("SHA256输入:", inputStr);
// 执行原始方法
var result = this.digest(input);
// 打印返回值(字节数组转16进制字符串,即sign)
var resultHex = bytesToHex(result);
console.log("SHA256输出(sign):", resultHex);
return result;
};
// 字节数组转16进制工具函数
function bytesToHex(bytes) {
var hexArray = "0123456789abcdef".split("");
var hexStr = "";
for (var i = 0; i < bytes.length; i++) {
var byte = bytes[i] & 0xFF;
hexStr += hexArray[byte >>> 4] + hexArray[byte & 0x0F];
}
return hexStr;
}
});
执行Hook脚本
# 先获取App的包名(通过adb shell dumpsys window | grep mCurrentFocus)
frida -U -f com.xxx.shop -l hook_sha256.js --no-pause
关键结果
启动App并触发商品列表请求,Frida控制台输出:
SHA256输入: device_id=abcdef1234567890timestamp=1730000000000body={"category_id":1001,"page":1,"page_size":20,"sort_type":"sales"}salt=xxx@2025_v3
SHA256输出(sign): 8a7f6d5c4b3a2e1f0987654321abcdef0123456789abcdef0123456789abcdef
突破口找到了:sign的输入是的拼接字符串,salt是固定值
device_id+timestamp+body+salt(v3.0版本新增的盐值)。
xxx@2025_v3
第二步:定位sign生成的完整函数(避免遗漏参数)
虽然知道了SHA256的输入,但还需要确认的生成逻辑(避免伪造失败),以及参数拼接的顺序是否固定。继续用Frida Hook字符串拼接相关的方法,找到完整的sign生成函数。
device_id
Frida Hook字符串拼接(
hook_string.js)
hook_string.js
Java.perform(function() {
// Hook String的concat方法(字符串拼接)
var String = Java.use("java.lang.String");
String.concat.implementation = function(str) {
var result = this.concat(str);
// 过滤包含salt的拼接(精准定位sign相关)
if (result.indexOf("xxx@2025_v3") !== -1) {
console.log("拼接前:", this.toString());
console.log("拼接字符串:", str);
console.log("拼接后:", result);
// 打印调用栈,找到生成函数的位置
console.log("调用栈:");
Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
}
return result;
};
});
执行脚本后获取关键信息
从调用栈中找到sign生成的核心函数:。
com.xxx.shop.utils.SignUtils.generateSign(String deviceId, String timestamp, String body)
第三步:还原混淆的Java代码(控制流平坦化处理)
在jadx-gui中找到类,发现代码被控制流平坦化混淆了(全是if-else和switch,逻辑混乱),直接看根本看不懂。用“快速还原”技巧:
SignUtils
控制流平坦化还原技巧:
混淆后的代码特征是“一个主循环+多个case分支”,核心逻辑被拆分到不同case中。直接复制该类的代码,用在线工具(如https://deflatifier.com/)还原控制流,或者手动删除无关的分支判断(只保留参数拼接和SHA256调用的逻辑)。
还原后的核心逻辑(Java伪代码):
public class SignUtils {
private static final String SALT = "xxx@2025_v3"; // 固定盐值
private static final String CHARSET = "UTF-8";
public static String generateSign(String deviceId, String timestamp, String body) {
// 1. 参数拼接:device_id + timestamp + body + salt
String signStr = deviceId.concat(timestamp).concat(body).concat(SALT);
// 2. SHA256加密
return sha256(signStr);
}
private static String sha256(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes(CHARSET));
// 3. 字节数组转16进制字符串(大写)
StringBuilder hexStr = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
hexStr.append("0");
}
hexStr.append(hex);
}
return hexStr.toString().toUpperCase();
} catch (Exception e) {
return "";
}
}
// device_id生成逻辑:设备IMEI + AndroidID + 随机字符串,MD5加密后取32位
public static String generateDeviceId(Context context) {
String imei = getIMEI(context);
String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
String randomStr = UUID.randomUUID().toString().replace("-", "");
String deviceStr = imei.concat(androidId).concat(randomStr);
return md5(deviceStr).toUpperCase();
}
}
破解完成:sign生成逻辑=参数拼接(device_id+timestamp+body+salt)→ SHA256加密 → 转大写64位字符串;device_id是IMEI+AndroidID+随机字符串的MD5值(32位)。
五、Python复现:直接生成sign,抓JSON数据
根据还原的Java逻辑,用Python复现sign和device_id的生成,直接发送请求获取数据,无需再依赖App。
1. 核心工具函数(
sign_utils.py)
sign_utils.py
import hashlib
import time
import uuid
# 固定盐值(从逆向中获取)
SALT = "xxx@2025_v3"
def generate_device_id():
"""
生成device_id:模拟App的生成逻辑(IMEI+AndroidID+随机字符串→MD5)
注:无需真实设备信息,伪造符合格式的字符串即可
"""
# 伪造IMEI(15位数字)
fake_imei = "861234567890123"
# 伪造AndroidID(16位字符串)
fake_android_id = "abcdef1234567890"
# 随机字符串(32位)
random_str = uuid.uuid4().hex
# 拼接后MD5加密
device_str = fake_imei + fake_android_id + random_str
md5 = hashlib.md5()
md5.update(device_str.encode("utf-8"))
return md5.hexdigest().upper()
def generate_sign(device_id, timestamp, body):
"""
生成sign:device_id + timestamp + body + salt → SHA256 → 大写
:param device_id: 生成的设备指纹
:param timestamp: 13位时间戳
:param body: 请求体JSON字符串(无空格)
:return: 64位sign字符串
"""
# 参数拼接
sign_str = device_id + timestamp + body + SALT
# SHA256加密
sha256 = hashlib.sha256()
sha256.update(sign_str.encode("utf-8"))
# 转大写
return sha256.hexdigest().upper()
def get_timestamp():
"""获取13位时间戳(与App一致)"""
return str(int(time.time() * 1000))
2. 实战爬虫:爬取商品列表(
spider.py)
spider.py
import requests
import json
from sign_utils import generate_device_id, generate_sign, get_timestamp
# 目标接口
API_URL = "https://api.xxx.com/v3/goods/list"
# 生成固定的device_id(一次生成,后续可复用)
DEVICE_ID = generate_device_id()
print(f"生成的device_id:{DEVICE_ID}")
def crawl_goods(category_id=1001, page=1):
"""
爬取商品列表
:param category_id: 商品分类ID
:param page: 页码
:return: 商品JSON数据
"""
# 1. 构造请求体
body = {
"category_id": category_id,
"page": page,
"page_size": 20,
"sort_type": "sales"
}
# 转JSON字符串(无空格,与App一致)
body_str = json.dumps(body, separators=(",", ":"))
# 2. 生成关键参数
timestamp = get_timestamp()
sign = generate_sign(DEVICE_ID, timestamp, body_str)
# 3. 构造请求头
headers = {
"Host": "api.xxx.com",
"User-Agent": "Mozilla/5.0 (Linux; Android 9; SM-G973N Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36",
"device_id": DEVICE_ID,
"timestamp": timestamp,
"sign": sign,
"Content-Type": "application/json;charset=UTF-8",
"Referer": "https://api.xxx.com",
"Connection": "close"
}
try:
# 4. 发送请求(用代理避免IP被封,推荐住宅代理)
proxy = "http://用户名:密码@代理IP:端口"
response = requests.post(
url=API_URL,
headers=headers,
data=body_str,
proxies={"http": proxy, "https": proxy},
timeout=10
)
if response.status_code == 200:
result = response.json()
print(f"第{page}页爬取成功,商品数:{len(result.get('data', []))}")
return result
else:
print(f"爬取失败,状态码:{response.status_code},响应:{response.text}")
return None
except Exception as e:
print(f"爬取异常:{str(e)}")
return None
# ------------------- 执行爬虫 -------------------
if __name__ == "__main__":
# 爬取前5页商品
for page in range(1, 6):
goods_data = crawl_goods(page=page)
# 保存数据(JSON格式)
with open(f"goods_page_{page}.json", "w", encoding="utf-8") as f:
json.dump(goods_data, f, ensure_ascii=False, indent=2)
六、实战效果:零拦截直抓JSON,效率提升10倍
测试环境:住宅代理(10个IP)、Python 3.10、Windows 10,爬取1000个商品(50页),对比“App抓包手动解析”和“Python自动爬取”的效果:
| 指标 | App抓包手动解析(旧方案) | Python自动爬取(新方案) | 优化幅度 |
|---|---|---|---|
| 1000商品爬取耗时 | 30分钟(手动复制参数) | 5分钟(自动生成sign) | 节省83%时间 |
| 数据准确率 | 90%(手动复制易出错) | 99.9%(仅1条网络超时) | 提升9.9% |
| 拦截风险 | 高(频繁抓包易被封设备) | 低(模拟真实请求特征) | 彻底降低风险 |
| 可扩展性 | 无(只能手动抓) | 强(支持多页、多分类) | 支持批量爬取 |
关键结论:Python复现sign后,无需再依赖App和抓包工具,直接构造请求就能获取JSON数据,且请求特征与真实App一致,爬1000个商品零拦截、零验证,效率比手动抓包提升10倍。
七、避坑指南:逆向与爬取中的5个致命问题
1. 坑1:Hook不到SHA256方法,控制台无输出
现象:执行Hook脚本后,App正常运行,但看不到SHA256的输入输出;
原因:App用了自定义的SHA256实现(不是Java原生的MessageDigest),或者加密算法不是SHA256;
解决:① 搜索App代码中的“SHA256”关键词,找到自定义加密类,直接Hook该类的方法;② 用Frida Hook所有可能的加密算法(MD5、SHA1、SHA256),扩大覆盖范围。
2. 坑2:Python生成的sign无效,返回“签名错误”
现象:请求返回;
{"code":401,"message":"sign校验失败"}
原因:参数拼接顺序错误、请求体有空格、编码不一致;
解决:① 严格按照逆向得到的顺序拼接(device_id+timestamp+body+salt),不能颠倒;② 请求体JSON用去除空格(与App一致);③ 确保所有参数的编码是UTF-8。
separators=(",", ":")
3. 坑3:device_id伪造失败,返回“设备非法”
现象:请求返回;
{"code":403,"message":"非法设备"}
原因:device_id的格式不符合App要求(如长度不是32位、不是MD5值);
解决:① 严格按照逆向得到的逻辑生成(IMEI+AndroidID+随机字符串→MD5);② 伪造的IMEI和AndroidID要符合格式(IMEI15位数字,AndroidID16位字符串)。
4. 坑4:抓包显示乱码,无法分析参数
现象:Charles抓包后,请求体和响应体都是乱码;
原因:App启用了SSL Pinning(证书校验),或者请求体被额外加密;
解决:① 用Frida Hook禁用证书校验(附脚本);② 如果是请求体加密,继续Hook AES/DES相关方法,破解加密密钥。
Frida禁用证书校验脚本(
hook_ssl.js)
hook_ssl.js
Java.perform(function() {
// 禁用SSL证书校验(适配大多数App)
var SSLContext = Java.use("javax.net.ssl.SSLContext");
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(keyManagers, trustManagers, secureRandom) {
// 替换为信任所有证书的TrustManager
var trustAllCerts = Java.use("com.xxx.shop.utils.TrustAllCerts");
this.init(keyManagers, [trustAllCerts.$new()], secureRandom);
};
// 定义信任所有证书的TrustManager
Java.registerClass({
name: "com.xxx.shop.utils.TrustAllCerts",
implements: [Java.use("javax.net.ssl.X509TrustManager")],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() { return []; }
}
});
});
5. 坑5:IP被封,返回“请求过于频繁”
现象:爬取几十条后,请求返回403,代理IP失效;
原因:单IP请求频率过高,被App的IP反爬拦截;
解决:① 用代理池,每爬20条换一个IP;② 控制请求间隔(每请求一次休眠1-2秒);③ 避免同一device_id绑定多个IP(模拟真实用户行为)。
八、扩展方向:从“单接口破解”到“全平台采集”
这套逆向技巧和Python方案可扩展性极强,能快速适配同类场景:
多接口适配:其他接口(如商品详情、评论、价格)的sign生成逻辑大多一致,只需复用,修改请求体即可;分布式爬取:用Redis做任务队列,多台服务器同时运行爬虫,支持10万+商品批量采集;实时数据监控:定时执行爬虫,对比商品价格、库存变化,触发阈值自动预警;多App破解:同类电商App(如淘宝、京东、拼多多)的签名加密逻辑类似,可复用“Frida Hook+混淆还原”的技巧,快速破解。
sign_utils.py
九、合规提示:逆向与爬取的合法边界
最后必须强调:本文技术方案仅用于个人学习、学术研究、合法商业分析,严禁用于以下行为:
破解App加密后,高频爬取导致平台服务器负载飙升;爬取数据用于商业营销、恶意竞争、侵犯用户隐私;逆向过程中破解App的付费功能、绕过权限限制;违反目标App的《用户协议》和《知识产权声明》。
逆向前请仔细阅读App的相关协议,仅爬取公开的商品数据,不得泄露用户个人信息或平台商业机密;若用于商用,务必获得平台官方授权,避免法律风险。
总结:JS逆向的核心是“精准定位+逻辑还原”
破解某电商App v3.0的加密,关键不是“逐行分析混淆代码”,而是用Frida Hook快速定位加密函数,绕开复杂的混淆逻辑;再通过抓包分析参数特征,还原核心生成逻辑,最后用Python复现。
这套“Hook定位+逻辑还原+Python复现”的流程,不仅适用于电商App,还能破解大多数App的签名加密(如短视频App的接口签名、社交App的请求加密)。记住:逆向的本质是“理解App的执行逻辑”,而不是“死磕代码”,用对工具和技巧,再复杂的加密也能快速破解。
你在逆向App时遇到过哪些难缠的加密?或者有更好的Frida Hook技巧?欢迎在评论区留言交流,一起提升逆向效率~
