我们常用的反编译三件套指的是ApkTool, dex2jar, jdgui。实际上经过ApkTool反编译的的代码都是smali,后面两个工具只是为了我们方便阅读,将smali语法转为java。但是工具不是万能的,很多时候转换结果不是那么令人满意。所以要想入门逆向工程,了解smali的语法是必须的。这种语言有点像汇编,但是因为我们有java源码可以对照着看,所以学起来还是很快的。而且java实在是太智能了,很多隐藏在底层的封装我们压根看不见,通过学习smali我们可以更深入的了解java的一些运行机制,对以后的开发也会有一些帮助
本文会为大家介绍smali的一些基本语法。通过实践修改反编译后的代码,替换资源并重新打包。让你的apk焕然一新
读取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object
赋值的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object
带“-object”表示操作的成员属性是对象类型,不带的表示操作的属性是基本数据类型(访问时会加入参数p0,即this)
boolean比较特殊,有-boolean后缀
v0 the first local register
v1 the second local register
v2 p0 this
v3 p1 I
v4 p2 I
p0 this
p1 I
p2, p3 J
我们先新建一个工程,包含了MainActivity, Person实体类,Utils工具类
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));
}
}
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;
}
}
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文件夹就是存放代码的地方
这里我们主要看一下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 ()V
.locals 0
.line 9
invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;->()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;->()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;->(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别名