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層的操作封裝,它是基於字節碼的,所以它效率更高,但是使用起來也更爲繁瑣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章