1,使用背景

最近工作中对公司接口进行抓包,发现接口路径和返回都是经过加密的,对于查看接口路径及接口返回结果带来了不便,于是想到了对Charles进行小改造,在Charles上增加一个按钮对加密的请求、响应结果解密,本质是执行一个Java方法,然后将解密结果通过文本框显示出来,效果如下

2,相关环境

Charles版本:4.6.2,界面是基于JDK11版本开发的

JDK版本:所以一定要用jdk11,不然会有坑

3,开发过程

3.1,新建普通工程Custom-Decrypt,引入Charles.jar

3.2,新建CustomDecrypt类,TransactionViewerPopupMenu类,代码如下

CustomDecrypt类中decrypt方法是解密方法,根据需要编写,代码注重实现可能有点low

package com.xk72.charles.gui.transaction.actions;
 
import com.xk72.charles.model.Transaction;
 
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.spec.KeySpec;
import java.util.logging.Logger;
 
/**
 * @author: create by libin
 * @date:2023/4/9
 */
public class CustomDecrypt extends AbstractAction {
    public final Transaction transaction;
    private static final Logger logger = Logger.getLogger("CustomDecryptLog");
 
    public CustomDecrypt(Transaction transaction) {
        super("CustomDecrypt");
        this.transaction = transaction;
    }
 
    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        String requestString = transaction.getDecodedRequestBodyAsString();
        String responseString = transaction.getDecodedResponseBodyAsString();
        logger.warning("__________________________________");
        logger.info("request" + requestString);
        logger.info("response" + responseString);
        logger.warning("__________________________________");
        try {
            String request;
            if (requestString == null || requestString.length() < 1) {
                request = "空";
            } else {
                request = decrypt(requestString, DES_REQUEST_KEY);
            }
            String respnse;
            if (responseString == null || responseString.length() < 1) {
                respnse = "空";
            } else {
                respnse = decrypt(responseString, DES_RESPONSE_KEY);
            }
            String method = transaction.getMethod();
            String host = transaction.getHost() + ":" + transaction.getPort();
            String path = transaction.getPath();
            String file = transaction.getFile();
            String query = transaction.getQuery();
            String content = "method:" + method + "\n" +
                    "host:" + host + "\n" +
                    "path:" + path + "\n" +
                    "file:" + file + "\n" +
                    "query:" + query + "\n";
            if ("GET".equals(method)) {
                String url = host + file;
                content += "url:" + url + "\n";
            }
    
 
            WaringDialog("request", content);
            WaringDialog("response", respnse);
        } catch (Exception e) {
            logger.warning("CustomDecrypt Exception" + e.getMessage());
        }
    }
 
 
 
 
    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;
    }
}
package com.xk72.charles.gui.transaction.popups;
 
import com.xk72.charles.gui.session.popups.TransactionPopupMenu;
import com.xk72.charles.gui.transaction.actions.Base64DecodeAction$Text;
import com.xk72.charles.gui.transaction.actions.Base64DecodeAction$TextComponent;
import com.xk72.charles.gui.transaction.actions.CopyToClipboardAction$Text;
import com.xk72.charles.gui.transaction.actions.CopyToClipboardAction$TextComponent;
import com.xk72.charles.gui.transaction.actions.CustomDecrypt;
import com.xk72.charles.model.Transaction;
 
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.MouseEvent;
 
public class TransactionViewerPopupMenu extends TransactionPopupMenu {
    // 定义 Transaction
    private final Transaction transaction;
 
    public TransactionViewerPopupMenu(Transaction paramTransaction) {
        super(paramTransaction, null, null, null);
        // 接收
        this.transaction = paramTransaction;
    }
 
    @Override
    protected void prepare(MouseEvent paramMouseEvent) {
        Component component = (Component)paramMouseEvent.getSource();
        if (component instanceof JTable) {
            JTable jTable = (JTable)component;
            Point point = paramMouseEvent.getPoint();
            int i = jTable.rowAtPoint(point);
            int j = jTable.columnAtPoint(point);
            if (i >= 0 && j >= 0) {
                Object object = jTable.getValueAt(i, j);
                if (object != null) {
                    add((Action)new CopyToClipboardAction$Text(object.toString()));
                    if (object instanceof String)
                        add((Action)new Base64DecodeAction$Text((String)object, component));
                    addSeparator();
                }
            }
        } else if (component instanceof JTextComponent) {
            add((Action)new CopyToClipboardAction$TextComponent((JTextComponent)component));
            add((Action)new Base64DecodeAction$TextComponent((JTextComponent)component));
            // 新增一个按钮,执行按钮的时候会调用CustomDecrypt的actionPerformed方法
            add((Action)new CustomDecrypt(this.transaction));
            addSeparator();
        }
        prepare(false);
    }
}

3.3,将上面的CustomDecrypt类和TransactionViewerPopupMenu类,编译成class

点击Build --> Build Project

3.4,执行命令前进入到outproductioncustom-decrypt路径

在custom-decrypt右键点击 选择 Open in Terminal

执行如下命令,意思是将这两个类的class加到charles.java包里面

jar -uvf D:\charles.jar com\xk72\charles\gui\transaction\actions\CustomDecrypt.class
jar -uvf D:\charles.jar com\xk72\charles\gui\transaction\actions\AESUtil.class
jar -uvf D:\charles.jar com\xk72\charles\gui\transaction\popups\TransactionViewerPopupMenu.class

3.5,将项目中libs下的charles.jar替换到charles的安装路径下的lib目录,例如:D:charleslocationlib

4.测试

替换后,打开charles软件,例如编写一个测试接口(根据需要自己编写),抓包如下

5.问题

5.1 写入现有的 jar 文件时出错

"jar -uvf" 写入现有的 jar 文件时出错 at sun.tools.jar.Main.run(Main.java:286)
原因: 在jar包已经被当前应用加载了,不能再去修改。重新copy一个文件 在新的jar文件里修改。不要修改idea里依赖的目录文件

5.2 编译jdk版本不对

 has been compiled by a more recent version of the Java Runtime (class file version 59.0), this version of the Java Runtime only recognizes class file versions up to 55.0

charles用的java11 。而那三个文件编译的时候用的java 15。

9 = Java 5
50 = Java 6
51 = Java 7
52 = Java 8
53 = Java 9
54 = Java 10
55 = Java 11
56 = Java 12
57 = Java 13
58 = Java 14
59 = Java 15
60 = Java 16
61 = Java 17
62 = Java 18
63 = Java 19