zoukankan      html  css  js  c++  java
  • [Android Security] 静态分析Android程序——smali文件解析

    cp : https://blog.csdn.net/hp910315/article/details/51823236

    cp : http://www.jb51.net/softjc/119036.html

    静态分析Android程序的两种方法: 
    一、阅读反编译生成的Dalvik字节码。 
    1、使用文本编辑器阅读baksmali反编译生成的smali文件 
    (1)解压apk包

    unzip xxx.apk
    • 1

    (2)用baksmali进行对解压出来的dex文件反编译

    java -jar baksmali-2.0.3.jar classes.dex
    • 1

    2、使用IDA Pro分析dex文件

    二、阅读反编译生成的Java源码 
    (1) 使用 dex2jar 把classes.dex转换成jar

    java -jar dex2jar classes.dex
    • 1

    (2)使用jd-gui 打开这个jar

    本篇文章注意介绍第一种方式得到smali文件之后,对应smali文件进行分析。

    无论是普通类、抽象类、接口类或者内部类,在反编译的代码中,它们都会以单独的smali文件存放。每个smali文件都由若干语句组成,所有的语句都遵循着一套语法规范。下面来具体介绍。

    一、头信息——类的主体信息

    在打开smali文件的时候,它的头三行描述了当前类的一些信息。

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

    例如:

    //===================================================================
    public class MainActivity extends AppCompatActivity {
        // ......
    }
    //===================================================================
    .class public Ltestdemo/hpp/cn/test/MainActivity;
    .super Landroid/support/v7/app/AppCompatActivity;
    .source "MainActivity.java"
    //===================================================================
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    .class指令表示当前的类名,类的访问权限是public,类名为Ltestdemo/hpp/cn/test/MainActivity,类开头的L是遵循Dalvik字节码的相关约定,表示后面跟随的字符串是一个类。

    .super指定了当前类所继承的父类,后面指的就是这个父类的类名,L表示后面跟的字符串是一个类

    .source指定了当前类的源文件名

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

    这三行就是类的主体部分了,另外一个类是由多个字段或者方法组成。

    二、接口 
    如果一个类实现了一个接口,那么会在smali文件中使用.implements指令指出。

    #interfaces
    .implements <接口名>
    • 1
    • 2

    同样,#interfaces是注释,.implements是接口关键字。

    例如:

    //===================================================================
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        // ......
    }
    //===================================================================
    # interfaces
    .implements Landroid/view/View$OnClickListener;
    //===================================================================
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、smali基本语法 
    Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示; 
    Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)

    1、原始类型

    V void (只能用于返回值类型) 
    Z boolean
    B byte
    S short
    C char
    I int
    J long(64位)
    F float
    D double(64位)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、对象类型 
    Lpackage/name/ObjectName; 相当于java中的package.name.ObjectName; 
    L 表示这是一个对象类型 
    package/name 该对象所在的包 
    ObjectName 对象名称 
    ; 标识对象名称的结束

    3、数组类型 
    [I :表示一个整形的一维数组,相当于java的int[]; 
    对于多维数组,只要增加[ 就行了,[[I = int[][];注:每一维最多255个;

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

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

    例如:

    //===================================================================
    private void print(String string) {
        Log.d(TAG, string);
    }
    //===================================================================
    .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
    //===================================================================
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

    5、基本指令 
    smali字节码是类似于汇编的,如果你有汇编基础,理解起来是非常容易的。 
    move v0, v3 把v3寄存器的值移动到寄存器v0上 
    const-string v0, “MainActivity” 把字符串”MainActivity”赋值给v0寄存器 
    invoke-super  调用父函数 
    return-void  函数返回void 
    new-instance  创建实例 
    iput-object  对象赋值 
    iget-object  调用对象 
    invoke-static  调用静态函数 
    invoke-direct  调用函数

    例如:

    //===================================================================
    @Override
    public void onClick(View view) {
        String str = "Hello World!";
        print(str);
    }
    //===================================================================
    # virtual methods
    # 参数类型为Landroid/view/View,返回类型为V
    .method public onClick(Landroid/view/View;)V
        # 表示有三个寄存器
        .registers 3
        # 参数View类型的view变量对应的是寄存器p1
        .param p1, "view"    # Landroid/view/View;
    
        .prologue
        .line 24
        #将"Hello World!"字符串放到寄存器v0中
        const-string v0, "Hello World!"
    
        .line 25
        # 定义一个Ljava/lang/String类型的str变量对应本地寄存器v0
        .local v0, "str":Ljava/lang/String;
        # 调用该类的print方法,该方法的参数类型为Ljava/lang/String,返回值为V
        # 调用print方法传入的参数为{p0, v0},及print(p0, v0),p0为this,v0为"Hello World!"字符串
        invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
    
        .line 26
        return-void
    .end method
    //===================================================================
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    6、if判断语句

    if判断一共有12条指令:

    if-eq vA, VB, cond_** 如果vA等于vB则跳转到cond_**。相当于if (vA==vB)
    if-ne vA, VB, cond_** 如果vA不等于vB则跳转到cond_**。相当于if (vA!=vB)
    if-lt vA, VB, cond_** 如果vA小于vB则跳转到cond_**。相当于if (vA<vB)
    if-le vA, VB, cond_** 如果vA小于等于vB则跳转到cond_**。相当于if (vA<=vB)
    if-gt vA, VB, cond_** 如果vA大于vB则跳转到cond_**。相当于if (vA>vB)
    if-ge vA, VB, cond_** 如果vA大于等于vB则跳转到cond_**。相当于if (vA>=vB)
    
    if-eqz vA, :cond_** 如果vA等于0则跳转到:cond_** 相当于if (VA==0)
    if-nez vA, :cond_** 如果vA不等于0则跳转到:cond_**相当于if (VA!=0)
    if-ltz vA, :cond_** 如果vA小于0则跳转到:cond_**相当于if (VA<0)
    if-lez vA, :cond_** 如果vA小于等于0则跳转到:cond_**相当于if (VA<=0)
    if-gtz vA, :cond_** 如果vA大于0则跳转到:cond_**相当于if (VA>0)
    if-gez vA, :cond_** 如果vA大于等于0则跳转到:cond_**相当于if (VA>=0)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    7、循环语句 
    常用的循环结构有:迭代器循环,for循环,do while循环。

    8、switch分支语句

    9、try/catch语句

    四、字段

    smali文件中,字段的声明使用.field指令,字段分为静态字段和实例字段。

    1、静态字段

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

    可以看到,baksmali在生成smali文件时,会在静态字段声明的起始处添加注释”static fields”,注释是以#开头。

    访问权限包括:private、protected、public(三者之一) 
    修饰关键字为字段的其他属性,例如,final 
    字段名和类型就不用解释了

    例如:

    //===================================================================
    private  static final String TAG = "MainActivity";
    //===================================================================
    # static fields
    .field private static final TAG:Ljava/lang/String; = "MainActivity"
    //===================================================================

    2、实例字段 
    相比于静态自动就少了一个static的静态声明而已,其他都一样。

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

    例如:

    //===================================================================
    private Button mButton;
    //===================================================================
    # instance fields
    .field private mButton:Landroid/widget/Button;
    //===================================================================

    五、方法 
    smali的方法声明使用的.method指令,方法分为直接方法和虚方法两种。

    函数调用
     invoke-direct 调用private函数
    invoke-super 调用父类函数
    invoke-static 调用静态函数
    invoke-virtual 用于调用protected或public函数(相当于C++的虚函数,java的重载函数,只有protect和public能够重载)
    还有一种比较特殊的:invoke-xxxxx/range:参数多于5个的时候,要加/rang


     例子:

    invoke-virtual {v4, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

     v4是this,代表 Ljava/lang/String的一个实例,v1是函数的第一个参数,在这里是调用放在V4寄存器中类型为Ljava/lang/String的实例的equal ()方法,并传入参数v1,返回的结果是Z类型,也就是boolean类型。

    如果是invoke-static{v4, v1}, 不同遇在于invoke-virtual {v4, v1}的是v4不是this,而是第一个参数。v1是第二个参数,所调用的方法需要两个参数。

    1、直接方法 
    直接方法指的是该类中定义的方法。

    #direct methods
    .method <访问权限> [修饰关键字] <方法原型>
        <.registers>
        [.param]
        [.prologue]
        [.line]
        <.local>
        <代码体>
    .end method
    #direct methods是注释,是baksmali添加的,访问权限和修饰关键字跟字段是一样的。
    方法原型描述了方法的名称、参数与返回值。
    .registers 指令指定了方法中寄存器的总数,这个数量是参数和本地变量总和。
    .param表明了方法的参数,每个.param指令表示一个参数,方法使用了几个参数就有几个.parameter指令。
    .prologue指定了代码的开始处,混淆过的代码可能去掉了该指令。
    .line指明了该处代码在源代码中的行号,同样,混淆后的代码可能去掉了行号。
    .local 使用这个指定表明方法中非参寄存器
    //===================================================================
    private void print(String string) {
        Log.d(TAG, string);
    }
    //===================================================================
    .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
    //===================================================================
    

     

    2、虚方法 
    虚方法指的是从父类中继承的方法或者实现的接口的方法,它的声明跟直接方法相同,只是起始的初始为virtual methods

    //===================================================================
    @Override
    public void onClick(View view) {
        String str = "Hello World!";
        print(str);
    }
    //===================================================================
    # virtual methods
    .method public onClick(Landroid/view/View;)V
        .registers 3
        .param p1, "view"    # Landroid/view/View;
    
        .prologue
        .line 24
        const-string v0, "Hello World!"
    
        .line 25
        .local v0, "str":Ljava/lang/String;
        invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
    
        .line 26
        return-void
    .end method
    //===================================================================
    

      

    3、静态方法

    //===================================================================
    public static void setTag(String str) {
        TAG = str;
    }
    //===================================================================
    .method public static setTag(Ljava/lang/String;)V
        .registers 1
        .param p0, "str"    # Ljava/lang/String;
    
        .prologue
        .line 64
        sput-object p0, Ltestdemo/hpp/cn/annotationtest/MainActivity;->TAG:Ljava/lang/String;
    
        .line 65
        return-void
    .end method
    //===================================================================
    

      

    六、注解

    如果一个类使用了注解,那么smali中会使用.annotation指令。

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

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

    1、注解类

    //===================================================================
    @BindInt(100)
    public class MainActivity extends AppCompatActivity {
    
    }
    //===================================================================
    # annotations
    .annotation build Ltestdemo/hpp/cn/annotationtest/BindInt;
        value = 0x64
    .end annotation
    //===================================================================

    2、注解字段

    //===================================================================
    @BindView(R.id.button)
    public Button mButton;
    //===================================================================
    # instance fields
    .field public mButton:Landroid/widget/Button;
        .annotation build Lbutterknife/BindView;
            value = 0x7f0c0050
        .end annotation
    .end field
    //===================================================================
    

      

    3、注解方法

    //===================================================================
    @OnClick(R.id.button)
    public void click() {
        String str = "Hello World!";
        print(str);
    }
    //===================================================================
    # virtual methods
    .method public click()V
        .registers 2
        .annotation build Lbutterknife/OnClick;
            value = {
                0x7f0c0050
            }
        .end annotation
    
        .prologue
        .line 29
        const-string v0, "Hello World!"
    
        .line 30
        .local v0, "str":Ljava/lang/String;
        invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
    
        .line 31
        return-void
    .end method
    //===================================================================
    

      

    七、应用——smali插桩

    插桩的原理就是静态的修改apk的samli文件,然后重新打包。

    1、使用上面的方法得到一个apk的smali文件

    2、在关键部位添加自己的代码,需要遵循smili语法,例如在关键地方打log,输出关键信息

    3、重新进行打包签名

    具体例子参考文章:http://drops.wooyun.org/papers/6045

    参考文章: 
    http://drops.wooyun.org/papers/6045 
    http://blog.isming.me/2015/01/14/android-decompile-smali/

  • 相关阅读:
    金山词霸注册表怎么删
    新手学习jquery
    《企业应用架构模式》(POEAA)读书笔记
    Silverlight 4 tools
    asp.net非常基础的面试题
    VS 2010 中文版正式版无法安装Silverlight4 Tools的解决办法
    OnPreRender(EventArgs e) 事件常用的方法
    各大搜索引擎网站登录入口
    向用户控件传递参数的问题
    URLRewriter
  • 原文地址:https://www.cnblogs.com/0616--ataozhijia/p/8947311.html
Copyright © 2011-2022 走看看