最近,我主导实现了一套请求加解密机制,虽然提升了安全性,但也给团队排查问题时的抓包分析带来了障碍。为了让后续工作更顺畅,我决定在Burp Suite上找到一个方案,能够像以前一样直接查看明文返回结果。
我主要使用Burp Suite,它支持插件扩展。理论上,自己写一个插件也是可行的,但本着效率优先的原则,我决定在现有的优秀插件中进行选型。
一、插件选型:为什么是Galaxy?
在评估了市面上几款主流插件后,我制作了下面的对比表格:
| 插件名称 |
特点与适用场景 |
获取方式 |
| MITM Decryption (Galaxy) |
功能最全面,支持多种语言编写解密逻辑,内置大量加解密算法示例,适合应对复杂的加密场景。 |
BApp Store / GitHub |
| autoDecoder |
灵活的自定义解密插件,可配置外部脚本或程序来处理数据包,适合需要自定义解密逻辑的渗透测试场景。 |
GitHub |
| Burpy |
专注于 JS 逆向场景,支持调用 Python 和 Java 脚本,特别适合处理前端加密的 Web 应用。 |
GitHub |
| iCrypto |
轻量级的自定义加解密插件,通过调用外部脚本来实现请求和响应的解密,易于上手。 |
GitHub |
| PyCript |
灵活性高,支持使用 Python、Node.js 等多种语言编写解密逻辑,能自动处理请求头和 Body 中的密钥。 |
GitHub |
| AES-Killer |
专为 AES-CBC 模式流量设计的“傻瓜式”解密插件,只需填入密钥和 IV,适合目标明确的场景。 |
GitHub |
我的原则是,要么不用,要用就用最好的。在查看了上述项目后,发现好几个项目已多年未更新。最终,我选择了 MITM Decryption (Galaxy)。
为什么是 Galaxy?
对于大部分需要处理加密流量的场景,Galaxy 堪称一站式解决方案。它直接上架了 Burp 官方的 BApp Store,在 Extender 标签页中就能搜索安装,非常便捷。
它的优点非常突出:
- 功能强大:支持 AES、DES、RSA、SM2、SM4 等多种算法,并能处理动态密钥等复杂情况。
- 语言灵活:你可以用 Java、Python、JavaScript 这三种语言来编写自己的解密逻辑(官方称之为 “Hook”),灵活度极高。
- 无缝集成:解密过程是全自动的,对 Repeater、Intruder 等核心模块透明。你在 Burp 中看到的是解密后的明文,修改后发送时,Galaxy 会自动帮你重新加密,不影响与服务器的正常交互。
Galaxy 由社区开发者 outlaws-bai 维护,其核心功能包括:
- 🤖 自动化加解密:一处配置,即可自动处理所有经过代理的流量(Proxy/Repeater/Intruder/Scanner 均支持)。
- 🛠️ 内置实用工具:包含解析 Swagger API 文档、绕过 Host 检查等辅助功能。
- 🔗 安全工具联动:可将解密后的请求直接发送给 sqlmap 或 xray 进行扫描。
- 🎯 支持复杂场景:能应对加密与加签并存、动态密钥、算法组合等复杂逻辑。
二、实战:配置与编写Hook脚本
安装好 Galaxy 插件后,Burp 主界面会出现一个 Galaxy 标签页。

