- 反編譯 AndroidKiller 逆向 實踐案例 MD

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

目錄

反編譯 AndroidKiller 逆向 實踐案例 MD

PS:如下全部內容,包括測試用來逆向的APP均已脫敏java

參考個人其餘博客內容:android

AndroidKiller 簡介

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

AndroidKiller的基本功能:github

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

插件升級

軟件中的Apktool可能會由於版本過低致使 apk 的反編譯失敗,此時須要到 Apktool 官網去下載最新版本的Apktool。api

下載完成後找到解壓好的 AndroidKiller 目錄下的bin\apktool\apktool目錄將下載的最新版的 apktool 複製進去。微信

而後修改 AndroidKiller 根目錄下的bin\apktool下的apktool.batapktool.ini文件,將裏面對應的文件名改成你下載的最新的 apktool 文件名。app

基本使用

使用 AndroidKiller 對 Apk 進行反編譯只須要將 Apk 文件拖入軟件便可。框架

反編譯後咱們能夠對資源文件等進行簡單的修改,修改後後點擊左上角菜單欄中的Android -- 編譯便可從新編譯成APK。ide

另外咱們也能夠點擊 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和編譯器的名稱規範的檢查,在運行時即可匹配混淆後的類或方法(運行時並不會進行規範性檢查)。

完整案例

基本步驟

一、安裝 XposedInstaller.apk,安裝後啓動此應用,安裝 framework ,重啓手機

二、AS工程中添加依賴

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

三、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"/>

四、編寫Hook邏輯

五、配置完整的Hook類類名

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

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

六、有任何代碼變更,都需重裝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-10-23

相關文章
相關標籤/搜索