转载:使用 frida hook 插件化 apk ( classloader )
?
使用 frida hook 插件化 apk:https://bbs.pediy.com/thread-258772.htm
?
最近拿到一個XX視頻apk樣本,里面有視頻、直播和小說,沒有VIP只能試看30秒,剛好最近學習frida,用來練習下,分析過程中發現是一個插件化的apk,本文記錄下分析的過程。
?
初步分析
首先從AndroidManifest.xml中獲取到apk的包名,并且查看下activity情況:
發現只有4個Activity,正常情況下一個apk肯定不止這些,所以初步懷疑這只是外殼,真正邏輯是在其他地方,會動態加載進來。
?
ps 查看
打開 apk
可以看到有兩個進程,從上圖也可以看到,2、3和4處的Activity是運行在plugin進程中,為了確認下視頻播放所在的進程,使用dumpsys meminfo查看
?
dumpsys meminfo查看
打開任意播放界面
確認視頻播放是在plugin進程中,此時真正的邏輯已經加載到進程中,查看下plugin進程的maps
?
cat /proc/7906/maps
從上圖可以看到,真正邏輯所在的apk是plugin-shadow-apk-debug.apk,是在該apk的私有文件目錄中。
從代碼中分析也可知道,此apk是插件化apk,使用的是騰訊開源的插件化框架Shadow,感興趣的可以去了解下。
?
定位關鍵代碼
既然已經找到真正的apk,那我們就需要定位到關鍵代碼地方。
?
字符串定位
從字符串中定位到有多個類滿足,此時一個一個去分析排查太耗時,接下來通過frida來枚舉出所有加載的類。
?
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
success
第一行為輸出結果,即表示當前使用的類為 com.iqiyi.plugin.widget.dkplayer.controller.VideoController;
第二行為執行完成的日志。
?
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) { // 此處是通過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該方法。
?
frida hook setVip
var videoController = Java.use("com.iqiyi.plugin.widget.dkplayer.controller.VideoController"); videoController.setVip.implementation = function() {console.log("hook setVip");this.setVip(true); };運行結果:
從運行結果來看,出現ClassNotFoundException錯誤,說明沒有找到我們要hook的類。
?
frida枚舉classloader
由于是插件化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。
?
frida指定classloader
來看下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; // 將當前class factory中的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; // 將當前class factory中的loader指定為我們需要的}},"onComplete": function() {console.log("success");}});// 此處需要使用Java.classFactory.usevar videoController = Java.classFactory.use("com.iqiyi.plugin.widget.dkplayer.controller.VideoController");videoController.setVip.implementation = function() {console.log("hook setVip");this.setVip(true);}; });運行結果:
可以看到,我們已經成功hook,并且視頻上已經沒有顯示剩余時間。
?
frida hook enum
直播和小說的vip判斷和視頻是不一致的,是通過enum中VIP字段值和1進行對比來判斷,具體定位過程和上面類似。
判斷代碼為:
if (TextUtils.equals("1", PluginEnum.VIP.getValue())) {...}
?
enum 測試
我們的目的是為了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;}}使用 javap 打開對應的 class 文件:
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") { // 此時this 或者 this.getString() 返回的是靜態成員名var vip = String.$new("1");this.setValue(vip); // 調用 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和插件化不熟的情況下。本文涉及到的有:
?
?
?
總結
以上是生活随笔為你收集整理的转载:使用 frida hook 插件化 apk ( classloader )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 爬虫 实例项目 大全
- 下一篇: 压栈, 跳转,执行,返回:从汇编看函数调