charles破解历程

题记

看文章看到javassist可以直接修改java字节码,之前没有尝试过,因为charles是用java写的跨平台抓包工具,之前我也用过,所以拿来进行测试!

简介

Javassist是一个开源的分析、编辑和创建Java字节码的类库。

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。

关于java字节码的处理,目前有很多工具,如asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

原理介绍

class文件简介及加载

Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象:

 

在运行期的代码中生成二进制字节码

由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了

基本功能

重要的类

ClassPool:javassist的类池,使用ClassPool 类可以跟踪和控制所操作的类,它的工作方式与 JVM 类装载 器非常相似, ​ CtClass: CtClass提供了检查类数据(如字段和方法)以及在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。不过,Javassist 并未提供删除类中字段、方法或者构造函数的任何方法。 ​ CtField:用来访问域 ​ CtMethod :用来访问方法 ​ CtConstructor:用来访问构造器

Constructor getConstructor(Class..c);获得某个公共的构造方法。
Constructor[] getConstructors();获得所有的构造方法。
Constructor getDeclaredConstructor(Class..c);获得某个构造方法。
Constructor[] getDeclaredConstructors();获得所有的构造方法
CtMethod 和CtConstructor 提供了 setBody() 的方法,可以替换方法或者构造函数里的所有内容

读取和输出字节码

ClassPool pool = ClassPool.getDefault();
//会从classpath中查询该类
CtClass cc = pool.get("test.Rectangle");
//设置.Rectangle的父类
​
cc.setSuperclass(pool.get("test.Point"));
​
 //输出.Rectangle.class文件到该目录中
​
 cc.writeFile("c://");
​
 //输出成二进制格式
​
 //byte[] b=cc.toBytecode();
​
 //输出并加载class 类,默认加载到当前线程的ClassLoader中,也可以选择输出的ClassLoader。
​
 //Class clazz=cc.toClass();
​

这里可以看出,Javassist的加载是依靠ClassPool类,输出方式支持三种

语法

使用javassist来编写的代码与java代码不完全一致,主要的区别在于 javassist提供了一些特殊的标记符(以开头),用来表示方法,构造函数参数、方法返回值等内容。示例:System.out.println(“Argument1:”+开头),用来表示方法,构造函数参数、方法返回值等内容。示例:System.out.println(“Argument1:”+1); 其中的$1表示第1个参数.

示例

可以通过javassist来修改java类的方法,来修改其实现。如下所示:

 ClassPool classPool = ClassPool.getDefault();
  CtClass ctClass = classPool.get("org.light.lab.JavassistTest");
  CtMethod ctMethod = ctClass.getDeclaredMethod("test");
  ctMethod.setBody("System.out.println(\"this method is changed dynamically!\");");
  ctClass.toClass();

上面的方法即是修改一个方法的实现,当调用ctClass.toClass()时,修改后的类将被当前的ClassLoader加载并实例化。

Tips

类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。每种类加载器都有设定好从哪里加载类。

package samples;  
/** 
 \* 自定义一个类加载器,用于将字节码转换为class对象 
*/  
public class MyClassLoader extends ClassLoader {  
public Class<?> defineMyClass( byte[] b, int off, int len)   
  {  
       return super.defineClass(b, off, len);  
   }  
       
  }  

然后编译成Programmer.class文件,在程序中读取字节码,然后转换成相应的class对象,再实例化

1. import java.io.File;  
2.  import java.io.FileInputStream;  
3.  import java.io.FileNotFoundException;  
4.  import java.io.IOException;  
5.  import java.io.InputStream;  
6.  import java.net.URL;  
7.    
8.  public class MyTest {  
9.    
10.      public static void main(String[] args) throws IOException {  
11.         //读取本地的class文件内的字节码,转换成字节码数组  
12.         File file = new File(".");  
13.          InputStream  input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");  
14.          byte[] result = new byte[1024];  
15.           
16.         int count = input.read(result);  
17.          // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象  
18.          MyClassLoader loader = new MyClassLoader();  
19.          Class clazz = loader.defineMyClass( result, 0, count);  
20.          //测试加载是否成功,打印class 对象的名称  
21.          System.out.println(clazz.getCanonicalName());  
22.                    
23.                 //实例化一个Programmer对象  
24.                 Object o= clazz.newInstance();  
25.                 try {  
26.                     //调用Programmer的code方法  
27.                      clazz.getMethod("code", null).invoke(o, null);  
28.                     } catch (IllegalArgumentException | InvocationTargetException  
29.                          | NoSuchMethodException | SecurityException e) {  
30.                       e.printStackTrace();  
31.                    }  
32.   }  
33.  }  
 

以上代码演示了,通过字节码加载成class 对象的能力

正文

我们在进行应用开发过程中有时候可以需要进行抓包测试数据,比如模拟服务端的下发数据和我们客户端的请求参数数据,特别是测试人员在进行测试的过程中都会进行抓包,当然我们在破解逆向的过程中也是需要用到抓包工具,因为我们抓到数据包可能就是我们破解的突破口,那么我们可能常用的都是Fiddler工具,但是这个工具有一个弊端就是只能在Windows系统中使用,但是还有一个厉害的工具就是跨平台抓包工具Charles,之所以他是跨平台的就是因为他使用Java语言开发的,而且也非常好用。但是这个工具有一个不好的地方就是有一个购买功能,如果不购买的话当然可以使用,但是有时间限制和各种提示,使用过程中也挺烦的,所以我决定把它破解了!

