字節碼 反編譯 APKTool 從新打jar包 MD

Markdown版本筆記 個人GitHub首頁 個人博客 個人微信 個人郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

字節碼 反編譯 從新打jar包 MDjava


目錄

如何修改一個 Jar 包中的內容

基本步驟android

  • 使用反編譯工具 JD-GUI 打開 jar 文件,這時你能夠看到源碼(多是混淆過的源碼)
  • 將某一個 class 文件保存爲 java 文件(也能夠一鍵保存整個jar包中全部的Java文件)
  • 根據你的需求修改此 java 文件
  • 使用 javac 命令將該 java 文件編譯爲 class 文件

這一步實際上是有問題的,通常狀況下不可能編譯成功,由於這個java文件極可能會引用到其餘你提供不了源碼的類,好比 Android 中的 Context,好比第三方庫中的類
固然這種狀況下你能夠經過新建一個Java或Android或JavaEE工程,並將此Java類以及此Java類所引用的類、jar包等引入進來,這樣能夠解決引用問題
可是使用這種方式的工做量每每遠超預期,由於此Java類所引用的類極可能還會引用更多的Java類,所以會致使你這個工程引入的類呈指數級增加git

  • 使用 Winrar 等解壓工具將 jar 包解壓到指定目錄,替換須要修改的 class 文件
  • 使用 cd 命令定位到要打包的目錄,使用 jar -cvf bqt.jar .jar cvf bqt.jar * 命令從新打包

注意。打包時要把META-INF也包含進去,不然雖然能打包成功,但使用時就會出錯。github

  • 至此,就會在指定目錄生成咱們修改後的新的jar包

反編譯工具簡介

最佳實踐

主要使用Android逆向助手來操做:spring

  • 點擊反編譯apk,完成後res下的全部資源就均可以正常使用了,至關於apktool的功能------目前已失效,可是直接用rar解壓是能夠的
  • 點擊提取dex,能夠提取出apk文件中的.dex文件,若是有多個.dex文件,則只會提取第一個;建議手動把.apk更改後綴後解壓並拷出.dex文件
  • 點擊dex轉jar,至關於dex2jar的功能
  • 點擊jd打開jar,能夠自動使用jd-gui打開jar文件,如今已經能夠查看源碼了
  • jd-gui中不能修改源碼,可點擊把全部源碼保存起來後就能修改了
  • 可以使用 AXMLPrinter2 提取、轉化XML文件

經常使用工具介紹

經常使用工具:sql

  • apktool:提取res目錄下的【資源】文件(drawable、layout、anim、color等)
  • dex2jar:將【.dex】文件(可執行文件)轉化爲【.jar】文件
  • jd-gui:將編譯後的【.jar】或【.class】文件以【.java】源碼格式查看
  • AXMLPrinter2,做用:在apktool搞不定的狀況下獲取xml佈局文件--目前apktool已失效,但本工具還能用
  • XJad:和jd-gui做用相似,是一款Java源程序反編譯軟件
  • Android逆向助手:整合以上工具的而成的一個圖形化工具集

官網及下載地址:微信

apktool

apktool的使用:apk反編譯生成程序的源代碼和圖片、XML配置、語言資源等文件框架

  • 解壓獲得3個文件:aapt.exe,apktool.bat,apktool.jar
  • 將須要反編譯的APK文件放到該目錄下
  • 打開命令行界面 ,定位到apktool文件夾,輸入如下命令:【apktool.bat d -f】【test.apk bqt】(test.apk是要反編譯的APK文件全名,能夠直接拖拽過來,bqt爲反編譯後存放的目錄)
  • 成功後發如今文件夾下多了個bqt文件夾,點擊即可以查看該應用的全部資源文件了。
  • 若是你想將反編譯完的文件從新打包成apk,輸入apktool.bat b test,完成後在test文件下便多了2個文件夾:build和dist(存放着打包出來的APK文件)

官方的apktool工具只包含如下三個文件,只能使用CMD命令執行
eclipse

