通用技术 charles 增加 自定义解密功能

ysctest · 2022年06月02日 · 最后由 ysctest 回复于 2022年09月22日 · 6700 次阅读

背景

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

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

准备

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

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

app.py

 定义 127.0.0.1:5000/login 接口,逻辑:将密文入参解析为明文赋值给 data.data,并返回 data.timestamp。统一加密返回

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":"响应明文"}

#!/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 加密、解密方法

#!/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
package com.xk72.charles.gui.transaction.actions;

import com.xk72.charles.CharlesContext;
import com.xk72.charles.gui.lib.yLDW;
import com.xk72.charles.gui.transaction.lib.HexAsciiTextPane;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.util.Base64;
import javax.swing.AbstractAction;
import javax.swing.JScrollPane;
import javax.swing.text.JTextComponent;

public abstract class Base64DecodeAction extends AbstractAction {
    private final Component source;

    public class Text extends Base64DecodeAction {
        private final String text;

        public Text(String str) {
            super((Component) null);
            this.text = str;
        }

        public Text(String str, Component component) {
            super(component);
            this.text = str;
        }

        /* access modifiers changed from: protected */
        public String getBody() {
            return this.text;
        }
    }

    public class TextComponent extends Base64DecodeAction {
        private final JTextComponent component;

        public TextComponent(JTextComponent jTextComponent) {
            super(jTextComponent);
            this.component = jTextComponent;
        }

        /* access modifiers changed from: protected */
        public String getBody() {
            String selectedText = this.component.getSelectedText();
            return selectedText == null ? this.component.getText() : selectedText;
        }
    }

    protected Base64DecodeAction(Component component) {
        super("Base 64 Decode");
        this.source = component;
    }

    public void actionPerformed(ActionEvent actionEvent) {
        try {
            byte[] decode = Base64.getDecoder().decode(getBody());
            HexAsciiTextPane hexAsciiTextPane = new HexAsciiTextPane();
            hexAsciiTextPane.setEditable(false);
            hexAsciiTextPane.setBytes(decode);
            JScrollPane jScrollPane = new JScrollPane(hexAsciiTextPane);
            jScrollPane.setPreferredSize(new Dimension(700, 200));
            yLDW.OZtq(jScrollPane, this.source, (Point) null);
        } catch (Exception e) {
            CharlesContext.getInstance().error("Failed to decode Base 64. Probably not valid Base 64 input.");
        }
    }

    /* access modifiers changed from: protected */
    public abstract String getBody();
}

修改后 Base64DecodeAction
package com.xk72.charles.gui.transaction.actions;

import com.xk72.charles.CharlesContext;
import com.xk72.charles.gui.lib.yLDW;
import com.xk72.charles.gui.transaction.lib.HexAsciiTextPane;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.util.Base64;
import javax.swing.AbstractAction;
import javax.swing.JScrollPane;
import javax.swing.text.JTextComponent;

public abstract class Base64DecodeAction extends AbstractAction {
  private final Component source;

  public static class Text extends Base64DecodeAction {
    private final String text;

    public Text(String str) {
      super((Component) null);
      this.text = str;
    }

    public Text(String str, Component component) {
      super(component);
      this.text = str;
    }

    /* access modifiers changed from: protected */
    public String getBody() {
      return this.text;
    }
  }

  public static class TextComponent extends Base64DecodeAction {
    private final JTextComponent component;

    public TextComponent(JTextComponent jTextComponent) {
      super(jTextComponent);
      this.component = jTextComponent;
    }

    /* access modifiers changed from: protected */
    public String getBody() {
      String selectedText = this.component.getSelectedText();
      return selectedText == null ? this.component.getText() : selectedText;
    }
  }

  protected Base64DecodeAction(Component component) {
    super("Base 64 Decode");
    this.source = component;
  }

