使用frida hook插件化apk

2021-03-02 看雪學院

目錄

初步分析

      ps 查看

      dumpsys meminfo查看

      cat /proc/7906/maps

定位關鍵代碼

      字符串定位

      frida枚舉所有加載的類

      VideoController 類分析

frida hook setVip

       frida枚舉classloader

       frida指定classloader

       最終腳本

frida hook enum

       enum測試

       最終腳本

整體腳本

總結

最近拿到一個XX視頻apk樣本,裡面有視頻、直播和小說,沒有VIP只能試看30秒,剛好最近學習frida,用來練習下,分析過程中發現是一個插件化的apk,本文記錄下分析的過程。首先從AndroidManifest.xml中獲取到apk的包名,並且查看下activity情況:發現只有4個Activity,正常情況下一個apk肯定不止這些,所以初步懷疑這只是外殼,真正邏輯是在其他地方,會動態加載進來。可以看到有兩個進程,從上圖也可以看到,2、3和4處的Activity是運行在plugin進程中,為了確認下視頻播放所在的進程,使用dumpsys meminfo查看。
確認視頻播放是在plugin進程中,此時真正的邏輯已經加載到進程中,查看下plugin進程的maps。從上圖可以看到,真正邏輯所在的apk是plugin-shadow-apk-debug.apk,是在該apk的私有文件目錄中。從代碼中分析也可知道,此apk是插件化apk,使用的是騰訊開源的插件化框架Shadow,感興趣的可以去了解下。既然已經找到真正的apk,那我們就需要定位到關鍵代碼地方。字符串定位從字符串中定位到有多個類滿足,此時一個一個去分析排查太耗時,接下來通過frida來枚舉出所有加載的類。
Java.enumerateLoadedClasses(callbacks) 是用來枚舉當前所有加載的類,通過和上述幾個關鍵類對比來找到實際調用的類,callbacks需要提供回調函數,對應onMatch和onComplete。具體如下面:
Java.perform(function () {         var key_class = ["com.facebook.plugin.widget.dkplayer.controller.PlayerVideoController",                     "com.iqiyi.plugin.widget.dkplayer.controller.PlayerVideoController",                     "com.facebook.plugin.widget.dkplayer.controller.VideoController",                     "com.iqiyi.plugin.widget.dkplayer.controller.VideoController"]     Java.enumerateLoadedClasses({        "onMatch": function(name, handle) {            for (var i = 0; i < key_class.length; i++) {                if (key_class[i] == name) {                    console.log(name);                }            }        },        "onComplete": function() {            console.log("success");        }    });});

com.iqiyi.plugin.widget.dkplayer.controller.VideoController第一行為輸出結果,即表示當前使用的類為 com.iqiyi.plugin.widget.dkplayer.controller.VideoController;
public int setProgress() {        ... ...      if (this.tryWatchTv != null && position > 0) {           pos = (int) (((long) this.stopPlayTime) - position);          TextView textView = this.tryWatchTv;          StringBuilder stringBuilder = new StringBuilder();          stringBuilder.append("剩餘試看時間: ");           if (pos > 0) {              j = (long) pos;          }          stringBuilder.append(stringForTime(j));          textView.setText(stringBuilder.toString());      }      if (!this.isVip) {           StringBuilder stringBuilder2 = new StringBuilder();          stringBuilder2.append("position = ");          stringBuilder2.append(position);          stringBuilder2.append(" showVipHintTime = ");          stringBuilder2.append(this.showVipHintTime);          LogHelper.i(stringBuilder2.toString());          if (position < ((long) this.showVipHintTime) || this.showVipHintTime <= 0) {              this.vipHintView.setVisibility(8);          } else {              this.vipHintView.setVisibility(0);          }          if (position >= ((long) this.stopPlayTime)) {              this.mMediaPlayer.pause();          }      }      ... ...  }

可以看到類中通過isVip變量來執行不同邏輯,繼續看下isVip是如何設置的:
public void setVip(boolean isVip) {    this.isVip = isVip;    this.tryWatchTv.setVisibility(this.isVip ? 8 : 0);    if (this.isVip) {        this.vipHintView.setVisibility(8);    }}

可以看到當前類有setVip方法,用於設置該變量,此時可以不用在繼續分析調用者,最終都會調用此處,所以我們可以使用frida hook該方法。
var videoController = Java.use("com.iqiyi.plugin.widget.dkplayer.controller.VideoController");videoController.setVip.implementation = function() {    console.log("hook setVip");    this.setVip(true);};

