Android反编译基础 - smali

        我们常用的反编译三件套指的是ApkTool, dex2jar, jdgui。实际上经过ApkTool反编译的的代码都是smali,后面两个工具只是为了我们方便阅读,将smali语法转为java。但是工具不是万能的,很多时候转换结果不是那么令人满意。所以要想入门逆向工程,了解smali的语法是必须的。这种语言有点像汇编,但是因为我们有java源码可以对照着看,所以学起来还是很快的。而且java实在是太智能了,很多隐藏在底层的封装我们压根看不见,通过学习smali我们可以更深入的了解java的一些运行机制,对以后的开发也会有一些帮助

        本文会为大家介绍smali的一些基本语法。通过实践修改反编译后的代码,替换资源并重新打包。让你的apk焕然一新

1.基本数据类型在smali中的表示

  •     B—byte
  •     C—char
  •     D—double
  •     F—float
  •     I—int
  •     S—short
  •     J—long
  •     Z—boolean
  •     V—void

2.操作符

  •     .method 一个方法的开始
  •     .end method 一个方法的结束
  •     .line 3 表示这段代码位于源码的第3行
  •     invoke-super 调用父类的方法
  •     invoke-direct 调用函数
  •     invoke-static 调用静态函数
  •     new-instance 创建一个实例
  •     iget,iput 获取/操作对象

3.获取和操作“静态成员变量”,“实例成员变量”的指令(s:指代static  i:指代instance)

  1. 读取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object

  2. 赋值的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object

  3. 带“-object”表示操作的成员属性是对象类型,不带的表示操作的属性是基本数据类型(访问时会加入参数p0,即this)

  4. boolean比较特殊,有-boolean后缀

4.函数

4.1函数的调用

  • invoke-static:调用static函数
                ?
  • invoke-super:调用父类的方法
                ? 
  • invoke-direct:调用private函数
    invoke-direct {p0, v2, v3}, Lcom/jack/smali/MainActivity;->sumInt(II)I
    move-result v2
  • invoke-virtual:调用public,protected函数 
    invoke-virtual {p0, v0}, Lcom/jack/smali/MainActivity;->findViewById(I)Landroid/view/View;
    move-result-object v0

4.2:获取函数返回结果

  • 在smali中调用函数和获取函数返回结果要分开来完成,分为以下两种
  • move-result: 获取基本数据类型返回值
  • move-result-object:获取对象返回值
  • 特殊情况:由于long和double占用2个寄存器,所以返回值为long或者double的时候需要使用move-resule-wide

5.寄存器

5.1:定义

  • 寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址对于dalviks字节码寄存器都是32位的,它能够表示任何类型,2个寄存器用于表示64位的类型(Long and Double)

5.2:方法中寄存器个数的指定    

  • .register:指定该方法中寄存器的总数(包含参数寄存器)
  • .local:指定该方法中非参寄存器的数量
  • 参数寄存器:被调用的方法中每一个参数都会占用一个寄存器,非static方法还会多一个”this“寄存器,它默认是方法中的第一个参数
  • 参数寄存器默认处于寄存器最后N个位置,比如一个方法包含2个参数,5个寄存器(V0-V4),那么参数寄存器保存在V3,V4
  • 例:LMyObject;->callMe(II)V,这个函数包含了两个int参数,但是默认的前面还会有一个隐藏的参数LMyObject(this),所以该方法一共有3个参数寄存器
  • 注:静态方法不包含this参数

5.3:寄存器的命名规则

  • 参数寄存器一般用P命名,p0,p1,p2....  非参寄存器一般用V命名,v0,v1,v2....,假设LMyObject;->callMe(II)中有2个本地寄存器,我们看一下寄存器的排列方式
v0          the first local register
v1          the second local register
v2    p0    this
v3    p1    I
v4    p2    I
  • 我们在第一条提到Long和Double会占用2个寄存器,定义一个方法LMyObject;->MyMethod(IJ)V(参数为int,long),我们再看一下寄存器的排列方式