  public void actionPerformed(ActionEvent actionEvent) {
    try {
      byte[] decode = Base64.getDecoder().decode(getBody());
      HexAsciiTextPane hexAsciiTextPane = new HexAsciiTextPane();
      hexAsciiTextPane.setEditable(false);
      hexAsciiTextPane.setBytes(decode);
      JScrollPane jScrollPane = new JScrollPane(hexAsciiTextPane);
      jScrollPane.setPreferredSize(new Dimension(700, 200));
      yLDW.OZtq(jScrollPane, this.source, (Point) null);
    } catch (Exception e) {
      CharlesContext.getInstance().error("Failed to decode Base 64. Probably not valid Base 64 input.");
    }
  }

  /* access modifiers changed from: protected */
  public abstract String getBody();
}

修改前 CopyToClipboardAction
package com.xk72.charles.gui.transaction.actions;

import com.xk72.charles.gui.lib.ExtendedJOptionPane;
import com.xk72.charles.gui.transaction.viewers.gen.Hyck;
import com.xk72.charles.model.Transaction;
import com.xk72.proxy.Fields;
import com.xk72.proxy.http2.Http2Fields;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.text.JTextComponent;

public abstract class CopyToClipboardAction extends AbstractAction {

    public class CurlCommand extends CopyToClipboardAction {
        private static final Set<String> OZtq = new HashSet();
        private final Transaction transaction;

        public CurlCommand(Transaction transaction2) {
            super("Copy cURL Request");
            OZtq.add("Content-Length".toLowerCase());
            OZtq.add("Transfer-Encoding".toLowerCase());
            OZtq.add("Connection".toLowerCase());
            OZtq.add("Content-Encoding".toLowerCase());
            OZtq.add("Accept-Encoding".toLowerCase());
            this.transaction = transaction2;
        }

        private static void OZtq(StringBuilder sb, String str) {
            if (str.indexOf(34) < 0) {
                sb.append('\"').append(str).append('\"');
            } else if (str.indexOf(39) < 0) {
                sb.append('\'').append(str).append('\'');
            } else {
                sb.append('\"');
                int i = 0;
                int indexOf = str.indexOf(34);
                while (indexOf >= 0) {
                    sb.append(str, i, indexOf);
                    sb.append('\\').append('\"');
                    i = indexOf + 1;
                    indexOf = str.indexOf(34, i);
                }
                sb.append(str, i, str.length());
                sb.append('\"');
            }
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            boolean z;
            StringBuilder sb = new StringBuilder();
            sb.append("curl ");
            Fields requestHeader = this.transaction.getRequestHeader();
            if (requestHeader instanceof Http2Fields) {
                sb.append("-H 'Host").append(": ").append(requestHeader.getHost()).append("' ");
                String cookies = requestHeader.getCookies();
                if (cookies != null) {
                    sb.append("-H 'Cookie").append(": ").append(cookies).append("' ");
                }
            }
            int i = 0;
            boolean z2 = false;
            boolean z3 = false;
            while (i < requestHeader.getFieldCount()) {
                String fieldName = requestHeader.getFieldName(i);
                if (OZtq.contains(fieldName.toLowerCase())) {
                    if (fieldName.equalsIgnoreCase("Accept-Encoding")) {
                        String fieldValue = requestHeader.getFieldValue(i);
                        if (fieldValue.contains("gzip") || fieldValue.contains("deflate")) {
                            z2 = true;
                        }
                        z = z2;
                    }
                    z = z2;
                } else {
                    if (!(requestHeader instanceof Http2Fields) || (!fieldName.startsWith(":") && !fieldName.equalsIgnoreCase("Host") && !fieldName.equalsIgnoreCase("Cookie"))) {
                        if (!this.transaction.hasRequestBody() || !"Content-Type".equals(fieldName) || !"application/x-www-form-urlencoded".equals(requestHeader.getFieldValue(i))) {
                            sb.append("-H '").append(fieldName);
                            String fieldValue2 = requestHeader.getFieldValue(i);
                            if (fieldValue2 == null) {
                                sb.append(";");
                            } else {
                                sb.append(": ").append(fieldValue2);
                            }
                            sb.append("' ");
                        } else {
                            z = z2;
                            z3 = true;
                        }
                    }
                    z = z2;
                }
                i++;
                z2 = z;
            }
            if (this.transaction.hasRequestBody()) {
                if (z3) {
                    sb.append("--data ");
                } else {
                    sb.append("--data-binary ");
                }
                OZtq(sb, this.transaction.getDecodedRequestBodyAsString());
                sb.append(' ');
                if (!"POST".equals(this.transaction.getMethod())) {
                    sb.append("-X ").append(this.transaction.getMethod()).append(' ');
                }
            } else if (!"GET".equals(this.transaction.getMethod())) {
                sb.append("-X ").append(this.transaction.getMethod()).append(' ');
            }
            if (z2) {
                sb.append("--compressed ");
            }
            String externalForm = this.transaction.toURL().toExternalForm();
            sb.append("'");
            sb.append(externalForm);
            sb.append("'");
            return new StringSelection(sb.toString());
        }
    }

