目錄
初步分析
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查看。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 原創,轉載請註明來自看雪社區。好書推薦