初识smali,vip终结者
一、课程目标
1.了解JVM、Dalvik、ART
2.初识smali语法
3.实战修改smali
二、工具
1.教程Demo(更新)
2.MT管理器/NP管理器
3.雷电模拟器
4.jadx-gui
5.核心破解
三、课程内容
1.什么是JVM、Dalvik、ART
JVM是JAVA虚拟机,运行JAVA字节码程序
Dalvik是Google专门为Android设计的一个虚拟机,Dalvik有专属的文件执行格式dex(Dalvik executable)
Art(Android Runtime)相当于Dalvik的升级版,本质与Dalvik无异
2.smali及其语法
smali是Dalvik的寄存器语言,smali代码是dex反编译而来的。
关键字
名称 | 注释 |
---|---|
.class | 类名 |
.super | 父类名,继承的上级类名名称 |
.source | 源名 |
.field | 变量 |
.method | 方法名 |
.register | 寄存器 |
.end method | 方法名的结束 |
public | 公有 |
protected | 半公开,只有同一家人才能用 |
private | 私有,只能自己使用 |
.parameter | 方法参数 |
.prologue | 方法开始 |
.line xxx | 位于第xxx行 |
数据类型对应
smali类型 | java类型 | 注释 |
---|---|---|
V | void | 无返回值 |
Z | boolean | 布尔值类型,返回0或1 |
B | byte | 字节类型,返回字节 |
S | short | 短整数类型,返回数字 |
C | char | 字符类型,返回字符 |
I | int | 整数类型,返回数字 |
J | long (64位 需要2个寄存器存储) | 长整数类型,返回数字 |
F | float | 单浮点类型,返回数字 |
D | double (64位 需要2个寄存器存储) | 双浮点类型,返回数字 |
string | String | 文本类型,返回字符串 |
Lxxx/xxx/xxx | object | 对象类型,返回对象 |
常用指令
关键字 | 注释 |
---|---|
const | 重写整数属性,真假属性内容,只能是数字类型 |
const-string | 重写字符串内容 |
const-wide | 重写长整数类型,多用于修改到期时间。 |
return | 返回指令 |
if-eq | 全称equal(a=b),比较寄存器ab内容,相同则跳 |
if-ne | 全称not equal(a!=b),ab内容不相同则跳 |
if-eqz | 全称equal zero(a=0),z即是0的标记,a等于0则跳 |
if-nez | 全称not equal zero(a!=0),a不等于0则跳 |
if-ge | 全称greater equal(a>=b),a大于或等于则跳 |
if-le | 全称little equal(a<=b),a小于或等于则跳 |
goto | 强制跳到指定位置 |
switch | 分支跳转,一般会有多个分支线,并根据指令跳转到适当位置 |
iget | 获取寄存器数据 |
其余指令可用语法工具查询
定位方法:搜索弹窗关键字、抓取按钮id
3.寄存器
在smali里的所有操作都必须经过寄存器来进行:本地寄存器用v开头数字结尾的符号来表示,如v0、 v1、v2。 参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2。特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this",p1表示函数的第一个 参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)
我们下面来查看第二关任务

任务目标:收集硬币并完成一键三连;但是我们一键三连点击后出现提示:请先充值大会员哦!
我们去jadk-jui里搜索一下

如果没有出现,就打开文件——>首选项——>unicode字符转义关掉