    public class Request extends CopyToClipboardAction {
        private final Transaction transaction;

        public Request(Transaction transaction2) {
            super("Copy Request");
            this.transaction = transaction2;
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            String decodedRequestBodyAsString = this.transaction.getDecodedRequestBodyAsString();
            if (decodedRequestBodyAsString != null) {
                return new StringSelection(decodedRequestBodyAsString);
            }
            return null;
        }
    }

    public class Response extends CopyToClipboardAction {
        private final Transaction transaction;

        public Response(Transaction transaction2) {
            super("Copy Response");
            this.transaction = transaction2;
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            if (this.transaction.getResponseHeader() != null && this.transaction.getResponseSize() > 0 && Hyck.Bgcz(this.transaction)) {
                return new idWS(this.transaction.getDecodedResponseBody());
            }
            String decodedResponseBodyAsString = this.transaction.getDecodedResponseBodyAsString();
            if (decodedResponseBodyAsString != null) {
                return new StringSelection(decodedResponseBodyAsString);
            }
            return null;
        }
    }

    public class Text extends CopyToClipboardAction {
        private final String text;

        public Text(String str) {
            super("Copy Selection");
            this.text = str;
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            return new StringSelection(this.text);
        }
    }

    public class TextComponent extends CopyToClipboardAction {
        private final JTextComponent component;

        public TextComponent(JTextComponent jTextComponent) {
            super("Copy Selection");
            this.component = jTextComponent;
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            String selectedText = this.component.getSelectedText();
            if (selectedText == null) {
                selectedText = this.component.getText();
            }
            return new StringSelection(selectedText);
        }
    }

    protected CopyToClipboardAction(String str) {
        super(str);
    }

    public void actionPerformed(ActionEvent actionEvent) {
        try {
            Transferable body = getBody();
            if (body != null) {
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(body, (ClipboardOwner) null);
            }
        } catch (IOException e) {
            ExtendedJOptionPane.OZtq((Component) actionEvent.getSource(), e, "Copy To Clipboard Error", 0);
        }
    }

    /* access modifiers changed from: protected */
    public abstract Transferable getBody();
}

修改后 CopyToClipboardAction
package com.xk72.charles.gui.transaction.actions;

import com.xk72.charles.gui.lib.ExtendedJOptionPane;
import com.xk72.charles.gui.transaction.viewers.gen.Hyck;
import com.xk72.charles.model.Transaction;
import com.xk72.proxy.Fields;
import com.xk72.proxy.http2.Http2Fields;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.text.JTextComponent;

public abstract class CopyToClipboardAction extends AbstractAction {

    public static class CurlCommand extends CopyToClipboardAction {
        private static final Set<String> OZtq = new HashSet();
        private final Transaction transaction;