首先我们去官网下载一个最新版,我下载的是windows版

官网地址:https://www.charlesproxy.com/

安装并打开软件

开启界面有段字符,延迟几秒后进入主界面,我们点击购买功能

首先的思路也是老套路,先利用字符串作为入口,寻找可能的关键代码,这里我们利用开启界面的字符串,This is a 30 day trial version....

找到charles.jar,用jd-gui打开打开,全局搜索This is a 30 day trial version....

如下

发现一个showRegistrationStatus()方法,方法名没有被混淆,大致能判断此方法跟注册有关,并且是根据lcjx()方法的返回值来判断,为true则成功,false则显示showSharewareStatus()的内容,也就是This is a 30 day trial version....,接下来我们进入lcjx()来验证我们的推断!

在JD-gui里点击相应方法函数,可以知道目标的调用位置,这个可以省不少事,这里我们点击第一个框中JZlU,找到调用位置

它返回的值是调用了boolean变量JZlU,默认为false,此时我们推想一下逻辑,也就是说正常情况下默认是未注册的状态,所以这个值默认为false,如果我们要破解的话,是不是可以直接把这个变量给初始化为true呢?答案是可以的

我们利用kKPk的构造方法进行初始化变量

如果我们想在初始界面显示我们想要显示的字符怎么办呢,我们可以修改JZlU方法,使之返回我们想要的字符

下面贴出利用代码

import javassist.*;
​
import java.io.IOException;
 public class javassivt {
    // 实例化类型池
    public static ClassPool pool = ClassPool.getDefault();
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, ClassNotFoundException {
    // 获取默认类型池对象
    pool.insertClassPath("K:/charles.jar");
   // 从类型池中读取指定类型
    CtClass oFTR = pool.get("com.xk72.charles.kKPk");
    try {// 获取指定方法
        CtMethod ct = oFTR.getDeclaredMethod("JZlU");
     // 修改原方法
        ct.setBody("return \"By.Ethan   http://www.luckydog.top:4000 QQ:798993306\";");
     // 为类设置构造器,获得全部的构造方法
        CtConstructor[] cca = oFTR.getDeclaredConstructors();
        cca[0].setBody("{this.yNVB = \"Cracked By Ethan   http://www.luckydog.top:4000 QQ:798993306\";\nthis.JZlU = true;}");
        cca[1].setBody("{this.yNVB = \"Cracked By Ethan   http://www.luckydog.top:4000 QQ:798993306\";\nthis.JZlU = true;}");
      //将上面构造好的类写入到指定的工作空间中
        oFTR.writeFile("K:");
 
    } catch (Exception e) {
        e.printStackTrace();
    }
   }}

以上脚本实现了初始化yNVB,JZlU,并且重写了JZlU类,使之返回相应字符。

修改后相应代码如下

 

运行完进入输出目录运行命令,把修改的内容更新到jar文件

jar -uvf charles.jar com

用破解的charles.jar替换原来的charles.jar,运行

成功破解,在使用过程中也无任何弹出消息框,注册状态也显示已经注册!整个破解也就结束了!

除了以上方法我们也可以番外知识我们可以修改smali文件,所以思路就是把jar转化成dex文件,这个直接用dx命令即可,然后在把dex弄成smali文件直接修改即可,然后在打包回去,同样也可以实现!

番外知识

java反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

详细介绍见:https://blog.csdn.net/sinat_38259539/article/details/71799078?utm_source=blogxgwz0

asm

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

与 BCEL 和 SERL 不同,ASM 提供了更为现代的编程模型。对于 ASM 来说,Java class 被描述为一棵树;使用 “Visitor” 模式遍历整个二进制结构;事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分,而不必了解 Java 类文件格式的所有细节:ASM 框架提供了默认的 “response taker”处理这一切。

详细介绍见:https://blog.csdn.net/zhuoxiuwu/article/details/78619645

构造方法

构造方法是一种特殊的方法,它是一个与类同名且返回值类型为同名类类型的方法。对象的创建就是通过构造方法来完成,其功能主要是完成对象的初始化。当类实例化一个对象时会自动调用构造方法。构造方法和其他方法一样也可以重载。

构造方法的作用

  • 为了初始化成员属性,而不是初始化对象,初始化对象是通过new关键字实现的

  • 通过new调用构造方法初始化对象,编译时根据参数签名来检查构造函数,称为静态联编和编译多态

    (参数签名:参数的类型,参数个数和参数顺序)

  • 创建子类对象会调用父类构造方法但不会创建父类对象,只是调用父类构造方法初始化父类成员属性;

关于重载和子类调用父类的构造方法、构造方法的作用域、构造方法的访问级别等,

详见:https://www.cnblogs.com/lwj820876312/p/7231271.html

Think one Think

在此之前,我的对于修改java字节码的观念还是把jar文件转为dex文件,再把dex文件弄成smali文件,在smali层进行修改然后再重新打包,这样工作量会相对大一些,如果直接可以对java字节码操作,可以并且是用java源码来执行操作,便会方便好多,而这一切便源于javassist对于我们操作的封装,asm不同的是少了java层的操作封装,它是基于字节码的,所以它效率更高,但是使用起来也更为繁琐。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值