{'type': 'error', 'description': 'Error: java.lang.ClassNotFoundException: Didn\'t find class "com.iqiyi.plugin.widget.dkplayer.controller.VideoController" on path: DexPathList[[dex file "InMemoryDexFile[cookie=[0, 3983850208]]", zip file "/system/framework/org.apache.http.legacy.boot.jar", zip file "/data/app/com.iqiyi.vider-U7T4aZIQOQyXS5iLBgsNGw==/base.apk"],nativeLibraryDirectories=[/data/app/com.iqiyi.vider-U7T4aZIQOQyXS5iLBgsNGw==/lib/arm, /data/app/com.iqiyi.vider-U7T4aZIQOQyXS5iLBgsNGw==/base.apk!/lib/armeabi-v7a, /system/lib]]', 'stack': 'Error: java.lang.ClassNotFoundException: Didn\'t find class "com.iqiyi.plugin.widget.dkplayer.controller.VideoController" on path: DexPathList[[dex file "InMemoryDexFile[cookie=[0, 3983850208]]", zip file "/system/framework/org.apache.http.legacy.boot.jar", zip file "/data/app/com.iqiyi.vider-U7T4aZIQOQyXS5iLBgsNGw==/base.apk"],nativeLibraryDirectories=[/data/app/com.iqiyi.vider-U7T4aZIQOQyXS5iLBgsNGw==/lib/arm, /data/app/com.iqiyi.vider-U7T4aZIQOQyXS5iLBgsNGw==/base.apk!/lib/armeabi-v7a, /system/lib]]\n    at frida/node_modules/frida-java-bridge/lib/env.js:124\n    at frida/node_modules/frida-java-bridge/lib/class-factory.js:400\n    at frida/node_modules/frida-java-bridge/lib/class-factory.js:781\n    at frida/node_modules/frida-java-bridge/lib/class-factory.js:90\n    at frida/node_modules/frida-java-bridge/lib/class-factory.js:44\n    at /script1.js:23\n    at frida/node_modules/frida-java-bridge/lib/vm.js:11\n    at frida/node_modules/frida-java-bridge/index.js:368\n    at frida/node_modules/frida-java-bridge/index.js:318', 'fileName': 'frida/node_modules/frida-java-bridge/lib/env.js', 'lineNumber': 124, 'columnNumber': 1}

從運行結果來看,出現ClassNotFoundException錯誤,說明沒有找到我們要hook的類。由於是插件化apk,類加載是在插件化框架自定義的,所以classloader不能使用默認的。我們可以使用Java.enumerateClassLoaders(callbacks)來列印出所有的加載器。
Java.perform(function () {        Java.enumerateClassLoaders({        "onMatch": function(loader) {            console.log(loader);        },        "onComplete": function() {            console.log("success");        }    });});

由上面分析可知,真正邏輯代碼是在plugin-shadow-apk-debug.apk中,那該apk對應的classloader是com.tencent.shadow.core.loader.classloaders.PluginClassLoader。來看下Java.ClassFactory中loader的介紹:"read-only property providing a wrapper for the class loader currently being used.",loader是當前classloader的wrapper,我們修改classloader可以通過修改該欄位。Java.classFactory是默認的class factory,所以我們需要修改的是Java.classFactory.loader。
Java.perform(function () {    Java.enumerateClassLoaders({        "onMatch": function(loader) {            if (loader.toString().startsWith("com.tencent.shadow.core.loader.classloaders.PluginClassLoader")) {                Java.classFactory.loader = loader;             }        },        "onComplete": function() {            console.log("success");        }    });});

最終腳本
Java.perform(function () {    Java.enumerateClassLoaders({        "onMatch": function(loader) {            if (loader.toString().startsWith("com.tencent.shadow.core.loader.classloaders.PluginClassLoader")) {                Java.classFactory.loader = loader;             }        },        "onComplete": function() {            console.log("success");        }    });         var videoController = Java.classFactory.use("com.iqiyi.plugin.widget.dkplayer.controller.VideoController");    videoController.setVip.implementation = function() {        console.log("hook setVip");        this.setVip(true);    };});

可以看到,我們已經成功hook,並且視頻上已經沒有顯示剩餘時間。直播和小說的vip判斷和視頻是不一致的,是通過enum中VIP欄位值和1進行對比來判斷,具體定位過程和上面類似。
if (TextUtils.equals("1", PluginEnum.VIP.getValue())) {...}


我們的目的是為了hook VIP,但是對enum的這種用法不是很熟,於是寫了個測試程序,來進一步了解:
public enum TestEnum {    A("a"),    B("b"),    C("c");     private String value;     private TestEnum(String value) {        this.value = value;    }     public String getValue() {        return this.value;    } }

Compiled from "TestEnum.java"public final class TestEnum extends java.lang.Enum<TestEnum> {  public static final TestEnum A;  public static final TestEnum B;  public static final TestEnum C;  public static TestEnum[] values();  public static TestEnum valueOf(java.lang.String);  public java.lang.String getValue();  static {};}

