Charles 增加自定义解密功能
Flask服务端演示目录
1
2
3├─AESCipherUtils.py # 加密、解密类
├─app.py # 服务端
└─app2.py # 解密端app.py
服务端定义 127.0.0.1:5000/login接口,逻辑:将密文入参解析为明文赋值给
data
,并返回timestamp
。统一加密返回1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25from flask import Flask, request
from AESCipherUtils import AESCipher
import time
app = Flask(__name__)
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”:”响应明文”}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38#!/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__)
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工具类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92#!/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,并在右键菜单显示,参考文章 https://www.cnblogs.com/Baylor-Chen/p/14963207.html
准备工具
jadx-gui
、jd-gui
、IDEA
具体实现
上面的参考文章写的已经很详细了,这里就不多概述怎么 ‘嗅’ 到要改哪里的
idea新建项目
testDecrypt
创建和charles相同的代码路径 以下是例子:
testDecrypt/src/com/xk72/charles/gui/transaction/actions/TestDecrypt.java
复制charles安装目录lib目录下 charles.jar ,到testDecrypt libs目录下,并设置依赖
使用jd-gui 打开libs 目录下 charles.jar. 选中
com/xk72/charles/gui/transaction/popups/TransactionViewerPopupMenu.class
ctrl + s 另存为java文件至项目目录下处理下错误信息
可以看到 Base64DecodeAction、CopyToClipboardAction 有错误提示
我们先把这两个文件也保存下来瞅瞅,可以看到有一些反编译之后的小错误
改改让代码可以正常编译不报错.
修改前 Base64DecodeAction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74package 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73package 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218package 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214package 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
1 | package com.xk72.charles.gui.transaction.actions; |
然后修改TransactionViewerPopupMenu.java
1 | add((Action)new CopyToClipboardAction.TextComponent((JTextComponent)component)); |
编译生成 out目录
1 | # 复制一份 |
然后将注入过class的charles.jar 替换原有安装目录lib目录下charles.jar,可以看到,按钮已经出来了,
我们右键点击下. 选中request时 log 正常打印,response 却显示 null.
而且由于我们的解密接口需要 入参出参组合作为参数, 由此看来 JTextComponent 无法满足我们.
分析之后我们发现 Transaction 对象包含 getDecodedRequestBodyAsString() 以及 getDecodedResponseBodyAsString() 方法.因此我们改下代码
新建 TestDecrypt.java
1 | package com.xk72.charles.gui.transaction.actions; |
修改 TransactionViewerPopupMenu.java
1 | // 定义 Transaction |
重新编译注入一下
1 | jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\TestDecrypt.class |
ok 到这一步,我们已经获取到了 requestBody 和 responseBody. 基本上就已经结束了.
为了省事我就不找charles自己的请求方法了,我们写一个 HttpClient.java 用来请求解密接口
HttpClient.java
1 | package com.xk72.charles.gui.transaction.actions; |
然后重新修改下 TestDecrypt.java 注入
1 | jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\TestDecrypt.class |
1 | package com.xk72.charles.gui.transaction.actions; |