裝X指南之用 Xposed 把某寶資產改成100w

幕後眼光發表於2019-01-21

第一節

一、前言

Xposed 能幹嘛?我可以告訴你 Root + Xposed ,真的可以為所欲為。而 Android 開源,為“搞機”帶了更多的樂趣的同時,當然也引入安全性問題,部分流氓軟體在 Root 下,會盜取使用者私密資訊,例如:號碼、照片、簡訊、密碼······,所以需要慎重使用 Root,此外本文僅作為技術學習。

二、Xposed 安裝

1.首先你的手機 必須 Root,關於各安卓機型的 Root 方法,請自行到對應機型論壇和貼吧找找 (注:Root 有風險,失敗可能導致手機變磚,風險自行承擔)

2.下載「Xposed Installer」軟體並安裝,需要留意的是你的手機系統版本,不同版本下載對應的 apk,如果上不了該網址,可百度搜尋——Xposed Installer 最新版本下載。

  • Xposed 官方網址:

repo.xposed.info/module/de.r…

  • Android 5.0 及以上版本的下載地址:

forum.xda-developers.com/showthread.…

官網說明

3.啟用 Xposed 框架(確保你手機刷入了第三方 Recovery),啟用後可能會使系統變的有些卡頓,但為了技術(裝B),我們犧牲點效能還是值得~

  • 點選【安裝/更新】,選擇【Install via recovery】
  • 等待【下載】,重啟到【Recovery】模式,期間請勿操作
  • 耐心等待,刷完會自動重啟,開啟【Xposed Installer】,顯示框架已啟用。

Install via recovery
框架已啟用

三、配置 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
複製程式碼

Activity資訊

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…

jadx

注:如果出現 Jdk 錯誤,請安裝 Jdk 64 位版本。 更多技術分享,請加微信公眾號——碼農茅草屋:

第二節

一、前言

2018 年眨眼就結束,2019 年即將新年,在外工作拼搏一年,看著身邊的朋友一個個升職加薪,買房買車,你是不是很羨慕!今天看我如何一步一步改掉我的支付寶賬戶餘額的,進階成為「百萬富翁」

雖然不是真的賬戶餘額,但是看著這個數字還是很感人的~

0.00
100w

二、尋找 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。

AlipayLogin

不過尷尬的是,這裡面我反覆找了下,沒能找到 Fragment 相關資訊

LauncherActivity

3、通過 Monitor 去跟蹤一下里面的方法的呼叫流程,看看是怎麼個執行順序

在點選【財富】按鈕前,馬上點選 Monitor 裡的 Start Method Profiling ,等到頁面完切過去【財富頁】,並且載入資料完畢,點下停止即可,這樣就能跟蹤到這個程式的方法呼叫資訊了。

Start Method Profiling

在 trace 裡面,重點關注包名為 com.alipay.xxx 開頭的類的方法,可以找到幾個可疑的方法呼叫,裡面有 TabHost、TabLauncherFragment、FortureWidgetGroup、FortureHomeView,最主要是 Fortune 這個單詞是財富的意思

基本可以肯定最裡面的是 FortureHomeView 這個作為介面檢視,下個步驟就是怎麼在 FortureHomeView 找到我們需要改的【總資產】這個佈局,還有這個佈局賦值的地方

trace

繼續跟蹤,找到 FortureHomeView.updateFortureHead() 方法,由方法名可知更新頭部,展開這個方法,看看裡面呼叫了什麼

FortureHomeView

點選 AssetCardV2View.renderData() 方法名大概知道是 View 處理資料地方

AssetCardV2View.renderData()

越來越清晰了,繼續找下去。看到裡面的 AssetHeaderV2View.setData() 知道是頭部的設定資料

AssetHeaderV2View

展開裡面的 AssetHeaderV2View.setData() 看到有呼叫 TextView 的 setText() 方法,還有一個 NumRunningTextView 的 setRunningText() ,這裡結合我們平時看到的介面效果,這是個自定義的帶動畫效果的View,可以猜測是【昨日收益】的 TextView,因為受益為了更好地使用者體驗,會帶滾動效果,平時有留意就知道了。

昨日收益

知道具體是 AssetHeaderV2View,在設定介面的值,現在去看看它的原始碼是咋回事。

4、第一個框框裡面,有個 string 的命名是 hide_status_text,我覺得就是我們隱藏資產的那個【*】符號

然後再看看條件 else if(assetsCardModel != null) 後面執行的程式碼,在第二個框裡面,發現 setRunningText() 這個方法,而且方法有個引數是:totalYesterdayProfitView ,意思是昨天總收益,那這裡應該就是【昨日收益】設值的地方

那就是說前一個setText() 方法,如果沒有錯誤的話,就是設值【總資產】的地方,設值變數為 latestTotalView

AssetHeaderV2View.setData()

為了確保沒有錯誤,跟一下變數 a 和變數 b 具體是什麼型別,可以看到一個是 AUAutoResizeTextView 另一個 NumRunningTextView,這應該是自定義的控制元件,為啥總資產要定義為自動調整大小的View呢?想一下如果寫死寬度的話或者字型大小的,就相容不了每個人的資產數目,如:1000000 和 1 長度的區別。

a和b變數

AssetsCardModel 應該是儲存使用者資產資訊的 Model 類,框框裡面的變數,就是儲存著最新的【總資產】資訊

AssetsCardModel

好的,到這裡分析完了,總結一下具體是怎麼個流程:

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);
                    }
                });
            }

        }
    }
}
複製程式碼

相關閱讀:

更多技術分享,請加微信公眾號——碼農茅草屋:

碼農茅草屋

相關文章