yangyunhui

功不唐捐,玉汝于成

Android逆向学习笔记(七)

Android逆向学习笔记(七)

《安卓逆向这档事》四、恭喜你获得广告&弹窗静默

目录


Activity生命周期

函数名称 描述
onCreate() 一个Activity启动后第一个被调用的函数,常用来在此方法中进行Activity的一些初始化操作。例如创建View,绑定数据,注册监听,加载参数等。
onStart() 当Activity显示在屏幕上时,此方法被调用但此时还无法进行与用户的交互操作。
onResume() 这个方法在onStart()之后调用,也就是在Activity准备好与用户进行交互的时候调用,此时的Activity一定位于Activity栈顶,处于运行状态。
onPause() 这个方法是在系统准备去启动或者恢复另外一个Activity的时候调用,通常在这个方法中执行一些释放资源的方法,以及保存一些关键数据。
onStop() 这个方法是在Activity完全不可见的时候调用的。
onDestroy() 这个方法在Activity销毁之前调用,之后Activity的状态为销毁状态。
onRestart() 当Activity从停止stop状态恢进入start状态时调用状态。

Q: 在AdActivity(本次demo)中这些生命周期的某些函数怎么没有被调用??还是说只是没有显示调用,其实都有执行??

A: 它们其实都在执行,只是没有被显式重写出来。在 Android 开发中,Activity 的生命周期就像是一套默认的剧本,即使你不在代码里写出来,系统也会在后台默默地完成每一个步骤。

教程demo去广告

启动广告

方法一

打开MT管理器中的Activity记录,得到教程demo中的启动广告是AdAcrivity

打开Dex编辑器搜索com.zj.wuaipojie.ui.AdActivity

转成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
36
//
// Decompiled by Jadx (from NP Manager)
//
package com.zj.wuaipojie.ui;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import com.zj.wuaipojie.ui.AdActivity$.ExternalSyntheticLambda0;
import kotlin.Metadata;

@Metadata(d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0002J\b\u0010\u0005\u001a\u00020\u0004H\u0002J\u0012\u0010\u0006\u001a\u00020\u00042\b\u0010\u0007\u001a\u0004\u0018\u00010\bH\u0014¨\u0006\t"}, d2 = {"Lcom/zj/wuaipojie/ui/AdActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "jump", "", "loadAd", "onCreate", "savedInstanceState", "Landroid/os/Bundle;", "app_release"}, k = 1, mv = {1, 7, 1}, xi = 48)
/* compiled from: AdActivity.kt */
public final class AdActivity extends AppCompatActivity {
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(2131427363);
loadAd();
}

private final void jump() {
startActivity(new Intent((Context) this, ChallengeThird.class));
finish();
}

private final void loadAd() {
new Handler().postDelayed(new ExternalSyntheticLambda0(this), 3000);
}

/* renamed from: loadAd$lambda-0 */
private static final void m0loadAd$lambda-0(AdActivity adActivity) {
adActivity.jump();
}
}

解释一下代码的逻辑:

  1. onCreat(系统触发)
    • 系统执行Actuvity,首先进入这个“房间”
    • 执行loadAd()
  2. loadAd(被onCreat调用)
    • 这里它做了一个很关键的事情:定了一个闹钟
    • new Handler().postDelayed(...):这行代码的意思是:3000毫秒之后去执行那个回调函数
    • 注意:此时 loadAd 已经执行完了,onCreate 也执行完了。
  3. m0loadAd$lambda-0(回调函数触发)
    • 当闹钟时间到3000ms,系统会自动执行回调函数
  4. jump(被回调函数调用)
    • 执行跳转和关闭

这里我有几个问题:

Q: 回调函数的作用是什么?
A: 回调函数的作用就是等待,它相当于是一个闹钟,到时间之后喊一声,执行jump函数, 回调函数负责处理时机,jump负责处理逻辑

知道程序逻辑之后,就好办了。

new Handler().postDelayed(new ExternalSyntheticLambda0(this), 3000);它的意思就是等3000ms之后再去执行回调函数,那我们将3000ms改成0就可以直接调用回调函数了。那么启动广告自然就跳过了。