这个方法名是onCreate$lambda,前面的数字和字母在反编译的时候开启了一个反混淆
我们去smali里也找到这个方法去
我们分析一下这个smali语句:
//一个私有、静态、不可变的方法 方法名
.method private static final onCreate$lambda-2(Lkotlin/jvm/internal/Ref$IntRef;Lcom/zj/wuaipojie/ui/ChallengeSecond;Landroid/widget/ImageView;Landroid/widget/ImageView;Landroid/widget/ImageView;Landroid/view/View;)Z //(这里面是方法的参数)这里是方法返回值类型,表示布尔值类型,返回假或真
.registers 7 //寄存器数量
.line 33 //代码所在的行数
iget p0, p0, Lkotlin/jvm/internal/Ref$IntRef;->element:I //读取p0(第一个参数,参考寄存器知识)中element的值赋值给p0
const/4 p5, 0x1 //p5赋值1
const/16 v0, 0xa //v0赋值10,在16进制里a表示10
if-ge p0, v0, :cond_15 //判断p0的值是否大于或等于v0的值(即p0的值是否大于或等于10),如果大于或等于则跳转到:cond_15
.line 34 //以下是常见的Toast弹窗代码
check-cast p1, Landroid/content/Context; //检查Context对象引用
const-string p0, "请先获取10个硬币哦" //弹窗文本信息,把""里的字符串数据赋值给p0
check-cast p0, Ljava/lang/CharSequence; //检查CharSequence对象引用
invoke-static {p1, p0, p5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
//将弹窗文本、显示时间等信息传给p1
move-result-object p0 //结果传递给p0
invoke-virtual {p0}, Landroid/widget/Toast;->show()V //当看到这个Toast;->show你就应该反应过来这里是弹窗代码
goto :goto_31 //跳转到:goto_31
:cond_15 //跳转的一个地址
invoke-virtual {p1}, Lcom/zj/wuaipojie/ui/ChallengeSecond;->isvip()Z //判断isvip方法的返回值是否为真(即结果是否为1)
move-result p0 //结果赋值给p0
if-eqz p0, :cond_43 //如果结果为0则跳转cond_43地址
const p0, 0x7f0d0018 //在arsc中的id索引,这个值可以进行查询
.line 37
invoke-virtual {p2, p0}, Landroid/widget/ImageView;->setImageResource(I)V //设置图片资源
const p0, 0x7f0d0008
.line 38
invoke-virtual {p3, p0}, Landroid/widget/ImageView;->setImageResource(I)V
const p0, 0x7f0d000a
.line 39
invoke-virtual {p4, p0}, Landroid/widget/ImageView;->setImageResource(I)V
.line 40
sget-object p0, Lcom/zj/wuaipojie/util/SPUtils;->INSTANCE:Lcom/zj/wuaipojie/util/SPUtils;
check-cast p1, Landroid/content/Context;
const/4 p2, 0x2 //p2赋值2
const-string p3, "level" //sp的索引
invoke-virtual {p0, p1, p3, p2}, Lcom/zj/wuaipojie/util/SPUtils;->saveInt(Landroid/content/Context;Ljava/lang/String;I)V //写入数据
goto :goto_50 //跳转地址
:cond_43
check-cast p1, Landroid/content/Context;
const-string p0, "\u8bf7\u5148\u5145\u503c\u5927\u4f1a\u5458\u54e6\uff01" //请先充值大会员哦!
check-cast p0, Ljava/lang/CharSequence;
invoke-static {p1, p0, p5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object p0
invoke-virtual {p0}, Landroid/widget/Toast;->show()V
:goto_50
return p5 //返回p5的值
.end method //方法结束
//判断是否是大会员的方法
.method public final isvip()Z
.registers 2
const/4 v0, 0x0 //v0赋值0
return v0 //返回v0的值
.end method
修改方法:修改判断、强制跳转、修改寄存器的值

-
修改判断
这里有个判断,判断是否够10个硬币
下面这个判断用来判断是否是大会员
我最好的办法就是把这个判断删除掉,但由于jadx不能直接修改,所以这里需要MT管理器的会员功能。
我们把if-ge改成if-le,就变成了小于等于跳转
下面判断它是不是vip,我们直接把这个判断用#注释掉,就能直接跳过这个判断了
然后我们保存,安装。
这样我们就修改完成了。
-
修改寄存器的值
这里是po和10作比较,我们这里直接改掉v0的值为0,就符合大于等于的判断标准了
我们对isvip长按,选择跳转,跳转到这个方法
我们把0改为1,这样它就会返回1,也就是真了。
保存退出,再安装。
这样我们也破解成功了。
3.强制跳转
我们把这里的判断改成强制跳转,不用让他判断直接跳转,这里就需要用到goto语句
这样他就会直接跳转到下面这个地方来
下面还有一个判断vip的地方,我们直接把它注释掉,这样不管他是不是为真,它都会直接走下面这段逻辑了。
保存退出,重新安装
恭喜你获得广告&弹窗静默卡
一、课程目标
1.了解安卓四大组件、Activity生命周期
2.弹窗定位、去更新
3.广告分析与布局优化
二、工具
1.教程Demo(更新)
2.MT管理器/NP管理器
3.算法助手
4.雷电模拟器
5.开发助手
三、课程内容
1.广告类型
启动广告 弹窗&更新广告 横幅广告
2.安卓四大组件
组件 | 描述 |
---|---|
Activity(活动) | 在应用中的一个Activity可以用来表示一个界面,意思可以理解为“活动”,即一个活动开始,代表 Activity组件启动,活动结束,代表一个Activity的生命周期结束。一个Android应用必须通过Activity来运行和启动,Activity的生命周期交给系统统一管理。 |
Service(服务) | Service它可以在后台执行长时间运行操作而没有用户界面的应用组件,不依赖任何用户界面,例如后台播放音乐,后台下载文件等。 |
Broadcast Receiver(广播接收器) | 一个用于接收广播信息,并做出对应处理的组件。比如我们常见的系统广播:通知时区改变、电量低、用户改变了语言选项等。 |
Content Provider(内容提供者) | 作为应用程序之间唯一的共享数据的途径,Content Provider主要的功能就是存储并检索数据以及向其他应用程序提供访问数据的接口。Android内置的许多数据都是使用Content Provider形式,供开发者调用的(如视频,音频,图片,通讯录等) |
activity的切换
<!---声明实现应用部分可视化界面的 Activity,必须使用 AndroidManifest 中的 <activity> 元素表示所有 Activity。系统不会识别和运行任何未进行声明的Activity。----->
<activity
android:label="@string/app_name"
android:name="com.zj.wuaipojie.ui.MainActivity"
android:exported="true"> <!--当前Activity是否可以被另一个Application的组件启动:true允许被启动;false不允许被启动-->
<!---指明这个activity可以以什么样的意图(intent)启动--->
<intent-filter>
<!--表示activity作为一个什么动作启动,android.intent.action.MAIN表示作为主activity启动--->
<action
android:name="android.intent.action.MAIN" />
<!--这是action元素的额外类别信息,android.intent.category.LAUNCHER表示这个activity为当前应用程序优先级最高的Activity-->
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.zj.wuaipojie.ui.ChallengeFirst" />
<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->广告Activity->主页Activity
修改方法:
1.修改加载时间
2.Acitivity切换定位,修改Intent的Activity类名
switch (position) {
case 0:
Intent intent = new Intent();
intent.setClass(it.getContext(), ChallengeFirst.class);
it.getContext().startActivity(intent);
return;
case 1:
Intent intent2 = new Intent();
intent2.setClass(it.getContext(), ChallengeSecond.class);
it.getContext().startActivity(intent2);
return;
case 2:
Intent intent3 = new Intent(); //new一个Intent,
intent3.setClass(it.getContext(), AdActivity.class); //传入要切换的Acitivity的类名
it.getContext().startActivity(intent3); //启动对应的Activity
return;
case 3:
Intent intent4 = new Intent();
intent4.setClass(it.getContext(), ChallengeFourth.class);
it.getContext().startActivity(intent4);
return;
default:
return;
}
3.Activity生命周期
函数名称 | 描述 |
---|---|
onCreate() | 一个Activity启动后第一个被调用的函数,常用来在此方法中进行Activity的一些初始化操作。例如创建View,绑定数据,注册监听,加载参数等。 |
onStart() | 当Activity显示在屏幕上时,此方法被调用但此时还无法进行与用户的交互操作。 |
onResume() | 这个方法在onStart()之后调用,也就是在Activity准备好与用户进行交互的时候调用,此时的Activity一定位于Activity栈顶,处于运行状态。 |
onPause() | 这个方法是在系统准备去启动或者恢复另外一个Activity的时候调用,通常在这个方法中执行一些释放资源的方法,以及保存一些关键数据。 |
onStop() | 这个方法是在Activity完全不可见的时候调用的。 |
onDestroy() | 这个方法在Activity销毁之前调用,之后Activity的状态为销毁状态。 |
onRestart() | 当Activity从停止stop状态恢进入start状态时调用状态。 |

4.弹窗定位&堆栈分析
修改方法:
1.修改xml中的versiocode
2.Hook弹窗(推荐算法助手开启弹窗定位)
3.修改dex弹窗代码
4.抓包修改响应体(也可以路由器拦截)
5.布局优化
1.开发者助手抓布局
2.MT管理器xml搜索定位
3.修改xml代码
android:visibility="gone"
四、操作
第四关的任务:去除广告全家桶

我们打开MT管理器,开启Activity记录

我们回到第四关可以发现是一个AD跳转到广告的,我们停止Activity记录,并找到这个Activity

这个就是我们刚才定位到的一个广告的Activity,我们复制一下。
提取安装包


我们这里需要将其转为Java,但是由于这个需要会员功能,所以我们改用NP,需要的朋友可以自己开个会员。

相同的操作我们将其转为Java看一下

我们可以确定loadAD()方法是加载广告的。然后我们返回smali里找到这个方法。

我们把0xbb8改成0x0(bb8是3000的十六进制),然后保存,再转为java看一下

三千毫秒变成了0。我们保存安装完看一下有没有效果。

直接就进入了这个页面,没有了3秒钟的等待时间。我们这里不是不经过这个Activity,而是经过,但是时间非常的短,我们看不到而已。如果想要证明可以通过Activity记录再次看一下。
我们换种方式
搜索刚才的记录,然后长按——复制,找到下图所示的内容复制,然后搜索,类型选择代码。

我们可以看到结果有很多

我们选择除了它以外的这个,看看它在哪里调用了这个。


我们转为java看一下

我们可以看到第三关new了一个Intent,传入要切换的Acitivity的类名,启动对应的Activity。
我们可以把它调用广告的类名换成别的关的类名,比如第二关。然后保存退出重新安装
更新的弹窗是版本不一样的时候就会弹出,我们直接去xml里就版本1改成2就能去掉了。
去掉更新弹窗后,出现了又一个弹窗

这个我们可以用返回键关掉

这时出现第二个广告弹窗,但是这个返回键被劫持了,我们不能用返回键关掉。
我们这里使用算法助手,进入算法助手,打开应用总开关和弹窗定位功能


点击右上角运行按钮,这时我们两个弹窗都可以用返回关掉了

我们还有个功能——屏蔽关键词弹窗

我们输入刚才出现的弹窗上的字

我们再运行,进来后可以发现两个弹窗都没有了,算法助手对我们来说是一个神器了。
我们关掉弹窗拦截,运行后返回算法助手,查看日志,找到最新的弹窗


看到调用堆栈最上面,是一个onCreate方法。我们复制这个

我们回到MT管理器的dex文件搜索刚才复制的那句话,类型选择方法名

我们可以看到搜索结果有很多,我们选择最下面那个最准确的

我们可以看到这个方法下面有一号广告和二号广告

来到最下面,我们可以看到一个show方法,这种广告弹窗最后一般都会调用一个show方法来显示。

一个很简单的方法就是把这个show方法这一行删掉或注释掉:我们在这句前打个#号就可以把这句话注释掉了。
保存退出,重新安装。我们就可以看到1号弹窗已经不见了。
我们刚才看见一号二号都是在那个文件里的,所以我们回到那里,找到二号的show
同理我们把它注释掉,然后保存退出,重新安装。

两个广告弹窗都消失了。这种方法的关键是定位弹窗。
我们最后一个方法是抓包修改响应体(也可以路由器拦截),这个我们放在后面来说。接下来是横幅广告,我们要用到开发助手这个工具。

我们主要用到的就是这个布局查看功能。

我们复制16进制数,然后回到MT管理器的安装包,右上角XML搜索这个


进入文件

有两种改法,第一种是把它的宽度或者高度改为0.

保存退出安装,我们就可以看到横幅广告不见了。

第二种方法需要用到一句代码
android:visibility="gone"
用这句代码把那个布局隐藏起来

也可以实现横幅广告的消除。

ps://typorazhd.oss-cn-beijing.aliyuncs.com/image-20221127120316798.png" alt=“image-20221127120316798” style=“zoom:67%;” />
有两种改法,第一种是把它的宽度或者高度改为0.

保存退出安装,我们就可以看到横幅广告不见了。

第二种方法需要用到一句代码
android:visibility="gone"
用这句代码把那个布局隐藏起来

也可以实现横幅广告的消除。
