smali文件和语法

smali文件格式

smali文件的头3行描述了当前类的一些信息,格式如下:

.class<访问权限>[修饰关键字]<类名>
.super<父类名>
.source<源文件名>

打开MainAcivity.smali,头三行代码:

.class public Lcom/droider/crackme0502/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"

第一行权限public ,类名为Lcom/droider/crackme0502/MainActivity;,类名开头的L是遵循Dalvik字节码的相关约定,表示后面跟随的字符串为一个类。

第二行,Lcom/droider/crackme0502/MainActivity; 的父类是 Landroid/app/Activity;

第三行,.source指令指定了当前类的源文件名

经过混淆的dex文件,反编译出来的smali代码可能没有源文件信息,因此,.source可能为空。

前三行代码过后是类的主体部分,一个类可以由多个字段或方法组成。smali文件中字段的声明使用.field指令。字段有静态字段与实例字段两种。静态字段的声明格式如下:

# static fields
.field<访问权限>[修饰关键字]<字段名>:<字段类型>

实例字段的声明:

# instance field
.field private btnAnno:Landroid/widget/Button;

private 表示私有;字段btnAnno,它的类型是Landroid/widget/Button;

直接方法的声明:

# direct methods
.method <访问权限> <方法名>(参数原型) <方法原型>
    [.prologue]    // 指定代码开始位置
    [.param]       // 指定方法参数
    [.line]        // 指定代码在源代码中的行数,混淆后可能不存在
    [.locals]  // 使用的局部变量个数
    <代码体>
.end method

虚方法的声明和直接方法相同,只是起始处的注释为virtual methods

如果一个类实现了接口,会在.smali文件中使用.implement指令指出。声明入下:

# interface
.implements<接口名>

注解格式声明:

# annotations
.annotation[注解属性]<注解类名>
    [注解字段=值]
.end annotation

注解的作用范围可以是类、方法和字段。如果作用范围是类,指令会直接定义在smali文件中,如果是方法或者字段,指令会包含在方法或字段的定义中。例如:

# instace fields
.field public sayWhat:Ljava/lang/String;
    .annotation runtime Lcom/droider/anno/MyAnnoField;
        info = "Hello my friend"
    .end annotation
.end field

转换成Java代码:

@ com.droid.anno MyAnnoField(info = "Hello my friend")
public String sayWhat;

原始类型

B—byte
C—char
D—double
F—float
I—int
J—long
S—short
V—void
Z—boolean
[XXX—array
Lpackage/name/ObjName—object;  // 前面表示对象所在包路径,分号表示类结束

寄存器操作

p命名法和v命名法:

假设一个函数中用到M个寄存器,实际传入的参数是N个。

根据传参规则,参数使用后N个寄存器,局部变量使用0到M-N个寄存器。

假如用到5个寄存器,2个局部参数,3个传入参数。

v命名法

v0,v1,v2,v3,v4;

p命名法

v0,v1,p0,p1,p2;

只改变传入参数寄存器名。

常量赋值

const                   v0, 0x7F030018  # R.layout.activity_challenge   #从R中取出静态值
const/4                 v3, 0x2   #4也可以换成16或者high16,表示取整数值
const-string            v2, "Challenge"  # 取字符串
const-class             v2, Context    #把类对象取出

变量赋值

move  vx,vy   # 将vy的值赋值给vx,也可以是move-object等
move-result vx  # 将上个方法调用后的结果赋值给vx,也可以是move-result-object
return-object vx # 将vx的对象作为函数返回值
new-instance v0, ChallengePagerAdapter  # 实例化一个对象存入v0中

对象赋值

iput-object a,(this),b   将a的值给b,一般用于b的初始化
iget-object a,(this),b   将b的值给a,一般用于获取b的地址,接着调用它
# eg.
iput-object v0, p0, ChallengeActivity->actionBar:ActionBar
iget-object v0, p0, ChallengeActivity->actionBar:ActionBar

函数操作

最基础的函数操作一般有以下四个:

1.private:invoke-direct
2.public|protected: invoke-virtual
3.static:invoke-static
4.parent:  invoke-super
基本调用形式:invoke-xxx {参数},类;->函数(参数原型)
# eg.
invoke-super {p0, p1}, Landroid/support/v4/app/FragmentActivity;->onCreate(Landroid/os/Bundle;)V
<=对应源码=>
super.onCreate(savedInstanceState);  // 其中p0是this,其父类是FragmentActivity,p1,是savedInstanceState,其原型是Bundle;即调用p0->onCreate(p1)

程序相关语法

判断语句

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

循环语句

public void encrypt(String str) {
    String ans = "";
    for (int i = 0 ; i < str.length();i++){
        ans += str.charAt(i);
    }
    Log.e("ans:",ans);
}
<=对应smali=>
.method public encrypt(Ljava/lang/String;)V   # 方法:public void encrypt(String str)
.locals 4           # 四个变量
.param p1, "str"    # 方法参数:Ljava/lang/String;
.prologue           # 代码起始处
const-string v0, ""  # 赋值给ans
.local v0, "ans":Ljava/lang/String; 
const/4 v1, 0x0     # 赋值给 i 
.local v1, "i":I   
:goto_0             # 循环的地方
invoke-virtual {p1}, Ljava/lang/String;->length()I   # 调用虚函数(参数p1)String类中的length方法,返回int
move-result v2      #把前一步的结果放在v2中
if-ge v1, v2, :cond_0  # 如果v1()V    # v2初始化
invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; # v2.append(v0)
move-result-object v2   #  v2.append(v0) => v2 这里 v2是v0的值,v2=ans
invoke-virtual {p1, v1}, Ljava/lang/String;->charAt(I)C   # str.charAt(i)
move-result v3      # str.charAt(i) => v3
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder; # ans + v3 
move-result-object v2   # ans + v3 =>v2
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; # v2.toString()
move-result-object v0   # v2=>v0
add-int/lit8 v1, v1, 0x1    # i++
goto :goto_0    # 跳转指令
:cond_0
const-string v2, "ans:"     # 常量赋值 v2 = "ans:"   
invoke-static {v2, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I # Log.e(v2,v0)
return-void 
.end method

switch语句

public void encrypt(int flag) {
        String ans = null;
        switch (flag){
            case 0:
                ans = "ans is 0";
                break;
            default:
                ans = "noans";
                break;
        }
        Log.v("ans:",ans);
    }
<=对应smali=>
.method public encrypt(I)V  # 方法 public void encrypt(int flag)
    .locals 2  # 两个变量
    .param p1, "flag"    # 一个参数 flag
    .prologue
    const/4 v0, 0x0     # v0赋值, 
    .local v0, "ans":Ljava/lang/String;  # String ans = null; v0就是ans

    packed-switch p1, :pswitch_data_0   # pswitch_data_0指定case区域的开头及结尾
    const-string v0, "noans"     # 默认 赋值 ans="noans"
    :goto_0
    const-string v1, "ans:"      # 赋值 v1 = "ans:" 
    invoke-static {v1, v0}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I # Log.v
    return-void
    :pswitch_0      #pswitch_   case 0: ans="ans is 0"
    const-string v0, "ans is 0"
    goto :goto_0  # break
    nop
    :pswitch_data_0 #case区域的结束
    .packed-switch 0x0   #定义case的情况
        :pswitch_0   #case 0
    .end packed-switch
.end method

其中case定义情况有两种:

1.从0开始递增
packed-switch p1, :pswitch_data_0
...
:pswitch_data_0
.packed-switch 0x0
    :pswitch_0
    :pswitch_1 
2.无规则switch
sparse-switch p1,:sswitch_data_0
...
sswitch_data_0
.sparse-switch
    0xa -> : sswitch_0
    0xb -> : sswitch_1 # 字符会转化成数组

try-catch语句

public void encrypt(int flag) {
    String ans = null;
    try {
        ans = "ok!";
    } catch (Exception e){
        ans = e.toString();
    }
    Log.d("error",ans);
}
<=对应smali=>
.method public encrypt(I)V      # public void encrypt(int flag) {
    .locals 3           # 3个变量
    .param p1, "flag"    # 参数
    .prologue           # 代码开始
    const/4 v0, 0x0     
    .line 20
    .local v0, "ans":Ljava/lang/String;     # String ans = null;
    :try_start_0  # 第一个try开始,
    const-string v0, "ok!"  # ans = "ok"
    :try_end_0   # 第一个try结束(主要是可能有多个try)
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
    :goto_0
    const-string v2, "error"
    invoke-static {v2, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
    return-void
    :catch_0 #第一个catch
    move-exception v1
    .local v1, "e":Ljava/lang/Exception;
    invoke-virtual {v1}, Ljava/lang/Exception;->toString()Ljava/lang/String;
    move-result-object v0
    goto :goto_0
.end method

baksmali在反编译时,为每个类单独生成一个smali文件,内部类作为一个独立类,也拥有自己独立的smali文件,只是内部类的文件名形式为[外部类]$[内部类].smali

class Outer{
    class Inner{}
}

上述代码生成两个文件:Outer.smaliOut$Inner.smali

this$0 是什么? 是内部类自动保留的一个指向所在外部类的引用。左边的this表示为父类的引用,右边的0表示引用层数。

public class Outer{                      //this$0
    public class FirstInner{            //this$1                                
        public class SecondInner{        //this$2
            public class ThirdInner{
                //在ThirdInner中访问FirstInner类的引用为this$1
            }
        }
    }
}

this$X型字段都被指定了synthetic属性,表明他们是被编译器合成的虚构的,开发者并没有声明该字段。

Reference

https://www.anquanke.com/post/id/85035

《Android软件安全与逆向分析》

你可能感兴趣的:(smali文件和语法)