Smali语言其实就是Davlik的寄存器语言;Smali语言就是android的应用程序.apk通过apktool反编译出来的都有一个smali文件夹,里面都是以.smali结尾的文件的展示语言,我们可以通过分析修改Smali程序达到修改源程序的目的。
下面给出修改一个android工程其中Textview的教程。
如图,下面是android工程listedittest的目录:
通过apktool反编译出来的Smali文件夹里面的目录
先打开一个主类MainActivity.smali文件,先来浏览一下里面的语言,再来说说smali的语法规则:
.class public Lcom/myandroid/listedittest/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"
# static fields
.field static str:[Ljava/lang/String;
# instance fields
.field private bt_cancel:Landroid/widget/Button;
.field private bt_confirmdelete:Landroid/widget/Button;
.field private bt_deselectall:Landroid/widget/Button;
.field private bt_selectall:Landroid/widget/Button;
.field private checkNum:I
.field private list:Ljava/util/ArrayList;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/util/ArrayList",
"<",
"Ljava/util/HashMap",
"<",
"Ljava/lang/String;",
"Ljava/lang/String;",
">;>;"
}
.end annotation
.end field
.field private lv:Landroid/widget/ListView;
.field private mAdapter:Lcom/myandroid/listedittest/MyAdapter;
.field private tv_show:Landroid/widget/TextView;
# direct methods
.method static constructor <clinit>()V
.locals 3
.prologue
.line 30
const/16 v0, 0xd
new-array v0, v0, [Ljava/lang/String;
const/4 v1, 0x0
const-string v2, "data1"
aput-object v2, v0, v1
const/4 v1, 0x1
const-string v2, "data2"
aput-object v2, v0, v1
const/4 v1, 0x2
const-string v2, "data3"
aput-object v2, v0, v1
const/4 v1, 0x3
const-string v2, "data4"
aput-object v2, v0, v1
const/4 v1, 0x4
const-string v2, "data5"
aput-object v2, v0, v1
const/4 v1, 0x5
const-string v2, "data6"
aput-object v2, v0, v1
const/4 v1, 0x6
const-string v2, "7"
aput-object v2, v0, v1
const/4 v1, 0x7
const-string v2, "data8"
aput-object v2, v0, v1
const/16 v1, 0x8
const-string v2, "data9"
aput-object v2, v0, v1
const/16 v1, 0x9
const-string v2, "data10"
aput-object v2, v0, v1
const/16 v1, 0xa
const-string v2, "data11"
aput-object v2, v0, v1
const/16 v1, 0xb
const-string v2, "data12"
aput-object v2, v0, v1
const/16 v1, 0xc
const-string v2, "data13"
aput-object v2, v0, v1
sput-object v0, Lcom/myandroid/listedittest/MainActivity;->str:[Ljava/lang/String;
return-void
.end method
.method public constructor <init>()V
.locals 0
.prologue
.line 20
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
return-void
.end method
.method static synthetic access$000(Lcom/myandroid/listedittest/MainActivity;)Ljava/util/ArrayList;
.locals 1
.parameter "x0"
.prologue
.line 20
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->list:Ljava/util/ArrayList;
return-object v0
.end method
.method static synthetic access$100(Lcom/myandroid/listedittest/MainActivity;)I
.locals 1
.parameter "x0"
.prologue
.line 20
iget v0, p0, Lcom/myandroid/listedittest/MainActivity;->checkNum:I
return v0
.end method
.method static synthetic access$102(Lcom/myandroid/listedittest/MainActivity;I)I
.locals 0
.parameter "x0"
.parameter "x1"
.prologue
.line 20
iput p1, p0, Lcom/myandroid/listedittest/MainActivity;->checkNum:I
return p1
.end method
.method static synthetic access$108(Lcom/myandroid/listedittest/MainActivity;)I
.locals 2
.parameter "x0"
.prologue
.line 20
iget v0, p0, Lcom/myandroid/listedittest/MainActivity;->checkNum:I
add-int/lit8 v1, v0, 0x1
iput v1, p0, Lcom/myandroid/listedittest/MainActivity;->checkNum:I
return v0
.end method
.method static synthetic access$110(Lcom/myandroid/listedittest/MainActivity;)I
.locals 2
.parameter "x0"
.prologue
.line 20
iget v0, p0, Lcom/myandroid/listedittest/MainActivity;->checkNum:I
add-int/lit8 v1, v0, -0x1
iput v1, p0, Lcom/myandroid/listedittest/MainActivity;->checkNum:I
return v0
.end method
.method static synthetic access$200(Lcom/myandroid/listedittest/MainActivity;)V
.locals 0
.parameter "x0"
.prologue
.line 20
invoke-direct {p0}, Lcom/myandroid/listedittest/MainActivity;->dataChanged()V
return-void
.end method
.method static synthetic access$300(Lcom/myandroid/listedittest/MainActivity;)Landroid/widget/TextView;
.locals 1
.parameter "x0"
.prologue
.line 20
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->tv_show:Landroid/widget/TextView;
return-object v0
.end method
.method private dataChanged()V
.locals 3
.prologue
.line 157
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->mAdapter:Lcom/myandroid/listedittest/MyAdapter;
invoke-virtual {v0}, Lcom/myandroid/listedittest/MyAdapter;->notifyDataSetChanged()V
.line 159
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->tv_show:Landroid/widget/TextView;
new-instance v1, Ljava/lang/StringBuilder;
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
const-string v2, "\ufffd\ufffd\u0461\ufffd\ufffd"
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
iget v2, p0, Lcom/myandroid/listedittest/MainActivity;->checkNum:I
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
move-result-object v1
const-string v2, "\ufffd\ufffd"
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 160
return-void
.end method
.method private initDate()V
.locals 4
.prologue
.line 146
const/4 v0, 0x0
.local v0, i:I
:goto_0
sget-object v2, Lcom/myandroid/listedittest/MainActivity;->str:[Ljava/lang/String;
array-length v2, v2
if-ge v0, v2, :cond_0
.line 147
new-instance v1, Ljava/util/HashMap;
invoke-direct {v1}, Ljava/util/HashMap;-><init>()V
.line 148
.local v1, map:Ljava/util/HashMap;,"Ljava/util/HashMap<Ljava/lang/String;Ljava/lang/String;>;"
const-string v2, "content"
sget-object v3, Lcom/myandroid/listedittest/MainActivity;->str:[Ljava/lang/String;
aget-object v3, v3, v0
invoke-virtual {v1, v2, v3}, Ljava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 149
const-string v2, "flag"
const-string v3, "false"
invoke-virtual {v1, v2, v3}, Ljava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 150
iget-object v2, p0, Lcom/myandroid/listedittest/MainActivity;->list:Ljava/util/ArrayList;
invoke-virtual {v2, v1}, Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z
.line 146
add-int/lit8 v0, v0, 0x1
goto :goto_0
.line 152
.end local v1 #map:Ljava/util/HashMap;,"Ljava/util/HashMap<Ljava/lang/String;Ljava/lang/String;>;"
:cond_0
return-void
.end method
# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.locals 2
.parameter "savedInstanceState"
.prologue
.line 36
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 37
const v0, 0x7f030001
invoke-virtual {p0, v0}, Lcom/myandroid/listedittest/MainActivity;->setContentView(I)V
.line 39
const v0, 0x7f080007
invoke-virtual {p0, v0}, Lcom/myandroid/listedittest/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/ListView;
iput-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->lv:Landroid/widget/ListView;
.line 40
const v0, 0x7f080003
invoke-virtual {p0, v0}, Lcom/myandroid/listedittest/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
iput-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->bt_selectall:Landroid/widget/Button;
.line 41
const v0, 0x7f080005
invoke-virtual {p0, v0}, Lcom/myandroid/listedittest/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
iput-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->bt_cancel:Landroid/widget/Button;
.line 42
const v0, 0x7f080004
invoke-virtual {p0, v0}, Lcom/myandroid/listedittest/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
iput-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->bt_deselectall:Landroid/widget/Button;
.line 43
const v0, 0x7f080008
invoke-virtual {p0, v0}, Lcom/myandroid/listedittest/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
iput-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->bt_confirmdelete:Landroid/widget/Button;
.line 44
const v0, 0x7f080006
invoke-virtual {p0, v0}, Lcom/myandroid/listedittest/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/TextView;
iput-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->tv_show:Landroid/widget/TextView;
.line 45
new-instance v0, Ljava/util/ArrayList;
invoke-direct {v0}, Ljava/util/ArrayList;-><init>()V
iput-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->list:Ljava/util/ArrayList;
.line 47
invoke-direct {p0}, Lcom/myandroid/listedittest/MainActivity;->initDate()V
.line 49
new-instance v0, Lcom/myandroid/listedittest/MyAdapter;
iget-object v1, p0, Lcom/myandroid/listedittest/MainActivity;->list:Ljava/util/ArrayList;
invoke-direct {v0, v1, p0}, Lcom/myandroid/listedittest/MyAdapter;-><init>(Ljava/util/ArrayList;Landroid/content/Context;)V
iput-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->mAdapter:Lcom/myandroid/listedittest/MyAdapter;
.line 51
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->lv:Landroid/widget/ListView;
iget-object v1, p0, Lcom/myandroid/listedittest/MainActivity;->mAdapter:Lcom/myandroid/listedittest/MyAdapter;
invoke-virtual {v0, v1}, Landroid/widget/ListView;->setAdapter(Landroid/widget/ListAdapter;)V
.line 53
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->bt_selectall:Landroid/widget/Button;
new-instance v1, Lcom/myandroid/listedittest/MainActivity$1;
invoke-direct {v1, p0}, Lcom/myandroid/listedittest/MainActivity$1;-><init>(Lcom/myandroid/listedittest/MainActivity;)V
invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 67
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->bt_cancel:Landroid/widget/Button;
new-instance v1, Lcom/myandroid/listedittest/MainActivity$2;
invoke-direct {v1, p0}, Lcom/myandroid/listedittest/MainActivity$2;-><init>(Lcom/myandroid/listedittest/MainActivity;)V
invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 82
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->bt_deselectall:Landroid/widget/Button;
new-instance v1, Lcom/myandroid/listedittest/MainActivity$3;
invoke-direct {v1, p0}, Lcom/myandroid/listedittest/MainActivity$3;-><init>(Lcom/myandroid/listedittest/MainActivity;)V
invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 101
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->bt_confirmdelete:Landroid/widget/Button;
new-instance v1, Lcom/myandroid/listedittest/MainActivity$4;
invoke-direct {v1, p0}, Lcom/myandroid/listedittest/MainActivity$4;-><init>(Lcom/myandroid/listedittest/MainActivity;)V
invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 119
iget-object v0, p0, Lcom/myandroid/listedittest/MainActivity;->lv:Landroid/widget/ListView;
new-instance v1, Lcom/myandroid/listedittest/MainActivity$5;
invoke-direct {v1, p0}, Lcom/myandroid/listedittest/MainActivity$5;-><init>(Lcom/myandroid/listedittest/MainActivity;)V
invoke-virtual {v0, v1}, Landroid/widget/ListView;->setOnItemClickListener(Landroid/widget/AdapterView$OnItemClickListener;)V
.line 142
return-void
.end method
上面的smali语言对应的java类就是Mainactivity.java类如下:
package com.myandroid.listedittest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.myandroid.listedittest.MyAdapter.ViewHolder;
public class MainActivity extends Activity {
private ListView lv;
private MyAdapter mAdapter;
private ArrayList<HashMap<String, String>> list;
private Button bt_selectall;
private Button bt_cancel;
private Button bt_deselectall;
private Button bt_confirmdelete;
private int checkNum; // 记录选中的条目数量
private TextView tv_show;// 用于显示选中的条目数量
static String str[] = { "data1", "data2", "data3", "data4", "data5", "data6",
"7", "data8", "data9", "data10", "data11", "data12", "data13" };
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/* 实例化各个控件 */
lv = (ListView) findViewById(R.id.lv);
bt_selectall = (Button) findViewById(R.id.bt_selectall);
bt_cancel = (Button) findViewById(R.id.bt_cancelselectall);
bt_deselectall = (Button) findViewById(R.id.bt_deselectall);
bt_confirmdelete = (Button) findViewById(R.id.bt_confirmdelete);
tv_show = (TextView) findViewById(R.id.tv);
list = new ArrayList<HashMap<String, String>>();
// 为Adapter准备数据
initDate();
// 实例化自定义的MyAdapter
mAdapter = new MyAdapter(list, this);
// 绑定Adapter
lv.setAdapter(mAdapter);
// 全选按钮的回调接口
bt_selectall.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 遍历list的长度,将MyAdapter中的map值全部设为true
for (int i = 0; i < list.size(); i++) {
list.get(i).put("flag", "true");
}
// 数量设为list的长度
checkNum = list.size();
// 刷新listview和TextView的显示
dataChanged();
}
});
// 取消按钮的回调接口
bt_cancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 遍历list的长度,将已选的按钮设为未选
for (int i = 0; i < list.size(); i++) {
if (list.get(i).get("flag").equals("true")) {
list.get(i).put("flag", "false");
checkNum--;// 数量减1
}
}
// 刷新listview和TextView的显示
dataChanged();
}
});
// 反选按钮的回调接口
bt_deselectall.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 遍历list的长度,将已选的设为未选,未选的设为已选
for (int i = 0; i < list.size(); i++) {
if (list.get(i).get("flag").equals("true")) {
list.get(i).put("flag", "false");
checkNum--;
} else {
list.get(i).put("flag", "true");
checkNum++;
}
}
// 刷新listview和TextView的显示
dataChanged();
}
});
// 确认删除的回调接口
bt_confirmdelete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Iterator<HashMap<String, String>> iterator = list.iterator();
while (iterator.hasNext()) {
HashMap<String, String> temp = iterator.next();
if (temp.get("flag").equals("true")) {
iterator.remove();
}
}
checkNum = 0;
// 通知列表数据修改
dataChanged();
}
});
// 绑定listView的监听器
lv.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
Toast.makeText(MainActivity.this, "当前位置:" + arg2,
Toast.LENGTH_SHORT);
// 取得ViewHolder对象,这样就省去了通过层层的findViewById去实例化我们需要的cb实例的步骤
ViewHolder holder = (ViewHolder) arg1.getTag();
// 改变CheckBox的状态
holder.cb.toggle();
// 将CheckBox的选中状况记录下来
// 调整选定条目
if (holder.cb.isChecked() == true) {
list.get(arg2).put("flag", "true");
checkNum++;
} else {
list.get(arg2).put("flag", "false");
checkNum--;
}
// 用TextView显示
tv_show.setText("已选中" + checkNum + "项");
}
});
}
// 初始化数据
private void initDate() {
for (int i = 0; i < str.length; i++) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("content", str[i]);
map.put("flag", "false");
list.add(map);
}
}
// 刷新listview和TextView的显示
private void dataChanged() {
// 通知listView刷新
mAdapter.notifyDataSetChanged();
// TextView显示最新的选中数目
tv_show.setText("已选中" + checkNum + "项");
}
}
通过对比发现基本的方法名称没有改变,多了一个.method public constructor <init>()V表示该类的不带参数缺省的构造方法,onCreate()方法是以.method public onCreate(Landroid/os/Bundle;)V开始,.end method结束;方法的表示形式就是这个样子的。
下面介绍一下Smali代码注入,原理:在已有APK或JAR包中插入一些Dalvik虚拟机的指令,从而改变原有程序的路径和行为。
如果想修改以下Textview的值,我们可以在smali文件里搜索“tv_show"。
对应
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
对应
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
对应
由以上分析可得
第44行执行初始化工作,并且在smali文件的第407行把参数p0的内容打入到寄存器v0。于是我们可以通过修改p0来修改textview的值。
iput-object v0, p0,
Lcom/myandroid/listedittest/MainActivity;->tv_show:Landroid/widget/TextView;
修改为
iput-object v0, "我是修改后的内容",
Lcom/myandroid/listedittest/MainActivity;->tv_show:Landroid/widget/TextView;
再通过apktool b 需要打包回去的文件
打包回去就在反编译后的Mainactivity文件里生成一个dist文件夹,里面就有打包的Mainactivity.apk。如图
注意:要直接安装到手机或模拟器是不行的,需要签名,可以通过APKSign_gr这个签名软件来签名,也可以通过其他方式签名,在签名的时候如果一开始在eclipse里运行生成的apk是使用了ADB的debug权限签名,如果使用APKSign_gr这个签名,需要在手机或模拟器中把原来的apk卸载掉,在安装新的apk,就ok了,这个是解决安装apk时“INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES”这个错误的。
Smali语法简单介绍如下:
Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;
Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)
原始类型:v void 只能用于返回值类型
Z boolean
B byte
S short
C char
I int
J long(64位)
F float
D double(64位)
对象类型:Lpackage/name/ObjectName; 相当于java中的package.name.ObjectName;解释如下:
L:表示这是一个对象类型
package/name:该对象所在的包
;:表示对象名称的结束
数组的表示形式:
[I :表示一个整形的一维数组,相当于java的int[];
对于多维数组,只要增加[ 就行了,[[I = int[][];注:每一维最多255个;
对象数组的表示形式:
[Ljava/lang/String 表示一个String的对象数组;
方法的表示形式:
Lpackage/name/ObjectName;——>methodName(III)Z 详解如下:
Lpackage/name/ObjectName 表示类型
methodName 表示方法名
III 表示参数(这里表示为3个整型参数)
说明:方法的参数是一个接一个的,中间没有隔开;
字段的表示形式:
Lpackage/name/ObjectName;——>FieldName:Ljava/lang/String;
即表示: 包名,字段名和各字段类型
有两种方式指定一个方法中有多少寄存器是可用的:
.registers 指令指定了方法中寄存器的总数
.locals 指令表明了方法中非参寄存器的总数,出现在方法中的第一行
方法的传参:
当一个方法被调用的时候,方法的参数被置于最后N个寄存器中;
例如,一个方法有2个参数,5个寄存器(v0~v4)
那么,参数将置于最后2个寄存器(v3和v4)
非静态方法中的第一个参数总是调用该方法的对象;
说明:对于静态方法除了没有隐含的this参数外,其他都一样
寄存器的命名方式:
V命名
P命名 第一个寄存器就是方法中的第一个参数寄存器
比较:使用P命名是为了防止以后如果在方法中增加寄存器,需要对参数寄存器重新进行编号的缺点
特别说明一下:Long和Double类型是64位的,需要2个寄存器
例如:对于非静态方法
LMyObject——>myMethod(IJZ)V;
有4个参数:LMyObject,int,long,bool; 需要5个寄存器来存储参数;
P0 this
P1 I (int)
P2,P3 J (long)
P4 Z(bool)