破解 Charles 可以学到逆向和修改 Java 程序
破解最新抓包工具 Charles
Charles 是常用的抓包工具,使用 Java 语言写的,支持跨平台。目前最新版本是 4.6.1,本文记录下对该版本的破解。
工具
JD-GUI
反编译 Java 程序,可将 Java class 文件反编译为 Java 源码。下载地址.
Recaf
Java bytecode editor,可对 class 文件进行修改。 下载地址.
逆向 Charles
使用 JD-GUI 打开 lib 目录下的 charles.jar,注册和校验逻辑都在该 jar 中。
寻找关键逻辑
点击菜单 Help –> Register Charles,会弹出下面注册对话框:

通过字符串 “Registered Name” 找到上面对话框代码 RegisterFrame:
package com.xk72.charles.gui.frames;
... ...
public class RegisterFrame extends JDialog {
... ...
public RegisterFrame(Frame paramFrame) {
super(paramFrame, true);
setTitle("Register Charles");
this.tName = new JTextField(20); // 输入 Name
this.tSerial = new JTextField(20); // 输入的 Key
this.bRegister = new JButton("Register");
this.bCancel = new JButton("Cancel");
Container container = getContentPane();
container.setLayout((LayoutManager)new MigLayout("wrap,fill", "[label][fill,grow]"));
container.add(new JLabel("Registered Name:"));
container.add(this.tName);
container.add(new JLabel("License Key:"));
container.add(this.tSerial);
container.add(this.bCancel, "tag cancel,split 2,span,center");
container.add(this.bRegister, "tag ok");
this.bCancel.addActionListener(new CHDR(this));
this.bRegister.addActionListener(new mYlx(this)); // 此处为点击 Register 后的逻辑
pack();
if (paramFrame != null) {
Point point = new Point(paramFrame.getLocation());
point.translate(20, 20);
setLocation(point);
}
getRootPane().setDefaultButton(this.bRegister);
getRootPane().getInputMap(1).put(KeyStroke.getKeyStroke("ESCAPE"), "escape");
getRootPane().getActionMap().put("escape", new RegisterFrame$3(this));
}
... ...
}
查看点击 Button 后的逻辑:
package com.xk72.charles.gui.frames;
... ...
class mYlx implements ActionListener {
mYlx(RegisterFrame paramRegisterFrame) {}
public void actionPerformed(ActionEvent paramActionEvent) {
String str1 = this.XdKP.tName.getText().trim();
String str2 = this.XdKP.tSerial.getText().trim();
if (str1.length() > 0 && str2.length() > 0) { // str1 和 str2 分别代表之前输入的 Name 和 Key
String str = MfoV.XdKP(str1, str2);
if (str != null) {
ExtendedJOptionPane.XdKP(this.XdKP, str, "Charles Registration", 2);
} else { // 若调用 MfoV.XdKP 返回为 NULL,则表示成功注册
ExtendedJOptionPane.XdKP(this.XdKP, "Thank you for registering. Charles will now close. Please start Charles again to continue.", "Charles Registration", 1);
CharlesContext charlesContext = CharlesContext.getInstance();
charlesContext.getConfiguration().getRegistrationConfiguration().setName(str1);
charlesContext.getConfiguration().getRegistrationConfiguration().setKey(str2);
charlesContext.exit(0, true);
}
}
}
}
上述可以看到,点击注册后,会调用 MfoV.XdKP 方法,若该方法返回 NULL 表示注册成功,返回非 NULL,则会将该字符串输出,该字符串是注册失败的提示信息。
注册校验逻辑
MfoV.XdKP 调用流程:
- MfoV.XdKP(name, key) –> MfoV.MfoV(name, key) –> MfoV.MfoV(name, key, 4)
MfoV(String name, String key, int paramInt) {
try {
if (!XdKP(name, key, paramInt))
throw new LicenseException(XdKP(2));
} catch (NumberFormatException numberFormatException) {
throw new LicenseException(XdKP(1));
}
this.aDeW = name;
this.DHBI = true;
}
此处调用 XdKP 方法,若返回为 true 则表示注册成功,会将 Name 保存到 aDeW 变量中,将校验结果保存在 DHBI 变量。
XdKP 调用流程:
- MfoV.XdKP –> MfoV.CYm
private long eCYm(String name, String key, int paramInt) {
if (key.length() != 18) // key 长度必须是 18
throw new LicenseException(XdKP(0));
if (key.equalsIgnoreCase("7055ce2f8cb4f9405f") || key.equalsIgnoreCase("5bae9d8cdea32760ae") || key.equalsIgnoreCase("f3264994d9ea6bc595") || key.equalsIgnoreCase("b9930cef009d3a7865") || key.equalsIgnoreCase("62bd6a5f95aa67998e") || key.equalsIgnoreCase("a1c536c35904e64584") || key.equalsIgnoreCase("d6e5590ecc05edd9b3") || key.equalsIgnoreCase("8fbe36ce2726458b18") || key.equalsIgnoreCase("042a8352caf1188945") || key.equalsIgnoreCase("9d26d5088770221c3c") || key.equalsIgnoreCase("e19b2a01905e4129bf") || key.equalsIgnoreCase("68ebe4c9d792f31057") || key.equalsIgnoreCase("4e4beb8a43e9feb9c7") || key.equalsIgnoreCase("d04d85b44b306fc9ec") || key.equalsIgnoreCase("2b5d21a38c9452e342") || key.equalsIgnoreCase("88cb89c26a813bce44") || key.equalsIgnoreCase("76c9ee78c8ab124054") || key.equalsIgnoreCase("729db7c98163ac7d3d") || key.equalsIgnoreCase("7c1d4761993c412472") || key.equalsIgnoreCase("08bc0b7ec91cd0f4aa") || key.equalsIgnoreCase("25bafae175decaedcc") || key.equalsIgnoreCase("3181aae6822ef90ccd") || key.equalsIgnoreCase("d7a8fe9dc9dc919f87") || key.equalsIgnoreCase("728dae81d9d22aca03") || key.equalsIgnoreCase("119a9b593348fa3e74") || key.equalsIgnoreCase("04ab87c8d69667878e") || key.equalsIgnoreCase("4b282d851ebd87a7bb") || key.equalsIgnoreCase("ed526255313b756e42") || key.equalsIgnoreCase("ed5ab211362ab25ca7") || key.