不過目前有不少從新打包後的版本,以下
編輯器

dex2jar、jd-gui、XJad

dex2jar,jd-gui,XJad的使用

  • 首先將apk文件後綴改成zip或rar並解壓,獲得其中的classes.dex,它就是java文件編譯再經過dx工具打包而成的
  • 而後將classes.dex複製到d2j-dex2jar.bat(名稱可能不太同樣)所在目錄文件夾中
  • 在命令行下定位到d2j-dex2jar.bat所在目錄,運行ded2j-dex2jar.bat classes.dex
  • 最後,進入jdgui文件夾雙擊jd-gui.exe,打開上面生成的jar包classes_dex2jar.jar,便可看到源代碼了
  • 不過,被混淆過的類文件名稱以及裏面的方法名稱都會以a,b,c....之類的樣式命名

XJad簡介

  • XJad是基於Jad核心的Java源程序反編譯軟件,內置Jad1.5.8e2
  • 可處理多個*.class文件,能夠處理文件夾內的全部文件,甚至能夠處理*.jar文件;
  • 帶有多頁面文本編輯器,也可集成在資源管理器中,隨時點擊右鍵均可進行操做;
  • 支持java語法的高亮顯示;

XJad使用說明

  • 打開一個或者多個*.class文件,XJad反編譯後,重命名爲*.java文件,保存至當前文件夾,並在編輯器中打開查看;
  • 打開一個文件夾,XJad將該文件夾下全部*.class文件進行反編譯,並保存至該文件夾下,依據包路徑信息生成文件夾路徑,如com.spring.framework.*,將創建com\spring\framework的文件夾結構;
  • 打開一個*.jar文件,XJad將該Jar文件中的全部*.class文件解壓縮到臨時目錄並進行反編譯,並將源文件帶包路徑信息保存至當前文件夾下名稱爲「~」 + *.jar的文件夾中;

AXMLPrinter2

做用:目前不少APP已經混淆,使用APKTOOL已經提取不出資源文件,使用這個工具能夠將二進制的xml佈局文件轉化爲正常的形式

弊端:須要一個一個的轉換,且轉換出來後有大量須要修改的地方

下載地址 網上流傳的部分版本可能不能使用

使用步驟:

  • 更改apk後綴名爲.rar並解壓
  • 找到解壓後res目錄下的要參考的佈局文件,並將此文件複製到和AXMLPrinter2同目錄下
  • 打開cmd並定位到此目錄下
  • 執行命令java -jar AXMLPrinter2.jar bqt.xml > bqt2.xml 即可把二進制的 bqt.xm文件反編譯成能夠閱讀的bqt2.xml文件

eclipse 安裝 jd-gui 插件

eclipse安裝使用JD插件:

  • Download and unzip the JD-Eclipse Update Site,
  • Launch Eclipse,
  • Click on "Help > Install New Software...",
  • Click on button "Add..." to add an new repository,
  • Enter "JD-Eclipse Update Site" and select the local site directory,
  • Check "Java Decompiler Eclipse Plug-in",
  • Next, next, next... and restart Eclipse.Installation

注意,由於衆所周知的緣由,不要選擇聯網更新之類的操做

藉助 Javassist 修改 jar 包

背景

這是實際項目中遇到的一個問題:lib目錄下某個第三方jar包中的某個方法的返回值不符合咱們要求,而這個方法在不少地方被調用到,咱們怎麼修改這個方法的返回值呢?

可能有不少解決方法,好比動態代理,好比AOP,可是這些方案都有不少問題。好比動態代理,咱們這個方法是一個普通的方法,使用JDK的動態代理是行不通的,而cglib在Android中是用不了的;而對於AOP,我也確實嘗試了,但發現使用aspectj時,對於混淆過的jar包中的方法老是攔截不成功(固然,使用滬江封裝的aspectjX是能夠的)。另一個問題是,使用這些方案對項目改動都是很是大的,特別是引用AOP這些框架,因爲很小的一點改動就這麼大動干戈的話有點不合適。