從這裡可以很明顯看到, A、B和C都屬於TestEnum中的靜態成員變量。來看下調用的smali代碼:
sget-object v3, Lcom/iqiyi/plugin/base/PluginEnum;->VIP:Lcom/iqiyi/plugin/base/PluginEnum;invoke-virtual {v3}, Lcom/iqiyi/plugin/base/PluginEnum;->getValue()Ljava/lang/String;

從smali上也能看出來類似的邏輯,VIP是com/iqiyi/plugin/base/PluginEnum的靜態成員,然後在調用getValue()方法。所以我們hook com/iqiyi/plugin/base/PluginEnum類的getValue方法,然後判斷調用者是否為VIP。最終腳本
Java.perform(function () {    var pluginEnum = Java.classFactory.use("com.iqiyi.plugin.base.PluginEnum");    var String = Java.use("java.lang.String");    pluginEnum.getValue.implementation = function() {        var value = this.getValue();        if (this == "VIP") {             var vip = String.$new("1");            this.setValue(vip);             return vip;        } else {            return value;        }    }});

Java.perform(function () {    Java.enumerateClassLoaders({        "onMatch": function(loader) {            if (loader.toString().startsWith("com.tencent.shadow.core.loader.classloaders.PluginClassLoader")) {                Java.classFactory.loader = loader;            }        },        "onComplete": function() {            console.log("success");        }    });     var videoController = Java.classFactory.use("com.iqiyi.plugin.widget.dkplayer.controller.VideoController");    videoController.setVip.implementation = function() {        console.log("hook setVip");        this.setVip(true);    };     var pluginEnum = Java.classFactory.use("com.iqiyi.plugin.base.PluginEnum");    var String = Java.use("java.lang.String");    pluginEnum.getValue.implementation = function() {        var value = this.getValue();        if (this == "VIP") {            var vip = String.$new("1");            this.setValue(vip);            return vip;        } else {            return value;        }    } });

通過對該樣本的分析,逆向找尋關鍵代碼相對簡單,但是在使用frida hook時相對難點,特別是對於frida和插件化不熟的情況下。

1. frida枚舉所有加載的類

2. frida枚舉classloader

3. frida對enum類型的hook

看雪ID:liwugang

https://bbs.pediy.com/user-403246.htm 

*這裡由看雪論壇 liwugang 原創,轉載請註明來自看雪社區。

好書推薦

