| 我的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.bat和apktool.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 類型的,動態框架肯定是無法修改的,一種可行的方式:
- 修改
.class文件,具體詳見:Javassist 字節碼 簡介 案例 MD - 重新打成jar包,具體詳見:字節碼 反編譯 APKTool 重新打jar包 MD
- 重新編譯APK(借助AndroidKiller)
如何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