方法二

查看xml文件,代码如下

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?xml version="1.0" encoding="utf-8"?>

<!--开通会员可提高反编译速度、资源代码自动转名称,以及激活智能编辑和自动补全功能-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

android:versionCode="1"

android:versionName="1.0"

android:compileSdkVersion="32"

android:compileSdkVersionCodename="12"

package="com.zj.wuaipojie"

platformBuildVersionCode="32"

platformBuildVersionName="12">

<uses-sdk

android:minSdkVersion="27"

android:targetSdkVersion="32" />

<!-- 拥有完全的网络访问权限 -->

<uses-permission android:name="android.permission.INTERNET" />

<!-- 修改或删除您共享存储空间中的内容 -->

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application

android:theme="@7F1101D3"

android:label="@7F10001B"

android:icon="@7F0D000F"

android:allowBackup="true"

android:supportsRtl="true"

android:extractNativeLibs="false"

android:fullBackupContent="@7F130000"

android:networkSecurityConfig="@7F130002"

android:appComponentFactory="androidx.core.app.CoreComponentFactory"

android:dataExtractionRules="@7F130001">

<activity

android:name="com.zj.wuaipojie.ui.ChallengeSixth"

android:exported="false" />

<activity

android:name="com.zj.wuaipojie.ui.ChallengeFifth"

android:exported="true" />

<activity

android:name="com.zj.wuaipojie.ui.ChallengeFourth"

android:exported="true" />

<activity

android:name="com.zj.wuaipojie.ui.ChallengeThird"

android:exported="false" />

<activity

android:name="com.zj.wuaipojie.ui.ChallengeSecond"

android:exported="false" />

<activity android:name="com.zj.wuaipojie.ui.AdActivity" />

<activity

android:label="@7F10001B"

android:name="com.zj.wuaipojie.ui.MainActivity"

android:exported="true">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name="com.zj.wuaipojie.ui.ChallengeFirst" />

</application>

</manifest>

可以了解到,程序的入口页面如下:

1
2
3
4
5
6
<activity android:name="com.zj.wuaipojie.ui.MainActivity" ...>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
  • MAIN:就是程序的主入口

为了绕过启动广告,可以直接将入口com.zj.wuaipojie.ui.MainActivity改成com.zj.wuaipojie.ui.ChallengeThird

但是这种方法是非常不建议使用的

  1. 全局初始化失效 :通常 MainActivity 或其更早的 Application 类会初始化数据库、网络框架、甚至是用户的 VIP 权限状态。直接跳过它们,ChallengeThird 运行时可能会因为找不到这些数据而瞬间 闪退 。
  2. 逻辑断裂 :有些关卡需要从上一关传递一个 score 参数,直接启动第三关会导致程序拿不到参数而报错。

方法三 

我们查找调用AdActivity函数的函数有哪些

首先长按该类选择复制,选择最下面的这个

查找之后:

因为AdActivity里面是一定会调用的,所以这个肯定不是我们要找的,那只有下面的这个了,我们查看一下它的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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//
// Decompiled by Jadx (from NP Manager)
//
package com.zj.wuaipojie.ui.Adapter;

import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.zj.wuaipojie.ui.AdActivity;
import com.zj.wuaipojie.ui.Adapter.ChallengeAdapter$.ExternalSyntheticLambda0;
import com.zj.wuaipojie.ui.Challenge;
import com.zj.wuaipojie.ui.ChallengeFifth;
import com.zj.wuaipojie.ui.ChallengeFirst;
import com.zj.wuaipojie.ui.ChallengeFourth;
import com.zj.wuaipojie.ui.ChallengeSecond;
import com.zj.wuaipojie.ui.ChallengeSixth;
import java.util.List;
import kotlin.Metadata;

@Metadata(d1 = {"\u00000\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010 \n\u0002\u0018\u0002\n\u0002\b\u0004\n\u0002\u0010\b\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0004\n\u0002\u0018\u0002\n\u0002\b\u0003\u0018\u00002\f\u0012\b\u0012\u00060\u0002R\u00020\u00000\u0001:\u0001\u0013B\u0013\u0012\f\u0010\u0003\u001a\b\u0012\u0004\u0012\u00020\u00050\u0004¢\u0006\u0002\u0010\u0006J\b\u0010\t\u001a\u00020\nH\u0016J\u001c\u0010\u000b\u001a\u00020\f2\n\u0010\r\u001a\u00060\u0002R\u00020\u00002\u0006\u0010\u000e\u001a\u00020\nH\u0016J\u001c\u0010\u000f\u001a\u00060\u0002R\u00020\u00002\u0006\u0010\u0010\u001a\u00020\u00112\u0006\u0010\u0012\u001a\u00020\nH\u0016R\u0017\u0010\u0003\u001a\b\u0012\u0004\u0012\u00020\u00050\u0004¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\b¨\u0006\u0014"}, d2 = {"Lcom/zj/wuaipojie/ui/Adapter/ChallengeAdapter;", "Landroidx/recyclerview/widget/RecyclerView$Adapter;", "Lcom/zj/wuaipojie/ui/Adapter/ChallengeAdapter$ViewHolder;", "mChallenges", "", "Lcom/zj/wuaipojie/ui/Challenge;", "(Ljava/util/List;)V", "getMChallenges", "()Ljava/util/List;", "getItemCount", "", "onBindViewHolder", "", "holder", "position", "onCreateViewHolder", "parent", "Landroid/view/ViewGroup;", "viewType", "ViewHolder", "app_release"}, k = 1, mv = {1, 7, 1}, xi = 48)
/* compiled from: ChallengeAdapter.kt */
public final class ChallengeAdapter extends Adapter<ViewHolder> {
private final List<Challenge> mChallenges;

/* renamed from: onCreateViewHolder$lambda-0 */
private static final void m0onCreateViewHolder$lambda-0(ViewHolder viewHolder, View view) {
int adapterPosition = viewHolder.getAdapterPosition();
Intent intent;
if (adapterPosition == 0) {
intent = new Intent();
intent.setClass(view.getContext(), ChallengeFirst.class);
view.getContext().startActivity(intent);
} else if (adapterPosition == 1) {
intent = new Intent();
intent.setClass(view.getContext(), ChallengeSecond.class);
view.getContext().startActivity(intent);
} else if (adapterPosition == 2) {
intent = new Intent();
intent.setClass(view.getContext(), AdActivity.class);
view.getContext().startActivity(intent);
} else if (adapterPosition == 3) {
intent = new Intent();
intent.setClass(view.getContext(), ChallengeFourth.class);
view.getContext().startActivity(intent);
} else if (adapterPosition == 4) {
intent = new Intent();
intent.setClass(view.getContext(), ChallengeFifth.class);
view.getContext().startActivity(intent);
} else if (adapterPosition == 5) {
intent = new Intent();
intent.setClass(view.getContext(), ChallengeSixth.class);
view.getContext().startActivity(intent);
}
}

public void onBindViewHolder(ViewHolder viewHolder, int i) {
Challenge challenge = (Challenge) this.mChallenges.get(i);
viewHolder.getChallengeName().setText(challenge.getChallenege_name());
viewHolder.getChallengeImage().setImageResource(challenge.getChallenge_img());
}

public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewHolder viewHolder = new ViewHolder(this, LayoutInflater.from(viewGroup.getContext()).inflate(2131427364, viewGroup, false));
viewHolder.getChallengeImage().setOnClickListener(new ExternalSyntheticLambda0(viewHolder));
return viewHolder;
}

public ChallengeAdapter(List<Challenge> list) {
this.mChallenges = list;
}

public int getItemCount() {
return this.mChallenges.size();
}

public final List<Challenge> getMChallenges() {
return this.mChallenges;
}
}

可以看到这一段:

1
2
3
4
5
else if (adapterPosition == 2) {
intent = new Intent();
intent.setClass(view.getContext(), AdActivity.class);
view.getContext().startActivity(intent);
}

可以知道当adapterPosition == 2时进入AdActivity,我们把它修改成ChallengeThird

以上三种对于这个demo来说都是可以跳过启动广告的。

弹窗广告

修改xml中的versiocode

