背景 接口请求通过特定方式进行了加密,在调试、测试时。查看入参、出参需要调用特定得解密接口解密获取 入参、出参明文,过于繁琐于是通过度娘确认了以下方案
编写 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, requestfrom AESCipherUtils import AESCipherimport timeapp = 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 import jsonfrom flask import Flask, requestfrom AESCipherUtils import AESCipherapp = 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 from Crypto.Cipher import AESimport base64class 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 length = len (text) bytes_length = len (bytes (text, encoding='utf-8' )) padding_size = length if (bytes_length == length) else bytes_length padding = bs - padding_size % bs 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) 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
开搞
上面的参考文章写的已经很详细了,这里就不多概述怎么 ‘嗅’ 到要改哪里的
1.idea新建项目 testDecrypt 2.创建和charles相同的代码路径 以下是例子: testDecrypt/src/com/xk72/charles/gui/transaction/actions/TestDecrypt.java
3.复制charles安装目录lib目录下 charles.jar ,到testDecrypt libs目录下,并设置依赖
4.使用jd-gui 打开libs 目录下 charles.jar. 选中 com/xk72/charles/gui/transaction/popups/TransactionViewerPopupMenu.class
ctrl + s 另存为java文件至项目目录下
5.处理下错误信息 可以看到 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 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; } public String getBody () { return this .text; } } public class TextComponent extends Base64DecodeAction { private final JTextComponent component; public TextComponent (JTextComponent jTextComponent) { super (jTextComponent); this .component = jTextComponent; } 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." ); } } 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; } 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; } 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." ); } } 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('\"' ); } } 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; } 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; } 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; } 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; } 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 ); } } 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('\"' ); } } 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; } 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; } 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; } 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; } 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 ); } } 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;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
可以看到,按钮已经出来了,
我们右键点击下. 选中request时 log 正常打印,response 却显示 null.
而且由于我们的解密接口需要 入参出参组合作为参数, 由此看来 JTextComponent 无法满足我们. 分析之后我们发现 Transaction 对象包含 getDecodedRequestBodyAsString() 以及 getDecodedResponseBodyAsString() 方法.因此我们改下代码
新建 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;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 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
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;public class HttpClient { public static String doGet (String httpurl) { HttpURLConnection connection = null ; InputStream is = null ; BufferedReader br = null ; String result = null ; try { URL url = new URL (httpurl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET" ); connection.setConnectTimeout(15000 ); connection.setReadTimeout(60000 ); connection.connect(); if (connection.getResponseCode() == 200 ) { is = connection.getInputStream(); 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); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST" ); connection.setConnectTimeout(15000 ); connection.setReadTimeout(60000 ); connection.setDoOutput(true ); connection.setDoInput(true ); connection.setRequestProperty("Content-Type" , "application/json" ); os = connection.getOutputStream(); os.write(param.getBytes()); if (connection.getResponseCode() == 200 ) { is = connection.getInputStream(); 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(); } } 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;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 ; } }