反編譯 AndroidKiller 逆向 字節碼 實踐案例 [MD]


博文地址

我的GitHub 我的博客 我的微信 我的郵箱
baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

反編譯 AndroidKiller 逆向 實踐案例 MD

PS:以下所有內容,包括測試用來逆向的APP均已脫敏

參考我的其他博客內容:

AndroidKiller 簡介

Android Killer 是一款可以對APK進行反編譯的工具,它能夠對反編譯后的Smali文件進行修改,並將修改后的文件進行打包。

AndroidKiller的基本功能:

  • 可以修改清單文件,比如修改包名
  • 可以修改(替換)任意資源文件,比如 string、drawable、color、layout 等,以及 assets、raw 等
  • 可以對整個工程中的字符或文件進行搜索(支持匹配編碼、匹配文件類型、匹配范圍)
  • 可以查看對應的 smali 、class 源碼,以及反編譯的 java 源碼
  • 可以對修改后的工程重新打包

插件升級

軟件中的Apktool可能會因為版本太低導致 apk 的反編譯失敗,此時需要到 Apktool 官網去下載最新版本的Apktool。

下載完成后找到解壓好的 AndroidKiller 目錄下的bin\apktool\apktool目錄將下載的最新版的 apktool 復制進去。

然后修改 AndroidKiller 根目錄下的bin\apktool下的apktool.batapktool.ini文件,將里面對應的文件名改為你下載的最新的 apktool 文件名。

基本使用

使用 AndroidKiller 對 Apk 進行反編譯只需要將 Apk 文件拖入軟件即可。

反編譯后我們可以對資源文件等進行簡單的修改,修改后后點擊左上角菜單欄中的Android -- 編譯即可重新編譯成APK。

另外我們也可以點擊 smali 目錄,查看反編譯的 smali 源碼,並可以在某一 smali 源碼文件中右鍵 -- 查看 -- 查看源碼通過 Java Decompiler 查看反編譯的 java 源碼。

實踐案例

修改清單文件

經常會修改包名、應用圖標、應用名稱等基本信息,只需在這里找到相應的res並修改即可。

另外還可以查看其使用的Application,因為一般一些初始化操作、全局常量的設置等都是在Application里面。

<manifest package="包名" >
<application android:icon="圖標" android:label="名稱" android:name="使用的Application">

打印 debug 級別的日志

或者說是開啟debug模式,一般有如下幾種處理思路

方式一:直接代理 Log 類

按如下方式便可以 hook 住系統 Log 的方法調用,我們可以在 Log.d 等方法被調用 前/后 做自己的邏輯,比如對日志進行過濾、把日志保存到文件中。

public class XposedZygoteInit implements IXposedHookZygoteInit {
    @Override
    public void initZygote(StartupParam startupParam) throws Throwable {

        //hook住某個方法。參數:類名,類加載器,方法名,參數列表,Hook成功后的回調
        XposedHelpers.findAndHookMethod("android.util.Log", StartupParam.class.getClassLoader(), "d", String.class, String.class, new XC_MethodHook() {

            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                //Log.i("bqt", "【代理系統的Log類,可以在這里把日志保存到文件中】" + param.args[0] + " ," + param.args[1]);
            }
        });
    }
}

方式二:通過修改字段值修改判斷條件

比如,反編譯一款 APP 后發現其打印日志的部分邏輯如下:

public class LogTool{
  public static boolean a = ;
  public static String b = "通用的tag";
  
  public static void a(String paramString) {
    if (a) {
      Log.d(b, paramString);
    }
  }

在 release 包中,這個值靜態字段 a 肯定是 false,我們只需將其改為 true 即可打印 debug 下才會打印的日志。

hook 方式如下:

public class XposedInit implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Exception {
        if (lpparam.packageName.equals(應用的包名,進入if代表此應用啟動了,可以開始hook了)) {
            Field field = XposedHelpers.findClass("完整路徑.LogTool", lpparam.classLoader).getField("a");
            field.setAccessible(true);
            field.setBoolean(null, true); //靜態字段的 the object whose field should be modified is null
        }
    }
}

PS:hook 系統類不能通過 IXposedHookLoadPackage ,而需要通過 IXposedHookZygoteInit,因為此應用啟動前,系統類已經被加載過了,已經錯過了 hook 的時機了。

方式三:通過修改方法返回值修改判斷條件

比如,反編譯一款 APP 后發現其打印日志的部分邏輯如下:

public class LogTool {
    public static void d(String paramString1, String paramString2) {
        if (isLogAble(LogLevel.DEBUG)) {
            //...
        }
    }

    private static boolean isLogAble(LogLevel paramLogLevel) {
        if (logcatLevel.value == LogLevel.OFF.value) {
            return false;
        }
        if (logcatLevel.value == LogLevel.ALL.value) {
            return true;
        }
        return logcatLevel.compare(paramLogLevel) >= 0;
    }
}

類似上述案例,我們只需hook住isLogAble方法,並且將返回值直接返回true即可跳過日志級別判斷。

hook 方式如下:

XposedHelpers.findAndHookMethod("類的完整路徑", lpparam.classLoader, "isLogAble", "參數的完整路徑", new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        Log.i("bqt", "【" + Arrays.toString(param.args) + "】");
        param.setResult(true);
    }
});

方式四:修改 BuildConfig.DEBUG 中的 DEBUG 常量值

這種方式和上面那種方式類似,不過適用場景更廣泛。

