使用 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和插件化不熟的情况下。本文涉及到的有:

  1. frida枚举所有加载的类;
  2. frida枚举classloader;
  3. frida对enum类型的hook。

转载:使用 frida hook 插件化 apk ( classloader )相关推荐

  1. 【Android 插件化】Hook 插件化框架 ( 创建插件应用 | 拷贝插件 APK | 初始化插件包 | 测试插件 DEX 字节码 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  2. 【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  3. 【Android 插件化】Hook 插件化框架 ( 使用 Hook 方式替换插件 Activity 的 mResources 成员变量 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  4. 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  5. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | 主线程创建 Activity 实例之前使用插件 Activity 类替换占位的组件 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  6. 【Android 插件化】Hook 插件化框架 ( 加载插件包资源 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  7. 【Android 插件化】Hook 插件化框架 ( 反射工具类 | 反射常用操作整理 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  8. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | AMS 启动前使用动态代理替换掉插件 Activity 类 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  9. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | 反射获取 IActivityManager 对象 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

最新文章

  1. Android WindowManager 解析与骗取 QQ 密码案例分析
  2. linux的零碎知识
  3. 微信开发直接访问本地调试
  4. 《深入理解Spark:核心思想与源码分析》——3.10节创建和启动ExecutorAllocationManager...
  5. 一起学nRF51xx 18 -  蓝牙协议栈烧录
  6. CSS之固定定位、相对定位、绝对定位
  7. 为什么同现矩阵*评分矩阵=推荐结果?
  8. java技术架构选型方案报告.pdf,来啦,2020开源报告!
  9. 图片压缩利器:TinyPNGWrapper
  10. 使用mysql打开什么文件格式_dbf是什么文件怎么打开
  11. 基于JS实现简单甘特图
  12. 使用MATLAB2010实现AVI视频播放
  13. 用word2003打开.docx文件
  14. 点线形系列1-计算两点之间的距离
  15. 【小波变换】小波变换入门----haar小波
  16. 浙江大学计算机博士申请考核,考博经验|2020年浙江大学博士申请考核经验分享...
  17. Hibernate各种查询方法
  18. 【CS224n-5】Linguistic Structure: Dependency Parsing
  19. python图形界面教程(tkinter)
  20. 2022-2028年中国度假村行业发展动态及投资规划分析报告

热门文章

  1. 跨性别,你所不知道的事
  2. 华为2019年校招(20届实习)机考题python版解答与思路(2019-3-13软件题)
  3. KubeVela 高可扩展的云原生应用平台与核心引擎
  4. 开源社区慌不慌?又一个 Linux 发行版宣告死亡
  5. C#穿透session隔离———Windows服务启动UI交互程序
  6. luogu1168 中位数
  7. 玩转python(2)多线程的历史2
  8. 96. Unique Binary Search Trees1和2
  9. 基于visual Studio2013解决面试题之0608找出两个只出现一次的数
  10. 计算机网络(二十三)-网络层-概述与数据交换方式