        public CurlCommand(Transaction transaction2) {
            super("Copy cURL Request");
            OZtq.add("Content-Length".toLowerCase());
            OZtq.add("Transfer-Encoding".toLowerCase());
            OZtq.add("Connection".toLowerCase());
            OZtq.add("Content-Encoding".toLowerCase());
            OZtq.add("Accept-Encoding".toLowerCase());
            this.transaction = transaction2;
        }

        private static void OZtq(StringBuilder sb, String str) {
            if (str.indexOf(34) < 0) {
                sb.append('\"').append(str).append('\"');
            } else if (str.indexOf(39) < 0) {
                sb.append('\'').append(str).append('\'');
            } else {
                sb.append('\"');
                int i = 0;
                int indexOf = str.indexOf(34);
                while (indexOf >= 0) {
                    sb.append(str, i, indexOf);
                    sb.append('\\').append('\"');
                    i = indexOf + 1;
                    indexOf = str.indexOf(34, i);
                }
                sb.append(str, i, str.length());
                sb.append('\"');
            }
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            boolean z;
            StringBuilder sb = new StringBuilder();
            sb.append("curl ");
            Fields requestHeader = this.transaction.getRequestHeader();
            if (requestHeader instanceof Http2Fields) {
                sb.append("-H 'Host").append(": ").append(requestHeader.getHost()).append("' ");
                String cookies = requestHeader.getCookies();
                if (cookies != null) {
                    sb.append("-H 'Cookie").append(": ").append(cookies).append("' ");
                }
            }
            int i = 0;
            boolean z2 = false;
            boolean z3 = false;
            while (i < requestHeader.getFieldCount()) {
                String fieldName = requestHeader.getFieldName(i);
                if (OZtq.contains(fieldName.toLowerCase())) {
                    if (fieldName.equalsIgnoreCase("Accept-Encoding")) {
                        String fieldValue = requestHeader.getFieldValue(i);
                        if (fieldValue.contains("gzip") || fieldValue.contains("deflate")) {
                            z2 = true;
                        }
                        z = z2;
                    }
                    z = z2;
                } else {
                    if (!(requestHeader instanceof Http2Fields) || (!fieldName.startsWith(":") && !fieldName.equalsIgnoreCase("Host") && !fieldName.equalsIgnoreCase("Cookie"))) {
                        if (!this.transaction.hasRequestBody() || !"Content-Type".equals(fieldName) || !"application/x-www-form-urlencoded".equals(requestHeader.getFieldValue(i))) {
                            sb.append("-H '").append(fieldName);
                            String fieldValue2 = requestHeader.getFieldValue(i);
                            if (fieldValue2 == null) {
                                sb.append(";");
                            } else {
                                sb.append(": ").append(fieldValue2);
                            }
                            sb.append("' ");
                        } else {
                            z = z2;
                            z3 = true;
                        }
                    }
                    z = z2;
                }
                i++;
                z2 = z;
            }
            if (this.transaction.hasRequestBody()) {
                if (z3) {
                    sb.append("--data ");
                } else {
                    sb.append("--data-binary ");
                }
                OZtq(sb, this.transaction.getDecodedRequestBodyAsString());
                sb.append(' ');
                if (!"POST".equals(this.transaction.getMethod())) {
                    sb.append("-X ").append(this.transaction.getMethod()).append(' ');
                }
            } else if (!"GET".equals(this.transaction.getMethod())) {
                sb.append("-X ").append(this.transaction.getMethod()).append(' ');
            }
            if (z2) {
                sb.append("--compressed ");
            }
            String externalForm = this.transaction.toURL().toExternalForm();
            sb.append("'");
            sb.append(externalForm);
            sb.append("'");
            return new StringSelection(sb.toString());
        }
    }

    public class Request extends CopyToClipboardAction {
        private final Transaction transaction;