APP編譯時會自動為每一個module生成一個 BuildConfig 類,其中包含一些經常使用到的與環境相關的常量,例如:

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true"); //是否是調試模式
  public static final String APPLICATION_ID = "com.bqt.test"; //包名
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
}

很多時候,我們判斷是否應打印日志就是根據 BuildConfig.DEBUG 來確定的,所以我們只需要修改這個值就可以了。

修改這個值的方式肯定不是通過Xposed框架,因為這些常量都是 final 類型的,動態框架肯定是無法修改的,一種可行的方式:

如何hook混淆后指定類中的指定方法

首先需要明白,混淆后的類名、方法名、成員變量名等都是固定的,反編譯后你看到的名字是什么,運行時它的名字就是什么,不會再變的

然后你還需要知道,他的名字雖然是固定的,但是往往你沒辦法直接hook,因為很多混淆后的名字都是非法字符,你即沒法輸入(IDE不識別),也沒法通過編譯(編譯器不識別),所以需要采用特殊的方式來hook。

以下是一種可供參考的案例:

//從指定類中混淆后的方法名中獲取匹配的方法
String methodName = "";
Method[] methods = XposedHelpers.findClass("o.ayc", lpparam.classLoader).getDeclaredMethods();
for (Method method : methods) {
    method.setAccessible(true);
    //匹配返回值,這里只是簡單字符串匹配,更精確的可以通過類型匹配
    if (method.getReturnType().toString().contains("HttpURLConnection")) {
        methodName = method.getName();
        break;
    }
}

通過上述方法,便可跳過IDE和編譯器的名稱規范的檢查,在運行時便可匹配混淆后的類或方法(運行時並不會進行規范性檢查)。

完整案例

基本步驟

1、安裝 XposedInstaller.apk,安裝后啟動此應用,安裝 framework ,重啟手機

2、AS工程中添加依賴

compileOnly 'de.robv.android.xposed:api:82:sources'//xposed依賴,注意這個版本號和framework版本號並不是一致的
implementation files('libs/javassist.jar')//非必須

3、application下添加三個meta-data

<meta-data
    android:name="xposedmodule"
    android:value="true"/>
<meta-data
    android:name="xposeddescription"
    android:value="這是對你使用xposed能完成功能的簡要描述"/>
<meta-data
    android:name="xposedminversion"
    android:value="89"/>

4、編寫Hook邏輯

5、配置完整的Hook類類名

新建assets文件夾,文件夾下新建xposed_init文件(文件名固定),在文件中填寫hook邏輯所在類的fully qualified class name:

com.bqt.test.temp.XposedInit
com.bqt.test.temp.XposedZygoteInit

6、有任何代碼變動,都需重裝APP后重啟手機才能生效

XposedInit

public class XposedInit implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Exception {
        if (lpparam.packageName.equals("應用1的包名,進入if代表此應用啟動了,可以開始hook了")) {

            //開啟日志打印
            XposedHelpers.findAndHookMethod("類的完整路徑", lpparam.classLoader, "isLogAble", "參數的完整路徑", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    Log.i("bqt", "日志級別【" + Arrays.toString(param.args) + "】");
                    param.setResult(true);
                }
            });

            //從指定類中混淆后的方法名中獲取匹配的方法
            String methodName = "";
            Method[] methods = XposedHelpers.findClass("o.ayc", lpparam.classLoader).getDeclaredMethods();
            for (Method method : methods) {
                method.setAccessible(true);
                //匹配返回值,這里只是簡單字符串匹配,更精確的可以通過類型匹配
                if (method.getReturnType().toString().contains("HttpURLConnection")) {
                    methodName = method.getName();
                    break;
                }
            }

            //hook住某個方法。參數:類名,類加載器,方法名,參數列表,Hook成功后的回調
            XposedHelpers.findAndHookMethod("o.ayc", lpparam.classLoader, methodName, String.class, new XC_MethodHook() {

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    Log.i("bqt", "1-【" + param.method.getName() + "】" + param.args[0]);
                }
            });

            //只能hook具體的方法,不能hook接口的方法或抽象的方法。比如只能hook住某個Runnable的實現類的run方法,而不能hook住所有Runnable的run方法
            XposedHelpers.findAndHookMethod("o.ayf", lpparam.classLoader, "run", new XC_MethodHook() {

                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    Log.i("bqt", "2-【" + param.thisObject.getClass().toString() + "】");
                }
            });

        } else if (lpparam.packageName.equals("應用2的包名")) {
            //開啟日志打印
            Field field = XposedHelpers.findClass("完整路徑.LogTool", lpparam.classLoader).getField("a");
            field.setAccessible(true);
            field.setBoolean(null, true); //靜態字段的 the object whose field should be modified is null

            //注意,基本類型變量的class文件不能使用其相應包裝類型來標識,例如 boolean.class 不能使用 Boolean.class 來代替
            XposedHelpers.findAndHookMethod("包名.a", lpparam.classLoader, "a", long.class, String.class, boolean.class, new XC_MethodHook() {

                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    Log.i("bqt", "3-【" + Arrays.toString(param.args) + "】");
                }
            });
        }
    }
}

XposedZygoteInit

public class XposedZygoteInit implements IXposedHookZygoteInit {
    @Override
    public void initZygote(StartupParam startupParam) throws Throwable {

        XposedHelpers.findAndHookMethod("android.util.Log", StartupParam.class.getClassLoader(), "d", String.class, String.class, new XC_MethodHook() {

            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                //Log.i("bqt", "【代理系統的Log類,可以在這里把日志保存到文件中】" + param.args[0] + " ," + param.args[1]);
            }
        });
    }
}

2019-09-30


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM