1. 项目概述:为什么我们需要一个自定义的解密插件?
在渗透测试和安全审计的日常工作中,我们经常遇到一个令人头疼的“黑盒”:应用与服务器之间的通信数据被加密了。你打开Burp Suite的Proxy历史记录,看到的请求和响应体不再是明文的JSON或XML,而是一串串毫无意义的十六进制字符串或Base64编码的乱码。这就像戴着眼罩去摸象,你只知道有数据在流动,却完全不清楚里面到底传输了什么敏感信息、调用了哪些业务接口、是否存在逻辑漏洞。这种情况在金融、游戏、物联网以及一些对数据安全要求较高的移动App中尤为常见,开发者会使用自定义的算法或标准的AES、RSA等对关键业务参数进行全程加密。
这时候,Burp Suite自带的Decoder和Comparer虽然强大,但面对每个请求都需要手动复制、粘贴到外部工具解密、再粘贴回来分析的流程,效率极其低下,而且容易出错。更麻烦的是,当你要测试一个需要连续发送多个加密请求的业务流程时(比如登录后修改资料再下单),这种手动操作几乎不可行。因此,一个能够无缝集成到Burp Suite工作流中的自定义解密插件,就成了安全测试人员的“刚需”。它能够自动识别经过代理的流量,在Burp Suite的界面里实时地将加密的请求参数和响应结果解密为明文,让测试者能够像处理普通HTTP流量一样,进行重放、修改、扫描和漏洞挖掘。这个项目,就是教你如何用Java亲手打造这样一把“万能钥匙”。
2. 插件核心设计思路与Burp Suite扩展API解析
在动手写代码之前,我们必须先理解Burp Suite插件的运行机制和它能给我们提供的“操作空间”。Burp Suite通过
IBurpExtender
这个核心接口与插件进行交互,这就像是一个插座的国际标准,你的插件必须实现这个接口,才能被Burp Suite识别并加载。
2.1 理解Burp Suite的事件驱动模型
Burp Suite的插件开发本质上是事件驱动的。你的插件会注册为一系列事件的监听器(Listener),当特定事件发生时,Burp Suite会回调你插件中对应的方法,并传入相关的数据对象,你可以在这些回调方法里“做手脚”。对于我们这个解密插件,最关键的两个扩展点是:
-
IHttpListener:这是核心中的核心。它监听所有经过Burp Suite代理的HTTP/HTTPS流量。无论是浏览器发出的请求,还是服务器返回的响应,都会触发这个监听器。我们的解密逻辑主要就写在这里的回调方法processHttpMessage中。 -
IMessageEditorTabFactory:这个接口允许我们为Burp Suite的“消息编辑器”(就是Proxy、Repeater里查看请求/响应详情那个窗口)添加自定义的标签页。我们可以创建一个新的标签页,专门用来显示解密后的明文,这比在原始视图里替换内容更加清晰和安全,因为原始加密数据得以保留。
2.2 插件工作流程设计
基于以上API,我们的插件工作流程可以这样设计:
-
加载与注册
:插件启动,实现
IBurpExtender接口的registerExtenderCallbacks方法。在这个方法里,我们获取Burp提供的IExtensionHelpers工具对象,并用callbacks.registerHttpListener(this)将自己注册为HTTP监听器。 -
流量拦截与判断
:每当有HTTP消息(请求或响应)经过时,
processHttpMessage方法被调用。我们首先需要判断这条消息是否是我们需要处理的“加密消息”。这通常通过检查URL路径、Content-Type头部、或者请求体/响应体的特征(例如,是否以特定前缀开头,是否是JSON但包含encryptedData字段)来实现。 - 解密执行 :如果判断为加密流量,则提取出加密的字符串(可能从请求参数、Cookie、HTTP头或Body中),调用我们预先编写好的解密函数(如AES解密、RSA私钥解密、Base64解码后解密等)。
-
内容替换或展示
:
-
方案A(
IHttpListener内直接修改) :直接在processHttpMessage中,使用iRequestResponse.setRequest()或setResponse()方法,将解密后的明文替换回原始的请求/响应体。这样做最直接,后续所有模块(Scanner, Intruder, Repeater)看到的都是解密后的数据。 但需极其谨慎 ,因为这会永久改变流量,如果解密出错可能导致服务端无法识别。 -
方案B(
IMessageEditorTab内展示) :不修改原始消息,而是创建一个新的编辑器标签页。在标签页的getUiComponent()方法中返回一个显示解密明文的文本区域。这是更推荐的做法,它非侵入式,只影响查看,不影响实际发送的数据,更安全可靠。
-
方案A(
-
密钥/算法管理
:插件需要有一个地方来配置解密所需的密钥、IV(初始化向量)、算法模式等。这可以通过在Burp Suite的界面上添加一个自定义的配置标签(
ITab接口)来实现,让用户能够方便地输入和修改这些敏感信息。
注意: 在实际选择方案时,我强烈建议初学者先从 方案B(自定义标签页) 开始。它风险低,逻辑清晰,能帮助你更好地理解Burp的UI扩展机制。等完全掌握后,再根据实际测试场景的需要,考虑是否引入方案A的流量修改功能。
3. 开发环境搭建与项目初始化
工欲善其事,必先利其器。开发Burp Suite插件,环境搭建有几步是绕不开的。
3.1 JDK选择与Burp Extender API获取
首先,你需要安装
Java Development Kit (JDK)
。由于Burp Suite本身是基于Java开发的,为了最好的兼容性,建议使用与你的Burp Suite版本相匹配的JDK 8或JDK 11。你可以在命令行输入
java -version
来确认。接下来,你需要获取Burp Suite的扩展API文件(JAR包)。这个文件就在Burp Suite的安装目录里。
- 启动Burp Suite(社区版或专业版均可)。
-
点击顶部菜单
Extender->APIs。 -
在APIs标签页中,你会看到一个“Save interface files”的按钮。点击它,选择一个目录(例如
D:\burp-api),Burp会生成一个名为burpsuite_api_vX.X.jar的文件(X.X是版本号)。这个JAR包包含了我们开发所需的所有接口(如IBurpExtender,IHttpListener)和工具类。
3.2 创建Maven项目与管理依赖
现代Java项目强烈推荐使用Maven或Gradle来管理依赖和构建。这里以Maven为例。你可以使用IDE(如IntelliJ IDEA或Eclipse)直接创建一个Maven项目。
项目的核心是
pom.xml
文件,我们需要做以下关键配置:
-
添加Burp Extender API依赖
:由于那个API JAR包不在公共的Maven仓库中,我们需要将其安装到本地仓库,或者使用
system作用域直接引用本地路径。
你需要把下载的API JAR包复制到项目根目录的<dependency> <groupId>net.portswigger.burp.extender</groupId> <artifactId>burp-extender-api</artifactId> <version>1.0</version> <!-- 版本号可自定义 --> <scope>system</scope> <systemPath>${project.basedir}/lib/burpsuite_api_v2024.6.jar</systemPath> </dependency>lib文件夹下,并修改systemPath中的文件名与你实际的文件名一致。 -
添加加解密库依赖
:Java自带的
javax.crypto包功能强大但略显底层。为了更方便地处理AES、RSA等操作,我们可以引入org.bouncycastle(Bouncy Castle)这个强大的加密库,或者使用Apache的commons-codec处理Base64编解码。<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.16.0</version> </dependency> -
配置Maven编译插件
:Burp Suite插件需要打包成一个单独的JAR文件,并且所有依赖(除了Burp API)最好被打包进去(即生成“uber-jar”或“fat-jar”)。我们可以使用
maven-shade-plugin或maven-assembly-plugin来实现。同时,需要指定编译的Java版本。<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
3.3 编写插件主类骨架
创建一个Java类,例如
DecryptorPlugin.java
,它需要实现
IBurpExtender
和
IHttpListener
接口。
package com.yourcompany.burp.decryptor;
import burp.*;
import java.io.PrintWriter;
public class DecryptorPlugin implements IBurpExtender, IHttpListener {
private IBurpExtenderCallbacks callbacks;
private IExtensionHelpers helpers;
private PrintWriter stdout;
private PrintWriter stderr;
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// 保存回调对象和帮助对象
this.callbacks = callbacks;
this.helpers = callbacks.getHelpers();
// 设置输出流,用于在Burp的Extender输出台打印日志
stdout = new PrintWriter(callbacks.getStdout(), true);
stderr = new PrintWriter(callbacks.getStderr(), true);
// 设置插件名称
callbacks.setExtensionName("Custom Crypto Decryptor");
// 注册自己为HTTP监听器
callbacks.registerHttpListener(this);
stdout.println("[+] Custom Crypto Decryptor Plugin Loaded Successfully!");
}
@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
// toolFlag: 标识消息来自哪个工具 (如 TOOL_PROXY=0x00000004)
// messageIsRequest: true为请求,false为响应
// messageInfo: 包含请求/响应原始数据及一些辅助信息
// 这里将是我们的核心解密逻辑
// 暂时先打印一条日志,确认插件被正确调用
if (messageIsRequest) {
stdout.println("[*] Intercepted a Request from tool: " + toolFlag);
} else {
stdout.println("[*] Intercepted a Response from tool: " + toolFlag);
}
}
}
完成以上步骤后,运行
mvn clean package
,在
target
目录下会生成一个类似
your-plugin-1.0-shaded.jar
的文件。在Burp Suite的
Extender
->
Extensions
标签页,点击
Add
,选择这个JAR包加载。如果一切顺利,你会在下方的输出面板看到
[+] Custom Crypto Decryptor Plugin Loaded Successfully!
的日志,并且代理经过的每一个请求响应都会触发打印日志。至此,你的插件开发环境和工作流水线就完全打通了。
4. 核心解密逻辑的实现与集成
现在,我们来到了最核心的部分:如何在一个具体的HTTP消息中识别出加密数据,并将其解密。这里我们以一个最常见的场景为例:一个移动端APP,其POST请求的JSON Body中,有一个名为
data
的字段,该字段的值是一个经过AES-128-CBC加密并Base64编码的字符串。我们需要解密它。
4.1 识别加密流量
首先,我们需要在
processHttpMessage
方法中增加过滤逻辑。不能对所有流量都进行解密尝试,那样效率低下且可能出错。
@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
// 只处理来自Proxy、Repeater或Intruder的流量,忽略Scanner等
if (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY &&
toolFlag != IBurpExtenderCallbacks.TOOL_REPEATER &&
toolFlag != IBurpExtenderCallbacks.TOOL_INTRUDER) {
return;
}
IRequestInfo reqInfo = helpers.analyzeRequest(messageInfo);
String url = reqInfo.getUrl().toString();
// 示例:只处理特定主机和路径的请求
if (!url.contains("api.vulnerable-app.com") || !url.contains("/secure/endpoint")) {
return;
}
// 如果是请求,且是POST方法,尝试解密Body
if (messageIsRequest && reqInfo.getMethod().equalsIgnoreCase("POST")) {
// 获取请求Body的字节数组
byte[] requestBytes = messageInfo.getRequest();
int bodyOffset = reqInfo.getBodyOffset();
byte[] bodyBytes = Arrays.copyOfRange(requestBytes, bodyOffset, requestBytes.length);
String bodyStr = helpers.bytesToString(bodyBytes);
// 尝试将Body解析为JSON,并查找加密字段
try {
// 这里可以使用简单的字符串查找,或引入JSON库如Gson/Jackson
// 为了简化示例,我们假设bodyStr是JSON且包含"data":"加密字符串"
if (bodyStr.contains("\"data\":")) {
// 提取加密字符串 (这是一个非常简单的正则,实际应用需要更健壮的解析)
// 假设格式是: "data": "Base64EncodedAESCipherText"
Pattern pattern = Pattern.compile("\"data\":\\s*\"([^\"]+)\"");
Matcher matcher = pattern.matcher(bodyStr);
if (matcher.find()) {
String encryptedBase64 = matcher.group(1);
stdout.println("[*] Found encrypted data field: " + encryptedBase64.substring(0, Math.min(20, encryptedBase64.length())) + "...");
// 调用解密函数
String decryptedPlaintext = decryptAES(encryptedBase64);
if (decryptedPlaintext != null) {
stdout.println("[+] Decrypted: " + decryptedPlaintext);
// TODO: 在这里处理解密后的明文,例如显示在自定义标签页
}
}
}
} catch (Exception e) {
stderr.println("[-] Error parsing request body: " + e.getMessage());
}
}
// 响应解密的逻辑类似,可以分析Content-Type和响应体结构
}
4.2 实现AES解密函数
接下来,实现
decryptAES
方法。这里演示AES-128-CBC模式,PKCS5Padding填充方式。
密钥和IV需要根据目标应用实际情况替换。
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
private String decryptAES(String encryptedBase64) {
try {
// 1. Base64解码
byte[] encryptedData = Base64.decodeBase64(encryptedBase64);
// 2. 准备密钥和IV (!!! 这里需要替换成实际的密钥和IV !!!)
String secretKey = "0123456789abcdef"; // 16字节密钥 for AES-128
String iv = "abcdefghijklmnop"; // 16字节IV for CBC mode
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
// 3. 初始化Cipher为解密模式
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// 4. 执行解密
byte[] decryptedBytes = cipher.doFinal(encryptedData);
// 5. 将解密后的字节转换为字符串
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
stderr.println("[-] AES Decryption failed: " + e.getMessage());
e.printStackTrace(stderr);
return null;
}
}
实操心得: 密钥和IV的管理是安全测试中的敏感环节。 绝对不要 将真实的测试目标密钥硬编码在插件代码中并上传到公开的代码仓库。最佳实践是:在插件中提供一个配置界面(通过实现
ITab接口),让测试人员在加载插件后手动输入密钥。或者,从本地一个加密的配置文件读取。这既是职业操守,也避免了因代码泄露导致目标系统密钥暴露的风险。
4.3 创建自定义消息编辑器标签页
为了安全、清晰地展示解密内容,我们实现
IMessageEditorTabFactory
和
IMessageEditorTab
。
// 在主类中注册Tab Factory
public class DecryptorPlugin implements IBurpExtender, IHttpListener, IMessageEditorTabFactory {
// ... 之前的成员变量和registerExtenderCallbacks方法 ...
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// ... 之前的初始化代码 ...
// 注册消息编辑器Tab工厂
callbacks.registerMessageEditorTabFactory(this);
stdout.println("[+] Message Editor Tab Factory Registered.");
}
@Override
public IMessageEditorTab createNewInstance(IMessageEditorController controller, boolean editable) {
// 返回我们自定义的Tab实例
return new DecryptorMessageEditorTab(controller, editable, helpers, stdout, stderr);
}
}
// 自定义的Tab实现
class DecryptorMessageEditorTab implements IMessageEditorTab {
private final JTextArea textArea;
private byte[] currentMessage;
private final IMessageEditorController controller;
private final boolean editable;
private final IExtensionHelpers helpers;
private final PrintWriter stdout;
public DecryptorMessageEditorTab(IMessageEditorController controller, boolean editable,
IExtensionHelpers helpers, PrintWriter stdout, PrintWriter stderr) {
this.controller = controller;
this.editable = editable;
this.helpers = helpers;
this.stdout = stdout;
this.textArea = new JTextArea();
this.textArea.setEditable(false); // 我们的解密展示页通常设为只读
this.textArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
}
@Override
public String getTabCaption() {
return "Decrypted View"; // 标签页显示的名称
}
@Override
public java.awt.Component getUiComponent() {
return new JScrollPane(textArea); // 返回一个带滚动条的文本区域
}
@Override
public boolean isEnabled(byte[] content, boolean isRequest) {
// 判断是否为此消息启用这个Tab。这里我们可以复用之前的流量识别逻辑。
// 简单示例:如果内容是请求且包含特定主机,则启用。
if (content == null) return false;
IRequestInfo reqInfo = helpers.analyzeRequest(controller.getHttpService(), content);
if (reqInfo != null && reqInfo.getUrl().toString().contains("api.vulnerable-app.com")) {
return true;
}
// 也可以检查响应,这里省略
return false;
}
@Override
public void setMessage(byte[] content, boolean isRequest) {
this.currentMessage = content;
if (content == null) {
textArea.setText("");
return;
}
// 这里是核心:当用户点击我们这个Tab时,Burp会调用此方法传入当前消息。
// 我们需要在这里执行解密并显示。
String displayText = "[Processing...]\n";
try {
if (isRequest) {
IRequestInfo reqInfo = helpers.analyzeRequest(controller.getHttpService(), content);
int bodyOffset = reqInfo.getBodyOffset();
byte[] bodyBytes = Arrays.copyOfRange(content, bodyOffset, content.length);
String bodyStr = helpers.bytesToString(bodyBytes);
// 调用之前写的解密逻辑(这里需要把decryptAES方法移到能访问的地方,或传递进来)
String decrypted = YourDecryptionUtil.decryptRequestBody(bodyStr); // 假设有一个工具类
if (decrypted != null) {
displayText = "=== Decrypted Request Body ===\n" + decrypted;
} else {
displayText = "[-] Decryption failed or no encrypted data found.";
}
} else {
// 响应解密的逻辑,类似
IResponseInfo respInfo = helpers.analyzeResponse(content);
int bodyOffset = respInfo.getBodyOffset();
byte[] bodyBytes = Arrays.copyOfRange(content, bodyOffset, content.length);
String bodyStr = helpers.bytesToString(bodyBytes);
String decrypted = YourDecryptionUtil.decryptResponseBody(bodyStr);
if (decrypted != null) {
displayText = "=== Decrypted Response Body ===\n" + decrypted;
} else {
displayText = "[-] Decryption failed or no encrypted data found in response.";
}
}
} catch (Exception e) {
displayText = "[-] Error during decryption: " + e.getMessage();
}
textArea.setText(displayText);
textArea.setCaretPosition(0); // 滚动到顶部
}
@Override
public byte[] getMessage() {
// 因为我们只是展示,不修改原始消息,所以直接返回null。
// 如果想让Tab可编辑并返回修改后的消息,可以在这里处理。
return null;
}
@Override
public boolean isModified() {
// 我们的展示页不修改内容,所以总是返回false。
return false;
}
@Override
public byte[] getSelectedData() {
// 返回文本区域中用户选中的内容,用于复制等操作。
return textArea.getSelectedText().getBytes(StandardCharsets.UTF_8);
}
}
完成这部分代码后,重新打包并加载插件。现在,当你从Proxy历史记录或Repeater中打开一个目标请求时,消息编辑器旁边会出现一个名为“Decrypted View”的新标签页,点击它就能直接看到解密后的明文内容,而原始的加密请求依然完好无损。
5. 插件配置界面与密钥管理
一个健壮的插件必须允许用户动态配置解密参数,而不是写死在代码里。我们将通过实现
ITab
接口来为Burp Suite添加一个配置面板。
public class DecryptorPlugin implements IBurpExtender, IHttpListener, IMessageEditorTabFactory, ITab {
// ... 之前的成员变量 ...
private JPanel configPanel; // Swing面板
private JTextField keyField;
private JTextField ivField;
private JComboBox<String> algorithmComboBox;
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// ... 之前的初始化代码 ...
// 初始化UI
initializeConfigUI();
// 注册自己为一个Tab
callbacks.addSuiteTab(this);
stdout.println("[+] Configuration Tab Added.");
}
private void initializeConfigUI() {
configPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 0;
// 算法选择
configPanel.add(new JLabel("Algorithm:"), gbc);
algorithmComboBox = new JComboBox<>(new String[]{"AES/CBC/PKCS5Padding", "AES/ECB/PKCS5Padding", "DESede/CBC/PKCS5Padding"});
gbc.gridx = 1;
configPanel.add(algorithmComboBox, gbc);
// 密钥输入
gbc.gridx = 0;
gbc.gridy++;
configPanel.add(new JLabel("Secret Key (Hex or Base64):"), gbc);
keyField = new JTextField(32);
gbc.gridx = 1;
configPanel.add(keyField, gbc);
// IV输入 (对于ECB模式不需要)
gbc.gridx = 0;
gbc.gridy++;
configPanel.add(new JLabel("IV (Hex, for CBC mode):"), gbc);
ivField = new JTextField(32);
gbc.gridx = 1;
configPanel.add(ivField, gbc);
// 保存按钮
gbc.gridx = 0;
gbc.gridy++;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
JButton saveButton = new JButton("Save Configuration");
saveButton.addActionListener(e -> saveConfiguration());
configPanel.add(saveButton, gbc);
// 加载默认配置或上次保存的配置
loadConfiguration();
}
private void saveConfiguration() {
// 这里应该将配置(算法、密钥、IV)保存到Burp的持久化设置中,或者一个本地文件。
// 使用 callbacks.saveExtensionSetting / loadExtensionSetting 是Burp推荐的方式。
String algo = (String) algorithmComboBox.getSelectedItem();
String key = keyField.getText();
String iv = ivField.getText();
callbacks.saveExtensionSetting("algorithm", algo);
callbacks.saveExtensionSetting("secret_key", key);
callbacks.saveExtensionSetting("iv", iv);
// 同时更新内存中的解密工具类的配置
YourDecryptionUtil.updateConfig(algo, key, iv);
stdout.println("[+] Configuration saved.");
JOptionPane.showMessageDialog(configPanel, "Configuration saved successfully!", "Info", JOptionPane.INFORMATION_MESSAGE);
}
private void loadConfiguration() {
String algo = callbacks.loadExtensionSetting("algorithm");
String key = callbacks.loadExtensionSetting("secret_key");
String iv = callbacks.loadExtensionSetting("iv");
if (algo != null) algorithmComboBox.setSelectedItem(algo);
if (key != null) keyField.setText(key);
if (iv != null) ivField.setText(iv);
}
// 实现 ITab 接口的方法
@Override
public String getTabCaption() {
return "Crypto Decryptor Config";
}
@Override
public Component getUiComponent() {
return configPanel;
}
}
现在,你的Burp Suite界面上会多出一个名为“Crypto Decryptor Config”的标签页。测试人员可以在这里输入从逆向工程、静态分析或沟通中获得的目标应用的加密密钥和算法参数。点击保存后,插件后续的解密操作都会使用这些新配置。这种方式使得插件可以灵活适配不同的测试目标,而无需重新编译代码。
6. 处理复杂场景与高级功能
基本的解密功能实现后,我们会发现真实世界的场景要复杂得多。下面探讨几个常见的高级需求及其实现思路。
6.1 多种加密算法与动态识别
目标应用可能不止使用一种加密算法,或者不同接口使用不同的密钥。我们需要一个更强大的解密引擎。
-
创建解密管理器
:设计一个
DecryptionManager类,它维护一个“规则列表”。每条规则包含:匹配条件(如URL正则、请求头特征)、算法类型、密钥、IV等。 - 规则配置 :在配置界面中,允许用户添加、编辑、删除多条解密规则。每条规则可以独立启用或禁用。
-
动态匹配
:在
processHttpMessage或isEnabled方法中,遍历所有启用的规则,找到第一条匹配当前HTTP消息的规则,然后使用该规则对应的算法和密钥进行解密。 -
算法工厂
:实现一个
CryptoFactory,根据算法字符串(如“AES-128-CBC”、“RSA-OAEP”)动态创建对应的解密器对象。
// 简化的规则示例
public class DecryptionRule {
private String name;
private Pattern urlPattern;
private String algorithm;
private String key;
private String iv;
private boolean enabled;
// getters and setters...
}
// 在插件主类中
private List<DecryptionRule> rules = new ArrayList<>();
private String applyDecryption(IHttpRequestResponse messageInfo, boolean isRequest) {
IRequestInfo reqInfo = helpers.analyzeRequest(messageInfo);
String url = reqInfo.getUrl().toString();
for (DecryptionRule rule : rules) {
if (rule.isEnabled() && rule.getUrlPattern().matcher(url).find()) {
// 使用此规则进行解密
return CryptoEngine.decrypt(extractCipherText(messageInfo, isRequest, rule), rule);
}
}
return null; // 没有匹配的规则
}
6.2 处理非对称加密(RSA)
有些应用使用RSA非对称加密。通常,客户端用服务器的公钥加密一个临时生成的对称密钥(如AES密钥),然后用这个对称密钥加密业务数据。服务器用私钥解密得到对称密钥,再解密数据。对于测试者,我们通常没有服务器的私钥。但如果我们通过逆向APP拿到了内嵌的公钥,或者能中间人获取到公钥,我们就可以模拟客户端加密过程。更常见的情况是,我们需要用服务器的 公钥 来验证签名,或者用我们自己的 私钥 来解密客户端发给我们的、用我们公钥加密的数据(在测试我们自己服务端时)。
在插件中集成RSA解密,需要使用
java.security.KeyFactory
和
Cipher
。
private String decryptRSA(String encryptedBase64, String privateKeyPEM) throws Exception {
// 移除PEM格式的头尾标记
privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] pkcs8EncodedKey = Base64.decodeBase64(privateKeyPEM);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); // 注意填充方式需与客户端一致
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] encryptedData = Base64.decodeBase64(encryptedBase64);
byte[] decryptedData = cipher.doFinal(encryptedData);
return new String(decryptedData, StandardCharsets.UTF_8);
}
注意事项: RSA解密对数据长度有限制(取决于密钥长度)。通常它只用于加密密钥或很短的数据。如果遇到解密失败,首先检查填充方式(PKCS1Padding, OAEP等),这是最常见的坑。其次确认你拿到的是正确的私钥,并且格式是PKCS#8。很多时候从各种工具导出的密钥是PKCS#1格式,需要转换。
6.3 与Burp其他工具链协作
解密插件最大的价值在于能让Burp Suite的其他核心工具在明文状态下工作。
-
Scanner(漏洞扫描)
:如果你在
IHttpListener中直接修改了请求/响应体(方案A),那么Scanner扫描的就是解密后的明文,能发现更多逻辑漏洞。但要注意,如果修改了原始请求结构,可能会影响扫描的准确性。 -
Intruder(爆破)
:这是插件发挥威力的地方。你可以在Intruder中,对解密后的明文中的某个字段(如用户ID)进行标记(§§),然后进行爆破。插件需要在每次Intruder发送请求前,将整个Payload(包含你标记的明文部分)重新加密。这需要更精细的控制,可能需要实现
IIntruderPayloadProcessor接口。这个接口允许你对Intruder生成的Payload进行自定义处理。 -
Repeater(重放)
:我们的自定义消息编辑器Tab已经让Repeater的查看和调试变得非常方便。你可以在解密视图中看到明文,修改后,如果需要发送,则需要一个反向的“加密”过程。这可以通过在Repeater的Tab中增加一个“Encrypt & Send”按钮来实现,或者更复杂地,实现一个
IHttpListener来拦截从Repeater发出的、在解密Tab中修改过的请求,并将其重新加密。
实现
IIntruderPayloadProcessor
的示例如下:
public class EncryptionPayloadProcessor implements IIntruderPayloadProcessor {
private final IExtensionHelpers helpers;
public EncryptionPayloadProcessor(IExtensionHelpers helpers) { this.helpers = helpers; }
@Override
public String getProcessorName() { return "Encrypt payload"; }
@Override
public byte[] processPayload(byte[] currentPayload, byte[] originalPayload, byte[] baseValue) {
// currentPayload: Intruder当前要替换进去的值(明文)
// baseValue: 被标记位置的原始值(可能是加密的,也可能是明文的)
// 我们需要构造一个完整的请求,其中将baseValue替换为加密后的currentPayload
// 这里逻辑比较复杂,需要结合具体的请求结构
// 通常做法是:先解密原始请求得到模板,替换标记部分,再整体加密。
String processedRequest = yourEncryptionLogic(currentPayload);
return helpers.stringToBytes(processedRequest);
}
}
// 在registerExtenderCallbacks中注册:callbacks.registerIntruderPayloadProcessor(this);
7. 调试、打包与发布
7.1 插件调试技巧
开发Burp插件最直接的调试方式就是使用
stdout
和
stderr
打印日志。所有打印的信息都会在Burp的Extender标签页的“Output”或“Errors”子标签中看到。
-
详细日志
:在关键决策点、解密前后、异常捕获处打印信息。例如:
stdout.println("[*] Trying to match rule: " + rule.getName()); -
错误堆栈
:在catch块中,使用
e.printStackTrace(stderr);打印完整的异常堆栈,这对于定位加密库的细微错误至关重要。 -
使用系统断点(高级)
:如果你使用IDE(如IntelliJ IDEA),可以以调试模式启动Burp Suite,然后将IDE的调试器附加到Burp的Java进程上。这需要一些额外的JVM参数配置(例如在Burp启动时添加
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005),但可以实现单步调试,效率极高。
7.2 打包与依赖管理
我们之前配置的
maven-shade-plugin
会创建一个包含所有依赖(除Burp API外)的“uber JAR”。这是最常用的分发方式。
-
检查生成的JAR
:使用
jar tf target/your-plugin-1.0-shaded.jar命令或解压工具,确认META-INF/MANIFEST.MF文件中的Main-Class属性是否被正确移除(Burp插件不需要Main-Class),并且你的主类(如com.yourcompany.burp.decryptor.DecryptorPlugin)的类文件在根目录下。 -
处理依赖冲突
:如果你的插件引入了某个库(如Bouncy Castle),而Burp Suite自身也带了不同版本的同一库,可能会引发
NoSuchMethodError或ClassNotFoundException。这时需要使用maven-shade-plugin的 relocation 功能,将你的依赖包名重命名。<configuration> <relocations> <relocation> <pattern>org.bouncycastle</pattern> <shadedPattern>com.yourcompany.shaded.bouncycastle</shadedPattern> </relocation> </relocations> </configuration> - 代码混淆(可选) :对于商业或敏感插件,可以考虑使用ProGuard等工具对代码进行混淆,增加逆向难度。
7.3 发布与分享
完成开发和测试后,你可以将插件的JAR文件分享给团队成员。
- 编写README :创建一个简单的说明文档,解释插件的功能、配置方法(如何设置密钥)、适用的加密场景以及已知的限制。
- 版本管理 :在代码中使用语义化版本号(如1.0.0),并在插件加载时打印出来,便于问题追踪。
- 考虑开源 :如果插件不包含敏感的业务逻辑或密钥,可以考虑在GitHub等平台开源。这不仅能帮助他人,也能收获社区的反馈和改进。记得在开源前, 务必移除所有硬编码的、与特定测试目标相关的密钥和配置 。
开发一个功能完善的Burp Suite自定义解密插件,是一个将密码学知识、Java编程能力和对HTTP协议及Burp Suite框架理解相结合的过程。从最初简单的AES解密,到支持多规则、非对称加密、与Intruder联动,每一步的深化都让这个工具在实战中变得更加强大和顺手。当你能够流畅地解密目标应用的流量,并利用Burp Suite的全套工具对其进行安全测试时,那种“拨云见日”的感觉,正是安全研究员工作乐趣和价值的体现。记住,密钥管理无小事,测试授权要牢记,在合规的范围内,让技术为我们所用。

385

被折叠的 条评论
为什么被折叠?



