charles 增加 自定义解密功能

背景

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

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

准备

 因相关隐私政策,不便暴漏相关业务接口,为演示效果使用 falsk 编写了两个demo,server端,解密端

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

app.py

 定义 127.0.0.1:5000/login接口,逻辑:将密文入参解析为明文赋值给 data.data,并返回 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
25
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”:”响应明文”}

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__)


@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 加密、解密方法

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

charles目录

准备工具

jadx-gui、jd-gui、IDEA

开搞

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

1.idea新建项目 testDecrypt

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

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

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

整体结构

设置依赖

一路ok下一步

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

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

5.处理下错误信息

处理下语法判断
可以看到 Base64DecodeAction、CopyToClipboardAction 有错误提示
错误提示
我们先把这两个文件也保存下来瞅瞅,可以看到有一些反编译之后的小错误
CopyToClipboardAction
Base64DecodeAction
改改让代码可以正常编译不报错.

修改前 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
74
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
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
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
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
218
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
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
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

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
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

1
2
3
4
5
6
add((Action)new CopyToClipboardAction.TextComponent((JTextComponent)component));
add((Action)new Base64DecodeAction.TextComponent((JTextComponent)component));

# 新增下面这一行
add(new TestDecryptOne((JTextComponent)component));

编译生成 out目录

1
2
3
4
5
6
7
8
# 复制一份
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

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

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

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

新建 TestDecrypt.java

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
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义 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));

重新编译注入一下

1
jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\TestDecrypt.class

upload successful

upload successful

ok 到这一步,我们已经获取到了 requestBody 和 responseBody. 基本上就已经结束了.

为了省事我就不找charles自己的请求方法了,我们写一个 HttpClient.java 用来请求解密接口
HttpClient.java

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
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 注入

1
2
jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\TestDecrypt.class
jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\HttpClient.class
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
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;
}
}

成功

  • 本文作者: Ysc Test
  • 本文链接: https://ysctest.cn/posts/6ab6ec7.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 法律声明: 本博客提供的所有包括但不限于(文章和API)等服务,仅用于学习,技术分享、交流。不得用于违法犯罪、损害国家利益。非法使用者本站不承担任何法律责任,并且本站保留追究其法律责任的权力!!!