smali语法从入门到精通
相关资源
视频中提到的apk和源代码资源,到这里下载
Smali 是什么
Smali是Android虚拟机的反汇编语言。Android虚拟机的可执行文件并不是普通的class文件,而是再重新整合打包后生成的dex文件。dex文件反编译之后就是Smali代码,所以说,Smali语言是Android虚拟机的反汇编语言。
掌握Smali** 有哪些好处**
- 动态调试APK,通常静态分析APK是不够的,如果需要彻底分析APK的执行逻辑,需要通过动态调试来进行。
- 修改APK运行逻辑,通过修改Smali代码,再重新编译打包成新的APK,是Android逆向的基本操作。
Smali基本语法
数据类型
|------------------------|-------------------------------------------|----------|---------------------------------------------------|
| Smali( 方法类型) | Smali (代码) | Java | 备注 |
| V | return-void | void | 只能用于返回值类型 |
| Z | const/4 v0,0x1 | boolean | 布尔值类型,判断逻辑 |
| Ljava/lang/String; | const-string v0,"字符串" | String | 字符串类型 |
| I | const/4 v0,0x1 | int | 整数类型 |
| Lpackage/name; | Lpackage/name; | 对象类型 | L表示这是一个对象类型,package表示该对象所在的包,; 表示对象名称的结束 |
| S | const/16 v0, 0x64 | short | 短整数类型 |
| J | const-wide/32 v0, 0x186a0 | long | 长整数类型 |
| F | const/high16 v0, 0x42c80000 | float | 浮点数类型 |
| D | const-wide/high16 v0, 0x4059000000000000L | double | 双浮点数类型 |
| C | const/16 v0, 0x61 | char | 字符类型 |
| B | const/4 v0, 0x1 | byte | 字节类型 |
| [类型 | [类型 | 数组 | [l表示一个int型数据,[Ljava/lang/String 表示一个String的对象数组 |
语法关键词
|-----------------|------------------------|
| 关键词 | 说明 |
| .class | 定义java类名 |
| .super | 定义父类名 |
| .source | 定义Java源文件名 |
| .filed | 定义字段 |
| .method | 定义方法开始 |
| .end method | 定义方法结束 |
| .annotation | 定义注解开始 |
| .end annotation | 定义注解结束 |
| .implements | 定义接口指令 |
| .local | 指定了方法内局部变量的个数 |
| .registers | 指定方法内使用寄存器的总数 |
| .prologue | 表示方法中代码的开始处 |
| .line | 表示java源文件中指定行 |
| .paramter | 指定了方法的参数 |
| .param | 和.paramter含义一致但是表达格式不同 |
寄存器
Java中变量都是存放在内存中的,Android为了提高性能,变量都是存放在寄存器中的,寄存器为32位,可以支持任何类型。
寄存器分为如下两类:
1、本地寄存器
用v开头数字结尾的符号来表示,v0, v1, v2,...
2、参数寄存器
用p开头数字结尾的符号来表示,p0,p1,p2,...
注意:
在非static方法中,p0代指this,p1为方法的第一个参数。
在static方法中,p0为方法的第一个参数。
Smali代码示例
|------------------------------------------------------------------------------------------------------------------------------------------------|
| const/4 v0, 0x1 //把值0x1存到v0本地寄存器 iput-boolean v0,p0,Lcom/aaa;->IsRegisterd:Z //把v0中的值赋给com.aaa.IsRegistered,p0代表this,相当于this.Isregistered=true |
成员变量
成员变量定义格式为:
.field public/private [static][final] varName:<类型>
获取指令
iget, sget, iget-boolean, sget-boolean, iget-object, sget-object
操作指令
iput, sput, iput-boolean, sput-boolean, iput-object, sput-object
array的操作是aget和aput
指令解析
sget-object v0,Lcom/aaa;->ID:Ljava/lang/String;
获取ID这个String类型的成员变量并放到v0这个寄存器中
iget-object v0,p0,Lcom/aaa;->view:Lcom/aaa/view;
iget-object比sget-object多一个参数p0,这个参数代表变量所在类的实例。这里p0就是this
Smali代码示例1:
|------------------------------------------------------------------|
| const/4 v3, 0x0 sput-object v3, Lcom/aaa;->timer:Lcom/aaa/timer; |
相当于java代码:this.timer = null;
Smali代码示例2:
|-----------------------------------------------------------------------------------------------|
| .local v0, args:Landroid/os/Message; const/4 v1, 0x12 iput v1,v0,Landroid/os/Message;->what:I |
相当于java代码:args.what = 18;
其中args为Message的实例
函数
函数定义格式为:
.method public/private [static][final] methodName()<类型>
.end method
Smali代码示例
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| .method private ifRegistered()Z .locals 2 // 本地寄存器的个数 .prologue const/4 v0, 0x1 //v0赋值为1 if-eqz v0, :cond_0 //判断v0是否等于0,等于0则跳到cond_0执行 const/4 v1, 0x1 //符合条件分支 :goto_0 //标签 return v1 //返回v1的值 :cond_0 //标签 const/4 v1, 0x0 //cond_0分支 goto :goto_0 //跳到goto_0执行 .end method |
函数分为两类:direct method和virtual method
direct method就是private方法,virtual method就是指其余的方法。
调用指令:
invoke-direct
invoke-virtual
invoke-static
invoke-super
invoke-interface
调用格式:
invoke-指令类型 {参数1, 参数2,...}, L类名;->方法名
如果不是是静态方法,参数1代表调用该方法的实例。
Smali代码示例:
|----------------------------------------------------------------------------------------------------|
| const-string v0, "NDKLIB" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V |
相当于java代码:System.loadLibrary("NDKLIB")
函数返回结果
Smali需要用指令move-result或move-result-object来保存函数返回的结果
Smali代码示例:
|----------------------------------------------------------------------------------------------------------------------|
| const-string v0, "Eric" invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String; move-result-object v2 |
表示将方法t返回的String对象保存到v2中。
方法的定义
|------------------------------------------------------|
| private static int sum(int a, int b) { return a+b; } |
使用编译后是这样
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| .method private static sum(II)I .locals 4 表示需要申请4个本地寄存器 .parameter .parameter 这里表示有两个参数 .prologue .line 27 move v0, p0 .local v0, a:I move v1, p1 .local v1, b:I move v2, v0 move v3, v1 add-int/2addr v2, v3 move v0, v2 .end local v0 a:I return v0 .end method |
从上面可以看到函数声明使用.method开始 .end method结束,java中的关键词private,static 等都可以使用,同时使用签名来表示唯一的方法,这里是sum(II)I。
指令执行
smali字节码是类似于汇编的,如果你有汇编基础,理解起来是非常容易的。
比如:
move v0, v3 把v3寄存器的值移动到寄存器v0上.
const v0, 0x1 把值0x1赋值到寄存器v0上。
invoke-static {v4, v5}, Lme/isming/myapplication/MainActivity;->sum(II)I 执行方法sum(),v4,v5的值分别作为sum的参数
条件跳转分支
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| "if-eq vA, vB, :cond_x" --- 如果vA等于vB则跳转到:cond_x "if-ne vA, vB, :cond_x" --- 如果vA不等于vB则跳转到:cond_x "if-lt vA, vB, :cond_x" --- 如果vA小于vB则跳转到:cond_x "if-ge vA, vB, :cond_x" --- 如果vA大于等于vB则跳转到:cond_x "if-gt vA, vB, :cond_x" --- 如果vA大于vB则跳转到:cond_x "if-le vA, vB, :cond_x" --- 如果vA小于等于vB则跳转到:cond_x "if-eqz vA, :cond_x" --- 如果vA等于0则跳转到:cond_x "if-nez vA, :cond_x" --- 如果vA不等于0则跳转到:cond_x "if-ltz vA, :cond_x" --- 如果vA小于0则跳转到:cond_x "if-gez vA, :cond_x" --- 如果vA大于等于0则跳转到:cond_x "if-gtz vA, :cond_x" --- 如果vA大于0则跳转到:cond_x "if-lez vA, :cond_x" --- 如果vA小于等于0则跳转到:cond_x |
常用修改技巧
const-string v0,"json数据" //写死抓包返回的vip信息的数据
goto :cond_x //无视后面的任何判断,直接跳转到校验成功后的代码段
return-void //如果方法是返回的void,直接在某个代码的后面加上,终止后面的代码的执行
const/4 v0,0x1 //对于判断,写死true 0x1或者false 0x0
//某行代码前添加#,代表该行代码被注释掉,和删掉这行代码一样,但方便恢复
if-eq/... //直接修改判断条件