前言:转轴拨弦三两声,未成曲调先有情,入了门但没有完全入门
课程目标
- 了解JVM,Dalvik,ART
- 初smali语法
- 实战修改smali
工具
- MT/NP管理器
- 雷电模拟器
- jadx-gui
- 核心破解
什么是JVM,Dalvik,ART
- JVM是JAVA虚拟机,运行JAVA字节码程序
- Dalvik是Google专门为Android设计的一个虚拟机,Dalvik有专属的文件执行格式dex(Dalvik executable)
- ART(Android Runtime)相当于Dalvik的升级版,本质与Dalvilk无异
smali及其语法
smali是Dalvik的寄存器语言,smali代码是dex反编译而来的
关键字
| 名称 | 注释 |
|---|---|
| .class | 类名 |
| .super | 父类名,继承的上级类名名称 |
| .source | 源名 |
| .field | 变量 |
| .method | 方法名 |
| .register | 寄存器 |
| .end register | 方法名的结束 |
| 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位,需要两个寄存器存储) | 双浮点类型,返回数字 |
| string | String | 文本类型,返回字符串 |
| Lxxx/xxx/xxx | object | 对象类型,返回对象 |
常用指令
| 关键字 | 注释 |
|---|---|
| const | 重写整数类型,真假属性内容,只能是数字类型 |
| const-string | 重写字符串内容 |
| 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 | 全称garden equal(a>=b),a大于或等于则跳 |
| if-le | 全称little equal(a<=b),小于或等于则跳 |
| goto | 强制跳到指定位置 |
| switch | 分支跳转,一般会有多少个分支线,并根据指令跳转到适当位置 |
| iget | 获取寄存器数据 |
| const-wide | 重写长整型类型,多用于修改到期时间 |
其余指令可用语法工具查询
定位方法:所有弹窗关键字、抓取按钮id、
接下来我们用实战的方式讲解:
实战-VIP终结者
下载解压jadx-gui-1.4.4-with-jre-win压缩包,运行jadx-gui-1.4.4,选择打开文件,选择教程demo(更新).apk,反编译结果如下:
之后安装压缩包里的核心破解。将该文件拖入到雷电模拟器下安装,出现如下提示:
点击,选择启用模块并勾选系统框架,然后重启
此时安装文件夹里的教程demo(更新).apk跟上一章节里的吾爱破解是同一个。但由于两次的签名不一样,所以再安装就会失败,此时安装核心破解之后,发现原来的图标发生了变化:
打开我们的第二关,这一关的要求是一键三连,当我们获取完硬币数量之后又要求我们长按,长按后的结果是要求我们充值大会员,暂时先记住关键词大会员。返回到之前的jadx-gui,选择工具栏中的搜索,输入大会员进行搜索:
有时候我们可能会搜索不到,这有可能是因为在反编译的时候将中文转换为了unicode编码的形式,
我们点击左上角文件,选择首选项,取消勾选Unicode字符转义即可
点击该关键字进去java代码
这段java代码还是很好解读的,注释如下:
1 | public static final boolean m457onCreate$lambda2(Ref.IntRef intRef, ChallengeSecond challengeSecond, ImageView imageView, ImageView imageView2, ImageView imageView3, View view) { |
在这串代码的一开始有一个反混淆的解释:
1 | /* renamed from: onCreate$lambda-2 */ |
所以我们根据这个找到smali中的部分,转为smali语法来理解:
1 | //一个私有、静态、不可变的方法 方法名 |
这边补充一下smali语法中的寄存器:
在smali里的所有操作都必须经过寄存器来进行:本地寄存器用v开头数字结尾的符号来表示,如v0、v1、v2。参数寄存器则使用p开头数字结尾符号来表示,如p0、p1、p2。特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指”this”,p1表示函数的第一个参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为java的static方法中没有this方法)
根据上面的讲述,我们就可以来分析一下代码:
查找到这句代码: if-ge p0, v0, :cond_15 //判断p0的值是否大于或等于v0的值(即p0的值是否大于或等于10),如果大于或等于则跳转到:cond_15用于判断硬币个数是否到了10个
之后会进行p0数值的判断,如果小于10,,就会出现一个弹窗:
1 | check-cast p1, Landroid/content/Context; //检查Context对象引用 |
1 | :cond_15 //跳转的一个地址 |
这句代码判断是否充值为大会员,如果是大会员的话,则跳转到cond_43
如果不是大会员,根据这串代码:
1 | :cond_43 |
屏幕显示要求先充值大会员
事实上,在另一个方法中:这部分始终将v0的值赋值为0,其实表示用户不是大会员
1 | .method public final isvip()Z |
整个代码并没有充值大会员的入口,所以要完成一键三连的话,需要对isvip进行修改,那该怎么修改呢
修改方法:修改判断、强制跳转、修改寄存器的内容
修改方法1-修改判断
我们在smali语法中找到第125行:
1 | if-ge p0, v0, :cond_15 //如果p0的值不等于v0的值,跳转到cond_15 |
跳转到cond_15,所以我们往下寻找cond_15的入口点:
1 | :cond_15 |
这三段就是在判断p0的值与v0的值是否相等,之后我们再寻找,找到148行:
1 | if-eqz p0, :cond_43 |
如果p0等于0 的话,跳转到conde_43:
1 | :cond_43 |
这里是提示要充值大会员,所以我们不能跳转到充值大会员,因为jdx不能直接修改代码,所以我们用MT管理器打开wuaipojie,打开其中的classes.dex文件:
选择Dex编辑器++的方式打开。在搜索中输入”硬币”,找到对应所在的代码,,向上寻找到
1 | if-ge p0, v0, :cond_15 |
这个部分是对硬币数量的判断,if-ge指的是如果p0大于或等于v0则跳转,所以我们修改为小于或等于,用if-le指令:
此时如果数量小于v0则直接跳转到cond_15,现在跳到下面147行,修改这部分的代码使其不会跳转:用注释的方式
此时程序会执行输出 “当前已经是大会员了哦”,一直到186行的
1 | goto :goto_50 |
1 | :goto_50 |
自动结束这一方法
我们保存试试看此时的效果:
不仅已经是大会员,同时一键三连图标全部点亮
修改方法2-修改寄存器的值
我们先删除原来的程序软件,恢复原来的备份,同样用dex编辑器++打开classes.dex文件,回到之前硬币数量的判断,上卖弄一行的代码其实是对v0的值赋值为10:
那我们可以修改v0为0,使p0永远大于v0,跳过中间获取硬币
之后代码跳转到cond_15,选择isvip,长按,选择跳转,之后会跳转到isvip的方法中:
通过分析发现,v0始终返回值为0,即假,所以我们只需要将0x0修改为0x1,这样v0的返回值就为真了,回到之前的代码部分:
1 | invokevirtual {p1} ,Lcom/zj/wuaipojie/ui/ChallengeSecond;->isvip()Z |
这里move-result p0是将上述代码的返回值给p0,也就是说修改完之后p0的值为1,不等于0,不会跳转到cond_43。
修改保存完,运行软件,此时硬币数量是0,选择长按“一键三连”同样也能点亮图标和弹出已经是大会员