东家的服务接口,请求时通过特定方式进行了加密,在调试、测试时。查看入参、出参需要调用特定得解密接口解密获取 入参、出参明文,过于繁琐于是通过度娘确认了以下方案
因相关隐私政策,不便暴漏相关业务接口,为演示效果使用 falsk 编写了两个 demo,server 端,解密端
├─AESCipherUtils.py # 加密、解密类
├─app.py # 服务端
└─app2.py # 解密端
定义 127.0.0.1:5000/login 接口,逻辑:将密文入参解析为明文赋值给 data.data,并返回 data.timestamp。统一加密返回
from flask import Flask, request
from AESCipherUtils import AESCipher
import time
app = Flask(__name__)
@app.route('/login')
def login():
AES = AESCipher()
data = {}
reqBodyString = request.data.decode()
try:
data['data'] = AES.decrypt(reqBodyString)
data['timestamp'] = int(time.time())
data = AES.encrypt(str(data))
except Exception as error:
data['error'] = str(error)
return data
if __name__ == '__main__':
app.run(
debug=True
)
127.0.0.1:5001/decrypt 接口逻辑,接受 json body 格式为{"req":"请求密文","res":"响应密文"} 处理并返回 {"req":"请求明文","res":"响应明文"}
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# time : 2022/2/9
# __author__ = Ysc
import json
from flask import Flask, request
from AESCipherUtils import AESCipher
app = Flask(__name__)
@app.route('/decrypt',methods=['GET','POST'])
def login():
AES = AESCipher()
data = {}
reqBodyJson = json.loads(request.data.decode())
reqEncryptStr = reqBodyJson['req']
resEncryptStr = reqBodyJson['res']
try:
data['req'] = AES.decrypt(reqEncryptStr)
except:
data['req'] = None
try:
data['res'] = AES.decrypt(resEncryptStr)
except:
data['res'] = None
return data
if __name__ == '__main__':
app.run(
debug=True,
port=5001
)
AES CBC 加密、解密方法
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# time : 2022/1/8
# __author__ = Ysc
from Crypto.Cipher import AES
import base64
class AESCipher:
def __init__(self):
self.key = bytes("1234567812345678", encoding='utf-8')
self.iv = bytes("1234567812345678", encoding='utf-8')
def pkcs7padding(self, text):
"""
明文使用PKCS7填充
最终调用AES加密方法时,传入的是一个byte数组,要求是16的整数倍,因此需要对明文进行处理
:param text: 待加密内容(明文)
:return:
"""
bs = AES.block_size # 16
length = len(text)
bytes_length = len(bytes(text, encoding='utf-8'))
# tips:utf-8编码时,英文占1个byte,而中文占3个byte
padding_size = length if (bytes_length == length) else bytes_length
padding = bs - padding_size % bs
# tips:chr(padding)看与其它语言的约定,有的会使用'\0'
padding_text = chr(padding) * padding
return text + padding_text
def pkcs7unpadding(self, text):
"""
处理使用PKCS7填充过的数据
:param text: 解密后的字符串
:return:
"""
try:
length = len(text)
unpadding = ord(text[length - 1])
return text[0:length - unpadding]
except Exception as e:
pass
def encrypt(self, content):
"""
AES加密
key,iv使用同一个
模式cbc
填充pkcs7
:param key: 密钥
:param content: 加密内容
:return:
"""
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
# 处理明文
content_padding = self.pkcs7padding(content)
# 加密
aes_encode_bytes = cipher.encrypt(bytes(content_padding, encoding='utf-8'))
# 重新编码
result = str(base64.b64encode(aes_encode_bytes), encoding='utf-8')
return result
def decrypt(self, content):
"""
AES解密
key,iv使用同一个
模式cbc
去填充pkcs7
:param key:
:param content:
:return:
"""
try:
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
# base64解码
aes_encode_bytes = base64.b64decode(content)
# 解密
aes_decode_bytes = cipher.decrypt(aes_encode_bytes)
# 重新编码
result = str(aes_decode_bytes, encoding='utf-8')
# 去除填充内容
result = self.pkcs7unpadding(result)
except Exception as e:
pass
if result == None:
return ""
else:
return result
反编译 charles.jar 注入自定义 class,并在右键菜单显示
jadx-gui、jd-gui、IDEA
上面的参考文章写的已经很详细了,这里就不多概述怎么 '嗅' 到要改哪里的
testDecrypt/src/com/xk72/charles/gui/transaction/actions/TestDecrypt.java
com/xk72/charles/gui/transaction/popups/TransactionViewerPopupMenu.class
ctrl + s 另存为 java 文件至项目目录下
可以看到 Base64DecodeAction、CopyToClipboardAction 有错误提示
我们先把这两个文件也保存下来瞅瞅,可以看到有一些反编译之后的小错误
改改让代码可以正常编译不报错.
package com.xk72.charles.gui.transaction.actions;
import com.xk72.charles.CharlesContext;
import com.xk72.charles.gui.lib.yLDW;
import com.xk72.charles.gui.transaction.lib.HexAsciiTextPane;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.util.Base64;
import javax.swing.AbstractAction;
import javax.swing.JScrollPane;
import javax.swing.text.JTextComponent;
public abstract class Base64DecodeAction extends AbstractAction {
private final Component source;
public class Text extends Base64DecodeAction {
private final String text;
public Text(String str) {
super((Component) null);
this.text = str;
}
public Text(String str, Component component) {
super(component);
this.text = str;
}
/* access modifiers changed from: protected */
public String getBody() {
return this.text;
}
}
public class TextComponent extends Base64DecodeAction {
private final JTextComponent component;
public TextComponent(JTextComponent jTextComponent) {
super(jTextComponent);
this.component = jTextComponent;
}
/* access modifiers changed from: protected */
public String getBody() {
String selectedText = this.component.getSelectedText();
return selectedText == null ? this.component.getText() : selectedText;
}
}
protected Base64DecodeAction(Component component) {
super("Base 64 Decode");
this.source = component;
}
public void actionPerformed(ActionEvent actionEvent) {
try {
byte[] decode = Base64.getDecoder().decode(getBody());
HexAsciiTextPane hexAsciiTextPane = new HexAsciiTextPane();
hexAsciiTextPane.setEditable(false);
hexAsciiTextPane.setBytes(decode);
JScrollPane jScrollPane = new JScrollPane(hexAsciiTextPane);
jScrollPane.setPreferredSize(new Dimension(700, 200));
yLDW.OZtq(jScrollPane, this.source, (Point) null);
} catch (Exception e) {
CharlesContext.getInstance().error("Failed to decode Base 64. Probably not valid Base 64 input.");
}
}
/* access modifiers changed from: protected */
public abstract String getBody();
}
package com.xk72.charles.gui.transaction.actions;
import com.xk72.charles.CharlesContext;
import com.xk72.charles.gui.lib.yLDW;
import com.xk72.charles.gui.transaction.lib.HexAsciiTextPane;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.util.Base64;
import javax.swing.AbstractAction;
import javax.swing.JScrollPane;
import javax.swing.text.JTextComponent;
public abstract class Base64DecodeAction extends AbstractAction {
private final Component source;
public static class Text extends Base64DecodeAction {
private final String text;
public Text(String str) {
super((Component) null);
this.text = str;
}
public Text(String str, Component component) {
super(component);
this.text = str;
}
/* access modifiers changed from: protected */
public String getBody() {
return this.text;
}
}
public static class TextComponent extends Base64DecodeAction {
private final JTextComponent component;
public TextComponent(JTextComponent jTextComponent) {
super(jTextComponent);
this.component = jTextComponent;
}
/* access modifiers changed from: protected */
public String getBody() {
String selectedText = this.component.getSelectedText();
return selectedText == null ? this.component.getText() : selectedText;
}
}
protected Base64DecodeAction(Component component) {
super("Base 64 Decode");
this.source = component;
}
public void actionPerformed(ActionEvent actionEvent) {
try {
byte[] decode = Base64.getDecoder().decode(getBody());
HexAsciiTextPane hexAsciiTextPane = new HexAsciiTextPane();
hexAsciiTextPane.setEditable(false);
hexAsciiTextPane.setBytes(decode);
JScrollPane jScrollPane = new JScrollPane(hexAsciiTextPane);
jScrollPane.setPreferredSize(new Dimension(700, 200));
yLDW.OZtq(jScrollPane, this.source, (Point) null);
} catch (Exception e) {
CharlesContext.getInstance().error("Failed to decode Base 64. Probably not valid Base 64 input.");
}
}
/* access modifiers changed from: protected */
public abstract String getBody();
}
package com.xk72.charles.gui.transaction.actions;
import com.xk72.charles.gui.lib.ExtendedJOptionPane;
import com.xk72.charles.gui.transaction.viewers.gen.Hyck;
import com.xk72.charles.model.Transaction;
import com.xk72.proxy.Fields;
import com.xk72.proxy.http2.Http2Fields;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.text.JTextComponent;
public abstract class CopyToClipboardAction extends AbstractAction {
public class CurlCommand extends CopyToClipboardAction {
private static final Set<String> OZtq = new HashSet();
private final Transaction transaction;
public CurlCommand(Transaction transaction2) {
super("Copy cURL Request");
OZtq.add("Content-Length".toLowerCase());
OZtq.add("Transfer-Encoding".toLowerCase());
OZtq.add("Connection".toLowerCase());
OZtq.add("Content-Encoding".toLowerCase());
OZtq.add("Accept-Encoding".toLowerCase());
this.transaction = transaction2;
}
private static void OZtq(StringBuilder sb, String str) {
if (str.indexOf(34) < 0) {
sb.append('\"').append(str).append('\"');
} else if (str.indexOf(39) < 0) {
sb.append('\'').append(str).append('\'');
} else {
sb.append('\"');
int i = 0;
int indexOf = str.indexOf(34);
while (indexOf >= 0) {
sb.append(str, i, indexOf);
sb.append('\\').append('\"');
i = indexOf + 1;
indexOf = str.indexOf(34, i);
}
sb.append(str, i, str.length());
sb.append('\"');
}
}
/* access modifiers changed from: protected */
public Transferable getBody() {
boolean z;
StringBuilder sb = new StringBuilder();
sb.append("curl ");
Fields requestHeader = this.transaction.getRequestHeader();
if (requestHeader instanceof Http2Fields) {
sb.append("-H 'Host").append(": ").append(requestHeader.getHost()).append("' ");
String cookies = requestHeader.getCookies();
if (cookies != null) {
sb.append("-H 'Cookie").append(": ").append(cookies).append("' ");
}
}
int i = 0;
boolean z2 = false;
boolean z3 = false;
while (i < requestHeader.getFieldCount()) {
String fieldName = requestHeader.getFieldName(i);
if (OZtq.contains(fieldName.toLowerCase())) {
if (fieldName.equalsIgnoreCase("Accept-Encoding")) {
String fieldValue = requestHeader.getFieldValue(i);
if (fieldValue.contains("gzip") || fieldValue.contains("deflate")) {
z2 = true;
}
z = z2;
}
z = z2;
} else {
if (!(requestHeader instanceof Http2Fields) || (!fieldName.startsWith(":") && !fieldName.equalsIgnoreCase("Host") && !fieldName.equalsIgnoreCase("Cookie"))) {
if (!this.transaction.hasRequestBody() || !"Content-Type".equals(fieldName) || !"application/x-www-form-urlencoded".equals(requestHeader.getFieldValue(i))) {
sb.append("-H '").append(fieldName);
String fieldValue2 = requestHeader.getFieldValue(i);
if (fieldValue2 == null) {
sb.append(";");
} else {
sb.append(": ").append(fieldValue2);
}
sb.append("' ");
} else {
z = z2;
z3 = true;
}
}
z = z2;
}
i++;
z2 = z;
}
if (this.transaction.hasRequestBody()) {
if (z3) {
sb.append("--data ");
} else {
sb.append("--data-binary ");
}
OZtq(sb, this.transaction.getDecodedRequestBodyAsString());
sb.append(' ');
if (!"POST".equals(this.transaction.getMethod())) {
sb.append("-X ").append(this.transaction.getMethod()).append(' ');
}
} else if (!"GET".equals(this.transaction.getMethod())) {
sb.append("-X ").append(this.transaction.getMethod()).append(' ');
}
if (z2) {
sb.append("--compressed ");
}
String externalForm = this.transaction.toURL().toExternalForm();
sb.append("'");
sb.append(externalForm);
sb.append("'");
return new StringSelection(sb.toString());
}
}
public class Request extends CopyToClipboardAction {
private final Transaction transaction;
public Request(Transaction transaction2) {
super("Copy Request");
this.transaction = transaction2;
}
/* access modifiers changed from: protected */
public Transferable getBody() {
String decodedRequestBodyAsString = this.transaction.getDecodedRequestBodyAsString();
if (decodedRequestBodyAsString != null) {
return new StringSelection(decodedRequestBodyAsString);
}
return null;
}
}
public class Response extends CopyToClipboardAction {
private final Transaction transaction;
public Response(Transaction transaction2) {
super("Copy Response");
this.transaction = transaction2;
}
/* access modifiers changed from: protected */
public Transferable getBody() {
if (this.transaction.getResponseHeader() != null && this.transaction.getResponseSize() > 0 && Hyck.Bgcz(this.transaction)) {
return new idWS(this.transaction.getDecodedResponseBody());
}
String decodedResponseBodyAsString = this.transaction.getDecodedResponseBodyAsString();
if (decodedResponseBodyAsString != null) {
return new StringSelection(decodedResponseBodyAsString);
}
return null;
}
}
public class Text extends CopyToClipboardAction {
private final String text;
public Text(String str) {
super("Copy Selection");
this.text = str;
}
/* access modifiers changed from: protected */
public Transferable getBody() {
return new StringSelection(this.text);
}
}
public class TextComponent extends CopyToClipboardAction {
private final JTextComponent component;
public TextComponent(JTextComponent jTextComponent) {
super("Copy Selection");
this.component = jTextComponent;
}
/* access modifiers changed from: protected */
public Transferable getBody() {
String selectedText = this.component.getSelectedText();
if (selectedText == null) {
selectedText = this.component.getText();
}
return new StringSelection(selectedText);
}
}
protected CopyToClipboardAction(String str) {
super(str);
}
public void actionPerformed(ActionEvent actionEvent) {
try {
Transferable body = getBody();
if (body != null) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(body, (ClipboardOwner) null);
}
} catch (IOException e) {
ExtendedJOptionPane.OZtq((Component) actionEvent.getSource(), e, "Copy To Clipboard Error", 0);
}
}
/* access modifiers changed from: protected */
public abstract Transferable getBody();
}
package com.xk72.charles.gui.transaction.actions;
import com.xk72.charles.gui.lib.ExtendedJOptionPane;
import com.xk72.charles.gui.transaction.viewers.gen.Hyck;
import com.xk72.charles.model.Transaction;
import com.xk72.proxy.Fields;
import com.xk72.proxy.http2.Http2Fields;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.text.JTextComponent;
public abstract class CopyToClipboardAction extends AbstractAction {
public static class CurlCommand extends CopyToClipboardAction {
private static final Set<String> OZtq = new HashSet();
private final Transaction transaction;
public CurlCommand(Transaction transaction2) {
super("Copy cURL Request");
OZtq.add("Content-Length".toLowerCase());
OZtq.add("Transfer-Encoding".toLowerCase());
OZtq.add("Connection".toLowerCase());
OZtq.add("Content-Encoding".toLowerCase());
OZtq.add("Accept-Encoding".toLowerCase());
this.transaction = transaction2;
}
private static void OZtq(StringBuilder sb, String str) {
if (str.indexOf(34) < 0) {
sb.append('\"').append(str).append('\"');
} else if (str.indexOf(39) < 0) {
sb.append('\'').append(str).append('\'');
} else {
sb.append('\"');
int i = 0;
int indexOf = str.indexOf(34);
while (indexOf >= 0) {
sb.append(str, i, indexOf);
sb.append('\\').append('\"');
i = indexOf + 1;
indexOf = str.indexOf(34, i);
}
sb.append(str, i, str.length());
sb.append('\"');
}
}
/* access modifiers changed from: protected */
public Transferable getBody() {
boolean z;
StringBuilder sb = new StringBuilder();
sb.append("curl ");
Fields requestHeader = this.transaction.getRequestHeader();
if (requestHeader instanceof Http2Fields) {
sb.append("-H 'Host").append(": ").append(requestHeader.getHost()).append("' ");
String cookies = requestHeader.getCookies();
if (cookies != null) {
sb.append("-H 'Cookie").append(": ").append(cookies).append("' ");
}
}
int i = 0;
boolean z2 = false;
boolean z3 = false;
while (i < requestHeader.getFieldCount()) {
String fieldName = requestHeader.getFieldName(i);
if (OZtq.contains(fieldName.toLowerCase())) {
if (fieldName.equalsIgnoreCase("Accept-Encoding")) {
String fieldValue = requestHeader.getFieldValue(i);
if (fieldValue.contains("gzip") || fieldValue.contains("deflate")) {
z2 = true;
}
z = z2;
}
z = z2;
} else {
if (!(requestHeader instanceof Http2Fields) || (!fieldName.startsWith(":") && !fieldName.equalsIgnoreCase("Host") && !fieldName.equalsIgnoreCase("Cookie"))) {
if (!this.transaction.hasRequestBody() || !"Content-Type".equals(fieldName) || !"application/x-www-form-urlencoded".equals(requestHeader.getFieldValue(i))) {
sb.append("-H '").append(fieldName);
String fieldValue2 = requestHeader.getFieldValue(i);
if (fieldValue2 == null) {
sb.append(";");
} else {
sb.append(": ").append(fieldValue2);
}
sb.append("' ");
} else {
z = z2;
z3 = true;
}
}
z = z2;
}
i++;
z2 = z;
}
if (this.transaction.hasRequestBody()) {
if (z3) {
sb.append("--data ");
} else {
sb.append("--data-binary ");
}
OZtq(sb, this.transaction.getDecodedRequestBodyAsString());
sb.append(' ');
if (!"POST".equals(this.transaction.getMethod())) {
sb.append("-X ").append(this.transaction.getMethod()).append(' ');
}
} else if (!"GET".equals(this.transaction.getMethod())) {
sb.append("-X ").append(this.transaction.getMethod()).append(' ');
}
if (z2) {
sb.append("--compressed ");
}
String externalForm = this.transaction.toURL().toExternalForm();
sb.append("'");
sb.append(externalForm);
sb.append("'");
return new StringSelection(sb.toString());
}
}
public class Request extends CopyToClipboardAction {
private final Transaction transaction;
public Request(Transaction transaction2) {
super("Copy Request");
this.transaction = transaction2;
}
/* access modifiers changed from: protected */
public Transferable getBody() {
String decodedRequestBodyAsString = this.transaction.getDecodedRequestBodyAsString();
if (decodedRequestBodyAsString != null) {
return new StringSelection(decodedRequestBodyAsString);
}
return null;
}
}
public class Response extends CopyToClipboardAction {
private final Transaction transaction;
public Response(Transaction transaction2) {
super("Copy Response");
this.transaction = transaction2;
}
/* access modifiers changed from: protected */
public Transferable getBody() {
if (this.transaction.getResponseHeader() != null && this.transaction.getResponseSize() > 0 && Hyck.Bgcz(this.transaction)) {
return new idWS(this.transaction.getDecodedResponseBody());
}
String decodedResponseBodyAsString = this.transaction.getDecodedResponseBodyAsString();
if (decodedResponseBodyAsString != null) {
return new StringSelection(decodedResponseBodyAsString);
}
return null;
}
}
public static class Text extends CopyToClipboardAction {
private final String text;
public Text(String str) {
super("Copy Selection");
this.text = str;
}
/* access modifiers changed from: protected */
public Transferable getBody() {
return new StringSelection(this.text);
}
}
public static class TextComponent extends CopyToClipboardAction {
private final JTextComponent component;
public TextComponent(JTextComponent jTextComponent) {
super("Copy Selection");
this.component = jTextComponent;
}
/* access modifiers changed from: protected */
public Transferable getBody() {
String selectedText = this.component.getSelectedText();
if (selectedText == null) {
selectedText = this.component.getText();
}
return new StringSelection(selectedText);
}
}
protected CopyToClipboardAction(String str) {
super(str);
}
public void actionPerformed(ActionEvent actionEvent) {
Transferable body = getBody();
if (body != null) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(body, (ClipboardOwner) null);
}
}
/* access modifiers changed from: protected */
public abstract Transferable getBody();
}
根据上面的参考文章,我们已经可以知道只需要接收下 JTextComponent 就可以获取到当前 TEXT 数据,那我们先试验下
新建一个 TestDecryptOne.java
package com.xk72.charles.gui.transaction.actions;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;
/**
* DEMO1
*
* @author ysc
* @date 2022/02/10 15:22
**/
public class TestDecryptOne extends AbstractAction {
private static final Logger logger = Logger.getLogger("test decrypt one");
private final JTextComponent component;
public TestDecryptOne(JTextComponent jTextComponent) {
// 按钮名
super("test decrypt one");
this.component = jTextComponent;
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
logger.info(this.component.getSelectedText());
}
@Override
public boolean accept(Object sender) {
return false;
}
}
然后修改 TransactionViewerPopupMenu.java
add((Action)new CopyToClipboardAction.TextComponent((JTextComponent)component));
add((Action)new Base64DecodeAction.TextComponent((JTextComponent)component));
# 新增下面这一行
add(new TestDecryptOne((JTextComponent)component));
编译生成 out 目录
# 复制一份
copy libs\charles.jar out\production\testDecrypt\
cd out\production\testDecrypt
# 注入编译好的class
jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\TestDecryptOne.class
jar -uvf charles.jar com\xk72\charles\gui\transaction\popups\TransactionViewerPopupMenu.class
然后将注入过 class 的 charles.jar 替换原有安装目录 lib 目录下 charles.jar
可以看到,按钮已经出来了,
我们右键点击下. 选中 request 时 log 正常打印,response 却显示 null.
而且由于我们的解密接口需要 入参出参组合作为参数, 由此看来 JTextComponent 无法满足我们.
分析之后我们发现 Transaction 对象包含 getDecodedRequestBodyAsString() 以及 getDecodedResponseBodyAsString() 方法.因此我们改下代码
新建 TestDecrypt.java
package com.xk72.charles.gui.transaction.actions;
import com.xk72.charles.model.Transaction;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;
/**
* demo
*
* @author ysc
* @date 2022/02/10 10:52actionPerformed
**/
public class TestDecrypt extends AbstractAction {
public final Transaction transaction;
private static final Logger logger = Logger.getLogger("test decrypt");
public TestDecrypt(Transaction transaction) {
super("test decrypt");
this.transaction = transaction;
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
String requestString = transaction.getDecodedRequestBodyAsString();
String responseString = transaction.getDecodedResponseBodyAsString();
logger.warning("__________________________________");
logger.info(requestString);
logger.info(responseString);
logger.warning("__________________________________");
}
@Override
public boolean accept(Object sender) {
return false;
}
}
修改 TransactionViewerPopupMenu.java
// 定义 Transaction
private final Transaction transaction;
public TransactionViewerPopupMenu(Transaction paramTransaction) {
super(paramTransaction, null, null, null);
// 接收
this.transaction = paramTransaction;
}
add((Action)new CopyToClipboardAction.TextComponent((JTextComponent)component));
add((Action)new Base64DecodeAction.TextComponent((JTextComponent)component));
# 新增下面这一行
add(new TestDecryptOne((JTextComponent)component));
# 再新增一行接收 Transaction的
add(new TestDecrypt(this.transaction));
重新编译注入一下
jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\TestDecrypt.class
ok 到这一步,我们已经获取到了 requestBody 和 responseBody. 基本上就已经结束了.
为了省事我就不找 charles 自己的请求方法了,我们找一个 HttpClient.java 用来请求解密接口
HttpClient.java
package com.xk72.charles.gui.transaction.actions;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* 请求方法
*
* @author ysc test
* @date 2022/01/24 16:16
**/
public class HttpClient {
public static String doGet(String httpurl) {
HttpURLConnection connection = null;
InputStream is = null;
BufferedReader br = null;
String result = null;// 返回结果字符串
try {
// 创建远程url连接对象
URL url = new URL(httpurl);
// 通过远程url连接对象打开一个连接,强转成httpURLConnection类
connection = (HttpURLConnection) url.openConnection();
// 设置连接方式:get
connection.setRequestMethod("GET");
// 设置连接主机服务器的超时时间:15000毫秒
connection.setConnectTimeout(15000);
// 设置读取远程返回的数据时间:60000毫秒
connection.setReadTimeout(60000);
// 发送请求
connection.connect();
// 通过connection连接,获取输入流
if (connection.getResponseCode() == 200) {
is = connection.getInputStream();
// 封装输入流is,并指定字符集
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
// 存放数据
StringBuffer sbf = new StringBuffer();
String temp = null;
while ((temp = br.readLine()) != null) {
sbf.append(temp);
sbf.append("\r\n");
}
result = sbf.toString();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
connection.disconnect();// 关闭远程连接
}
return result;
}
public static String doPost(String httpUrl, String param) {
HttpURLConnection connection = null;
InputStream is = null;
OutputStream os = null;
BufferedReader br = null;
String result = null;
try {
URL url = new URL(httpUrl);
// 通过远程url连接对象打开连接
connection = (HttpURLConnection) url.openConnection();
// 设置连接请求方式
connection.setRequestMethod("POST");
// 设置连接主机服务器超时时间:15000毫秒
connection.setConnectTimeout(15000);
// 设置读取主机服务器返回数据超时时间:60000毫秒
connection.setReadTimeout(60000);
// 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
connection.setDoOutput(true);
// 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无
connection.setDoInput(true);
// 设置传入参数的格式:请求参数应该是 json 的形式。
connection.setRequestProperty("Content-Type", "application/json");
// 通过连接对象获取一个输出流
os = connection.getOutputStream();
// 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
os.write(param.getBytes());
// 通过连接对象获取一个输入流,向远程读取
if (connection.getResponseCode() == 200) {
is = connection.getInputStream();
// 对输入流对象进行包装:charset根据工作项目组的要求来设置
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
StringBuffer sbf = new StringBuffer();
String temp = null;
// 循环遍历一行一行读取数据
while ((temp = br.readLine()) != null) {
sbf.append(temp);
sbf.append("\r\n");
}
result = sbf.toString();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != os) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 断开与远程地址url的连接
connection.disconnect();
}
return result;
}
}
然后重新修改下 TestDecrypt.java 注入
jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\TestDecrypt.class
jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\HttpClient.class
package com.xk72.charles.gui.transaction.actions;
import com.xk72.charles.model.Transaction;
import org.json.JSONObject;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;
/**
* demo
*
* @author ysc
* @date 2022/02/10 10:52actionPerformed
**/
public class TestDecrypt extends AbstractAction {
public final Transaction transaction;
private static final Logger logger = Logger.getLogger("test decrypt");
public TestDecrypt(Transaction transaction) {
super("test decrypt");
this.transaction = transaction;
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
decrypt(this.transaction);
}
public static void decrypt(Transaction transaction){
String requestString = transaction.getDecodedRequestBodyAsString();
String responseString = transaction.getDecodedResponseBodyAsString();
JSONObject jsonObject = new JSONObject();
jsonObject.put("req",requestString);
jsonObject.put("res",responseString);
String decryptJsonString = HttpClient.doPost("http://127.0.0.1:5001/decrypt",jsonObject.toString());
JSONObject decryptJsonObj = new JSONObject(decryptJsonString);
JSONObject res = new JSONObject(decryptJsonObj.get("req").toString());
JSONObject req = new JSONObject(decryptJsonObj.get("res").toString());
WaringDialog("req",req.toString());
WaringDialog("res",res.toString());
}
public static void WaringDialog(String title, String content) {
JFrame JFrame = new JFrame(title);
JFrame.setPreferredSize(new Dimension(800, 500));
JTextArea textArea = new JTextArea();
textArea.setText(content + "\n");
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
JScrollPane jScrollPane = new JScrollPane(textArea);
jScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
jScrollPane.setAutoscrolls(false);
JFrame.setContentPane(jScrollPane);
JFrame.pack();
JFrame.setVisible(true);
}
@Override
public boolean accept(Object sender) {
return false;
}
}
大致思路及实现方式就是这样,各位可以根据自己的情况进行修改