那有沒有其餘方法呢?

其實有一個最簡單的方案,那就是反編譯,咱們只須要將jar包中那個相應的class文件中方法修改一下就能夠了。

可是正如上面描述的那樣,這種方式是有侷限性的!可是若是藉助 Javassist 去作的話,問題就至關簡單了。

案例

好比,我想將Gson庫中Gson類的下面的方法

public String toJson(Object src) {
    if (src == null) {
        return toJson(JsonNull.INSTANCE);
    }
    return toJson(src, src.getClass());
}

修改成:

public String toJson(Object src) {
    if (src == null) {
        return "";
    }
    return src.toString();
}

那麼我只需執行如下邏輯就能夠了:

public class Test {

    public static void main(String[] args) {
        try {
            ClassPool pool = ClassPool.getDefault();
            pool.insertClassPath("D:/test/gson-2.8.1.jar");//加載指定路徑下的庫。若是此庫已被引入項目中,能夠省略這一步
            CtClass cc = pool.get("com.google.gson.Gson");//加載指定的類
            CtClass[] params = new CtClass[] { pool.get("java.lang.Object") };//基本類型和引用類型的描述方式是不同的
            CtMethod method = cc.getDeclaredMethod("toJson", params);//取得須要修改的方法
            method.setBody("{" + // 你只須要正常寫代碼邏輯就能夠了,複製過來時,一些IDE,好比AS會自動幫你添加轉義字符
                    "if ($1 == null) {\n" + //$0表明的是this,$1表明方法參數的第一個參數、$2表明方法參數的第二個參數
                    "\treturn \"\";\n" + //
                    "}\n" + //
                    "return $1.toString();" + //
                    "}"); //修改方法
            cc.writeFile("D:/test");//保存到指定位置,執行完這一步後就會在指定目錄生成你須要的 class 文件
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行後就會在指定目錄生成你須要的 class 文件:

而後咱們按照上面的步驟打成jar包(注意最後的一個.表明的是當前路徑):

D:\test>jar -cvf mygson.jar .

要打包的路徑下的文件:

而後咱們就可使用咱們修改後的Gson庫了,反編譯后里面的邏輯以下:

拓展

一、上面這種方式的思路是,在編譯期修改指定Java類編譯後生成的class文件,而後從新打成jar包。

二、其實還有另一種思路,那就是不在編譯期處理,而在運行期處理,也即咱們能夠在運行期先修改、而後直接加載修改後的class文件。

通過在Android和Java工程中測試發現,這種方式也是很是靠譜的。

三、在代碼層面,和上面那種方式的區別是,咱們最後不是調用

cc.writeFile("D:/test");

方法將修改後的class文件保存到指定位置,而是調用

cc.toClass();

方法返回、並加載此新生成的class文件。

四、可是這種方式有一個侷限性。

由於同一個 class 是不能在同個 ClassLoader 中被加載兩次的,因此在調用 toClass() 前要保證此要修改的類不能是被加載過的類,不然直接報異常。

五、可是這種狀況下也並非無解的。

咱們能夠自定義一個 ClassLoader 去加載新生成的類:

Class clazz = classLoader.loadClass("com.bqt.test.Person")

六、可是要知道,若是這樣作的話,此時JVM中是有兩個Person類的,而且除非是經過返回的這個 clazz 建立的對象,其使用的是新生成的類:

clazz.getDeclaredMethod("hello", String.class).invoke(clazz.newInstance(), "你妹"); //調用的是新類的方法

其餘任何方式建立的對象,使用的還是原始的類(包括經過反射方式):

new Person().hello("泥煤"); //調用的是原始類的方法
Class clazz2 = Class.forName("com.bqt.test.Person");
clazz2.getDeclaredMethod("hello", String.class).invoke(clazz2.newInstance(), "你美"); //調用原始類的方法

使用 Javac 命令編譯類的注意事項

在使用原始的javac編譯Java類文件時,若是當前類文件對其餘類有依賴,那麼就可能會出現問題。

舉例以下:在桌面新建一個文件夾test,而後創建兩個類:A.java和B.java,兩個類的代碼都很簡單,其中B類對A類有依賴:

import java.sql.Date;

public class A {
    public static void i(Date date) {
        System.out.println(date.toString());
    }
}
import java.sql.Date;

public class B {
    public static void i(String info) {
        Date date = new Date(System.currentTimeMillis());
        A.i(date);
    }
}

基本使用

直接用javac命令編譯兩個源文件,且先編譯 A.java,結果以下:

C:\Users\baiqi>cd/d C:\Users\baiqi\Desktop\test
C:\Users\baiqi\Desktop\test>javac A.java
C:\Users\baiqi\Desktop\test>javac B.java

能夠看到編譯成功,生成了對應的class文件。

注意:

  • 若在沒有編譯A.java狀況下直接編譯 B.java,則會同時生成A和B對應的class文件。
  • 雖然咱們代碼中導入了import java.sql.Date;但由於是JDK中的類,因此仍能夠直接編譯

引入 jar 包的狀況

咱們在B.java中利用import語句導入一個包:

import java.sql.Date;
import com.google.gson.Gson;

public class B {
    public static void i(String info) {
        Date date = new Date(System.currentTimeMillis());
        System.out.println(new Gson().toJson(date));
        A.i(date);
    }
}

而後直接編譯B.java

C:\Users\baiqi\Desktop\test>javac B.java
B.java:2: 錯誤: 程序包com.google.gson不存在
import com.google.gson.Gson;
                      ^
B.java:7: 錯誤: 找不到符號
                System.out.println(new Gson().toJson(date));
                                       ^
  符號:   類 Gson
  位置: 類 B
2 個錯誤

能夠看到,B.java文件編譯失敗,這是由於Gson並不是Java標準類庫中的內容,所以編譯器找不到對應的包,就會出錯。

咱們將須要的jar文件放入當前目錄,並使用-cp參數將庫文件導入,而後繼續編譯:

C:\Users\baiqi\Desktop\test>javac -cp gson-2.8.1.jar B.java
B.java:8: 錯誤: 找不到符號
                A.i(date);
                ^
  符號:   變量 A
  位置: 類 B
1 個錯誤

怎麼提示找不到 A 呢?
這是由於,默認狀況下,編譯器會在當前目錄下尋找須要的類文件,可是若是咱們使用cp參數修改了類文件查找路徑,而並無包含當前目錄,那麼就會編譯失敗。由於咱們在使用cp參數時,須要將當前目錄包含進去:

C:\Users\baiqi\Desktop\test>javac -cp gson-2.8.1.jar;. B.java

添加 package 的狀況

咱們在A.javaB.java文件中添加package語句:

package com.bqt.test;

import java.sql.Date;

public class A {
    public static void i(Date date) {
        System.out.println(date.toString());
    }
}
package com.bqt.test;

import java.sql.Date;

public class B {
    public static void i(String info) {
        Date date = new Date(System.currentTimeMillis());
        A.i(date);
    }
}

而後咱們將A.javaB.java放入相應的包中:

而後直接編譯B.java(注意要指定相對或絕對路徑):

C:\Users\baiqi\Desktop\test>javac com\bqt\test\B.java

引入 jar 包並添加 package 的狀況

咱們在上面第三種狀況下再給B引入jar包:

package com.bqt.test;

import java.sql.Date;
import com.google.gson.Gson;

public class B {
    public static void i(String info) {
        Date date = new Date(System.currentTimeMillis());
        System.out.println(new Gson().toJson(date));
        A.i(date);
    }
}

一樣,命令也結合以上兩種形式便可:

C:\Users\baiqi\Desktop\test>javac -cp gson-2.8.1.jar;. com\bqt\test\B.java

2019-1-6

相關文章
相關標籤/搜索