记一次使用 Xposed RPC 和 BurpGuard 应对金融APP参数签名及加密的详细过程
字数 1710 2025-08-24 07:48:23
使用Xposed RPC和BurpGuard应对金融APP参数签名及加密的详细教程
前言
本教程详细记录了如何通过Xposed RPC和BurpGuard解决金融APP参数签名及加密问题的完整过程。通过学习本教程,你将掌握以下知识:
- APP请求逆向分析思路
- 如何查阅相关资料解决技术难题
- APP请求签名和加密的原理分析
- 白盒WbSM4加密算法的理解
- Xposed RPC的实现方法
- BurpGuard的使用技巧
测试环境准备
- 模拟器:Mumu模拟器
- 系统工具:Magisk、Lsposed
- 分析工具:算法助手Plus、frida、Jadx
- 代理工具:BurpSuite
初探签名及加密机制
请求包分析
观察APP的HTTPS请求,发现以下关键字段:
MsgId:请求标识AppSign:请求签名cTxt:加密的请求内容
测试发现修改MsgId会返回401错误,说明APP实现了请求签名验证机制。
加密机制分析
脱壳与反编译
- 使用算法助手Plus配合frida脚本对APP进行脱壳(APP使用爱加密企业版加固)
- 使用Jadx反编译脱壳后的代码
- 搜索关键词发现代码被抽空,部分逻辑在native层实现
加密算法定位
- 通过关键词搜索找到两处加密相关代码,发现ECB模式
- 跟进代码发现
xxx.WbSM4Util$Companion类的encryptDataECB方法 - 关键加密调用:
WbSm4().encode() - 加密流程:
- 传入明文字节对象和长度
- 不直接可见密钥相关操作
- 最终进行Base64编码
白盒SM4算法分析
WbSm4是native实现- 查询资料确认是国密SM4算法
- 白盒SM4特点:
- 密钥与算法高度融合
- 难以直接提取密钥
- 需要采用RPC远程调用方式处理
签名机制分析
签名生成流程
- 搜索关键词
appsign定位签名相关代码 - 发现
WelfareTaskRequestService.APPSIGN常量 - 签名生成调用:
HeaderUtils.generateAppSign( ServiceUrls.getAccessKeyListUrl(...), uuid, HeaderUtils.requestBodyToString(create) ) - 签名参数:
- 完整URL
- UUID(作为MsgId)
- 完整请求Body
签名验证特点
- 请求body与msgid和appsign强相关
- 任何参数错误都会导致请求不合法
Xposed RPC实现
技术选型
- 使用Xposed框架(无检测,而frida有检测)
- 参考过检测方法(可无脑过爱加密企业版检测)
RPC实现方案
采用NanoHTTPD在APP内部启动HTTP服务器与外部通信:
- 开放两个接口:
/encrypt:用于加密数据和生成签名/decrypt:用于解密数据
核心代码实现
HTTP服务器基础框架
class HTTPServer extends NanoHTTPD {
public HTTPServer(int port) { super(port); }
@Override
public Response serve(IHTTPSession session) {
JsonObject responseJson = new JsonObject();
String encryptData = "";
String decryptData = "";
Map<String, String> map = new HashMap<>();
try {
session.parseBody(map);
} catch (Exception e) {
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", e.getMessage());
}
if (session.getMethod() == Method.POST) {
switch (session.getUri()) {
case "/encrypt":
responseJson.addProperty("encryptData", encryptData);
return newFixedLengthResponse(Response.Status.OK, "application/json", responseJson.toString());
case "/decrypt":
responseJson.addProperty("decryptData", decryptData);
return newFixedLengthResponse(Response.Status.OK, "application/json", responseJson.toString());
default:
return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "Not Found");
}
} else {
return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "Not Found");
}
}
}
Xposed模块完整实现
package com.example.xposed;
import android.util.Log;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import fi.iki.elonen.NanoHTTPD;
public class Hooker implements IXposedHookLoadPackage {
private HTTPServer httpServer = new HTTPServer(50000);
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
try {
Class<?> clazz = Class.forName("xxx.WbSM4Util$Companion", true, loadPackageParam.classLoader);
Method methods[] = clazz.getDeclaredMethods();
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Crypto crypto = new Crypto();
crypto.setClassLoader(loadPackageParam.classLoader);
crypto.setObject(constructor.newInstance());
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals("decryptDataECB")) {
methods[i].setAccessible(true);
crypto.setDecrypt(methods[i]);
}
if (methods[i].getName().equals("encryptDataECB")) {
methods[i].setAccessible(true);
crypto.setEncrypt(methods[i]);
}
}
if (crypto.getEncrypt() != null && crypto.getDecrypt() != null) {
this.httpServer.setCrypto(crypto);
startHttpServer();
}
} catch (Exception e) {
Log(e.toString());
}
}
private void startHttpServer() {
if (httpServer != null && httpServer.getCrypto() != null) {
new Thread(() -> {
try {
httpServer.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
Log("HTTP Server started on port " + httpServer.getListeningPort());
} catch (IOException e) {
Log("Error starting HTTP Server: " + e.toString());
}
}).start();
}
}
public static class HTTPServer extends NanoHTTPD {
private Crypto crypto;
public HTTPServer(int port) { super(port); }
public Crypto getCrypto() { return crypto; }
public void setCrypto(Crypto crypto) { this.crypto = crypto; }
@Override
public Response serve(IHTTPSession session) {
JsonObject responseJson = new JsonObject();
JsonObject body = new JsonObject();
Map<String, String> map = new HashMap<>();
try {
session.parseBody(map);
String data;
if (session.getMethod() == Method.POST) {
body = JsonParser.parseString(map.get("postData")).getAsJsonObject();
switch (session.getUri()) {
case "/encrypt":
// 从BurpGuard获取需要加密的数据和签名需要的url
data = body.get("data").getAsString();
String url = body.get("url").getAsString();
// 反射获取签名方法
Class<?> headerUtil = Class.forName("xxx.HeaderUtils", false, crypto.getClassLoader());
java.lang.reflect.Method generateAppSign = headerUtil.getDeclaredMethod(
"generateAppSign", String.class, String.class, String.class);
generateAppSign.setAccessible(true);
// 加密数据
String encryptData = this.crypto.getEncrypt().invoke(
crypto.getObject(), data, false).toString();
JsonObject jsondata = new JsonObject();
jsondata.addProperty("cTxt", encryptData);
String uuid = UUID.randomUUID().toString();
// 生成签名
String appsign = generateAppSign.invoke(
null, url, uuid, jsondata.toString()).toString();
// 构建响应
responseJson.addProperty("msgid", uuid);
responseJson.addProperty("appsign", appsign);
responseJson.addProperty("encryptData", encryptData);
return newFixedLengthResponse(
Response.Status.OK, "application/json", responseJson.toString());
case "/decrypt":
// 解密数据
data = body.get("data").getAsString();
String decryptData = this.crypto.getDecrypt().invoke(
crypto.getObject(), data, false).toString();
responseJson.addProperty("decryptData", decryptData);
return newFixedLengthResponse(
Response.Status.OK, "application/json", responseJson.toString());
default:
return newFixedLengthResponse(
Response.Status.NOT_FOUND, "text/plain", "Not Found");
}
} else {
return newFixedLengthResponse(
Response.Status.NOT_FOUND, "text/plain", "Not Found");
}
} catch (InvocationTargetException e) {
Log(e.getCause().fillInStackTrace().toString());
return newFixedLengthResponse(
Response.Status.BAD_REQUEST, "text/plain", "Invalid JSON data");
} catch (Exception e) {
Log(e.getCause().fillInStackTrace().toString());
}
return newFixedLengthResponse(
Response.Status.INTERNAL_ERROR, "text/plain", "Invalid Request");
}
}
}
端口转发与测试
- 使用ADB转发端口:
adb forward tcp:50000 tcp:50000 - 测试HTTP服务器是否启动成功
- 测试接口:
- 解密接口:发送加密数据,获取明文
- 加密接口:发送明文和URL,获取加密数据和签名
BurpGuard实现
项目介绍
BurpGuard项目地址:https://github.com/yinsel/BurpGuard
实现原理
- 使用mitmproxy作为中间人代理
- 分为两个处理程序:
- ClientProxyHandler:处理客户端请求(解密)
- BurpProxyHandler:处理Burp请求(加密)
核心代码实现
ClientProxyHandler.py
from mitmproxy import http
import httpx
import json
import traceback
class ClientProxyHandler:
def __init__(self) -> None:
self.client = httpx.Client(timeout=None, verify=False)
def request(self, flow: http.HTTPFlow):
try:
req = flow.request
if req.method == "POST" and "json" in req.headers["Content-Type"] and "\"cTxt\"" in req.text:
json_data = req.json()
result = self.client.post(
"http://127.0.0.1:50000/decrypt",
json={"data": json_data["cTxt"]}
).json()
req.text = result["decryptData"]
req.headers["burp"] = "1"
except Exception as e:
traceback.print_exception(e)
finally:
return flow
addons = [ClientProxyHandler()]
BurpProxyHandler.py
from mitmproxy import http
import httpx
import json
import traceback
class BurpProxyHandler:
def __init__(self) -> None:
self.client = httpx.Client(timeout=None, verify=False)
def request(self, flow: http.HTTPFlow):
try:
req = flow.request
if req.headers.get("burp"):
json_data = req.json()
result = self.client.post(
"http://127.0.0.1:50000/encrypt",
json={"data": req.text, "url": req.url}
).json()
req.text = json.dumps({"cTxt": result["encryptData"]}, separators=(',', ':'))
req.headers["msgid"] = result["msgid"]
req.headers["appsign"] = result["appsign"]
except Exception as e:
traceback.print_exception(e)
finally:
return flow
addons = [BurpProxyHandler()]
代理配置
- 配置模拟器代理为8081(ClientProxyHandler)
- 配置BurpSuite上游代理为8082(BurpProxyHandler)
- 运行BurpGuard:
python BurpGuard.py
最终效果验证
- 在模拟器安装并激活Xposed模块
- 操作APP,使用Burp拦截请求
- 观察请求body已自动解密
- 在重发器中修改请求内容,测试签名和加密自动处理功能
- 确认可以正常进行渗透测试操作
总结
通过本教程,我们完整实现了:
- 对加固APP的脱壳和逆向分析
- 白盒SM4加密算法的RPC调用
- 复杂签名机制的自动化处理
- 与BurpSuite的无缝集成
- 自动化加解密和签名验证流程
这种方法不仅适用于金融APP,也可应用于其他使用类似安全机制的移动应用测试场景。