p0        this
p1        I
p2, p3    J

6.反编译/重新打包命令

  • 反编译:java -jar apktool.jar -r d smali.apk(你的apk名称) -o smali(反编译后生成的文件夹名称)
  • 重新打包:java -jar apktool.jar b smali
  •  重新签名

6.1.使用autoSign签名(没有apk签名文件的情况)

  • 需要用到autoSign工具,将重新打包的apk放到autoSign文件夹下,然后执行命令java -jar signapk.jar testkey.x509.pem testkey.pk8 smali.apk smali_signed.apk(签名后的apk名称)

6.2. 用自带的jarSigner签名(有apk签名文件的情况)

  • 将重新打包的apk放到“jdk路径\bin”目录下,然后执行命令jarsigner -verbose -keystore keystore路劲 -signedjar smali_signed.apk smali.apk keystore别名

7.实践

7.1.反编译apk

我们先新建一个工程,包含了MainActivity, Person实体类,Utils工具类

  • MainActivity
package com.jack.smali;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {


    public static final String TEST = "smali";

    private Button button;
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        button = findViewById(R.id.button);
        imageView = findViewById(R.id.imageView);

        button.setOnClickListener(this);
        imageView.setImageResource(R.mipmap.picture);
    }

    private int multiplication(int i, int j) {
        return i * j;
    }

    private int subtraction(int i, int j) {
        return i - j;
    }

    private long sum(long i, double j) {
        return (long) (i + j);
    }

    private String sum(String value1, String value2) {
        return value1 + value2;
    }

    @Override
    public void onClick(View view) {
        //操作1:调用一个静态方法
        Utils.test("invoke a static method");

        //操作2:new一个person实例,调用实例中的方法并打印方法返回值
        Person person = new Person("sunqi", 18);
        System.out.println(person.getAge());

        //操作3:调用sum,并打印返回值
        System.out.println(sum(1, 2.2));

        //操作4:调用subtraction,并打印返回值
        System.out.println(subtraction(3, 1));

        //操作5:调用multiplication并打印返回值
        System.out.println(multiplication(3, 4));
    }
}
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button" />

</FrameLayout>
  • Person.java
package com.jack.smali;

public class Person {

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
  • Utils.java
package com.jack.smali;

public class Utils {

    public static void test(String content) {
        System.out.println(content);
    }

}

非常简单的几个类,下面我们执行几个步骤

1.编译一个apk文件,Build->Build Apk(s)

2.将生成的apk文件重命名为smali.apk,拷贝到ApkTool文件夹下

3.执行java -jar apktool.jar -r d smali.apk -o smali,这时候会生成一个smali文件夹,这是文件夹的目录,smali文件夹就是存放代码的地方

7.2.修改smali代码/资源并重新打包

这里我们主要看一下MainActivity生成的源码,只要了解清楚了这里面的代码,其他两个类就很好理解

对于类的解释全都写在了注释中,请仔细品读

.class public Lcom/jack/smali/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"

# interfaces 申明该类实现的接口
.implements Landroid/view/View$OnClickListener;


# static fields 定义的静态常量
.field public static final TEST:Ljava/lang/String; = "smali"


# instance fields 全部变量
.field private button:Landroid/widget/Button;

.field private imageView:Landroid/widget/ImageView;


# direct methods 类的默认构造方法
.method public constructor <init>()V
    .locals 0

    .line 9
    invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V

    return-void
.end method

#初始化方法init();
.method private init()V #返回值为void
    .locals 2 #定义两个本地寄存器

    .line 25
	#取出资源的常量值
    const v0, 0x7f070021

	#调用Activity的findViewById方法,传入一个值v0,p0表示this
    invoke-virtual {p0, v0}, Lcom/jack/smali/MainActivity;->findViewById(I)Landroid/view/View;

	#将findViewById的返回值赋值给v0
    move-result-object v0

	#将V0强转为Button
    check-cast v0, Landroid/widget/Button;

