Charles 4.5.1逆向工程/破解

Charles 4.5.1逆向破解

前两天剁了Burp Pro 2.1.04破解版,今天写写如何破解Charles 4.5.1。该文主要以逆向与软件安全为方向编写(作者scz)。你也可参阅Charles破解版/破解工具找到你想要的。

Charles如果未注册,启动时有个10s倒计时,30天试用期,每运行30分钟就自动退出。

Charles缺省是用其自带JDK 11启动的:

C:\Program Files\Charles\jdk\

正常注册流程是:

启动Charles->Help->Register Charles->输入Registered Name/License Key

这里会弹一个对话框,有RegisterCancel两个按钮。如何找到Register按钮对应的代码?假设Register按钮用到了JButton,在按钮上显示"Register"这个字符串时会用到javax.swing.AbstractButton.setText(),查看它的调用栈回溯,或许就能定位Register按钮对应的代码。

javax.swing.AbstractButton.setText()设断,命中时:

[1] javax.swing.AbstractButton.setText (AbstractButton.java:297), pc = 0
[2] javax.swing.AbstractButton.init (AbstractButton.java:2,128), pc = 6
[3] javax.swing.JButton.<init> (JButton.java:131), pc = 18
[4] javax.swing.JButton.<init> (JButton.java:104), pc = 3
[5] com.xk72.charles.gui.frames.RegisterFrame.<init> (null), pc = 45
[6] com.xk72.charles.gui.menus.HelpMenu$4.actionPerformed (null), pc = 8
[7] javax.swing.AbstractButton.fireActionPerformed (AbstractButton.java:1,967), pc = 83

爽到了,关注"com.xk72.charles.gui.frames.RegisterFrame.class"。

比起Burp Pro 2.1.04Charles 4.5.1的混淆程度太弱,说是一马平川不为过。据说Charles早期版本没有混淆过,比如4.0.2,我以前没接触过。

Charles 4.5.1反编译

C:\Program Files\Charles\lib\charles.jar

com.xk72.charles.gui.frames.RegisterFrame.class

public class RegisterFrame extends JDialog
{
private final JTextFieldtName;
private final JTextFieldtSerial;
/*
 * Register按钮
 */
private final JButton bRegister;
/*
 * Cancel按钮
 */
private final JButton bCancel;

public RegisterFrame ( Frame paramFrame )
{
super( paramFrame, true );
setTitle( "Register Charles" );
this.tName= new JTextField( 20 );
this.tSerial= new JTextField( 20 );
this.bRegister= new JButton( "Register" );
this.bCancel= new JButton( "Cancel" );

Container localContainer;

(localContainer = getContentPane()).setLayout( new MigLayout( "wrap,fill", "[label][fill,grow]" ) );

localContainer.add( new JLabel( "Registered Name:" ) );
localContainer.add( this.tName );
localContainer.add( new JLabel( "License Key:" ) );
localContainer.add( this.tSerial );
localContainer.add( this.bCancel, "tag cancel,split 2,span,center" );
localContainer.add( this.bRegister, "tag ok" );
this.bCancel.addActionListener( new gTDF( this ) );
/*
 * 在此添加Register按钮的处理代码,据此关注
 *
 * com.xk72.charles.gui.frames.PeRA.class
 */
this.bRegister.addActionListener( new PeRA( this ) );
pack();
if ( paramFrame != null )
{
 ( paramFrame = new Point( paramFrame.getLocation() ) ).translate( 20, 20 );
setLocation( paramFrame );
}
getRootPane().setDefaultButton( this.bRegister );
getRootPane().getInputMap(1).put( KeyStroke.getKeyStroke( "ESCAPE" ), "escape" );
getRootPane().getActionMap().put( "escape", new RegisterFrame.3( this ) );
}

com.xk72.charles.gui.frames.PeRA.class

import com.xk72.charles.YQUd;

final class PeRA implements ActionListener
{
PeRA( RegisterFrame paramRegisterFrame )
{
}

public final void actionPerformed ( ActionEvent paramActionEvent )
{
paramActionEvent= RegisterFrame.tEdg( this.tEdg ).getText().trim();

Stringstr = RegisterFrame.TryJ( this.tEdg ).getText().trim();

if ( ( paramActionEvent.length() > 0 ) && ( str.length() > 0 ) )
{
ObjectlocalObject;

/*
 * 为了注册成功,YQUd.tEdg()必须返回null。据此关注
 *
 * com.xk72.charles.YQUd.class
 */
if ( ( localObject = YQUd.tEdg( paramActionEvent, str ) ) != null )
{
/*
 * 注册失败
 */
ExtendedJOptionPane.tEdg( this.tEdg, localObject, "Charles Registration", 2 );
return;
}
/*
 * 注册成功,显示注册信息
 */
ExtendedJOptionPane.tEdg( this.tEdg, "Thank you for registering. Charles will now close. Please start Charles again to continue.", "Charles Registration", 1 );
( localObject = CharlesContext.getInstance() ).getConfiguration().getRegistrationConfiguration().setName( paramActionEvent );
( ( CharlesContext )localObject ).getConfiguration().getRegistrationConfiguration().setKey( str );
( ( CharlesContext )localObject ).exit( 0, true );
}
}
}

YQUd.class共有5个public method:

  1. public com.xk72.charles.YQUd();
  2. public static boolean tEdg();
  3. public static void TryJ();
  4. public static java.lang.String NCuT();
  5. public static java.lang.String tEdg(java.lang.String, java.lang.String);

com.xk72.charles.YQUd.class

public final class YQUd
{
private static YQUd TryJ;
/*
 * 为true时表示已注册
 */
private boolean NCuT;

public YQUd ()
{
this.NCuT = false;
this.Rarr = "Unregistered";
}

/*
 * 为true时表示已注册
 */
public static boolean tEdg ()
{
YQUdvar0= TryJ;

return TryJ.NCuT;
}

public static void TryJ ()
{
TryJ= new YQUd();
}

/*
 * 这是要显示到"About Charles"的内容
 */
public static String NCuT ()
{
YQUdvar0= TryJ;

switch( gTDF.tEdg[var0.aqrV.ordinal()] )
{
case 1:
return var0.Rarr;
case 2:
return var0.Rarr + " - Site License";
case 3:
return var0.Rarr + " - Multi-Site License";
default:
return var0.Rarr;
}
}

/*
 * 务必让本函数返回null
 */
public static String tEdg ( String var0, String var1 )
{
YQUdvar3;

try
{
var3= new YQUd( var0, var1 );
}
catch ( LicenseException var2 )
{
return var2.getMessage();
}
TryJ= var3;
return null;
}

编辑YQUd.java:

package com.xk72.charles;

public final class YQUd
{
/*
 * 只Patch这5个public method,其他private的扔掉即可
 */

public YQUd ()
{
}

/*
 * 为true时表示已注册
 */
public static boolean tEdg ()
{
return( true );
}

public static void TryJ ()
{
}

/*
 * 这是要显示到"About Charles"的内容,这个内容任意
 */
public static String NCuT ()
{
return "Anyting - Site License";
}

/*
 * 务必让本函数返回null
 */
public static String tEdg ( String var0, String var1 )
{
return null;
}
}

编译YQUd.java得到新的YQUd.class,替换charles.jar中的YQUd.class

比起Burp,破解Charles实在是太简单了。要点是定位YQUd.class,每个版本混淆后的名字都不一样,需要重新定位。由于已经讲清楚原理,以后破解都不需要动态调试设断,直接在反编译器中静态定位。

作者:scz