接下来是关键的实战步骤。
第一步:配置目标范围(关键步骤)
你需要告诉 Galaxy,只对特定的目标流量生效。例如,假设我的目标服务地址是 192.168.0.165:8088。
- 在 Burp Suite 中,进入
Galaxy -> Http Hook。
- 在
Expression 输入框中,可以配置过滤表达式,例如 request.host='192.168.0.165' && request.port=8088。更精确的配置可以在 Filter 子标签页中设置目标主机和端口。
第二步:编写自定义AES解密Hook脚本
Galaxy 的强大之处在于其四个核心 Hook 函数,它们允许你精准控制流量的加解密流程:
hookRequestToBurp:请求刚到达 Burp 时调用,用于解密请求。
hookRequestToServer:请求将发往服务器时调用,用于加密请求。
hookResponseToBurp:响应刚到达 Burp 时调用,用于解密响应。
hookResponseToClient:响应将发回客户端时调用,用于加密响应。
在我的实际场景中,请求需要附加签名头,而整个响应体是一个经过 Base64 编码的 AES/ECB 密文。下面是我修改后的 AesEcb.java 脚本,核心逻辑仅约30行代码。
import org.m2sec.core.models.*;
import org.m2sec.shaded.slf4j.Logger;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AesEcb {
private static final String ALGORITHM = "AES/ECB/PKCS5Padding";
private static final byte[] SECRET = "你的key".getBytes();
private Logger log;
public AesEcb(Logger log) {
this.log = log;
}
// ==================== 请求方向 ====================
// 请求到达 Burp:不做任何修改
public Request hookRequestToBurp(Request request) {
return request;
}
// 请求发送到服务器:生成签名并添加 headers
public Request hookRequestToServer(Request request) {
try {
String method = request.getMethod();
String path = request.getPath();
// 去掉 query 参数(如果存在)
int queryIdx = path.indexOf('?');
String pathWithoutQuery = (queryIdx == -1) ? path : path.substring(0, queryIdx);
// 生成时间戳(秒)和随机字符串
long timestampSec = System.currentTimeMillis() / 1000;
String timestamp = Long.toString(timestampSec);
String nonce = generateNonce(16); // 16位随机字符串
// 构造待签名字符串:method + pathWithoutQuery + timestamp + nonce
String signString = method + pathWithoutQuery + timestamp + nonce;
byte[] encrypted = aesEncrypt(signString.getBytes());
String signature = Base64.getEncoder().encodeToString(encrypted);
// 添加 headers
request.getHeaders().put("X-Signature", signature);
request.getHeaders().put("X-Timestamp", timestamp);
request.getHeaders().put("X-Nonce", nonce);
} catch (Exception e) {
log.error("Request signature generation failed", e);
}
return request;
}
// ==================== 响应方向:整体解密 ====================
// 响应从服务器到达 Burp:整个响应体是 Base64 密文 -> 解码 -> AES解密 -> 明文
public Response hookResponseToBurp(Response response) {
try {
byte[] content = response.getContent();
// 整个响应体当作 Base64 字符串解码
byte[] encrypted = Base64.getDecoder().decode(content);
byte[] plain = aesDecrypt(encrypted);
response.setContent(plain);
} catch (Exception e) {
log.error("Response decryption failed (maybe not Base64 or invalid key)", e);
// 解密失败时保持原样
}
return response;
}
// 响应从 Burp 发送到客户端:明文 -> AES加密 -> Base64编码 -> 作为整个响应体
public Response hookResponseToClient(Response response) {
try {
byte[] plain = response.getContent();
byte[] encrypted = aesEncrypt(plain);
byte[] encryptedB64 = Base64.getEncoder().encode(encrypted);
response.setContent(encryptedB64);
} catch (Exception e) {
log.error("Response encryption failed", e);
}
return response;
}
// ==================== 辅助方法 ====================
private String generateNonce(int length) {
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int idx = (int) (Math.random() * chars.length());
sb.append(chars.charAt(idx));
}
return sb.toString();
}
private byte[] aesDecrypt(byte[] encryptedData) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(SECRET, "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(encryptedData);
}
private byte[] aesEncrypt(byte[] plainData) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(SECRET, "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(plainData);
}
}
脚本逻辑简述:
hookRequestToServer: 提取请求方法和路径,结合时间戳和随机数生成待签名字符串,用 AES/ECB 加密后生成 X-Signature 等头部。
hookResponseToBurp: 将整个响应体进行 Base64 解码,然后用 AES/ECB 解密,得到明文响应。
hookResponseToClient: 将明文响应加密并 Base64 编码后返回。
第三步:加载并启动Hook
- 在
Http Hook 主界面,点击 Add。
Hook Type 选择 Java,Hook Script 选择或创建你的 .java 文件(例如 AesEcb.java)。
- 点击
Start 按钮启动插件。


启动后,效果立竿见影。原本密文的请求和响应,在 Burp 的 History 或 Repeater 中都已显示为明文。


三、关键细节与排错指南
上面的流程是理想情况,实际使用中可能会遇到问题。以下是几个必须注意的核心细节和排错方法:
- JDK环境:插件(尤其是Java Hook)必须运行在完整的JDK环境下,而非JRE。很多Burp绿色启动器可能使用了精简环境。
- 查看完整日志:如果插件不生效,除了检查Burp的
Extender -> Errors 标签页,务必通过命令行启动Burp,因为控制台会输出更详细的错误堆栈信息。
- 版本匹配:确保你的JDK版本与插件兼容。
如何通过命令行(macOS/Linux)启动Burp以排查问题?
创建一个启动脚本(例如 start_burp.sh)是确保环境可控的好方法。
#!/bin/bash
# 设置你的 JDK 路径(请修改为你的实际路径)
JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home"
# 设置 Burp Suite JAR 文件路径(请修改为你的实际路径)
BURP_JAR="/Applications/Burp Suite Professional.app/Contents/Resources/app/burpsuite_pro.jar"
# 使用指定的 JDK 启动 Burp Suite
"$JAVA_HOME/bin/java" \
--add-opens=java.desktop/javax.swing=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED \
--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED \
--add-opens=java.base/jdk.internal.org.objectweb.asm.Opcodes=ALL-UNNAMED \
-javaagent:/path/to/your/agent.jar \ # 可选,根据实际情况
-noverify \
-jar "$BURP_JAR"
给脚本执行权限后运行:chmod +x start_burp.sh && ./start_burp.sh。通过这种方式启动,可以确保Burp运行在正确的JDK上,并能直接在终端看到类似下图的错误信息,这对于解决JCE cannot authenticate the provider BC这类密码学相关错误至关重要。

四、总结
如果你正在为Burp Suite中复杂的加解密流量分析而头疼,Galaxy (MITM Decryption) 是一个强大而全面的选择。它支持多语言Hook,能够优雅地处理诸如请求签名、响应整体加解密等混合场景。
本文以一次实战为例,分享了如何利用Galaxy和约30行Java代码,实现请求自动生成X-Signature签名头以及响应体Base64解码 + AES/ECB解密的全流程。整个过程严格使用Java标准JCE库,避免了第三方加密Provider可能带来的兼容性问题。
关键点回顾:
- 选型:Galaxy功能全面,对Java开发者友好,是处理加密流量的首选插件。
- 配置:务必正确设置目标过滤(
Expression或Filter),并确保Burp代理配置正确。
- 编码:理解四个Hook函数的调用时机,根据你的加密协议(是整体加密还是JSON内嵌套)编写对应逻辑。
- 排错:确保Burp由完整JDK启动,并通过命令行查看完整错误日志。
希望这份结合实战经验的记录,能帮助你在应对加密流量时少走弯路,让安全测试和问题排查更加顺畅。技术交流可以前往云栈社区的相关板块进一步探讨。