	#将v0赋值给button
    iput-object v0, p0, Lcom/jack/smali/MainActivity;->button:Landroid/widget/Button;

    .line 26
    const v0, 0x7f07003b

    invoke-virtual {p0, v0}, Lcom/jack/smali/MainActivity;->findViewById(I)Landroid/view/View;

    move-result-object v0

    check-cast v0, Landroid/widget/ImageView;

    iput-object v0, p0, Lcom/jack/smali/MainActivity;->imageView:Landroid/widget/ImageView;

    .line 28
	#拿到button变量
    iget-object v0, p0, Lcom/jack/smali/MainActivity;->button:Landroid/widget/Button;

	#调用button的setOnClickListener
    invoke-virtual {v0, p0}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

    .line 29
	#拿到imageView变量,并赋值给V0
    iget-object v0, p0, Lcom/jack/smali/MainActivity;->imageView:Landroid/widget/ImageView;

	#获取图片资源的常量值
    const v1, 0x7f0a0002

	#调用imageView的setImageResource方法,接受参数为V1,V0表示imageView
    invoke-virtual {v0, v1}, Landroid/widget/ImageView;->setImageResource(I)V

    .line 30
    return-void
.end method

#乘法运算
.method private multiplication(II)I #返回值为int

	#定义本地寄存器个数为1
    .locals 1
	#定义两个参数寄存器p1,p2,分别指代参数i,j
    .param p1, "i"    # I
    .param p2, "j"    # I

    .line 33
	#进行乘法运算,将p1,p2的乘积赋值给v0
    mul-int v0, p1, p2

	#返回v0
    return v0
.end method

#减法运算
.method private subtraction(II)I #返回值为int
	#定义本地寄存器个数为1
    .locals 1
	#定义两个参数寄存器p1,p2,分别指代参数i,j
    .param p1, "i"    # I
    .param p2, "j"    # I

    .line 37
	#进行减法运算,将p1,p2相减的结果赋值给v0
    sub-int v0, p1, p2

	#返回v0
    return v0
.end method

#加法运算
.method private sum(JD)J #返回值为Long
	#定义两个本地寄存器,虽然本地只用到了v0,但是实际上运算结果会占用两个寄存器
    .locals 2
	#定义两个参数寄存器p1,p3,分别指代参数i,j。由于double和long都要占用两个寄存器,所以定义的寄存器是p1,p3
    .param p1, "i"    # J
    .param p3, "j"    # D

    .line 41
	#long转double(转换p1的类型并赋值为v0)
    long-to-double v0, p1

	#double运算,v0+p3
    add-double/2addr v0, p3

	#将运算结果转为long
    double-to-long v0, v0

