Android逆向之路---为什么从后台切换回app又显示广告了

问题

最近发现自己的android机在开网易新闻,知乎等app的时候,明明自己没有杀进程,从后台返回回来的时候还是会再次显示广告,而且又正巧在知乎上看到了有人在提问,于是逆向分析。就用知乎作为例子吧。我用的5.4.1版本

先说结果:

不是因为杀进程、杀后台的原因造成的,这是知乎的业务逻辑。

知乎就是为了显示更多次数的广告,故意设计成这个样子的。

你启动知乎的时候如果没显示广告,那么等你切到后台再回来就显示一次。

你启动知乎的时候如果显示广告了,那么你切到后台了不杀死知乎,那么他就不显示。

开始逆向

讨厌的广告界面如下

准备工作,利用apktool拆包,然后开始逆向分析。
首先通过搜索字符串方法,搜索“发现更大的直接”然后找到了下面的字符串

1
<string name="title_find_bigger_world">发现更大的世界</string>

然后根据这个name的值发现了这个广告界面布局文件是”fragment_launch_ad.xml”
继续找,
根据id然后对应”public.xml”等方法,
可以最终找到其实这个页面就是”LaunchAdFragment.java”
首先我们找到讨厌的广告页面了

换一种思路开始寻找Activity

我从后台切回来的时候肯定是当前Activity在作祟,他执行了一些程序才唤起的”LaunchAdFragment”

按照这个思路我就像看看知乎嘴上层的activity叫什么名字,连上adb执行如下命令

1
adb shell dumpsys activity

然后发现了,原来这个activity的名字叫”MainActivity”啊
activity栈如下

