深入学习smali语法
前言
在对某个 apk 文件进行代码注入的时候,我们面对的往往是反编译后的 smali 代码,而不是直接的 Java 源码文件,因而了解 smali 语法基础还是很有必要的。在这里先介绍下 Dalvik 虚拟机:Dalvik 是 Google 专门为 Android 平台设计的虚拟机。虽然 Android 程序可以使用 Java 语言来进行开发,但 Dalvik VM 和 Java VM 是两款不同的虚拟机。Dalvik VM 基于寄存器,而 Java VM 基于栈 。Dalvik VM 有专门的文件执行格式 dex (Dalvik Executable),而 Java VM 则执行的是 Java 字节码。DVM 比 JVM 速度更快,占用的空间更少。
smali文件结构
下面的 smali 代码取自某个测试 demo(通过 apktool 反编译 .apk 文件获取,这里先对 smali 语法格式进行介绍),目的是先对 smali 的文件内容结构有个大概的了解,有利于后面对语法细节讲解的时候有个整体把握。
.class public abstract Lcom/happy/learnsmali/BaseActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "BaseActivity.kt"# interfaces
.implements Lcom/happy/learnsmali/action/ActivityAction;
.implements Lcom/happy/learnsmali/action/ClickAction;
.implements Lcom/happy/learnsmali/action/HandlerAction;
.implements Lcom/happy/learnsmali/action/BundleAction;
.implements Lcom/happy/learnsmali/action/KeyboardAction;# annotations
.annotation system Ldalvik/annotation/MemberClasses;value = {Lcom/happy/learnsmali/BaseActivity$Companion;,Lcom/happy/learnsmali/BaseActivity$OnActivityCallback;}
.end annotation.annotation system Ldalvik/annotation/SourceDebugExtension;value = "SMAP\nBaseActivity.kt\nKotlin\n*S Kotlin\n*F\n+ 1 BaseActivity.kt\ncom/happy/learnsmali/BaseActivity\n+ 2 fake.kt\nkotlin/jvm/internal/FakeKt\n*L\n1#1,179:1\n1#2:180\n*E\n"
.end annotation# static fields
.field public static final Companion:Lcom/happy/learnsmali/BaseActivity$Companion;.field public static final RESULT_ERROR:I = -0x2# instance fields
.field private final activityCallbacks$delegate:Lkotlin/Lazy;# direct methods
.method public static synthetic $r8$lambda$mAxgPA6JBXhjuhBfNvUeqmKUmlk(Lcom/happy/learnsmali/BaseActivity;Landroid/view/View;)V.locals 0invoke-static {p0, p1}, Lcom/happy/learnsmali/BaseActivity;->initSoftKeyboard$lambda-0(Lcom/happy/learnsmali/BaseActivity;Landroid/view/View;)Vreturn-void
.end method.method static constructor <clinit>()V.locals 2new-instance v0, Lcom/happy/learnsmali/BaseActivity$Companion;const/4 v1, 0x0invoke-direct {v0, v1}, Lcom/happy/learnsmali/BaseActivity$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)Vsput-object v0, Lcom/happy/learnsmali/BaseActivity;->Companion:Lcom/happy/learnsmali/BaseActivity$Companion;return-void
.end method.method public constructor <init>()V// ...
.end method
上面的代码中,如果你刚开始接触 smali 代码,看得是一头雾里云里的话那是正常的,下面我将进行解析,读懂这些符号的含义有利于在我们反编译 apk 进行注入代码的时候达到事半功倍的效果。
smali中的继承、接口、包信息
首先我们先看看开头的几行:
.class public abstract Lcom/happy/learnsmali/BaseActivity; // .class 表示类路径 包名+类名
.super Landroidx/appcompat/app/AppCompatActivity; // .super 表示父类的路径
.source "BaseActivity.kt" // 表示源码文件名# interfaces
.implements Lcom/happy/learnsmali/action/ActivityAction;
.implements Lcom/happy/learnsmali/action/ClickAction;
.implements Lcom/happy/learnsmali/action/HandlerAction;
.implements Lcom/happy/learnsmali/action/BundleAction;
.implements Lcom/happy/learnsmali/action/KeyboardAction;# annotations
.annotation system Ldalvik/annotation/MemberClasses;value = {Lcom/happy/learnsmali/BaseActivity$Companion;,Lcom/happy/learnsmali/BaseActivity$OnActivityCallback;}
.end annotation
1-3行定义基本信息:表示有源文件 BaseActivity.kt 反编译得到的 smali 文件(第三行),文件路径位于 com/happy/learnsmali/(第二行),继承于 androidx/appcompat/app/AppCompatActivity(第三行)。
5-9行定义接口信息:表示 BaseActivity 类实现的接口类有:
- com/happy/learnsmali/action/ActivityAction
- com/happy/learnsmali/action/ClickAction
- com/happy/learnsmali/action/HandlerAction
- com/happy/learnsmali/action/BundleAction
- com/happy/learnsmali/action/KeyboardAction
11-16行定义内部类:表示 BaseActivity 类有两个内部类 – Companion 和 OnActivityCallback。
分析完 smali 开头的文件信息,我们可以据此可以构造出 java 代码:
class BaseActivity extends AppCompatActivity implements ActivityAction, ClickAction, HandlerAction, BundleAction, KeyboardAction {class Companion {// ...}class OnActivityCallback {// ...}
}
其他方法
# virtual methods //Representation is a virtual method
.method protected onCreate(Landroid/os/Bundle;)V.locals 1.param p1, "savedInstanceState" # Landroid/os/Bundle;.line 10invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V.line 11const/high16 v0, 0x7f050000invoke-virtual {p0, v0}, Lcom/justart/samlidemo/MainActivity;->setContentView(I)V.line 12return-void
.end method
- 方法以
.method
开始,以.end method
结束; - 位于第一行的最后 V 表示返回类型为 void;
- 方法参数 Landroid/os/Bundle; 表示方法 onCreate() 的参数为 Bundle 类型;
- . param 表示方法的参数名称为 savedInstanceState;
- 最后 return-void 表示返回的值类型为 void;
数据类型
- byte:B
- char:C
- double:D
- float:F
- int:I
- long:J
- short:S
- void:V
- boolean:Z
- array:[XXX
- Object:Lxxx/yyy
相信有 JNI 基础会对上面的数据类型好明白,这里解析上面的最后两项:
array:[XXX
在基础类型前加 [
表示数组类型,例如 int 数组和 byte 数组为 [I
、[B
。
Object:Lxxx/yyy
以 L
开头的类型表示为对象,如 String 对象对应表示为 Ljava/lang/String;
(对象类型需要后面跟分号),其中 java/lang 表示 java.lang 包,String 表示该包路径下的一个对象。
这里可能会有童鞋有疑惑,如果类是使用 Ljava/lang/String;
来表示,那么内部类又应该在 smali 中如何定义呢?可能使用过 Java 反射的童鞋脑海里面闪过 $
符号。是的,在 smali 语法中同样是使用 Ljava/lang/String$xxx;
来表示 xxx 是 String 类的内部类。
寄存器
Dalvik VM 与 JVM 最大的区别之一就是 Dalvik VM 是基于寄存器的。基于寄存器是什么意思呢?个人理解的是有点类似于汇编语言,通过寄存器来存储数据、传递数据。在 smali 中本地寄存器用 v 开头的字母 + 数字来表示,如 v0、v1、v2 、…,而参数寄存器则使用 p 开头 + 数字来表示,如 p1、p2、p3 、…。特别注意的是,p0 参数寄存器不一定是表示第一个参数,在非 static 函数中,p0 表示 this
,p1 则表示第一个参数,p2 表示函数中的第二个参数。而在 static 函数中 p0 则才对应第一个参数(因为 Java 的 static 的方法没有对象的概念)。本地寄存器没有限制,理论上是可以任意使用的。
成员变量
下面继续介绍有关成员变量的内容:
# static field
.field private static final PREFS_INSTALLATION_ID:Ljava/lang/String; = "installationId"
//...# instance field
.field private _activityPackageName:Ljava/lang/String;
上面定义的 static field 和 instance field 均为成员变量,格式是:
.field pubilc/private [static] [final] varName:<类型>
static field 和 instance field 虽然均为成员变量,但它们还是存在区别的。当然最明显的区别就是是否与对象相关,static field 是类层面的概念,而 instance field 是对象层面的概念。
出现成员变量,那就意味着有变量的赋值与取值。在 smali 语法中,取值指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object 等,而赋值指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object 等。
iget / iput 分别表示 instance field 成员变量的取值和赋值;
sget / sput 分别表示 static field 成员变量的取值和赋值;
是否为 instance field 还是 static field 成员的取值和赋值指令,根据指令前缀判断即可。带
-object
后缀表示操作的是成员变量是对象类型,而不带该后缀则表示操作的是基本数据类型。特别地,boolean 基本数据类型使用带-boolean
后缀。
下面有个例子:
const/4 v0, 0x0
iput-boolean v0, p0, Lcom/disney/xx/XxActivity;->isRunning:Z
在上面的例子中,使用了 v0 本地寄存器,并且把 0x0 传递到 v0 本地寄存器,然后第二句使用 iput-boolean
指令把 v0 寄存器中的值传递到 com.disney.xx.XxActivity
的成员变量 isRunning
。即相当于:this.isRunning = false;
(上面提到,在非 static 函数中 p0 表示为 this
,在这里则表示为 com.disney.xx.XxActivity
的对象实例)。
static field 成员变量
sget-object v0, Lcom/disney/xx/XxActivity;->PREFS_INSTALLATION_ID:Ljava/lang/String;
操作指令 sget-object
是用来获取静态成员变量并保存在紧接的本地参数列表中。在这里,把位于 com.disney.xx.XxActivity
类中的静态成员 PREFS_INSTALLATION_ID
的值传递给本地寄存器 v0
。
instance field 成员变量
iget-object v0, p0, Lcom/disney/xx/XxActivity;->_view:Lcom/disney/common/WMWView;
操作指令 iget-object
也是用来获取类成员变量并保存在紧接的本地参数列表中。这里把 com.disney.xx.XxActivity
类中的对象成员 _view
赋值给本地寄存器 v0
中。
通过观察上面的 static field 静态成员变量 和 instance field 类成员变量,可以总结出以下的格式:
** <本地寄存器>, [<参数寄存器>], <变量所属的类变量> ->varName:<变量类型> **
put 指令和上面提到的 get 指令格式是类似的,这里可以直接通过看下面的例子:
const/4 v3, 0x0
sput-object v3, p0, Lcom/disney/xx/XxActivity;->globalIapHandler:Lcom/disney/config/GlobalPurchaseHandler;
Java 代码表示: this.globalIapHandler = null; (null = 0x0)
.local v0, wait:Landroid/os/Message;
const/4 v1, 0x2
iput v1, v0, Landroid/os/Message;->what:I
Java 代码表示: wait.what = 0x2;(wait 是 Message 的实例)
函数调用
函数定义的格式:
function (type1type2type3…)RetValue
需要注意的是函数的参数类型需要定义为 smali 语法中的类型,同时参数之间不可以有其他的分隔符,例子如下:
helloSmali ()V
表示 void helloSmali()
helloSmali ([BI)Z
表示 boolean helloSmali(byte[], int)
helloSmali (ZLjava/lang/String;[I[I)V
表示 void helloSmali(boolean, String, int[], int[])
在 smali 中函数和成员变量一样也分为两种类型,但不同于成员变量中的 static field 静态成员变量 和 instance field 类成员变量,函数中的是 direct method 和 virtual method。那么函数的 direct method 和 virtual method 有什么区别呢?简单来说,direct method 就是 private 函数,而 virtual method 则是 public 和 protect 函数。
所以在调用函数的时候,有 invoke-direct
、invoke-virtual
,另外还有 invoke-static
、invoke-super
以及 invoke-interface
等几种不同的指令。同时还存在着 invoke-XXX/range
指令,这是参数传参个数大于 4 个的时候调用的指令。
invoke-static
invoke-static {}, Lcom/disney/xx/UnlockHelper;->unlockCrankypack()Z
invoke-static 表示调用的是类静态函数。Java 代码表示为:UnlockHelper.unlockCrankypack()
,这里注意到 invoke-static 后紧接着 {}
,表示的是调用该方法的实例 + 参数列表,由于这个方法既不需要参数,也是类静态方法,所以 {}
内为空,再看一个例子:
const-string v0, "fmodex"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
这里调用的是 static void System.loadLibrary(String)
来加载 so 库,而 v0 则表示传参 fmodex
。
invoke-super
表示调用父类方法用的指令,在重载的方法都可以看到。
invoke-direct
表示调用 private 函数的方法,如:
invoke-direct {p0}, Lcom/disney/xx/XxActivity;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;
这里的 GlobalPurchaseHandler getGlobalIapHandler() 表示 getGlobalIapHandler() 是定义在 XxActivity 类中,权限为 private 的方法。
invoke-virtual
表示调用的是 protected 或 public 函数。
sget-object v0, Lcom/disney/xx/XxActivity;->shareHandler:Landroid/os/Handler;
invoke-virtual {v0, v3}, Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V
这里的 v0 可以表示为 shareHandler:Landroid/os/Handler,v3 则表示为 removeCallbacksAndMessages 方法的 Ljava/lang/Object; 类型的传参。
invoke-xxxxx/range
表示当方法参数 >= 5 时,需要在后面加上 /range
。
可能有童鞋会注意到,上面的例子都是在 调用函数
这个操作,貌似没有取函数返回值的操作?在 smali 代码中,如果调用的函数返回非 void,那么还需要用到 move-result
(返回基本数据类型) 和 move-result-object
(返回对象):
const/4 v2, 0x0
invoke-virtual {p0, v2}, Lcom/disney/xx/XxActivity;->getPreferences(I)Landroid/content/SharedPreferences;
move-result-object v1
v1 表示调用 this.getPreferences(0) 方法返回的 SharedPreferences 类型的对象。
invoke-virtual {v2}, Ljava/lang/String;->length()I
move-result v2
v2 表示 String.length() 返回的 int 基本类型。
举例分析
上面初步对函数变量、方法定义、调用的进行解析,下面通过举例进一步对 smali 语法进行分析:
.method protected onDestroy()V.locals 0.line 79invoke-super {p0}, Landroidx/appcompat/app/AppCompatActivity;->onDestroy()V.line 80invoke-virtual {p0}, Lcom/happy/learnsmali/BaseActivity;->removeCallbacks()V.line 81return-void
.end method
这个是我们熟悉的 onDestroy() 函数。首先我们看到函数内第一句:.locals 0
,表示在这个函数中用到的本地寄存器的个数,这里因为调用的方法没有使用到本地本地寄存器,因而本地寄存器的个数为 0。如果我在该方法中添加:this.isExited = true,那么上述方法应该修改为:
.method protected onDestroy()V.locals 1.line 79invoke-super {p0}, Landroidx/appcompat/app/AppCompatActivity;->onDestroy()V.line 80invoke-virtual {p0}, Lcom/happy/learnsmali/BaseActivity;->removeCallbacks()V.line 81const/4 v0, 0x1iput-boolean v0, p0, Lcom/happy/learnsmali/BaseActivity;->exited:Z.line 82return-void
.end method
因为修改后的 onDestroy() 函数使用到了一个本地寄存器 v0,所以把 .locals 0
修改为 .locals 1
。另外可能你也会注意到 .line 这个标识符,它表示 smali 这一行代码在 Java 中对应代码中所在的位置行号。平常当我们在 Android Studio 上调试程序发生崩溃的时候, logcat 中提示发生崩溃所在的代码行号也是该值。当然,该标识符不是必须的,但为了方便调试还是建议保留吧。
资料分享
文章的最后,笔者把在学习 smali 语法过程中编写 & 整理的资料分享给有需要的小伙伴:
包含 smali 的一些更加基础的细节运算符(可以当作手册查询)、如何对一个 APP 进行逆向步骤等。
获取方式: 微信搜索、并关注公众号 Android安全工程,然后回复 smali 关键字获取。
深入学习smali语法相关推荐
- Android逆向入门7——Smali语法学习(1)
这一节我们一起探讨smali语法和smali在Android逆向中的应用,它是Android逆向世界中不可或缺的一部分. 简单的来说,Dex反编译的结果就是Smali,Smali和dex之间的关系,我 ...
- smali语法中文版
这是学习Smali重中之中,不过现在有些反编译的软件已经存在相应的插件,可以直接看到这些操作码名称的中文解释(如:Android killer),但是对其进行学习还是非常有必要的.以下是 ...
- 《0基础学安卓逆向》第2集:初始apk文件和smali语法
1.APK文件 apk=android Application PacKage=APKapk文件是什么:是安卓app的安装文件本质:(apk文件其实就是个)zip压缩包 意味着可以用解压缩工具把apk ...
- notepad++ smali语法高亮模板分享
某论坛也有,但是太难看了, 前面介绍了一些工具可以反编译dex文件为smali文件,在Android程序逆向分析中,阅读smali代码已然是十分重要的,但各种代码编辑器都无法较好的支持smali文件的 ...
- Smali语法简单介绍
Smali语言其实就是Davlik的寄存器语言: Smali语言就是android的应用程序.apk通过apktool反编译出来的都有一个smali文件夹,里面都是以.smali结尾的文件,文件的展示 ...
- php学语法,PHP入门学习——PHP语法
PHP入门学习--PHP语法 一.PHP简介 (1)PHP:中文名,超文本预处理器,是一种通用开源脚本语言: (2)Dreamweaver介绍 (3)四种定界符标准风格:: 短标记风格: ?>: ...
- Android:Smali语法中文介绍
原文地址:http://jishu521.com/post/annokie/7978799.html dalvik字节码有两种类型,原始类型和引用类型.对象和数组是引用类型,其它都是原始类型. Vvo ...
- rust 入门笔记: rustlings(推荐一些学习rust语法的一些非常好的小练习)
rustlings 推荐一个学习rust非常好的repo: Small exercises to get you used to reading and writing Rust code! - 学习 ...
- 【学习python语法】
学习python语法 功能快捷键 导入python包 输入与打印 global的使用 pthon字符串的部分操作 python中的数学模型 学习python中的列表模型 学习python中的元组模型 ...
- Smali 语法解析——Hello World,android原生开发技术
.class public LHello; .super Ljava/lang/Object; .source "Hello.java" static fields .field ...
最新文章
- C++动态二维数组演示的代码
- 干货|卷积有多少种?一文读懂深度学习中的各种卷积
- 杨辉再发声明:没有及时交流工作进展,深表歉意
- 如何使用 Python 创建一名可操控的角色玩家
- MySQL:SELECT COUNT 小结
- 关于程序猿鄙视链,哽咽
- iOS轻量分组日志工具 Log4OC
- 深度学习:dropout和BN的实现
- Jenkins加上linux slave出现[SSH] Connection closed.问题
- 算法高级(18)-Redis Cluster选举机制
- Worktile 移动团队如何使用 C++ 完成 Worktile Pro 跨平台应用开发2
- 关于javaweb中sql语句中使用变量的情况
- 无人驾驶技术的发展趋势
- 智头条:3月智能圈投融资大事记:极米、涂鸦上市,大华获中国移动56亿投资,凯迪仕获近1亿美元融资,小米投100亿美金造车
- 北大AI公开课第十课--人工智能在生命科学中的应用by碳云智能李英睿
- gitlab配置发送邮件
- Hashtable的深拷贝
- 9.27 英语听力练习
- 你既言而无信,我即出尔反尔
- 瑞神要考研(山东科技大学第四届ACM校赛)
热门文章
- winpe安装服务器系统教程,【实测】U盘启动WinPE安装服务器Windows_Server_2008r2.doc
- android robotium测试,Android robotium自动化测试
- 不能创建对象qmdispatch_win7系统打开某些软件提示“Activex部件不能创建对象”的解决方法...
- 讯飞语音离线版本集成
- 计算机i网络管理员证书四级,软考网络管理员试题练习(4)
- J2EE和J2se的区别
- epsonl360打印机连接电脑_epsonl360打印机脱机如何解决
- 晓庄师范的3 4 计算机应用,2021全国应用心理学专业大学排名(5篇)
- Wallpaper透视效果的C++实现(含源文件)
- 时钟芯片RX8025T的电源设计