smali基础.docx
- 文档编号:29483869
- 上传时间:2023-07-23
- 格式:DOCX
- 页数:45
- 大小:32.85KB
smali基础.docx
《smali基础.docx》由会员分享,可在线阅读,更多相关《smali基础.docx(45页珍藏版)》请在冰豆网上搜索。
smali基础
Smali基础
1.背景知识
使用Apktool反编译apk文件后,会在反编译工程目录下生成一个smali文件夹,里面存放着所有反编译出的smali文件,这些文件会根据程序包的层次结构生成相应的目录,程序中所有的类都会在相应的目录下生成独立的smali文件。
smali文件的代码通常情况下比较长,而且指令繁多,在阅读时很难用肉眼捕捉到重点。
无论是普通类、抽象类、接口类或者内部类,在反编译出的代码中,它们都以单独的smali文件来存放。
每个smali文件都由若干条语句组成,所有的语句都遵循着一套语法规范。
2.文件头
在smali文件的头3行描述了当前类的一些信息,格式如下。
.class<访问权限>[修饰关键字]<类名>
.super<父类名>
.source<源文件名>
2.1.数据类型
在smali中,数据类型和Android中的一样,只是对应的符号有变化:
B---byte
C---char
D---double
F---float
I---int
J---long
S---short
V---void
Z---boolean
[XXX---array
Lxxx/yyy---object
这里解析下最后两项,数组的表示方式是:
在基本类型前加上前中括号“[”,例如int数组和float数组分别表示为:
[I、[F;对象的表示则以L作为开头,格式是LpackageName/objectName;(注意必须有个分号跟在最后),例如String对象在smali中为:
Ljava/lang/String;,其中java/lang对应java.lang包,String就是定义在该包中的一个对象。
或许有人问,既然类是用LpackageName/objectName;来表示,那类里面的内部类又如何在smali中引用呢?
答案是:
LpackageName/objectName$subObjectName;。
也就是在内部类前加“$”符号,关于“$”符号更多的规则将在后面谈到。
2.2.Class
.classpublicLcom/droider/crackme0502/MainActivity;
第1行“.class”指令指定了当前类的类名。
在本例中,类的访问权限为public,类名为“Lcom/droider/crackme0502/MainActivity;”,类名开头的L是遵循Dalvik字节码的相关约定,表示后面跟随的字符串为一个类。
2.3.Super
.superLandroid/app/Activity;
第2行的“.super”指令指定了当前类的父类。
本例中的Lcom/droider/crackme0502MainActivity;”的父类为“Landroid/app/Activity;”。
2.4.source
.source"MainActivity.java"
第3行的“.source”指令指定了当前类的源文件名。
注意 经过混淆的dex文件,反编译出来的smali代码可能没有源文件信息,因此,“.source”行的代码可能为空。
2.5.声明内部类
8]#annotations
9].annotationsystemLdalvik/annotation/MemberClasses;
10] value={
11] Lcom/disney/WMW/WMWActivity$MessageHandler;,
12] Lcom/disney/WMW/WMWActivity$FinishActivityArgs;
13] }
14].endannotation
8-14行定义的则是内部类:
它有两个成员内部类——MessageHandler和FinishActivityArgs,内部类将在后面小节中会有提及。
3.主体内容
前3行代码过后就是类的主体部分了,一个类可以由多个字段或方法组成。
3.1.字段(fields、属性、成员变量)
smali文件中字段的声明使用“.field”指令。
字段有静态字段与实例字段两种。
3.1.1.声明静态字段
静态字段的声明格式如下。
#staticfields
.field<访问权限>static[修饰关键字]<字段名>:
<字段类型>
baksmali在生成smali文件时,会在静态字段声明的起始处添加“staticfields”注释,smali文件中的注释与Dalvik语法一样,也是以井号“#”开头。
“.field”指令后面跟着的是访问权限,可以是public、private、protected之一。
修饰关键字描述了字段的其它属性,如synthetic。
指令的最后是字段名与字段类型,使用冒号“:
”分隔,语法上与Dalvik也是一样的。
3.1.2.声明实例字段
实例字段的声明与静态字段类似,只是少了static关键字,它的格式如下。
#instancefields
.field<访问权限>[修饰关键字]<字段名>:
<字段类型>
比如以下的实例字段声明。
#instancefields
.fieldprivatebtnAnno:
Landroid/widget/Button;
第1行的“instancefields”是baksmali生成的注释,第2行表示一个私有字段btnAnno,它的类型为“Landroid/widget/Button;”。
3.1.3.使用字段
一般来说,获取的指令有:
iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等,操作的指令有:
iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。
没有“-object”后缀的表示操作的成员变量对象是基本数据类型,带“-object”表示操作的成员变量是对象类型,特别地,boolean类型则使用带“-boolean”的指令操作。
例子:
(1)、获取staticfields的指令类似是:
sget-objectv0,Lcom/disney/WMW/WMWActivity;->PREFS_INSTALLATION_ID:
Ljava/lang/String;
Java代码:
v0=com.disney.WMW.WMWActivity.PREFS_INSTALLATION_ID
复制代码
sget-object就是用来获取变量值并保存到紧接着的参数的寄存器中,在这里,把上面出现的PREFS_INSTALLATION_ID这个String成员变量获取并放到v0这个寄存器中,注意:
前面需要该变量所属的类的类型,后面需要加一个冒号和该成员变量的类型,中间是“->”表示所属关系。
(2)、获取instancefields的指令与staticfields的基本一样,只是由于不是static变量,不能仅仅指出该变量所在类的类型,还需要该变量所在类的实例。
看例子:
iget-objectv0,p0,Lcom/disney/WMW/WMWActivity;->_view:
Lcom/disney/common/WMWView;
复制代码
可以看到iget-object指令比sget-object多了一个参数,就是该变量所在类的实例,在这里就是p0即“this”。
(3)、获取array的还有aget和aget-object,指令使用和上述类似,不细述。
(4)、put指令的使用和get指令是统一的,直接看例子不解释:
[color=rgb(72,72,72)][font=Verdana,sans-serif]const/4v3,0x0
sput-objectv3,Lcom/disney/WMW/WMWActivity;->globalIapHandler:
Lcom/disney/config/GlobalPurchaseHandler;[/font][/color]
复制代码
相当于:
this.globalIapHandler=null;(null=0x0)
3.2.方法(methods)
如果一个类中含有方法,那么类中必然会有相关方法的反汇编代码,smali文件中方法的声明使用“.method”指令。
方法有直接方法与虚方法两种。
3.2.1.声明直接方法
直接方法的声明格式如下。
#directmethods
.method<访问权限>[修饰关键字]<方法原型>
<.locals>
[.parameter]
[.prologue]
[.line]
<代码体>
.endmethod
“directmethods”是baksmali添加的注释,访问权限和修饰关键字与字段的描述相同,方法原型描述了方法的名称、参数与返回值。
“.locals”指定了使用的局部变量的个数。
“.parameter”指定了方法的参数,与Dalvik语法中使用“.parameters”指定参数个数不同,每个“.parameter”指令表明使用一个参数,比如方法中有使用到3个参数,那么就会出现3条“.parameter”指令。
“.prologue”指定了代码的开始处,混淆过的代码可能去掉了该指令。
“.line”指定了该处指令在源代码中的行号,同样的,混淆过的代码可能去除了行号信息。
3.2.2.声明虚方法
虚方法的声明与直接方法相同,只是起始处的注释为“virtualmethods”。
3.2.3.声明方法例子
注意参数与参数之间没有任何分隔符,同样举几个例子就容易明白了:
1).foo()V
没错,这就是voidfoo()。
2).foo(III)Z
这个则是booleanfoo(int,int,int)。
3).foo(Z[I[ILjava/lang/String;J)Ljava/lang/String;
看出来这是Stringfoo(boolean,int[],int[],String,long)了吗?
3.2.4.调用方法
使用invoke...调用方法,使用move-result...获取函数的返回值。
smali中的函数调用smali中的函数和成员变量一样也分为两种类型,但是不同成员变量中的static和instance之分,而是direct和virtual之分。
那么directmethod和virtualmethod有什么区别呢?
直白地讲,directmethod就是private函数,其余的public和protected函数都属于virtualmethod。
所以在调用函数时,有invoke-direct,invoke-virtual,另外还有invoke-static、invoke-super以及invoke-interface等几种不同的指令。
当然其实还有invoke-XXX/range指令的,这是参数多于4个的时候调用的指令,比较少见,了解下即可。
(1)、invoke-static:
顾名思义就是调用static函数的,因为是static函数,所以比起其他调用少一个参数,例如:
invoke-static{},Lcom/disney/WMW/UnlockHelper;->unlockCrankypack()Z
复制代码
这里注意到invoke-static后面有一对大括号“{}”,其实是调用该方法的实例+参数列表,由于这个方法既不需参数也是static的,所以{}内为空,再看一个例子:
复制代码
这个是调用staticvoidSystem.loadLibrary(String)来加载NDK编译的so库用的方法,同样也是这里v0就是参数"fmodex"了。
(2)、invoke-super:
调用父类方法用的指令,在onCreate、onDestroy等方法都能看到,略。
(3)、invoke-direct:
调用private函数的,例如:
invoke-direct{p0},Lcom/disney/WMW/WMWActivity;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;
复制代码
这里GlobalPurchaseHandlergetGlobalIapHandler()就是定义在WMWActivity中的一个private函数,如果修改smali时错用invoke-virtual或invoke-static将在回编译后程序运行时引发一个常见的VerifyError(更多错误汇总可参照APK反编译之番外三:
常见错误汇总)。
(4)、invoke-virtual:
用于调用protected或public函数,同样注意修改smali时不要错用invoke-direct或invoke-static,例子:
sget-objectv0,Lcom/disney/WMW/WMWActivity;->shareHandler:
Landroid/os/Handler;
invoke-virtual{v0,v3},Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V
复制代码
这里相信大家都已经明白了,主要搞清楚v0是shareHandlerandroid/os/Handler,v3是传递给removeCallbackAndMessage方法的Ljava/lang/Object参数就可以了。
(5)、invoke-xxxxx/range:
当方法的参数多于5个时(含5个),不能直接使用以上的指令,而是在后面加上“/range”,使用方法也有所不同:
复制代码
这个是电信SDK中的付费接口,需要传递6个参数,这时候大括号内的参数需要用省略形式,且需要连续(未求证是否需要从v0开始)。
有人也许注意到,刚才看到的例子都是“调用函数”这个操作而已,貌似没有取函数返回的结果的操作?
在Java代码中调用函数和返回函数结果是一条语句完成的,而在smali里则需要分开来完成,在使用上述指令后,如果调用的函数返回非void,那么还需要用到move-result(返回基本数据类型)和move-result-object(返回对象)指令:
const/4v2,0x0
invoke-virtual{p0,v2},Lcom/disney/WMW/WMWActivity;->getPreferences(I)Landroid/content/SharedPreferences;
move-result-objectv1
复制代码
v1保存的就是调用getPreferences(int)方法返回的SharedPreferences实例。
invoke-virtual{v2},Ljava/lang/String;->length()I
move-resultv2
复制代码
v2保存的则是调用String.length()返回的整型。
3.3.实现接口(interfaces)
如果一个类实现了接口,会在smali文件中使用“.implements”指令指出。
相应的格式声明如下。
#interfaces
.implements<接口名>
“#interfaces”是baksmali添加的接口注释,“.implements”是接口关键字,后面的接口名是DexClassDef结构中interfacesOff字段指定的内容。
3.4.注释(annotations、注解)
.annotation[注解属性]<注解类名>
[注解字段= 值]
.endannotation
注解的作用范围可以是类、方法或字段。
如果注解的作用范围是类,“.annotation”指令会直接定义在smali文件中,如果是方法或字段,“.annotation”指令则会包含在方法或字段定义中。
例如下面的代码。
#instancefields
.fieldpublicsayWhat:
Ljava/lang/String;
.annotationruntimeLcom/droider/anno/MyAnnoField;
info="Hellomyfriend"
.endannotation
.endfield
实例字段sayWhat为String类型,它使用了com.droider.anno.MyAnnoField注解,注解字段info值为“Hellomyfriend”。
将其转换为Java代码为:
@com.droider.annoMyAnnoField(info="Hellomyfriend")
publicStringsayWhat;
3.4.1.MemberClasses注解
在MainActivity.smali中有如下代码:
#annotations.annotationsystemLdalvik/annotation/MemberClasses;value={Lcom/yiji/test/MainActivity$SNChecker;}.endannotations
MemberClasses是编译时自动加上的,查看MemberClasses注解类源码,如下:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)@interfaceMemberClasses{}
可以看出MemberClasses是“系统注解”,记录一个内部类列表。
3.4.2.EnclosingMethod注解
在MainActivity$1.smali中有一段代码如下:
#annotations.annotationsystemLcom/dalvik/annotation/EnclosingMethod;value=Lcom/yiji/test/MainActivity;->onCreate(Landroid/os/Bundle;)V.endannotation
EnclosingMethod注解用来说明MainActivity$1类的作用范围,其中的Method说明它作用于一个方法,而value表明它位于MainActivity的onCreate()方法中。
3.4.3.EnclosingClass注解
在MainActivity$SNChecker.smali文件中,有如下代码:
#annotations.annotationsystemLdalvik/annotation/EnclosingClass;value=Lcom/yiji/test/MainActivity;.endannotation
EnclosingClass表明MainActivity$SNChecker作用于一个类,value表明这个类是MainActivity。
3.4.4.InnerClass注解
.annotationsystemLdalvik/annotation/InnerClass;accessFlags=0x1name="SNChecker".endannotation
InnerClass表明是一个内部类,name表示内部类的名称,accessFlags访问标志,声明如下:
enum{kDexVisibilityBuild=0x00,/*annotationvisibility*/kDexVisibilityRuntime=0x01,kDexVisibilitySystem=0x02,};
3.4.5.AnnotationDefault注解
如果注解在声明时提供了默认值,那么会用到AnnotationDefault注解,示例:
#annotations.annotationsystemLdalvik/annotation/AnnotationDefault;value=.subannotationLcom/yiji/test/anno/MyAnnoClass;value="MyAnnoClass".endsubannotation.endannotation
可以看出MyAnnoClass类有一个默认值”MyAnnoClass”。
3.4.6.Signature注解
用于验证方法的签名
.methodpubilconItemClick(Landroid/widget/AdapterView;Landroid/view/View;IJ)V.locals6.parameter.parameter"v".parameter"position".parameter"id".annotationsystemLdalvik/annotation/Signature;value={"(","Landroid/widget/AdapterView","<*>;","Landroid/view/View;","IJ)V"}.endannotation....endmethod
3.4.7.Throws注解
如果方法抛出异常则生成相应的Throws注解
.methodpublicfinalget()Ljava/lang/Object;.locals1.annotationsystemLdalvik/annotation/Throws;value={Ljava/lang/InterruptedException;Ljava/util/concurrent/ExcutionException;}.endannotation....endmethod
3.4.8.其他注解
-SuppressLint注解:
去掉代码检查器的警告信息
-TargetApi注解:
去掉代码版本检查的错误信息
-SdkConstant注解:
被标记为@hide,指定sdk中可以被导出的常量
-Widget注解:
被标记为@hide,表明是UI类
3.5.内部类
Java语言允许在一个类的内部定义另一个类,这种在类中定义的类被称为内部类(InnerClass)。
内部类可分为成员内部类、静态嵌套类、方法内部类、匿名内部类。
前面我们曾经说过,baksmali在反编译dex文件的时候,会为每个类单独生成了一个smali文件,内部类作为一个独立的类,它也拥有自己独立的smali文件,只是内部类的文件名形式为“[外部类]$[内部类].smali”,例如下面的类。
classOuter{
classInner{}
}
baksmali反编译上述代码后会生成两个文件:
Outer.smali与Outer$Inner.smali。
查看5.2节生成的smali文件,发现在smali\com\droider\crackme0502目录
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- smali 基础