侧边栏壁纸
  • 累计撰写 45 篇文章
  • 累计创建 87 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Smali基础语法

Even
2022-10-24 / 0 评论 / 0 点赞 / 1042 阅读 / 13156 字

安卓逆向Smali语法

一、什么是Smali?

  1. Smali语言是Davlik的寄存器语言,语法上和汇编语言相似,Dalvik VM与JVM的最大的区别之一就是Dalvik VM是基于寄存器的。基于寄存器的意思是,在smali里的所有操作都必须经过寄存器来进行。
  2. Smali,Baksmali 分别是指安卓系统里的 Java 虚拟机(Dalvik)所使用的一种 dex 格式文件的汇编器,反汇编器。其语法是一种宽松式的 Jasmin/dedexer 语法,而且它实现了 .dex 格式所有功能(注解,调试信息,线路信息等)
  3. 当我们对 APK 文件进行反编译后,便会生成此类文件。在Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)

二、基本数据类型

1.原始类型

V void     	(只能用于返回值类型) 
Z boolean   布尔型
B byte 		byte型
S short 	短整型
C char 		字节型
I int 		整型
J long 		长整型(64位)两个寄存器
F float 	浮点型
D double 	双精度小数(64位)

2.对象类型

Lpackage/name/ObjectName; 相当于java中的package.name.ObjectName; 

L 表示这是一个对象类型 
package/name 该对象所在的包 
ObjectName 对象名称 
; 标识对象名称的结束

3.数组类型

[I:表示一个整形的一维数组,相当于java的int[];

对于多维数组,只要增加[就行了,[[I = int[][];注:每一维最多255个;

对象数组的表示形式:[Ljava/lang/String表示一个String的对象数组;

三、Smali语法

1、寄存器与变量

Android变量都是存放在寄存器中的,寄存器为32位,可以支持任何类型,其中long和double是64为的,需要使用两个寄存器保存。寄存器采用v和p来命名,v表示本地寄存器,p表示参数寄存器。

// java原代码
private void print(String string) {
    Log.d(TAG, string);
}
// Smali代码
.method private print(Ljava/lang/String;)V
    .registers 3
    
    .param p1, "string"    # Ljava/lang/String;

    .prologue
    
    .line 29
    
    const-string v0, "MainActivity"
    
    invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    .line 30
    return-void
.end method

解读:

.registers 3说明该方法有三个寄存器,其中一个本地寄存器v0,两个参数寄存器p0,p1,细心的人可能会注意到没有看到p0,原因是p0存放的是this。如果是静态方法的话就只有2个寄存器了,不需要存this了。

  1. 本地寄存器(local register,非参寄存器)用v开头数字结尾的符号来表示,如v0、v1、v2、…,

  2. 参数寄存器(parameter register)用p开头数字结尾的符号来表示,如p0、p1、p2、…,

  3. .registers 用来标明方法中寄存器的总数,即参数寄存器和非参寄存器的总数。

  4. .local 0,标明在这个函数中最少要用到的本地寄存器的个数,出现在方法中的第一行。在这里,由于只需要调用一个父类的onDestroy()处理,所以只需要用到p0,所以使用到的本地寄存器数为0,在植入代码后不要忘记可能要修改.local的值。如 .local 4,则可以使用的寄存器是v0-v3。

  5. 当一个方法被调用的时候,方法的参数被置于最后N个寄存器中。

  6. 在实例函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…,

  7. 在static函数中,p1表示函数的第一个参数,p2代表函数中的第二个参数…,因为Java的static方法中没有this方法。

2、基本指令

.field private isFlag:z		定义变量
.annotation    				类使用了注解,那么smali中会使用
.method						方法
.parameter					方法参数
.prologue					方法开始

.line 12					此方法位于第12行

move v0, v3					把v3寄存器的值移动到寄存器v0上

const-string v0, "MainActivity"   把字符串"MainActivity"赋值给v0寄存器

new-instance  创建实例

iput-object   对象赋值
iget-object	  调用对象

invoke-super  调用父函数

invoke-static  调用静态函数
invoke-direct  调用函数

return-void   函数返回void

3、条件跳转指令

"if-eq vA, vB, :cond_**"   	如果vA等于vB则跳转到:cond_**
"if-ne vA, vB, :cond_**"   	如果vA不等于vB则跳转到:cond_**
    
"if-lt vA, vB, :cond_**"    如果vA小于vB则跳转到:cond_**
"if-gt vA, vB, :cond_**"   	如果vA大于vB则跳转到:cond_**
"if-ge vA, vB, :cond_**"   	如果vA大于等于vB则跳转到:cond_**
"if-le vA, vB, :cond_**"    如果vA小于等于vB则跳转到:cond_**

"if-eqz vA, :cond_**"   	如果vA等于0则跳转到:cond_**
"if-nez vA, :cond_**"   	如果vA不等于0则跳转到:cond_**

"if-ltz vA, :cond_**"    	如果vA小于0则跳转到:cond_**
"if-gtz vA, :cond_**"   	如果vA大于0则跳转到:cond_**

"if-lez vA, :cond_**"    	如果vA小于等于0则跳转到:cond_**
"if-gez vA, :cond_**"   	如果vA大于等于0则跳转到:cond_**

4、函数调用

smali中的函数调用也分为directvirtual两种类型,direct method就是private函数,public和protected函数都属于virtual method。在调用函数时,有invoke-direct,invoke-virtual,invoke-static、invoke-super以及invoke-interface等几种不同的指令。

invoke-static:就是调用static函数的,示例:

invoke-static {}, Lcom/disney/Class1;->fun()Z

上句invoke-static后面有一对大括号“{}”,内部是调用该方法的实例和参数列表,由于这是static方法也不需要参数,所以{}内为空。
invoke-super:调用父类方法,在onCreate、onDestroy等方法都能看到。
invoke-direct:调用private函数,示例:

invoke-direct {p0}, Lcom/disney/Class1;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;

上句即this->getGlobalIapHandler(),函数GlobalPurchaseHandler getGlobalIapHandler()是定义在Class1中的一个private函数。
invoke-virtual:用于调用protected或public函数,示例:

sget-object v0, Lcom/disney/Class1;->shareHandler:Landroid/os/Handler;
invoke-virtual {v0, v3}, Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V

上句v0是shareHandler android/os/Handler,v3是传递给removeCallbackAndMessage方法的Ljava/lang/Object参数。

5、获取函数返回结果

在Smali里调用函数和返回函数结果需要分开来完成,在调用的函数返回非void后,用move-result(返回基本数据类型)和move-result-object(返回对象)指令获取返回结果。

const/4 v2, 0x0
invoke-virtual {p0, v2}, Lcom/disney/Class1;->getPreferences(I)Landroid/content/SharedPreferences;
move-result-object v1

上句v1保存的就是调用this.getPreferences(int)方法返回的SharedPreferences实例。

6、函数体

.method.end method之间。

.method protected onDestroy()V
.locals 0
 
.prologue
.line 277
invoke-super {p0}, Lcom/disney/common/BaseActivity;->onDestroy()V
 
.line 279
return-void
.end method

上段是onDestroy()函数。

.line 277,标注了该代码在原Java文件中的行数,它不是必须的,去掉没有编译问题。它在出错时可以指出错误位置,jd-gui工具即是通过分析这些信息将smali代码还原成Java代码的。

四、基本语句语法

1、if语句

///Java语法
private boolean ifSense(){
        boolean tempFlag = ((3-2)==1)? true : false;
        if (tempFlag) {
            return true;
        }else{
            return false;
        }
    }
//Smali语法
.method private ifSense()Z
    .locals 2
    .prologue
    .line 22
    const/4 v0, 0x1     // v0赋值为1
    .line 24
    .local v0, tempFlag:Z
    if-eqz v0, :cond_0            // 判断v0是否等于0, 不符合条件向下走, 符合条件执行cond_0分支
    .line 25
    const/4 v1, 0x1            // 符合条件分支
    .line 27
    :goto_0
    return v1
    :cond_0
    const/4 v1, 0x0            // cond_0分支
    goto :goto_0
.end method
###文字描述:如果符合if分支则程序往下走,最终return ; 而如果条件不符合则会走到 :cond_0分支 , 最终执行 goto :goto_0走回 :goto_0返回

2、for语句

//java----------------------------------------
private void forSense(){
    listStr = new ArrayList<String>(COUNT);
    for (int i = 0; i < COUNT; i++) {
        listStr.add("现在轮到我上场乐");
    }
}
 
//Smali----------------------------------------
 
.line 40
    const/4 v0, 0x0
 
    .local v0, i:I
    :goto_0
    if-lt v0, v3, :cond_0            //  if-lt判断数值v0小于v3 ,    如不符合往下走, 符合执行分支 :cond_0
 
    .line 43
    return-void
 
    .line 41
    :cond_0                // 标签
    iget-object v1, p0, Lcom/example/smalidemo/MainActivity;->listStr:Ljava/util/List;                // 引用对象
 
    const-string v2, "\u73b0\u5728\u8f6e\u5230\u6211\u4e0a\u573a\u4e50"
 
    invoke-interface {v1, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z        // List是接口, 所以执行接口方法add
 
    .line 40
    add-int/lit8 v0, v0, 0x1    // 将第二个v0寄存器中的值,加上0x1的值放入第一个寄存器中, 实现自增长
 
    goto :goto_0                // 回去:goto_0标签
 
###文字描述:设定一个标签goto_0, 判断v0小于v3, 符合执行分支:cond_0 ,然后又跑回:goto_0做继续判断

3、switch分支语句

.method private packedSwitch(I)Ljava/lang/String;
.locals 1
.parameter "i"
.prologue
.line 21
const/4 v0, 0x0
.line 22
.local v0, str:Ljava/lang/String;  #v0为字符串,0表示null
packed-switch p1, :pswitch_data_0  #packed-switch分支,pswitch_data_0指定case区域
.line 36
const-string v0, "she is a person"  #default分支
.line 39
:goto_0      #所有case的出口
return-object v0 #返回字符串v0
.line 24
:pswitch_0    #case 0
const-string v0, "she is a baby"
.line 25
goto :goto_0  #跳转到goto_0标号处
.line 27
:pswitch_1    #case 1
const-string v0, "she is a girl"
.line 28
goto :goto_0  #跳转到goto_0标号处
.line 30
:pswitch_2    #case 2
const-string v0, "she is a woman"
.line 31
goto :goto_0  #跳转到goto_0标号处
.line 33
:pswitch_3    #case 3
const-string v0, "she is an obasan"
.line 34
goto :goto_0  #跳转到goto_0标号处
.line 22
nop
:pswitch_data_0
.packed-switch 0x0    #case  区域,从0开始,依次递增
    :pswitch_0  #case 0
    :pswitch_1  #case 1
    :pswitch_2  #case 2
    :pswitch_3  #case 3
.end packed-switch
.end method

packed-switch 指令。p1为传递进来的 int 类型的数值,pswitch_data_0 为case 区域,在 case 区域中,第一条指令“.packed-switch”指定了比较的初始值为0 ,pswitch_0~ pswitch_3分别是比较结果为“case 0 ”到“case 3 ”时要跳转到的地址。可以发现,标号的命名采用 pswitch_ 开关,后面的数值为 case 分支需要判断的值,并且它的值依次递增。再来看看这些标号处的代码,每个标号处都使用v0 寄存器初始化一个字符串,然后跳转到了goto_0 标号处,可见goto_0 是所有的 case 分支的出口。另外,“.packed-switch”区域指定的case 分支共有4 条,对于没有被判断的 default 分支,会在代码的 packed-switch指令下面给出。
java语言如下:

private String packedSwitch(int i) {
String str = null;
switch (i) {
    case 0:
        str = "she is a baby";
        break;
    case 1:
        str = "she is a girl";
        break;
    case 2:
        str = "she is a woman";
        break;
    case 3:
        str = "she is an obasan";
        break;
    default:
        str = "she is a person";
        break;
}
return str;
}

4、try/catch语句

# virtual methods
.method public statementTry()Z
    .locals 4
    .prologue
    .line 14
    const-wide/16 v2, 0x3e8
    :try_start_0
    invoke-static {v2, v3}, Ljava/lang/Thread;->sleep(J)V
    :try_end_0
    .catch Ljava/lang/InterruptedException; {:try_start_0 .. :try_end_0} :catch_0
    .line 18
    :goto_0
    const/4 v1, 0x1
    return v1
    .line 15
    :catch_0
    move-exception v0
    .line 16
    .local v0, "e":Ljava/lang/InterruptedException;
    invoke-virtual {v0}, Ljava/lang/InterruptedException;->printStackTrace()V
    goto :goto_0
.end method

代码中的try语句块使用try_start_开头的标号注明,以try_end_开头的标号结束。第一个try语句的开头标号为try_start_0,结束标号为 try_end_0。使用多个try语句块时标号名称后面的数值依次递增,本实例代码中最多使用到了try_end_2。

在try_end_0 标号下面使用“.catch”指令指定处理到的异常类型与catch的标号,格式如下。

.catch < 异常类型> {<try起始标号> .. <try 结束标号>} <catch标号>

五、最后

你能否将下面的Smali代码变成Java代码呢?

.locals 4    
const/4 v2, 0x1    
const/16 v1, 0x10    
.local v1, "length":I    
if-nez v1, :cond_1    
:cond_0    
:goto_0    
return v2    
:cond_1    
const/4 v0, 0x0    
.local v0, "i":I    
:goto_1    
if-lt v0, v1, :cond_2    
const/16 v3, 0x28    
if-le v1, v3, :cond_0
const/4 v2, 0x0
goto :goto_0    
:cond_2    
xor-int/lit8 v1, v1, 0x3b    
add-int/lit8 v0, v0, 0x1    
goto :goto_1

如果可以的话,你已经基本掌握了Smali语法了!

先按顺序写出代码:

int length=0x10;
if(length!=0){
    int i=0;
}
if(i<length){
    length^=0x3b;
    i+=1;
}
else if(length<=0x28){
    return 1;
}
else{
    return 0;
}

其中0、1用v2寄存器传值,v1是length,v3存储的是0x28,v0存的是for循环计数器i

然后观察语句特点,发现像for循环,修改代码:

int length=0x10;
for(int i=0;i<length;i++){
    length^=0x3b;
}
if(length<=0x28)
    return 1;
return 0;

🔗原文链接:https://blog.csdn.net/cpongo3/article/details/89708340

0

评论区