        public Request(Transaction transaction2) {
            super("Copy Request");
            this.transaction = transaction2;
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            String decodedRequestBodyAsString = this.transaction.getDecodedRequestBodyAsString();
            if (decodedRequestBodyAsString != null) {
                return new StringSelection(decodedRequestBodyAsString);
            }
            return null;
        }
    }

    public class Response extends CopyToClipboardAction {
        private final Transaction transaction;

        public Response(Transaction transaction2) {
            super("Copy Response");
            this.transaction = transaction2;
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            if (this.transaction.getResponseHeader() != null && this.transaction.getResponseSize() > 0 && Hyck.Bgcz(this.transaction)) {
                return new idWS(this.transaction.getDecodedResponseBody());
            }
            String decodedResponseBodyAsString = this.transaction.getDecodedResponseBodyAsString();
            if (decodedResponseBodyAsString != null) {
                return new StringSelection(decodedResponseBodyAsString);
            }
            return null;
        }
    }

    public static class Text extends CopyToClipboardAction {
        private final String text;

        public Text(String str) {
            super("Copy Selection");
            this.text = str;
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            return new StringSelection(this.text);
        }
    }

    public static class TextComponent extends CopyToClipboardAction {
        private final JTextComponent component;

        public TextComponent(JTextComponent jTextComponent) {
            super("Copy Selection");
            this.component = jTextComponent;
        }

        /* access modifiers changed from: protected */
        public Transferable getBody() {
            String selectedText = this.component.getSelectedText();
            if (selectedText == null) {
                selectedText = this.component.getText();
            }
            return new StringSelection(selectedText);
        }
    }

    protected CopyToClipboardAction(String str) {
        super(str);
    }

    public void actionPerformed(ActionEvent actionEvent) {
        Transferable body = getBody();
        if (body != null) {
            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(body, (ClipboardOwner) null);
        }
    }

    /* access modifiers changed from: protected */
    public abstract Transferable getBody();
}

解密方法 (重头戏)

根据上面的参考文章,我们已经可以知道只需要接收下 JTextComponent 就可以获取到当前 TEXT 数据,那我们先试验下
新建一个 TestDecryptOne.java

package com.xk72.charles.gui.transaction.actions;

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;

/**
 * DEMO1
 *
 * @author ysc
 * @date 2022/02/10 15:22
 **/
public class TestDecryptOne extends AbstractAction {
    private static final Logger logger = Logger.getLogger("test decrypt one");

    private final JTextComponent component;


    public TestDecryptOne(JTextComponent jTextComponent) {
        // 按钮名
        super("test decrypt one");
        this.component = jTextComponent;
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        logger.info(this.component.getSelectedText());
    }

    @Override
    public boolean accept(Object sender) {
        return false;
    }
}


然后修改 TransactionViewerPopupMenu.java

add((Action)new CopyToClipboardAction.TextComponent((JTextComponent)component));
add((Action)new Base64DecodeAction.TextComponent((JTextComponent)component));

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

编译生成 out 目录

# 复制一份
copy 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() 方法.因此我们改下代码

com.xk72.charles.model.Transaction

新建 TestDecrypt.java

package com.xk72.charles.gui.transaction.actions;

import com.xk72.charles.model.Transaction;

import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;

/**
 * demo
 *
 * @author ysc
 * @date 2022/02/10 10:52actionPerformed
 **/
public class TestDecrypt extends AbstractAction {
    public final Transaction transaction;
    private static final Logger logger = Logger.getLogger("test decrypt");

