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 | // |
解释一下代码的逻辑:
onCreat(系统触发)- 系统执行Actuvity,首先进入这个“房间”
- 执行
loadAd()
loadAd(被onCreat调用)- 这里它做了一个很关键的事情:定了一个闹钟
new Handler().postDelayed(...):这行代码的意思是:3000毫秒之后去执行那个回调函数- 注意:此时
loadAd已经执行完了,onCreate也执行完了。
m0loadAd$lambda-0(回调函数触发)- 当闹钟时间到3000ms,系统会自动执行回调函数
jump(被回调函数调用)- 执行跳转和关闭
这里我有几个问题:
Q: 回调函数的作用是什么?
A: 回调函数的作用就是等待,它相当于是一个闹钟,到时间之后喊一声,执行jump函数, 回调函数负责处理时机,jump负责处理逻辑
知道程序逻辑之后,就好办了。
new Handler().postDelayed(new ExternalSyntheticLambda0(this), 3000);它的意思就是等3000ms之后再去执行回调函数,那我们将3000ms改成0就可以直接调用回调函数了。那么启动广告自然就跳过了。
方法二
查看xml文件,代码如下
1 |
|
可以了解到,程序的入口页面如下:
1 | <activity android:name="com.zj.wuaipojie.ui.MainActivity" ...> |
- MAIN:就是程序的主入口
为了绕过启动广告,可以直接将入口com.zj.wuaipojie.ui.MainActivity改成com.zj.wuaipojie.ui.ChallengeThird
但是这种方法是非常不建议使用的
- 全局初始化失效 :通常
MainActivity或其更早的Application类会初始化数据库、网络框架、甚至是用户的 VIP 权限状态。直接跳过它们,ChallengeThird运行时可能会因为找不到这些数据而瞬间 闪退 。 - 逻辑断裂 :有些关卡需要从上一关传递一个
score参数,直接启动第三关会导致程序拿不到参数而报错。
方法三
我们查找调用AdActivity函数的函数有哪些
首先长按该类选择复制,选择最下面的这个
查找之后:
因为AdActivity里面是一定会调用的,所以这个肯定不是我们要找的,那只有下面的这个了,我们查看一下它的java代码
1 | // |
可以看到这一段:
1 | else if (adapterPosition == 2) { |
可以知道当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 | at LSPHooker_.show(Unknown Source:8) |
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 | <ImageView |
android:visibility="gone"几乎是安卓逆向和开发中隐藏的UI元素黄金准则。
android:visibility的三个选项
在安卓中,一个控件的“可见性”有三种状态:
| 状态 | 表现 | 占用空间 | 你的需求 |
|---|---|---|---|
| visible | 正常显示 | 是 | 默认状态 |
| invisible | 隐身(看不见) | 是 | 依然留下一块大空白 |
| gone | 彻底消失 | 否 | 完美去广告且不留空白 |
- 为什么要加
gone?作用是什么?
不参与布局计算: 当你设置成
gone时,安卓系统在绘制界面时会直接“无视”这个控件。它不仅看不见,原本属于它的位置会被其他控件挤上来占掉。比改
0dp更稳: 如果改宽高为0dp,控件虽然没大小,但它设置的margin(外边距,比如你代码里的android:layout_marginTop="100dp")可能依然会生效,导致界面留下莫名其妙的间隙。
gone会连同这个控件的margin效果一起抹去。
- 为什么加在宽高中间?
其实,加在xml标签内的任何位置都是可以的,只要它是一个独立的属性。加在宽高之间,通常是为了:
方便管理: 宽高定义了“大小”,
visibility定义了“显隐”,放在一起逻辑上比较直观。快速定位: 以后你再回来修改这个文件,一眼就能看到这个控件的大小被你通过
gone给“屏蔽”了。
课后作业
我们要做的就是将这个弹窗广告去除(返回键被劫持),由于这和版本并没有什么关系,所以我们不能去修改versionCode来去除这个弹窗,这里有几种方法:
- 使用算法助手,恢复返回键。
- 使用算法助手,屏蔽带有
弹窗字样的弹窗。 - 修改xml代码,将show注释掉。
方法一:
**注:**因为这个apk文件是新下载的,所以需要在LSPosed中重新勾选作业demo。
打开算法助手,点击作业demo,勾选弹窗定位(返回键可取消),点击右上角,弹窗启动之后,直接点击返回键就可以了。
这种方法其实并没有将弹窗去除,而是恢复被劫持的返回键,当弹窗弹出时,使用返回键关闭弹窗。
方法二:
同样使用算法助手,打开屏蔽关键词弹窗,输入弹窗,点击右上角,弹窗被关闭了。
方法三:
使用算法助手,打开弹窗定位,点击右上角,弹窗弹出之后,回到算法助手。
打开算法助手的日志
可以看到com.zj.wuaipojie.ui.MainActivity.onCreate,大概率该弹窗就是在这个方法中,我们去MT管理器中搜索该方法
1 | .method protected onCreate(Landroid/os/Bundle;)V |
这是该方法的所有代码,可以发现我们并找不到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:意味着行号信息彻底没了,系统只知道在运行这个方法,但不知道是哪一行。
- Unknown Source:意味着类名对应的原始文件名(如
言归正传,既然找不到show,那我们接下来怎么做?
在日志中我们看到
1 | at android.app.AlertDialog$Builder.show(AlertDialog.java:1125) |
调用完ށ这个方法之后,调用了show方法,这说明:弹窗的动作,就是在这个 ށ 方法里被触发的。
所以我们可以直接将ށ注释掉,没有这个方法,那么就调用不了show了
保存安装看看弹窗还有没有了,发现我们成功了。
在写作业时,我本来想注释掉invoke-virtual {p0}, Lcom/zj/wuaipojie/ui/MainActivity;->alertFirst()V这一行,因为我们知道有时候alert也是弹窗的意思,但是并没有用,这就是逆向中的“对抗”。开发者会故意写好几个弹窗方法,有的叫 alert,有的叫 check,有的用乱码命名。 不要被方法的名字迷惑,要相信日志里的堆栈信息。 日志指哪,你就打哪。
总结
启动广告:
- 使用MT管理器中的Activity记录,得到启动广告在哪一个方法中,修改时间
- 将程序的入口页面换成程序页面,跳过广告(不建议使用)
- 找到哪里调用了该启动广告,修改程序逻辑
弹窗广告:
- 如果是更新之类的广告,在xml文件中修改versionCode
- 使用算法助手,恢复被劫持的返回键
- 使用算法助手,屏蔽带有关键词的弹窗
- 查看日志,找到调用该广告的方法,注释掉show
横幅广告:
- 使用开发助手,获得广告的id,在xml文件中修改广告的宽高为0bp
- 在文件的宽高之间加上
android:visibility="goon"