1
2
3
4
5
6
7
8
9
10
11
Running activities (most recent first):
TaskRecord{b4d2961 #339 A=com.zhihu.android U=0 StackId=1 sz=1}
Run #4: ActivityRecord{bc733c u0 com.zhihu.android/.app.ui.activity.MainActivity t339}
TaskRecord{35c713e #340 A=com.android.gallery3d U=0 StackId=1 sz=1}
Run #3: ActivityRecord{cd14b9f u0 com.android.gallery3d/com.huawei.gallery.app.GalleryMain t340}
TaskRecord{1fb8a4b #333 A=com.android.settings U=0 StackId=1 sz=1}
Run #2: ActivityRecord{d3eec28 u0 com.android.settings/.HWSettings t333}
TaskRecord{69570b7 #335 A=com.tencent.mm U=0 StackId=1 sz=1}
Run #1: ActivityRecord{a82f024 u0 com.tencent.mm/.ui.LauncherUI t335}
TaskRecord{e43bab9 #295 I=com.android.settings/.deviceinfo.UsbConnModeChooserActivity U=0 StackId=1 sz=1}
Run #0: ActivityRecord{9d10efe u0 com.android.settings/.deviceinfo.UsbConnModeChooserActivity t295}

综上所述,这个MainActivity和LaunchAdFragment肯定有关系,我就在MainActivity里面,然后就在MainActivity里面搜索LaunchAdFragment,

发现了居然在onResume里面调用了LaunchAdFragment
根据直觉来说,我觉得应该问题就在这里了。我找到了知乎居然有下面这些逻辑。(看不懂没关系,往下读,我已经手动的转成了Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.line 533
:cond_3
invoke-static {}, Lcom/zhihu/android/app/util/LaunchAdHelper;->getInstance()Lcom/zhihu/android/app/util/LaunchAdHelper;
move-result-object v5
invoke-virtual {v5}, Lcom/zhihu/android/app/util/LaunchAdHelper;->isShowLaunchAd()Z
move-result v5
if-eqz v5, :cond_4
.line 535
const-string v5, "input_method"
invoke-virtual {p0, v5}, Lcom/zhihu/android/app/ui/activity/MainActivity;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
move-result-object v0
check-cast v0, Landroid/view/inputmethod/InputMethodManager;
.line 536
.local v0, "imm":Landroid/view/inputmethod/InputMethodManager;
invoke-virtual {v0}, Landroid/view/inputmethod/InputMethodManager;->isActive()Z
move-result v5
if-nez v5, :cond_4
.line 538
invoke-static {}, Lcom/zhihu/android/app/ui/fragment/LaunchAdFragment;->buildIntent()Lcom/zhihu/android/app/util/ZHIntent;
move-result-object v5
invoke-virtual {p0, v5}, Lcom/zhihu/android/app/ui/activity/MainActivity;->startFragment(Lcom/zhihu/android/app/util/ZHIntent;)V

对应的Java代码

1
2
3
4
5
6
7
8
9
10
11
void onResume() {
// 判断是否需要加载广告,
boolean showAd = LaunchAdHelper.getInstance().isShowLaunchAd();
if(!showAd) return ;
//判断系统键盘是否是active的
InputMethodManager imm = ((InputMethodManager)getSystemService("input_method"));
if(imm.isActive()) return ;
//打开广告页面
Intent intent = LaunchAdFragment.buildIntent();
startFragment(intent);
}

具体的逻辑已经在代码里面加了注释,所以说,到底显示还是不显示广告主要看这个方法了

1
LaunchAdHelper.getInstance().isShowLaunchAd()

继续寻找LaunchAdHelper里面的控制变量

然后找到这个类,跟进这个方法,具体看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.method public isShowLaunchAd()Z
.locals 2
.prologue
const/4 v0, 0x0
.line 105
iget-boolean v1, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsAllowShowLaunchAd:Z
if-eqz v1, :cond_0
.line 106
iput-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsAllowShowLaunchAd:Z
.line 107
const/4 v0, 0x1
.line 110
:cond_0
return v0
.end method

转换成java代码如下

1
2
3
4
5
6
7
public boolean isShowLaunchAd() {
if(!mIsAllowShowLaunchAd) {
return false;
}
mIsAllowShowLaunchAd = false;
return true;
}

好了,至少到现在大家应该知道了,显示不显示广告主要靠这个LaunchAdHelper里面的成员变量mIsAllowShowLaunchAd

谁能够改变控制这个变量,业务逻辑主要就是控制他了。
而且已经出现了业务逻辑就是,如果显示了一次,那么这个变量就会为false,然后就不显示了。(至少现在是这样)

然后搜索LaunchAdHelper发现了他在onStart里面初始了这个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.method public onStart(Landroid/content/Context;)V
//------略------
iput-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsAllowShowLaunchAd:Z
.line 88
:cond_0
const/4 v0, 0x0
iput-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsFromInnerActivity:Z
.line 89
iget-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsAllowShowLaunchAd:Z
iput-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsLaunchAdShow:Z
//------略------
.end method

其实这段代码的意思就是初始化了一下这个mIsAllowShowLaunchAd,设置成true,那么紧接着问题就来了,谁调用了LaunchAdHelper的onStart方法呢,

他的父类是Object啊,只是一个普通的类,

继续全局搜索

再次找回到了MainActivity

然后发现绕了一圈我们又绕回来了,在MainActivity里面调用了这个方法,结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.method protected onStart()V
.locals 1
.prologue
.line 460
invoke-super {p0}, Lcom/zhihu/android/app/ui/activity/BaseFragmentActivity;->onStart()V
.line 461
invoke-static {}, Lcom/zhihu/android/app/util/LaunchAdHelper;->getInstance()Lcom/zhihu/android/app/util/LaunchAdHelper;
move-result-object v0
#下面这两句就是调用了初始化广告的方法
invoke-virtual {v0, p0}, Lcom/zhihu/android/app/util/LaunchAdHelper;->onStart(Landroid/content/Context;)V
.line 462
return-void
.end method

好了,现在为止,大概明白了吧。

在MainActivity的onStart方法里面调用了LaunchAdHelper的onStart方法,随后在这里面初始化将mIsAllowShowLaunchAd 设置成true的。

然后再onResume里面再显示广告显示一次了再次onResume就不显示了
但是如果再次调用onStart那么又初始化了,设置成了显示广告,
那么具体什么时候会调用Activity的onStart呢,
官方文档是这么说的:

具体意思就是显示的时候就会调用onStart。

结果

其实结果已经在文章开始部分说过了,不记得的话往前翻翻吧

写在结尾

这就是知乎的这个闪屏广告显示的逻辑,

有个以为接到了这种业务逻辑的时候程序员会不会心里内心一万只羊驼奔腾飞过

还有,他们没混淆app吗,混淆过的方法不应该是

1
a.b.a1(p1,p3);

这个样子的吗。

转眼一看软件版本5.4.1,恍然大悟。

关于我

个人博客:MartinHan的小站

博客网站:hanhan12312的专栏

知乎:MartinHan01