这个弹窗广告是检测是否需要更新的,与versionCode有关,这是版本号,只要将这个版本号修改成比较大的数字,他就不会检测出需要更新

ok,将1改成2之后就没有这个弹窗了

这种方法在某些特定的情况下有用,但他并不是“删除弹窗”,而是一种欺骗手段。

在安卓开发中versionCodea是一个整数,用来标识应用的版本。系统和应用逻辑通常通过比较这个数字来判断是否有新版本。

为什么要改versionCode?

  • 本地逻辑: 如果 服务器版本号 > 本地 versionCode,则弹出更新窗口。

  • 你的操作: 把本地的 1 改成了 2(甚至改成 99999)。

  • 结果: 软件运行时发现 2 >= 服务器版本号,它就会“认为”自己已经是最新版,从而不触发弹窗。

这种方法的局限性

虽然这招很省事,但它有很多“翻车”的情况:

  • 强制更新: 如果服务器要求必须是某个特定版本(例如必须是 3),而你改成 2 依然没达到要求,弹窗还在。
  • 服务端校验: 现在的 APP 往往会把 versionCode 传给服务器。如果服务器发现你的版本号不存在(或者是未来的版本),可能会直接拒绝提供服务(报错或闪退)。
  • 硬编码弹窗: 有些弹窗根本不是根据版本号触发的,而是根据日期,或者是开发者在代码里写死了“只要运行就弹窗广告”。
  • 签名校验: 修改 XML 后需要重新签名。如果软件有签名校验,修改后可能无法启动。

Hook弹窗(推荐算法助手开启弹窗定位)

方法一:

首先下载算法助手,启用模块,勾选apk文件

设置完之后,打开算法助手,点击apk文件,勾选应用总开关与弹窗定位(返回键可取消)

我的算法助手中没有应用总开关这个选项,我就只勾选了弹窗定位(返回键可取消),但是还是可以绕过这个弹窗广告的,也就是说返回键是可以用的,可能是新版本布局不太一样?

方法二:

同样使用算法助手勾选屏蔽关键词弹窗,填写广告(弹窗中的关键词)

ok,没有了

修改dex弹窗代码

算法助手中还是选择弹窗定位,右上角开始。

查看算法助手中的日志,点击我们的apk文件,选择一号弹窗

在日志中

1
2
at LSPHooker_.show(Unknown Source:8)
at com.zj.wuaipojie.ui.ChallengeThird.onCreate(ChallengeThird.kt:53)
  • LSPHooker_.show:这是算法助手拦截到的弹窗显示动作,它是“结果”,不用管它。

  • com.zj.wuaipojie.ui.ChallengeThird.onCreate:这是关键!它告诉我们:在 ChallengeThird 这个界面的 onCreate(创建界面)方法中,触发了弹窗。

在MT管理器中搜索com.zj.wuaipojie.ui.ChallengeThird.onCreate

找到两个show方法将它们注释掉

  • 在算法助手中,日志at com.zj.wuaipojie.ui.ChallengeThird.onCreate(ChallengeThird.kt:53)后面的kt: 53对应的就是代码中的.line 53

  • 原理: 编译器在把 Java/Kotlin 转成 Smali 时,会保留原始代码的行号。算法助手抓取的是运行时的行号。

到这里突然我想到了一个问题:算法助手这个软件是不是只对LSPosed开发的文件管用??

不是的。算法助手并不只对 LSPosed 开发的文件管用,,可以对绝大多数安卓应用生效。

横幅广告

方法一

下载开发助手,点击布局查看

id是0x7f0801ca,在MT管理器中点击xml搜索,搜索该id

将150dp改成0dp(可以只改宽度或高度或者都改)

这样广告就去掉了

方法二

1
2
3
4
5
6
7
<ImageView
android:id="@7F0801CA"
android:background="@7F0D0017"
android:layout_width="-2"
android:visibility="gone"
android:layout_height="150dp"
android:layout_marginTop="100dp" />

android:visibility="gone"几乎是安卓逆向和开发中隐藏的UI元素黄金准则。

  1. android:visibility 的三个选项

在安卓中,一个控件的“可见性”有三种状态:

状态 表现 占用空间 你的需求
visible 正常显示 是 默认状态
invisible 隐身(看不见) 是 依然留下一块大空白
gone 彻底消失 否 完美去广告且不留空白
  1. 为什么要加gone?作用是什么?
  • 不参与布局计算: 当你设置成 gone 时,安卓系统在绘制界面时会直接“无视”这个控件。它不仅看不见,原本属于它的位置会被其他控件挤上来占掉。

  • 比改 0dp 更稳: 如果改宽高为 0dp,控件虽然没大小,但它设置的 margin(外边距,比如你代码里的 android:layout_marginTop="100dp")可能依然会生效,导致界面留下莫名其妙的间隙。

  • gone 会连同这个控件的 margin 效果一起抹去。
  1. 为什么加在宽高中间?

其实,加在xml标签内的任何位置都是可以的,只要它是一个独立的属性。加在宽高之间,通常是为了:

  • 方便管理: 宽高定义了“大小”,visibility 定义了“显隐”,放在一起逻辑上比较直观。

  • 快速定位: 以后你再回来修改这个文件,一眼就能看到这个控件的大小被你通过 gone 给“屏蔽”了。

课后作业

我们要做的就是将这个弹窗广告去除(返回键被劫持),由于这和版本并没有什么关系,所以我们不能去修改versionCode来去除这个弹窗,这里有几种方法:

  1. 使用算法助手,恢复返回键。
  2. 使用算法助手,屏蔽带有弹窗字样的弹窗。
  3. 修改xml代码,将show注释掉。

方法一:

**注:**因为这个apk文件是新下载的,所以需要在LSPosed中重新勾选作业demo。

打开算法助手,点击作业demo,勾选弹窗定位(返回键可取消),点击右上角,弹窗启动之后,直接点击返回键就可以了。

这种方法其实并没有将弹窗去除,而是恢复被劫持的返回键,当弹窗弹出时,使用返回键关闭弹窗。

方法二:

同样使用算法助手,打开屏蔽关键词弹窗,输入弹窗,点击右上角,弹窗被关闭了。

方法三:

使用算法助手,打开弹窗定位,点击右上角,弹窗弹出之后,回到算法助手。

打开算法助手的日志

可以看到com.zj.wuaipojie.ui.MainActivity.onCreate,大概率该弹窗就是在这个方法中,我们去MT管理器中搜索该方法

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
.method protected onCreate(Landroid/os/Bundle;)V
.registers 9

invoke-static/range {p0 .. p0}, Lnp/֏;->ށ(Landroid/app/Activity;)V

.line 28
invoke-super {p0, p1}, Landroidx/appcompat/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V

.line 30
invoke-virtual {p0}, Lcom/zj/wuaipojie/ui/MainActivity;->getLayoutInflater()Landroid/view/LayoutInflater;

move-result-object p1

invoke-static {p1}, Lcom/zj/wuaipojie/databinding/ActivityMainBinding;->inflate(Landroid/view/LayoutInflater;)Lcom/zj/wuaipojie/databinding/ActivityMainBinding;

move-result-object p1

iput-object p1, p0, Lcom/zj/wuaipojie/ui/MainActivity;->binding:Lcom/zj/wuaipojie/databinding/ActivityMainBinding;

const-string v0, "binding"

const/4 v1, 0x0

if-nez p1, :cond_19

.line 31
invoke-static {v0}, Lkotlin/jvm/internal/Intrinsics;->throwUninitializedPropertyAccessException(Ljava/lang/String;)V

move-object p1, v1

:cond_19
invoke-virtual {p1}, Lcom/zj/wuaipojie/databinding/ActivityMainBinding;->getRoot()Landroidx/constraintlayout/widget/ConstraintLayout;

move-result-object p1

check-cast p1, Landroid/view/View;

invoke-virtual {p0, p1}, Lcom/zj/wuaipojie/ui/MainActivity;->setContentView(Landroid/view/View;)V

.line 33
iget-object p1, p0, Lcom/zj/wuaipojie/ui/MainActivity;->binding:Lcom/zj/wuaipojie/databinding/ActivityMainBinding;

if-nez p1, :cond_2a