	#由于double是64位,占用两个寄存器,所以返回应该用return-wide
    return-wide v0
.end method

#两个字符串相加
.method private sum(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    .locals 1
    .param p1, "value1"    # Ljava/lang/String;
    .param p2, "value2"    # Ljava/lang/String;

    .line 45
	#创建一个StringBuilder对象,赋值给V0
    new-instance v0, Ljava/lang/StringBuilder;

	#调用StringBuilder默认的构造方法
    invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V

	#调用StringBuilder的append(String)方法,参数为p1
    invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

	#调用StringBuilder的append(String)方法,参数为p2
    invoke-virtual {v0, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

	#调用StringBuilder的toString方法,返回值为String
    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

	#将上一个方法的返回值赋值给V0
    move-result-object v0

	#返回V0
    return-object v0
.end method


# virtual methods
.method public onClick(Landroid/view/View;)V
    .locals 6
    .param p1, "view"    # Landroid/view/View;

    .line 51
	#定义一个常量字符串并赋值给V0
    const-string v0, "invoke a static method"

	#调用Utils的静态方法test(string),参数为v0
    invoke-static {v0}, Lcom/jack/smali/Utils;->test(Ljava/lang/String;)V

    .line 54
	#创建一个Person对象并赋值给V0
    new-instance v0, Lcom/jack/smali/Person;

	#定义一个常量字符串并赋值给V1
    const-string v1, "sunqi"

	#定义一个int值并赋值给V2
    const/16 v2, 0x12

	#调用Person的构造方法,参数为V1,V2
    invoke-direct {v0, v1, v2}, Lcom/jack/smali/Person;-><init>(Ljava/lang/String;I)V

    .line 55
	#创建的Person对象取名为person,并赋值给V0
    .local v0, "person":Lcom/jack/smali/Person;
	
	#获取System的静态变量out,返回值为PrintStream
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

	#调用Person的getAge()方法,返回值为int
    invoke-virtual {v0}, Lcom/jack/smali/Person;->getAge()I

	#将上一个方法的结果赋值给v2
    move-result v2

	#调用PrintStream的println(int)方法,v2作为参数
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V

    .line 58
	#调用并打印sum(long,double)方法的返回值
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

	#由于参数是64位,所以要使用const-wide
    const-wide/16 v2, 0x1

    const-wide v4, 0x400199999999999aL    # 2.2

    invoke-direct {p0, v2, v3, v4, v5}, Lcom/jack/smali/MainActivity;->sum(JD)J

	#赋值时也需要使用move-resule-wide
    move-result-wide v2

    invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->println(J)V

    .line 61
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const/4 v2, 0x3

    const/4 v3, 0x1

    invoke-direct {p0, v2, v3}, Lcom/jack/smali/MainActivity;->subtraction(II)I

    move-result v3

    invoke-virtual {v1, v3}, Ljava/io/PrintStream;->println(I)V

    .line 64
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const/4 v3, 0x4

    invoke-direct {p0, v2, v3}, Lcom/jack/smali/MainActivity;->multiplication(II)I

    move-result v2

    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V

    .line 65
    return-void
.end method

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 1
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;

    .line 19
    invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V

    .line 20
    const v0, 0x7f09001a

    invoke-virtual {p0, v0}, Lcom/jack/smali/MainActivity;->setContentView(I)V

    .line 21
    invoke-direct {p0}, Lcom/jack/smali/MainActivity;->init()V

    .line 22
    return-void
.end method

接下来我们修改一下smali中的代码

substraction(II)I是一个做减法运算的方法,我们将它改为一个加法运算,改动起来比较简单

#减法运算
.method private subtraction(II)I #返回值为int
	#定义本地寄存器个数为1
    .locals 1
	#定义两个参数寄存器p1,p2,分别指代参数i,j
    .param p1, "i"    # I
    .param p2, "j"    # I

    .line 37
	#进行减法运算,将p1,p2相减的结果赋值给v0
    #sub-int v0, p1, p2  我们将源码中的这一行注释,改为下面的代码
    add-int v0, p1, p2

	#返回v0
    return v0
.end method

修改资源

这个就更加简单了,在MainActivity.java中我们给ImageView设置了一张图,名称叫picture。只需要在smali->res目录下找到这张图并替换为名称相同的另一张图就可以了

重新打包

命令行cd到ApkTool文件夹下,执行java -jar apktool.jar b smali(源码文件夹名称),执行完后在smali文件夹下会多出一个dist文件夹,里面就包含了重新打包的apk

再次签名

修改smali源码后重新打包的apk是无法运行的,就算是我们平时调试生成的debug.apk也是有一个默认的签名,接下来我们说一下如何对apk进行签名

1.使用autoSign签名(没有apk签名文件的情况)
        需要用到autoSign工具,将重新打包的apk放到autoSign文件夹下,然后执行以下命令
        java -jar signapk.jar testkey.x509.pem testkey.pk8 smali.apk smali_signed.apk
    
 2.用自带的jarSigner签名(有apk签名文件的情况)
        将重新打包的apk放到“jdk路径\bin”目录下,然后执行下面命令
        jarsigner -verbose -keystore keystore路劲 -signedjar smali_signed.apk smali.apk keystore别名

猜你喜欢

转载自blog.csdn.net/u013894711/article/details/81409216