输出apk_使用frida hook插件化apk

868a338eba611f2da14bb61a56aef505.png 本文为看雪论坛优秀文章 看雪论坛作者ID:liwugang

目录

初步分析

      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情况:   02d6675608f45ac35693ac9ca89000e3.png   发现只有4个Activity,正常情况下一个apk肯定不止这些,所以初步怀疑这只是外壳,真正逻辑是在其他地方,会动态加载进来。

>>>>

ps 查看

打开apk: 24810a490a6efe9ebffb9bfcefcee96a.png   可以看到有两个进程,从上图也可以看到,2、3和4处的Activity是运行在plugin进程中,为了确认下视频播放所在的进程,使用dumpsys meminfo查看。

>>>>

dumpsys meminfo 查看

打开任意播放界面:   3638df3d51253bde7ed2202d95b7a7ac.png   确认视频播放是在plugin进程中,此时真正的逻辑已经加载到进程中,查看下plugin进程的maps。

>>>>

cat/proc/7906/maps

7fde728e5589be9bf056b98d8163790a.png   从上图可以看到,真正逻辑所在的apk是plugin-shadow-apk-debug.apk,是在该apk的私有文件目录中。   从代码中分析也可知道,此apk是插件化apk,使用的是腾讯开源的插件化框架Shadow,感兴趣的可以去了解下。 定位关键代码 既然已经找到真正的apk,那我们就需要定位到关键代码地方。   bd915138897e803cea727d362e27e3f7.png

>>>>  字符串定位

7dafd3fc3e43740027ef74ed99190b1e.png   从字符串中定位到有多个类满足,此时一个一个去分析排查太耗时,接下来通过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);};
运行结果:
{'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的类。

>>>>

frida 枚举 classloader

由于是插件化apk,类加载是在插件化框架自定义的,所以classloader不能使用默认的。我们可以使用Java.enumerateClassLoaders(callbacks)来打印出所有的加载器。
Java.perform(function () {        Java.enumerateClassLoaders({        "onMatch": function(loader) {            console.log(loader);        },        "onComplete": function() {            console.log("success");        }    });});
运行结果:   54e2cc767d54f57c96e1f460dc7304be.png   由上面分析可知,真正逻辑代码是在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.use    var videoController = Java.classFactory.use("com.iqiyi.plugin.widget.dkplayer.controller.VideoController");    videoController.setVip.implementation = function() {        console.log("hook setVip");        this.setVip(true);    };});
运行结果:   7bdbc76586dab62b52457a15b52b4043.png   可以看到,我们已经成功hook,并且视频上已经没有显示剩余时间。 frida hook enum 直播和小说的vip判断和视频是不一致的,是通过enum中VIP字段值和1进行对比来判断,具体定位过程和上面类似。   7918b312619a8c1755588d23f9854ea4.png   判断代码为:
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

5a7cd4eda5ebd020a6380022a0b202d9.gif - End - 62e4b11e992bcfc8e7bbf43e03175f3b.png

看雪ID:liwugang

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

*这里由看雪论坛 liwugang 原创,转载请注明来自看雪社区。

推荐文章++++

bccfd000e0390ef0676b7bf498ee85ac.png

*   CVE-2018-0798及利用样本分析

*   Android逆向之一款有缘的apk第一篇

*   ptmalloc代码研究

*   深入窥探动态链接

*   PspCidTable引起的思考和探索

好书推荐96ddedb26325f77d8da9f8c96ae22006.png

﹀ ﹀ ﹀ 19ea8117a01d5d3928898693738c2c83.png 公众号ID:ikanxue 官方微博:看雪安全 商务合作:wsc@kanxue.com 9590eb007a36237b20939a48fe611297.gif 戳 “阅读 原文  ”  一起来充电吧!
使用Frida对UniApp APK进行动态分析并非易事,需要掌握一定的技能和工具。以下是一些步骤和工具供您参考: 1. 安装Frida和Python Frida是一个动态插桩工具,可以用于分析和修改运行中的应用程序。在使用Frida之前,您需要先安装Frida和Python。具体安装步骤可以参考Frida官网和Python官网。 2. 下载UniApp APK并解压 您需要下载UniApp APK,并使用解压工具将其解压缩。 3. 安装UniApp插件 UniApp插件是一个Frida插件,可以帮助您分析UniApp应用程序。您可以使用以下命令安装UniApp插件: ``` pip install frida-universal ``` 4. 启动Frida-server 在您的设备上启动Frida-server,具体步骤可以参考Frida官网。 5. 使用Frida分析UniApp应用程序 现在,您可以使用Frida分析UniApp应用程序了。以下是一些常用的Frida命令: - 查看应用程序中的所有类和方法: ``` frida -U -l <UniApp插件路径> -f <应用程序包名> --no-pause ``` - 查看应用程序中指定类的所有方法: ``` frida -U -l <UniApp插件路径> -f <应用程序包名> --no-pause -m <类名> ``` - 监听应用程序中指定方法的调用: ``` frida -U -l <UniApp插件路径> -f <应用程序包名> --no-pause -m <类名>.<方法名> --print ``` 请注意,以上命令仅供参考,具体命令可能因应用程序的不同而异。建议您先了解Frida的基本用法,再根据具体情况进行分析。 希望以上信息对您有所帮助。如果您有其他问题或需求,请随时提
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值