invoke-static {v0}, Lkotlin/jvm/internal/Intrinsics;->throwUninitializedPropertyAccessException(Ljava/lang/String;)V

move-object p1, v1

:cond_2a
iget-object p1, p1, Lcom/zj/wuaipojie/databinding/ActivityMainBinding;->navView:Lcom/google/android/material/bottomnavigation/BottomNavigationView;

.line 35
move-object v0, p0

check-cast v0, Landroid/app/Activity;

const v2, 0x7f080132

invoke-static {v0, v2}, Landroidx/navigation/ActivityKt;->findNavController(Landroid/app/Activity;I)Landroidx/navigation/NavController;

move-result-object v0

const/4 v2, 0x3

new-array v2, v2, [Ljava/lang/Integer;

const v3, 0x7f08013c

.line 38
invoke-static {v3}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;

move-result-object v3

const/4 v4, 0x0

aput-object v3, v2, v4

const v3, 0x7f08013a

invoke-static {v3}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;

move-result-object v3

const/4 v5, 0x1

aput-object v3, v2, v5

const/4 v3, 0x2

const v6, 0x7f080135

invoke-static {v6}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;

move-result-object v6

aput-object v6, v2, v3

.line 37
invoke-static {v2}, Lkotlin/collections/SetsKt;->setOf([Ljava/lang/Object;)Ljava/util/Set;

move-result-object v2

.line 96
move-object v3, v1

check-cast v3, Landroidx/customview/widget/Openable;

.line 97
sget-object v3, Lcom/zj/wuaipojie/ui/MainActivity$onCreate$$inlined$AppBarConfiguration$default$1;->INSTANCE:Lcom/zj/wuaipojie/ui/MainActivity$onCreate$$inlined$AppBarConfiguration$default$1;

check-cast v3, Lkotlin/jvm/functions/Function0;

.line 98
new-instance v6, Landroidx/navigation/ui/AppBarConfiguration$Builder;

invoke-direct {v6, v2}, Landroidx/navigation/ui/AppBarConfiguration$Builder;-><init>(Ljava/util/Set;)V

.line 99
invoke-virtual {v6, v1}, Landroidx/navigation/ui/AppBarConfiguration$Builder;->setOpenableLayout(Landroidx/customview/widget/Openable;)Landroidx/navigation/ui/AppBarConfiguration$Builder;

move-result-object v1

.line 100
new-instance v2, Lcom/zj/wuaipojie/ui/MainActivity$inlined$sam$i$androidx_navigation_ui_AppBarConfiguration_OnNavigateUpListener$0;

invoke-direct {v2, v3}, Lcom/zj/wuaipojie/ui/MainActivity$inlined$sam$i$androidx_navigation_ui_AppBarConfiguration_OnNavigateUpListener$0;-><init>(Lkotlin/jvm/functions/Function0;)V

check-cast v2, Landroidx/navigation/ui/AppBarConfiguration$OnNavigateUpListener;

invoke-virtual {v1, v2}, Landroidx/navigation/ui/AppBarConfiguration$Builder;->setFallbackOnNavigateUpListener(Landroidx/navigation/ui/AppBarConfiguration$OnNavigateUpListener;)Landroidx/navigation/ui/AppBarConfiguration$Builder;

move-result-object v1

.line 101
invoke-virtual {v1}, Landroidx/navigation/ui/AppBarConfiguration$Builder;->build()Landroidx/navigation/ui/AppBarConfiguration;

move-result-object v1

const-string v2, "AppBarConfiguration.Buil…eUpListener)\n .build()"

invoke-static {v1, v2}, Lkotlin/jvm/internal/Intrinsics;->checkExpressionValueIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V

.line 41
move-object v2, p0

check-cast v2, Landroidx/appcompat/app/AppCompatActivity;

invoke-static {v2, v0, v1}, Landroidx/navigation/ui/ActivityKt;->setupActionBarWithNavController(Landroidx/appcompat/app/AppCompatActivity;Landroidx/navigation/NavController;Landroidx/navigation/ui/AppBarConfiguration;)V

.line 42
invoke-static {p1, v0}, Landroidx/navigation/ui/BottomNavigationViewKt;->setupWithNavController(Lcom/google/android/material/bottomnavigation/BottomNavigationView;Landroidx/navigation/NavController;)V

.line 45
sget-object p1, Lcom/zj/wuaipojie/util/SPUtils;->INSTANCE:Lcom/zj/wuaipojie/util/SPUtils;

move-object v0, p0

check-cast v0, Landroid/content/Context;

const-string v1, "FIRST_START"

invoke-virtual {p1, v0, v1, v5}, Lcom/zj/wuaipojie/util/SPUtils;->getBoolean(Landroid/content/Context;Ljava/lang/String;Z)Z

move-result p1

if-ne p1, v5, :cond_9d

.line 47
invoke-virtual {p0}, Lcom/zj/wuaipojie/ui/MainActivity;->alertFirst()V

.line 48
sget-object p1, Lcom/zj/wuaipojie/util/SPUtils;->INSTANCE:Lcom/zj/wuaipojie/util/SPUtils;

invoke-virtual {p1, v0, v1, v4}, Lcom/zj/wuaipojie/util/SPUtils;->saveBoolean(Landroid/content/Context;Ljava/lang/String;Z)V

:cond_9d
return-void
.end method

这是该方法的所有代码,可以发现我们并找不到show方法,这里我还有一个问题:

Q: 之前看日志的时候,后面有一个数字,比如这篇文章中讲的弹窗,他后面会跟kt: 53,那我们就知道show方法是在line : 53,但是这个日志中却没有,这是为什么。

A: 在安卓编译过程中,存在一个叫做 Line Number Table(行号表) 的东西。

  • 有行号时(如之前的 ChallengeThird): 开发者在编译 APK 时,保留了原始源文件(Kotlin/Java)与编译后的字节码(Smali)之间的对应关系。算法助手能读取到这个表,所以能精确告诉你“这是第 53 行”。
  • 没行号时(当前的 MainActivity): 开发者开启了混淆(ProGuard/R8),并配置了类似 -del_sourceinfo 的指令。编译工具会把所有的行号表删掉,以减小 APK 体积并增加逆向难度。
    • Unknown Source:意味着类名对应的原始文件名(如 MainActivity.kt)被删除了。
    • 数字 0:意味着行号信息彻底没了,系统只知道在运行这个方法,但不知道是哪一行。

言归正传,既然找不到show,那我们接下来怎么做?

在日志中我们看到

1
2
3
at android.app.AlertDialog$Builder.show(AlertDialog.java:1125)
at np.֏.ށ(Unknown Source:38)
at com.zj.wuaipojie.ui.MainActivity.onCreate(Unknown Source:0)

调用完ށ这个方法之后,调用了show方法,这说明:弹窗的动作,就是在这个 ށ 方法里被触发的。

所以我们可以直接将ށ注释掉,没有这个方法,那么就调用不了show了

保存安装看看弹窗还有没有了,发现我们成功了。

在写作业时,我本来想注释掉invoke-virtual {p0}, Lcom/zj/wuaipojie/ui/MainActivity;->alertFirst()V这一行,因为我们知道有时候alert也是弹窗的意思,但是并没有用,这就是逆向中的“对抗”。开发者会故意写好几个弹窗方法,有的叫 alert,有的叫 check,有的用乱码命名。 不要被方法的名字迷惑,要相信日志里的堆栈信息。 日志指哪,你就打哪。

总结

启动广告:

  1. 使用MT管理器中的Activity记录,得到启动广告在哪一个方法中,修改时间
  2. 将程序的入口页面换成程序页面,跳过广告(不建议使用)
  3. 找到哪里调用了该启动广告,修改程序逻辑

弹窗广告:

  1. 如果是更新之类的广告,在xml文件中修改versionCode
  2. 使用算法助手,恢复被劫持的返回键
  3. 使用算法助手,屏蔽带有关键词的弹窗
  4. 查看日志,找到调用该广告的方法,注释掉show

横幅广告:

  1. 使用开发助手,获得广告的id,在xml文件中修改广告的宽高为0bp
  2. 在文件的宽高之间加上android:visibility="goon"