    public TestDecrypt(Transaction transaction) {
        super("test decrypt");
        this.transaction = transaction;
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {

        String requestString = transaction.getDecodedRequestBodyAsString();
        String responseString = transaction.getDecodedResponseBodyAsString();
        logger.warning("__________________________________");
        logger.info(requestString);
        logger.info(responseString);
        logger.warning("__________________________________");
    }


    @Override
    public boolean accept(Object sender) {
        return false;
    }
}

修改 TransactionViewerPopupMenu.java

// 定义 Transaction 
private final Transaction transaction;

public TransactionViewerPopupMenu(Transaction paramTransaction) {
  super(paramTransaction, null, null, null);
  // 接收
  this.transaction = paramTransaction;
}


add((Action)new CopyToClipboardAction.TextComponent((JTextComponent)component));
add((Action)new Base64DecodeAction.TextComponent((JTextComponent)component));

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

# 再新增一行接收 Transaction的
add(new TestDecrypt(this.transaction));

重新编译注入一下

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

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

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

package com.xk72.charles.gui.transaction.actions;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * 请求方法
 *
 * @author ysc test
 * @date 2022/01/24 16:16
 **/

public class HttpClient {
    public static String doGet(String httpurl) {
        HttpURLConnection connection = null;
        InputStream is = null;
        BufferedReader br = null;
        String result = null;// 返回结果字符串
        try {
            // 创建远程url连接对象
            URL url = new URL(httpurl);
            // 通过远程url连接对象打开一个连接,强转成httpURLConnection类
            connection = (HttpURLConnection) url.openConnection();
            // 设置连接方式:get
            connection.setRequestMethod("GET");
            // 设置连接主机服务器的超时时间:15000毫秒
            connection.setConnectTimeout(15000);
            // 设置读取远程返回的数据时间:60000毫秒
            connection.setReadTimeout(60000);
            // 发送请求
            connection.connect();
            // 通过connection连接,获取输入流
            if (connection.getResponseCode() == 200) {
                is = connection.getInputStream();
                // 封装输入流is,并指定字符集
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                // 存放数据
                StringBuffer sbf = new StringBuffer();
                String temp = null;
                while ((temp = br.readLine()) != null) {
                    sbf.append(temp);
                    sbf.append("\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 注入

jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\TestDecrypt.class
jar -uvf charles.jar com\xk72\charles\gui\transaction\actions\HttpClient.class
package com.xk72.charles.gui.transaction.actions;

import com.xk72.charles.model.Transaction;
import org.json.JSONObject;

import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;

/**
 * demo
 *
 * @author ysc
 * @date 2022/02/10 10:52actionPerformed
 **/
public class TestDecrypt extends AbstractAction {
    public final Transaction transaction;
    private static final Logger logger = Logger.getLogger("test decrypt");

    public TestDecrypt(Transaction transaction) {
        super("test decrypt");
        this.transaction = transaction;
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        decrypt(this.transaction);

    }

    public static void decrypt(Transaction transaction){

        String requestString = transaction.getDecodedRequestBodyAsString();
        String responseString = transaction.getDecodedResponseBodyAsString();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("req",requestString);
        jsonObject.put("res",responseString);
        String decryptJsonString = HttpClient.doPost("http://127.0.0.1:5001/decrypt",jsonObject.toString());

        JSONObject decryptJsonObj = new JSONObject(decryptJsonString);

        JSONObject res = new JSONObject(decryptJsonObj.get("req").toString());
        JSONObject req = new JSONObject(decryptJsonObj.get("res").toString());

        WaringDialog("req",req.toString());
        WaringDialog("res",res.toString());

    }
    public static void WaringDialog(String title, String content) {
        JFrame JFrame = new JFrame(title);
        JFrame.setPreferredSize(new Dimension(800, 500));
        JTextArea textArea = new JTextArea();
        textArea.setText(content + "\n");
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);

        JScrollPane jScrollPane  = new JScrollPane(textArea);
        jScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        jScrollPane.setAutoscrolls(false);
        JFrame.setContentPane(jScrollPane);
        JFrame.pack();
        JFrame.setVisible(true);
    }

    @Override
    public boolean accept(Object sender) {
        return false;
    }
}

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

共收到 3 条回复 时间 点赞
仅楼主可见
仅楼主可见
ysctest · #3 · 2022年09月22日 Author
仅楼主可见
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册