charles 增加 自定义解密功能

hcoder
hcoder
hcoder
订阅者
422
文章
0
评论
测试交流1 557字数 4339阅读14分27秒阅读模式

背景

东家的服务接口,请求时通过特定方式进行了加密,在调试、测试时。查看入参、出参需要调用特定得解密接口解密获取 入参、出参明文,过于繁琐于是通过度娘确认了以下方案

  • 编写 chrome extention 适用于浏览器端相关业务接口(已经编写完毕,下篇写)
  • charles、fiddler 扩展 抓包时 适用于其他各端相关业务接口

准备

因相关隐私政策,不便暴漏相关业务接口,为演示效果使用 falsk 编写了两个 demo,server 端,解密端文章源自玩技e族-https://www.playezu.com/190771.html

├─AESCipherUtils.py  # 加密、解密类
├─app.py     # 服务端
└─app2.py    # 解密端

app.py

定义 127.0.0.1:5000/login 接口,逻辑:将密文入参解析为明文赋值给 data.data,并返回 data.timestamp。统一加密返回文章源自玩技e族-https://www.playezu.com/190771.html

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
)

app2.py

127.0.0.1:5001/decrypt 接口逻辑,接受 json body 格式为{"req":"请求密文","res":"响应密文"} 处理并返回 {"req":"请求明文","res":"响应明文"}文章源自玩技e族-https://www.playezu.com/190771.html

#!/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
)

AESCipherUtils.py

AES CBC 加密、解密方法文章源自玩技e族-https://www.playezu.com/190771.html

#!/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)看与其它语言的约定,有的会使用''
        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 增加 自定义解密功能插图
文章源自玩技e族-https://www.playezu.com/190771.html

charles 增加 自定义解密功能插图1
文章源自玩技e族-https://www.playezu.com/190771.html

实现

反编译 charles.jar 注入自定义 class,并在右键菜单显示文章源自玩技e族-https://www.playezu.com/190771.html

参考文章 https://www.cnblogs.com/Baylor-Chen/p/14963207.html文章源自玩技e族-https://www.playezu.com/190771.html

charles 增加 自定义解密功能插图2
文章源自玩技e族-https://www.playezu.com/190771.html

准备工具

jadx-gui、jd-gui、IDEA文章源自玩技e族-https://www.playezu.com/190771.html

开搞

上面的参考文章写的已经很详细了,这里就不多概述怎么 '嗅' 到要改哪里的

1.idea 新建项目 testDecrypt

2.创建和 charles 相同的代码路径 以下是例子:

testDecrypt/src/com/xk72/charles/gui/transaction/actions/TestDecrypt.java

3.复制 charles 安装目录 lib 目录下 charles.jar ,到 testDecrypt libs 目录下,并设置依赖

charles 增加 自定义解密功能插图3

charles 增加 自定义解密功能插图4

charles 增加 自定义解密功能插图5

4.使用 jd-gui 打开 libs 目录下 charles.jar. 选中

com/xk72/charles/gui/transaction/popups/TransactionViewerPopupMenu.class
ctrl + s 另存为 java 文件至项目目录下

charles 增加 自定义解密功能插图6

charles 增加 自定义解密功能插图7

5.处理下错误信息

charles 增加 自定义解密功能插图8

可以看到 Base64DecodeAction、CopyToClipboardAction 有错误提示

charles 增加 自定义解密功能插图9

我们先把这两个文件也保存下来瞅瞅,可以看到有一些反编译之后的小错误

charles 增加 自定义解密功能插图10

charles 增加 自定义解密功能插图11

改改让代码可以正常编译不报错.

修改前 Base64DecodeAction
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();
}
修改后 Base64DecodeAction
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();
}
修改前 CopyToClipboardAction
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();
}
修改后 CopyToClipboardAction
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 libscharles.jar outproductiontestDecrypt
cd outproductiontestDecrypt
# 注入编译好的class
jar -uvf charles.jar comxk72charlesguitransactionactionsTestDecryptOne.class
jar -uvf charles.jar comxk72charlesguitransactionpopupsTransactionViewerPopupMenu.class

然后将注入过 class 的 charles.jar 替换原有安装目录 lib 目录下 charles.jar

可以看到,按钮已经出来了,

charles 增加 自定义解密功能插图12

我们右键点击下. 选中 request 时 log 正常打印,response 却显示 null.

charles 增加 自定义解密功能插图13

而且由于我们的解密接口需要 入参出参组合作为参数, 由此看来 JTextComponent 无法满足我们.
分析之后我们发现 Transaction 对象包含 getDecodedRequestBodyAsString() 以及 getDecodedResponseBodyAsString() 方法.因此我们改下代码

charles 增加 自定义解密功能插图14

新建 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 comxk72charlesguitransactionactionsTestDecrypt.class

charles 增加 自定义解密功能插图15

charles 增加 自定义解密功能插图16

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("rn");
}
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("rn");
}
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 comxk72charlesguitransactionactionsTestDecrypt.class
jar -uvf charles.jar comxk72charlesguitransactionactionsHttpClient.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;
}
}

charles 增加 自定义解密功能插图17

大致思路及实现方式就是这样,各位可以根据自己的情况进行修改测试显卡的软件

 
    • 陈卿
      陈卿 9

      仅楼主可见

    匿名

    发表评论

    匿名网友
    确定

    拖动滑块以完成验证