第一節
一、前言
Xposed 能幹嘛?我可以告訴你 Root + Xposed ,真的可以為所欲為。而 Android 開源,為“搞機”帶了更多的樂趣的同時,當然也引入安全性問題,部分流氓軟體在 Root 下,會盜取使用者私密資訊,例如:號碼、照片、簡訊、密碼······,所以需要慎重使用 Root,此外本文僅作為技術學習。
二、Xposed 安裝
1.首先你的手機 必須 Root,關於各安卓機型的 Root 方法,請自行到對應機型論壇和貼吧找找 (注:Root 有風險,失敗可能導致手機變磚,風險自行承擔)
2.下載「Xposed Installer」軟體並安裝,需要留意的是你的手機系統版本,不同版本下載對應的 apk,如果上不了該網址,可百度搜尋——Xposed Installer 最新版本下載。
- Xposed 官方網址:
- Android 5.0 及以上版本的下載地址:
3.啟用 Xposed 框架(確保你手機刷入了第三方 Recovery),啟用後可能會使系統變的有些卡頓,但為了技術(裝B),我們犧牲點效能還是值得~
- 點選【安裝/更新】,選擇【Install via recovery】
- 等待【下載】,重啟到【Recovery】模式,期間請勿操作
- 耐心等待,刷完會自動重啟,開啟【Xposed Installer】,顯示框架已啟用。
三、配置 Xposed 外掛
如何配置 Android Studio 專案為 Xposed 外掛?
1、配置專案 Gradle 的依賴
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
複製程式碼
注:需要 compileOnly 來依賴,如果不想通過 Gradle 配置,也可以下載 XposedBridgeApi.jar ,放到專案 libs 目錄。
2、配置 AndroidManifest.xml
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="XXX外掛" />
<meta-data
android:name="xposedminversion"
android:value="89" />
複製程式碼
- xposedmodule:是否配置為 Xposed 外掛,設定為 true
- xposeddescription:模組名稱
- xposedminversion:最低版本號
3、新建 Hook 入口類
該類需要實現介面 IXposedHookLoadPackage
,並實現裡面關鍵方法handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam)
,該方法會在每個軟體被啟動的時候回撥,所以一般需要通過目標包名過濾。
/**
* @author zhicheng.chen
*/
public class TargetHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
//通過目標包名過濾
if (lpparam.packageName.equals("com.xxx.xxx")) {
XposedBridge.log("啟動了xxx軟體");
}
}
}
複製程式碼
4、Xposed 免重啟除錯
Xposed外掛每次程式碼改動,都需要重啟手機才能生效,有時候重啟一次還不生效(我的手機有一次重啟 3 次,才看到生效,還好是公司測試機,不心疼),所以程式碼最好寫上相關 Log 資訊,來看程式碼生效沒。
XposedBridge.log("啟動了xxx軟體");
不過這裡分享一個免重啟除錯的方法,方法來自網上,感謝 DX :
/**
* @author DX
* 這種方案建議只在開發除錯的時候使用,因為這將損耗一些效能(需要額外載入apk檔案),除錯沒問題後,直接修改xposed_init檔案為正確的類即可
* 可以實現免重啟,由於存在快取,需要殺死宿主程式以後才能生效
* 這種免重啟的方式針對某些特殊情況的hook無效
* 例如我們需要implements IXposedHookZygoteInit,並將自己的一個服務註冊為系統服務,這種就必須重啟了
* Created by DX on 2017/10/4.
*/
public class HookLoader2 implements IXposedHookLoadPackage {
//按照實際使用情況修改下面幾項的值
/**
* 當前Xposed模組的包名,方便尋找apk檔案
*/
private final String modulePackage = "com.xxx.plugin";
/**
* 宿主程式的包名(允許多個),過濾無意義的包名,防止無意義的apk檔案載入
*/
private static List<String> hostAppPackages = new ArrayList<>();
static {
// TODO: Add the package name of application your want to hook!
hostAppPackages.add("com.eg.android.AlipayGphone");
hostAppPackages.add("com.xxx.plugin");
}
/**
* 實際hook邏輯處理類
*/
private final String handleHookClass = TargetHook.class.getName();
/**
* 實際hook邏輯處理類的入口方法
*/
private final String handleHookMethod = "handleLoadPackage";
@Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (hostAppPackages.contains(loadPackageParam.packageName)) {
//將loadPackageParam的classloader替換為宿主程式Application的classloader,解決宿主程式存在多個.dex檔案時,有時候ClassNotFound的問題
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Context context=(Context) param.args[0];
loadPackageParam.classLoader = context.getClassLoader();
invokeHandleHookMethod(context,modulePackage, handleHookClass, handleHookMethod, loadPackageParam);
}
});
}
}
/**
* 安裝app以後,系統會在/data/app/下備份了一份.apk檔案,通過動態載入這個apk檔案,呼叫相應的方法
* 這樣就可以實現,只需要第一次重啟,以後修改hook程式碼就不用重啟了
* @param context context引數
* @param modulePackageName 當前模組的packageName
* @param handleHookClass 指定由哪一個類處理相關的hook邏輯
* @param loadPackageParam 傳入XC_LoadPackage.LoadPackageParam引數
* @throws Throwable 丟擲各種異常,包括具體hook邏輯的異常,尋找apk檔案異常,反射載入Class異常等
*/
private void invokeHandleHookMethod(Context context, String modulePackageName, String handleHookClass, String handleHookMethod, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
File apkFile=findApkFile(context,modulePackageName);
if (apkFile==null){
throw new RuntimeException("尋找模組apk失敗");
}
//載入指定的hook邏輯處理類,並呼叫它的handleHook方法
PathClassLoader pathClassLoader = new PathClassLoader(apkFile.getAbsolutePath(), ClassLoader.getSystemClassLoader());
Class<?> cls = Class.forName(handleHookClass, true, pathClassLoader);
Object instance = cls.newInstance();
Method method = cls.getDeclaredMethod(handleHookMethod, XC_LoadPackage.LoadPackageParam.class);
method.invoke(instance, loadPackageParam);
}
/**
* 根據包名構建目標Context,並呼叫getPackageCodePath()來定位apk
* @param context context引數
* @param modulePackageName 當前模組包名
* @return return apk file
*/
private File findApkFile(Context context, String modulePackageName){
if (context==null){
return null;
}
try {
Context moudleContext = context.createPackageContext(modulePackageName, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
String apkPath=moudleContext.getPackageCodePath();
return new File(apkPath);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
複製程式碼
四、反編譯工具
1、TopActivity.apk
安裝這個工具可以直接檢視當前 App 顯示的最前 Activity,後面我們接觸別人寫的專案,這個工具可以很方便跟蹤程式碼的入口,專案原理是通過 AccessibilityService
服務,監聽所有的介面變化,讀取當前介面 Activity,需要給軟體開啟【輔助許可權】。
專案地址: github.com/109021017/a…
當然也可以通過 Adb 命令,獲取 Dumpsys 當前 Activity 的資訊:
adb shell dumpsys activity top
複製程式碼
2、BDOpener.apk
這是 Xposed 的外掛模組,也就是說需要刷入 Xposed 框架後,才能使用該,通過安裝這個軟體,我們可以使手機安裝的軟體,變為可除錯狀態,這樣就可以通過 Android Studio 的 Monitor 工具,檢視所有的程式狀態,並 dumpsys 方法呼叫資訊。
安裝這個軟體之後,需要在 Xposed 裡面的【模組】裡面,把該軟體勾選後,【重啟手機】才能生效。
有人說找不到 Monitor 工具,該工具在新的 As 版本把入口取消了,我們可以在:C:\Users\Administrator\AppData\Local\Android\Sdk\tools\monitor.bat
目錄下找到。
開啟除錯狀態之前,只能看到 AS 除錯安裝的程式:
開啟除錯狀態之後,可以看到所有的程式資訊:
3、jadx-gui 工具
關於這個工具的介紹,這裡我就不再贅述,貼一篇我覺得寫得很好的博文,作者講得很詳細易懂。
Android 反編譯利器,jadx 的高階技巧:www.jianshu.com/p/e5b021df2…
注:如果出現 Jdk 錯誤,請安裝 Jdk 64 位版本。 更多技術分享,請加微信公眾號——碼農茅草屋:
第二節
一、前言
2018 年眨眼就結束,2019 年即將新年,在外工作拼搏一年,看著身邊的朋友一個個升職加薪,買房買車,你是不是很羨慕!今天看我如何一步一步改掉我的支付寶賬戶餘額的,進階成為「百萬富翁」
雖然不是真的賬戶餘額,但是看著這個數字還是很感人的~
二、尋找 Hook 入口
1、結合 Top-Activity
獲取到當前首頁的 Activity 名稱,可以看到截圖支付寶首頁的介面的 Activity 名稱是 com.eg.android.AlipayGphone.AlipayLogin
,然後我們開啟 Jadx-gui , 並開啟下載好的 alipay_wap_main.apk
,反編譯出原始碼
2、首頁這樣的排版,能想到這是常見的佈局形式:TabHost + Fragment
。那重點去找【財富】對應的 Tab 下的 Fragment 類,開啟 AlipayLogin 原始碼,它是繼承 LauncherActivity
,不出意外的話,應該可以在這個類找到下面四個 Tab 各自對應的 Fragment。
不過尷尬的是,這裡面我反覆找了下,沒能找到 Fragment 相關資訊
3、通過 Monitor 去跟蹤一下里面的方法的呼叫流程,看看是怎麼個執行順序
在點選【財富】按鈕前,馬上點選 Monitor
裡的 Start Method Profiling
,等到頁面完切過去【財富頁】,並且載入資料完畢,點下停止即可,這樣就能跟蹤到這個程式的方法呼叫資訊了。
在 trace 裡面,重點關注包名為 com.alipay.xxx
開頭的類的方法,可以找到幾個可疑的方法呼叫,裡面有 TabHost、TabLauncherFragment、FortureWidgetGroup、FortureHomeView
,最主要是 Fortune 這個單詞是財富的意思
基本可以肯定最裡面的是 FortureHomeView
這個作為介面檢視,下個步驟就是怎麼在 FortureHomeView
找到我們需要改的【總資產】這個佈局,還有這個佈局賦值的地方
繼續跟蹤,找到 FortureHomeView.updateFortureHead()
方法,由方法名可知更新頭部,展開這個方法,看看裡面呼叫了什麼
點選 AssetCardV2View.renderData()
方法名大概知道是 View 處理資料地方
越來越清晰了,繼續找下去。看到裡面的 AssetHeaderV2View.setData()
知道是頭部的設定資料
展開裡面的 AssetHeaderV2View.setData()
看到有呼叫 TextView 的 setText() 方法,還有一個 NumRunningTextView 的 setRunningText() ,這裡結合我們平時看到的介面效果,這是個自定義的帶動畫效果的View,可以猜測是【昨日收益】的 TextView,因為受益為了更好地使用者體驗,會帶滾動效果,平時有留意就知道了。
知道具體是 AssetHeaderV2View
,在設定介面的值,現在去看看它的原始碼是咋回事。
4、第一個框框裡面,有個 string 的命名是 hide_status_text
,我覺得就是我們隱藏資產的那個【*】符號
然後再看看條件 else if(assetsCardModel != null)
後面執行的程式碼,在第二個框裡面,發現 setRunningText()
這個方法,而且方法有個引數是:totalYesterdayProfitView
,意思是昨天總收益,那這裡應該就是【昨日收益】設值的地方
那就是說前一個setText()
方法,如果沒有錯誤的話,就是設值【總資產】的地方,設值變數為 latestTotalView
。
為了確保沒有錯誤,跟一下變數 a 和變數 b 具體是什麼型別,可以看到一個是 AUAutoResizeTextView
另一個 NumRunningTextView
,這應該是自定義的控制元件,為啥總資產要定義為自動調整大小的View呢?想一下如果寫死寬度的話或者字型大小的,就相容不了每個人的資產數目,如:1000000 和 1 長度的區別。
AssetsCardModel
應該是儲存使用者資產資訊的 Model 類,框框裡面的變數,就是儲存著最新的【總資產】資訊
好的,到這裡分析完了,總結一下具體是怎麼個流程:
LauncherActivity -> TabHost -> TabLauncherFragment -> FortureHomeView.updateFortureHead() -> AssetCardV2View.renderData() -> AssetHeaderV2View.setData() -> AUAutoResizeTextView.setText()
三、Hook 程式碼
現在只需要對 AssetHeaderV2View.setData()
加工處理,在呼叫這個方法之前,對引數進行更改,通過反射把裡面的 latestTotalView
,改成你想要的金額。
/**
* @author zhicheng.chen
* @date 2018/11/26
*/
public class AlipayHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (lpparam.packageName.equals("com.eg.android.AlipayGphone")) {
XposedBridge.log("load alipay");
ClassLoader classLoader = lpparam.classLoader;
Class<?> aClass = classLoader.loadClass("com.alipay.android.render.engine.viewbiz.AssetsHeaderV2View");
Class<?> aClass2 = classLoader.loadClass("com.alipay.android.render.engine.model.AssetsCardModel");
if (aClass != null) {
XposedHelpers.findAndHookMethod(aClass, "setData", aClass2, boolean.class, boolean.class, boolean.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Object arg = param.args[0];
try {
Log.w("czc", arg.getClass().getField("latestTotalView").get(arg).toString());
arg.getClass().getField("latestTotalView").set(arg,"1000000.00");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
}
}
}
}
複製程式碼
相關閱讀:
更多技術分享,請加微信公眾號——碼農茅草屋: