https://jcode.lanzoul.com/ijWdM0p40i4h
密码:78nd
我也是2月底工作之余才开始接触逆向这个板块,缘由是因为梦幻西游有个记账本的工具软件,需要购买注册码,就跟着教程小试牛刀,破解过去了。
(E语言编写的工具包确实足够简单,比较容易破解)
再到后来,开始想着看看手机软件相关的,接触到安卓逆向后,发现安卓源码也是Java写的,这不是我的本职工作吗,顿时兴趣就更大了。就开始入
坑,破解。
回归正题,我们来讲一下iqiyi的主要破解去广告思路。
​ 当我们拿到1个apk文件,第一件事是什么,不是安装apk,而是去看一下AndroidMainfest.xml文件中,了解一下大概有哪些Activity。
​ 观察应用启动的Acitivity顺序(先从主入口切入Main)
​ 从应用显示的关键环节的关键字全局搜索代码,找到切入点(关于该点,我后面做更多介绍)
​ 定位到关键逻辑,进行代码修改。
理想和现实总是处于对立面的,上面的思路针对破解一些简单的小软件是不成问题的,但是针对比较复杂的客户端/软件就显得不是那么回事了。我参
照上述方法,解决了首屏启动的广告加载问题,但是针对视频播放时长达30-90s的广告却是无能为力。针对于客户端软件常用的一些命名规则:XXXConfig-
配置类信息,XXXProxy-代{过}{滤}理工具类(很重要,尤其是Java)。在解决播放广告时,我第一时间想到的就是这个config类,通过查找,找到com.iqiyi
.video.qyplayersdk.model.QYPlayerADConfig这个配置类中有很多配置项,初次尝试通过修改部分项true/false,并未能成功。基于Java开发的本能,一
下子就想到了Proxy这个动态代{过}{滤}理的想法,再通过搜索,找到了com.iqiyi.video.qyplayersdk.player.QYMediaPlayerProxy这个类,初次进去这个
类,因为里面的方法很多,也没办法确定具体是哪个类去控制的广告,也是以失败告终。
晚上下班回家之后,硬啃源码,通过登录会员账号/不登录的方式,把整个PlayerActivity的onCreate()的所有子方法都看了个遍,同时通过debug,对
比在某些节点,某些数据的差异。最终将入口定位在了QYMediaPlayerProxy类中的performBigCorePlayback(PlayData playData, PlayerInfo playerInfo,
String str)方法下的else分支中,会员/非会员/不登录走的是2个分支,大概这里的方法就是初始化一个广告id,如果是会员,那么该id=0,非会员,就会调
用后台接口,获取几个随机id(这也是大家为什么看的广告能更替的原因)。那么这里让非会员不去获取广告id,即为0就过去了。
在本次破解过程中,也踩了不少坑,player相关的realPlay、doPlay、play等方法也看了个遍。针对本次的客户端破解尝试,做几点总计:
针对复杂客户端,尽量不采用关键字搜索的方式去破解,因为复杂的客户端代码都是有设计思想的,具有结构化、配置化的特征的。尽量采用动态调试
的方法追踪调用链路。
​提高英文水平,比如player代表播放器、ad代表广告等,这样你在看混淆后的源码才有机会能猜到入口。
​先想再干,就拿这一次举例子,客户端的广告功能,肯定不是v1.0的时候就存在的,也是后期商业化过程中不断新增的功能,那么复杂化的代码肯定不
可能是通过简单的if/else就能搞定的,如果是这样,那么这个软件离倒闭也不远了。播放器的核心功能就是播放视频,这是他的核心功能,如果是初期有的
功能,可能是可以通过新增if/else解决的,但是,对于经过几个大版本间隔后的大功能,是不太可能的,那么这里就体现到了动态代{过}{滤}理的强大之处
了,它是用来干嘛的呢,简单理解就是在核心功能上,可以任意扩展别的功能。
所以,想对于安卓逆向深挖的同学们,还是需要去学习一下Java基础,不然有些思路确实是很难想到的。--------------------------------------------------------------------------分割线,接上面,贴源码-----------------------------------------------------------------------------------------------------
1.首屏广告的破解
package com.qiyi.video.speaker.activity;
import
...
/* loaded from: classes.dex */
public class PlayerActivity extends aux implements aux.con {
/* JADX WARN: Can't fix incorrect switch cases order, some code will duplicate */
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleVoiceMessage(VoiceMessageEvent voiceMessageEvent) {
...
try {
...
// ad打头的基本都是广告相关的
switch (msg.hashCode()) {
case -1502088746:
if (msg.equals("ad_volume_resume")) {
c2 = 22;
break;
}
c2 = 65535;
break;
case -1152274405:
if (msg.equals("ad_skip")) {
c2 = 19;
break;
}
c2 = 65535;
break;
}
// 这里观察发现 c2为0时,是属于正常播放的,所以这里修改办法为将上述c2都赋值为0,首屏广告就解决了
switch (c2) {
case 0:
play();
return;
...
}
}
}
2.播放视频时的广告
package com.iqiyi.video.qyplayersdk.player;
import
...
/* loaded from: classes2.dex */
public class QYMediaPlayerProxy implements com.iqiyi.video.qyplayersdk.player.a.com1, com.iqiyi.video.qyplayersdk.player.a.com3, com.iqiyi.video.qyplayersdk.player.a.com5, com.iqiyi.video.qyplayersdk.player.a.con {
...
//performBigCorePlayback 这个方法有3个,注意是三个参数的这个
private void performBigCorePlayback(PlayData playData, PlayerInfo playerInfo, String str) {
int i;
com.iqiyi.video.qyplayersdk.f.con conVar = this.mDoPlayInterceptor;
if (conVar != null && conVar.e(playerInfo)) {
com.iqiyi.video.qyplayersdk.g.aux.d("PLAY_SDK", TAG, "DoPlayInterceptor is intercept!");
lpt5 lpt5Var = this.mInvokerQYMediaPlayer;
if (lpt5Var == null) {
return;
}
lpt5Var.amX();
} else if (this.mPlayerInfo == null) {
} else {
// 这里就是去除广告的核心,注意i这个参数
org.qiyi.android.coreplayer.d.com7.beginSection("QYMediaPlayerProxy.performBigCorePlayback");
if (com.iqiyi.video.qyplayersdk.player.data.b.nul.A(playerInfo) || playData == null) {
// 会员账号登陆走的是这里
i = 0;
} else {
// 非会员/不登陆走的是这里
com.iqiyi.video.qyplayersdk.cupid.data.model.com9 a2 = com.iqiyi.video.qyplayersdk.cupid.util.con.a(playData, playerInfo, false, this.mPlayerRecordAdapter, 0);
a2.eV(isIgnoreFetchLastTimeSave());
// 它会去后台获取广告的id
int generateCupidVvId = CupidAdUtils.generateCupidVvId(a2, playData.getPlayScene());
com.iqiyi.video.qyplayersdk.cupid.com4 com4Var = this.mAd;
if (com4Var != null) {
com4Var.la(generateCupidVvId);
}
org.qiyi.android.coreplayer.d.aux.boe();
// 最后把id赋值给了i,所以这里的破解核心就是更换条件不走else,或者在这里最后,强制把i的值赋成0
i = generateCupidVvId;
}
com.iqiyi.video.qyplayersdk.core.data.model.com1 a3 = com.iqiyi.video.qyplayersdk.core.data.a.aux.a(this.mSigt, i, playData, playerInfo, str, this.mControlConfig);
com.iqiyi.video.qyplayersdk.g.aux.d("PLAY_SDK", TAG, " performBigCorePlayback QYPlayerMovie=", a3);
this.mPlayerInfo = new PlayerInfo.Builder().copyFrom(playerInfo).extraInfo(new PlayerExtraInfo.Builder().copyFrom(playerInfo.getExtraInfo()).sigt(a3.getSigt()).build()).build();
notifyPlayerInfoChanged();
if (!isNeedNetworkInterceptor(playerInfo)) {
if (playData == null || (TextUtils.isEmpty(playData.getPlayAddress()) && (TextUtils.isEmpty(playData.getTvId()) || "0".equals(playData.getTvId())))) {
PlayerExceptionTools.report(0, 0.1f, "1", com.iqiyi.video.qyplayersdk.player.data.b.con.i(playData));
}
com.iqiyi.video.qyplayersdk.core.com1 com1Var = this.mPlayerCore;
if (com1Var != null) {
com1Var.setVideoPath(a3);
this.mPlayerCore.ahF();
}
}
org.qiyi.android.coreplayer.d.com7.endSection();
}
}
}