Android逆向之smali语法

当我们发现某应用有个很牛逼功能的时候,还在为如何实现和拿不到源码而烦恼吗?
当我们写的app需要考虑安全性的时候,还在为明明用了混淆和第三方加固,但别人还是能破解而烦恼吗?
当我们发现api在某些手机上被弃用,而其他应用或系统应用又能实现该功能的时候,还在为相同的api自己却不能用而烦恼吗?

前车之鉴:当我们用一些工具去打开反编译后的代码,看着一团麻的指令和一些你从未见过的关键字、代码格式风格,赖不住性子就潦草的关掉了。其实是我们没有找对方法来学习它。

接下来介绍一下smali:

1、Dalvik字节码

Dalvik是google专门为Android操作系统设计的一个虚拟机,经过深度的优化。虽然Android上的程序是使用java来开发的,但是Dalvik和标准的java虚拟机JVM还是两回事。Dalvik VM是基于寄存器的,而JVM是基于栈的;Dalvik有专属的文件执行格式dex(dalvik executable),而JVM则执行的是java字节码。Dalvik VM比JVM速度更快,占用空间更少。

2、Smali定义

Smali是Davlik的寄存器语言,语法上和汇编语言类似。 Davlik是基于寄存器的,就是说smali里的所有操作都必须经过寄存器来进行。apk文件通过apktool反编译出来的都有一个smali文件夹,里面都是以.smali结尾的文件。

3、Smali基本数据类型

语法 类型
V void
Z boolean
B byte
S short
C char
I int
j long(64 bits)
f float
D double(64 bits)

注:
1、变量存放在寄存器中,寄存器为32位,支持任何类型,其中long,double是64位,使用两个相邻的寄存器。
2、V 只能用于返回值类型。

4、引用类型(数组和对象)