相關焦點

  • 初識Frida--Android逆向之Java層hook (二)
    ,建議先看看之前的文章:初識Frida--Android逆向之Java層hook (一) 文章涉及到的知識點:示例下載:whyshouldIpay 下載apk後安裝,一樣還是先來看看是什麼功能,這是一個比較簡單的驗證程序,簡單的使用後,了解到PREMIUM CONETNT內容需要輸入License驗證後才能查看
  • Android組件化和插件化的概念
    插件化: 和組件化開發略有不用,插件化開發時將整個app拆分成很多模塊,這些模塊包括一個宿主和多個插件,每個模塊都是一個apk(組件化的每個模塊是個lib),最終打包的時候將宿主apk和插件apk分開或者聯合打包。
  • 詳解Hook框架frida,讓你在逆向工作中效率成倍提升!
    一、frida簡介frida是一款基於python + javascript 的hook框架,可運行在androidioslinuxwinosx等各平臺,主要使用動態二進位插樁技術。本期「安仔課堂」,ISEC實驗室的彭老師為大家詳解frida,認真讀完這篇文章會讓你在逆向工作中效率成倍提升哦!
  • Hook夢幻旅途之Frida
    它主要提供了功能簡單的python接口和功能豐富的js接口,使得hook函數和修改so編程化,值得一提的是接口中包含了主控端與目標進程的交互接口,由此我們可以即時獲取信息並隨時進行修改。使用frida可以獲取進程的信息(模塊列表,線程列表,庫導出函數),可以攔截指定函數和調用指定函數,可以注入代碼,總而言之,使用frida我們可以對進程模塊進行手術刀式剖析。
  • 使用msfvenom生成惡意APP並對該APK進行拆包分析
    使用到的工具:jre、frida測試手機:Pixel XL、Android 9測試平臺:Ubuntu (Kali本身有自帶metasploit,但這裡為了同時記錄安裝過程,所以用Ubuntu)3.2 使用msfvenom生成APK輸入命令:msfvenom -p android/meterpreter/reverse_tcp LHOST=Ubuntu的ip LPORT=埠號 R > xxxxx.apk其中,-p是指定的payload,LHOST和LPORT
  • 從三道題目入門frida
    這三道題主要考察`Frida Java Hook`三板斧「hook、invoke、rpc」中的前兩板斧,即hook分析和主動調用。題目主要考察了以下知識點:1. frida java hook與靜態函數的主動調用2. Frida遍歷ClassLoader從而hook動態加載的dex的函數3. frida native hook去反調試ps. 題目附件請點擊「閱讀原文」下載。現在,看雪《安卓高級研修班(網課)》9月班開始招生啦!
  • Android插件化系列三:技術流派和四大組件支持
    DroidPlugin是通過hook系統服務來進行Activity跳轉,缺點是hook太多,代碼複雜且不夠穩定。第二代VirtualApk, Small(林光亮),RePlugin為了同時達到插件開發的低侵入性(像開發普通app一樣開發插件)和框架的穩定性,在實現原理上都是趨近於選擇儘量少的hook,並通過在manifest中預埋一些組件實現對四大組件的插件化。
  • 使用 Frida 來 hack 安卓 APP(一)
    你可以使用命令行窗口或者像 frida-trace 的記錄 low-level 函數(例如 libc.so 中的'open'調用)的工具來快速運行。你可以使用C,NodeJs或者Python綁定來完成更加複雜的工作。Frida 在內部使用了很多 JavaScript 語言,因此很多時候你可能都需要和這個語言打交道。
  • 記一次frida實戰——對某視頻APP的脫殼、hook破解、模擬抓包、協議分析一條龍服務
    最近在對一個 APP 進行分析的過程中,使用 frida 完成了脫殼、hook 破解、模擬抓包、協議分析的操作,可以說是一條龍服務了, 感覺十分有意義,學到了很多,對 frida 的理解和掌握程度也提高了不少,記錄下來這次實戰分享給各位正在學習 frida 的看雪用戶。
  • 【Hook】實現無清單啟動Activity的應用
    答案:插件化.插件化是一個寬泛的概念,只要是實現了 宿主app上插件功能的靈活拔插,實現了宿主app業務和插件功能的完全解耦,就可以稱之為插件化.之前寫過一篇 插件化的文章:手把手講解 Android插件化啟動Activity , 那時候用的插件化,原理是用 宿主中 真實Activity作為 代理,來啟動插件中的 Activity,管理插件中 Activity的 生命周期,並且處理好 插件原始碼和 資源文件。
  • 進階Frida--Android逆向之動態加載dex Hook(三)(上篇)
    前段時間看到有朋友在問在怎麼使用frida去hook動態加載的dex之類的問題,確實關於如何
  • 這恐怕是學習Frida最詳細的筆記(三)
    使用方法見下代碼。另外為了將jstring的值列印出來,可以使用jenv的函數getStringUtfChars,就像正常的寫native程序一樣。 RegisterNative使用下面這個腳本來列印出RegisterNatives的參數,這裡需要注意的是使用了enumerateSymbolsSync,它是enumerateSymbols的同步版本。
  • Hook Android C代碼(Cydia Substrate)
    之前對於Cydia Substrate這個框架的使用及如何hook到Android的Java層,是在學習了鬼哥的Hook Android Java
  • JS逆向-Sekiro框架的簡單使用
    配置代碼塊完成之後,打開chrome的reres插件(reres插件可自行搜索下載),進行js文件替換:在sekiro_test.js文件中直接使用registerAction註冊一個getaData接口,傳遞resolve和reject參數調用init方法,做如下配置:
  • apkpure下載安裝
    你是否在尋找apkpure下載安裝,18183遊戲庫為您提供最新最好的下載體驗! apkpure下載安裝官方介紹: 在apkpure下載安裝這裡,你可以使用掌上行動裝置隨時隨地進行遊戲 APKPure原版APP下載,APKPure原版APP是一款匯集了眾多遊戲資訊的綜合平臺。集遊社iOS版app每天都會提供最新的手遊信息,為你提供有趣、可玩性強的二次元手遊,讓你可輕鬆找到喜歡的遊戲。
  • Android上玩玩Hook:Cydia Substrate實戰
    安裝Cydia Substrate框架Android本地服務首先就是在Android設備中安裝Cydia Substrate框架的本地服務應用substrate.apk,我們可以在其官網下載到。下載使用Cydia Substrate庫 Cydia Substrate官方建議在Android SDK Manager中添加它們插件地址的方式進行更新下載。如:在用戶自定義網址中添加http://asdk.cydiasubstrate.com/addon.xml。
  • APP逆向神器之Frida【Android初級篇】
    那麼怎麼使用呢?首先我們在Frida官方文檔中的Installation頁可以看到,我們需要有Python環境,並且用pip安裝一個叫frida-tools的庫,然後才可以開始使用。Python環境相信大家都有了,直接打開命令行,執行一波pip install frida-tools吧。
  • apkpureapp官方下載
    來試試apkpureapp官方下載吧! apkpureapp官方下載版本記錄: apkpureapp官方下載雖然看起來不怎樣,但是只要你開始玩,相信你會停不下來的 APKPure應用市場,它擁有Google Play市場內所有免費付費應用和遊戲,無需科學上網在全球目前沒被封鎖屏蔽,應用更新的速度推送快,全部應用可以直接免費下載。