语法 类型
L java类
[ 数组

[x表示一维数组,x代表基本类型,如[I代表int[],[[x代表二维数组,如[[I表示int[][]。
对象类型以L作为开头来表示,格式是Lpackage/ClassName;(用分号表示对象结束是必须的)
如:Ljava/lang/String;

String对象在smali中为:Ljava/lang/String;
Class1对象的一个boolean成员表示为:Lcom/disney/Class1;->isRunning:Z
Class1对象的一个String对象成员表示为:Lcom/disney/Class1;->name:Ljava/lang/String;
可以总结为格式为对象类型->成员名:成员类型,->表示所属关系,类型尾部必须包括一个分号。
内部类表示为:Lpackage/ClassName i n n e r O b j e c t N a m e ; , 也 就 是 在 内 部 类 前 加 “ innerObjectName;,也就是在内部类前加“ innerObjectName;”符号。

5、运算符

smali 含义 符号
eq equals ==
ne not equals !=
lt less than <
gt bigger than >
le less and equals <=
ge bigger and equals >=
eqz equals zero =0
nez not equals zero !=0
ltz less than zero <0
btz bigger than zero >0
lez less equals zero <=0
gez bigger equals zero >=0

6、函数

格式:Func-Name (Para-Type1Para-Type2Para-Type3…)Return-Type
返回类型在最后,参数之间没有任何分隔符。
示例:
void fun()
fun()V

boolean fun(int, int, int)
fun(III)Z

String fun(boolean, int[], int[], String, long)
fun(Z[I[ILjava/lang/String;J)Ljava/lang/String;

7、局部变量

本地寄存器(local register,非参寄存器)用v开头数字结尾的符号来表示,如v0、v1、v2、…,
参数寄存器(parameter register)用p开头数字结尾的符号来表示,如p0、p1、p2、…,
.registers 用来标明方法中寄存器的总数,即参数寄存器和非参寄存器的总数。
.local 0,标明在这个函数中最少要用到的本地寄存器的个数,出现在方法中的第一行。在这里,由于只需要调用一个父类的onDestroy()处理,所以只需要用到p0,所以使用到的本地寄存器数为0,在植入代码后不要忘记可能要修改.local的值。
如: .local 4,则可以使用的寄存器是v0-v3

当一个方法被调用的时候,方法的参数被置于最后N个寄存器中。
在实例函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…,
在static函数中,p1表示函数的第一个参数,p2代表函数中的第二个参数…,因为Java的static方法中没有this方法。
示例:
const/4 v0, 0x0
iput-boolean v0, p0, Lcom/disney/Class1;->isRunning:Z

上面第一句中把值0x0存到v0本地寄存器中,
第二句用iput-boolean指令把v0中的值存放到this.isRunning这个成员变量中,即this.isRunning = false; 因为在实例函数中p0代表的是“this”,Lcom/disney/Class1;是类名,对应实例是p0。

8、成员变量和指令

#static fields
.field private static final PREFS_INSTALLATION_ID java/lang/String; = “installationId”
#instance fields
.field private _activityPackageName java/lang/String;
获取和操作静态成员变量和实例成员变量有不同的指令。
读取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等,
赋值的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。
带“-object”表示操作的成员变量是对象类型,没有“-object”后缀的表示操作的成员变量对象是基本数据类型,特别地boolean类型则使用带“-boolean”的指令操作。

获取static fields的指令。
示例:sget-object v0, Lcom/disney/Class1;->PREFS_INSTALLATION_ID:Ljava/lang/String;
上句中sget-object指令把PREFS_INSTALLATION_ID这个String成员变量获取并放到v0寄存器中。

获取instance fields的指令与static fields的类似,需要指明对象所属的实例。
示例:iget-object v0, p0, Lcom/disney/Class1;->_view:Lcom/disney/Class2;
上句iget-object指令比sget-object多了一个参数p0,就是该变量所在类的实例,在这里就是p0即“this”。

put指令的使用和get指令是统一的。
示例:
const/4 v3, 0x0
sput-object v3, Lcom/disney/Class1;->
globalIapHandler:Lcom/disney/config/GlobalPurchaseHandler;

上句相当于Class1.globalIapHandler = null;

9、函数调用

smali中的函数调用也分为direct和virtual两种类型,direct method就是private函数,public和protected函数都属于virtual method。在调用函数时,有invoke-direct,invoke-virtual,invoke-static、invoke-super以及invoke-interface等几种不同的指令。还有invoke-XXX/range 指令的,这是参数多于4个的时候调用的指令,比较少见。
1、invoke-static:就是调用static函数的,
示例:invoke-static {}, Lcom/disney/Class1;->fun()Z
上句invoke-static后面有一对大括号“{}”,内部是调用该方法的实例和参数列表,由于这是static方法也不需要参数,所以{}内为空。

2、invoke-super:调用父类方法,在onCreate、onDestroy等方法都能看到。
invoke-direct:调用private函数,
示例:invoke-direct {p0}, Lcom/disney/Class1;->
getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;

上句即this->getGlobalIapHandler(),函数GlobalPurchaseHandler getGlobalIapHandler()是定义在Class1中的一个private函数。

3、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参数。

10、获取函数调用结果

在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实例。

11、函数体

.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代码的。

12、条件语法

示例:
if-eq p1, v0, :cond_8
:cond_8
invoke-direct {p0}, Lcom/paul/test/a;->d()V

上段表示如果p1和v0相等,则执行cond_8的流程:调用com.paul.test.a的d()方法

示例:
if-ne p1, v0, :cond_b
:cond_b
const/4 v0, 0x0
invoke-virtual {p0, v0}, Lcom/paul/test/a;->setPressed(Z)V
invoke-super {p0, p1, p2}, Landroid/view/View;->onKeyUp(ILandroid/view/KeyEvent;)Z
move-result v0

上段表示不相等则执行cond_b的流程。

————个人学习总结,欢迎各路大神纠正,共同学习、共同进步!!!————

你可能感兴趣的:(Android开发)