1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

smail基本语法示例

准备工作

工欲善其事必先利其器,所以要学好smail语法,好的工具是必须的,这里推荐两个工具,一个是Smali2Java,也就是将smail文件转换成java的工具;另一个工具是J2S2J1.3,这个工具可以将简单的java或者smail代码进行转换。

案例

因为我是一开始直接看源码的,所以我们今天也是直接从源码开始:

.super Ljava/lang/Object;
.source "Test.java"


# instance fields
.field private name:Ljava/lang/String;


# direct methods
.method public constructor <init>()V
    .registers 2

    .prologue
    .line 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    .line 2
    const-string v0, "test"

    iput-object v0, p0, LTest;->name:Ljava/lang/String;

    return-void
.end method

.method public static main([Ljava/lang/String;)V
    .registers 4

    .prologue
    const/4 v2, 0x1

    .line 14
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v1, "Hello World!"

    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 15
    new-instance v0, LTest;

    invoke-direct {v0}, LTest;-><init>()V

    invoke-virtual {v0, v2, v2}, LTest;->test(IZ)Ljava/lang/String;

    move-result-object v0

    .line 16
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 17
    return-void
.end method


# virtual methods
.method public test(IZ)Ljava/lang/String;
    .registers 4

    .prologue
    .line 21
    const-string v0, "test"

    return-object v0
.end method

上面这些代码其实很简单,就是创建一个Test类,定义了一个私有成员属性name,并进行了赋值,然后定义了一个main方法,main方法中进行了Hello World!打印输出操作,对应的java代码:

import java.io.PrintStream;

public class Test{

    public Test()    {
        name = "test";
        id = 1;
    }

    public static void main(String args[]){
        System.out.println("Hello World!");
String result = new Test().test(1, true);
System.out.println(result);
    }

    public String test(int i, boolean flag) {
return "test";
    }

    private int id;
    private String name;
}

代码拆解

下面我们来逐步讲解下smail的语法,方便大家建立它与java代码语法直接的关系。

smail文件,我的理解是它其实就类似于java编译之后的.class文件,也是一种字节码文件。

基本信息

我们先看前三行:

.super Ljava/lang/Object;
.source "Test.java"


# instance fields
.field private name:Ljava/lang/String;

目前没有找到官方的相关文档,只能结合网上的资料和自己的推测进行分析,如果有不合理的地方,希望大家不吝指教。

第一行.super表示当前类的父类,表示继承关系的

第二行.source表示当前字节码文件对应的源码文件名

第三行.field表示定义一个属性(字段),具体语法是

.field 访问权限 字段名:字段类型;

字段类型的取值范围如下:

Dalvik字节码类型 java基本数据类型
V void
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L java类类型
[ 数组类型

我们这里的Ljava/lang/String就表示String类型的数据,如果是int类型,那语法应该是这样的:

.field private id:I

这里的对照关系,和class字节码绝大部分都一样

构造方法

紧接着,下面是构造方法对应的smail代码

# direct methods
.method public constructor <init>()V
    .registers 2

    .prologue
    .line 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    .line 2
    const-string v0, "test"

    iput-object v0, p0, LTest;->name:Ljava/lang/String;

    return-void
.end method
.method

其中.method表示方法的开始,具体语法是:

.method 访问权限 方法名()返回结果

这里的返回结果和字段的类型取值一样,可以参考上面的表格

.end method表示方法的结束,与.method相对应,两者之间的内容为方法体

.registers

.registers 2用于声明当前方法的寄存器个数,寄存器是用来存放方法的入参和方法的局部变量的,具体语法为:

.registers N

N代表需要的寄存器的总个数

同时,还有一个关键字.locals,它用于声明非参数的寄存器个数(也就是举报变量的个数,包含在registers声明的个数当中),也叫做本地寄存器,语法是一样的。

.prologue

.prologue表示方法代码的开始处,所以在方法中增加代码,只能在.prologue下面的区域进行

.line

.line 1用于标记java代码中的行数,没有实际含义

.invoke-direct

invoke-direct {p0}, Ljava/lang/Object;-><init>()V表示调用privateinit方法,p0表示java中的this,所以这个方法的含义是调用Objectinit方法,并把返回值赋给p0,这里需要补充一个小知识点:

在smali里的所有操作都必须经过寄存器来进行,本地寄存器用v开头数字结尾的符号来表示,如v0v1v2、...

参数寄存器则使用p开头数字结尾的符号来表示,如p0p1p2、...特别注意的是,p0不一定是函数中的第一个参数,在非static函数中, p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…,而在static函数中p0才对应第一个参数(因为Javastatic方法中没有this方法)。

本地寄存器没有限制,理论上是可以任意使用的。

这里需要注意的是,再使用vxpx的时候一定不要超过定义的限制(也就是.registers.locals),虽然超过代码编译时不会报错,但是在运行时会报错,而且在代码反编译的时候也会报错:

虽然这种报错不一定是参数个数超了,但如果之前反编译正常,修改之后不正常,这块可以作为一个检查点

const-string

const-string v0, "test"表示定义一个String常量,并把值赋给v0,也就是第一个本地寄存器。

下面再补充一些和const-string相关的内容

const
  • 定义常量

const/44代表4个字节,最大只允许存放4位数值(4个二进制位),取值范围为 -8 and 7

# 定义常量2,并将值赋给v2寄存器,
const/4 v2, 0x02

const/16 同上,最大值允许存放16位数值 第一位默认为符号位,所以计算后15位的数值,比如short类型数据 取值范围为-32768~32767

# 定义定义一个容器,将数字123123赋给v0
const/16 v0 , 0x123123

const 最大只允许存放32位数据,比如int类型数据, 取值范围-2147483647~2147483647

# 定义一个容器 将数字10赋值给v0
const v0 , 0x10

const/high16 最大只允许存放高16位数值

#定义一个容器  比如0xFFFF0000末四位补0 存入高四位0XFFFF
const/high16 v0,0xFFFF0000

const-wide 占用两个寄存器vxvx+1,共64位,数值必须以L结尾,否则编译不通过

const-wide v0,30 #占用v0和v1

const-wide/16 定义两个相连容器,最大只允许存放16位数据
const-wide/32 定义两个相连容器,最大只允许存放32位数据
const-wide 定义两个相连容器,最大只允许存放64位数据
const-wide/high16 定义两个相连容器,只允许存放高16位数据

iput-object

iput-object v0, p0, LTest;->name:Ljava/lang/String;表示把v0的值(也就是字符串常量test)赋给p0(也就是this)的name字段,Ltest标记的是p0的类型,:Ljava/lang/String是标记name的类型。

具体语法是:

iput-object 要设置的值, 要设置值的对象, 对象类型;->字段名:字段类型;

这里的iput表示设置非静态成员变量,设置静态变量要用sput,语法如下

sput-object 要设置的值, 类型;->字段名:字段类型;

这里的-object表示对非基本类型赋值,如果是基本类型可以通过iputsputiput-booleansput-boolean进行设置,语法是一样的。

iget-obejct

iput-object对应,iget-object是为了获取数据,具体语法是:

iget-object 接收值的寄存器, 接受值来源对象(谁的字段), 对象类型;->字段名:字段类型;

例如:

iget-object v0, p0, Lcom/syske/android/Activity;->_view:Lcom/syske/common/View;

静态变量的字段也是一样的:

sget-object 接收值的寄存器, 静态对象类型;->字段名:字段类型;

基本类型的字段也是类似的,需要通过igetsgetiget-boolean、sget-boolean来获取

return-void

return-void表示方法没有返回值,也就是void,同时返回值还可以是:

  • return vx:返回 vx 寄存器中的值。
  • return-wide vx:返回在 vx,vx+1 寄存器的 doubl e/long 值。
  • return-object vx:返回在 vx 寄存器的对象引用。
smali方法返回关键字 java
return byte
return short
return int
return-wide long
return float
return-wide double
return char
return boolean
return-void void
return-object 数组
return-object object

静态方法

自此,构造方法相关方法我们算是基本讲解完了,下面我们分析下静态main方法的源码,前面已经分享的内容这里直接忽略了。

.method public static main([Ljava/lang/String;)V
    .registers 4

    .prologue
    const/4 v2, 0x1

    .line 14
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v1, "Hello World!"

    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 15
    new-instance v0, LTest;

    invoke-direct {v0}, LTest;-><init>()V

    invoke-virtual {v0, v2, v2}, LTest;->test(IZ)Ljava/lang/String;

    move-result-object v0

    .line 16
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 17
    return-void
.end method
invoke-virtual

invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V表示
调用protectedpublic方法(非私有实例方法),这里的v0就表示java/lang/Systemv1表示方法的入参,具体语法如下:

invoke-virtual {对象实例, 方法入参}, 实例类型;->方法名(入参类别;)返回值

如果参数是多参数则直接写多个参数即可,如上面的源码:

invoke-virtual {v0, v2, v2}, LTest;->test(IZ)Ljava/lang/String;

另外与invoke-virtual类似的语句还有几个在,这里做一个简单的总结:

方法 说明
invoke-virtual 用于非私有实例方法的调用
invoke-direct 用于构造方法以及私有方法的调用
invoke-static 调用静态方法
invoke-super 调用父类的方法
invoke-interface 调用接口方法
new-instance

new-instance v0, LTest;表示创建LTest的实例对象,并将对象引用赋值给v0寄存器
紧接着,调用LTets的实例化方法:

invoke-direct {v0}, LTest;-><init>()V表示调用实例化方法,并把返回值赋给v0

move-result-object

move-result-object v0表示将上一次(紧挨着的)方法执行结果指向v0,也就是接收方法返回值。

扩展知识

好了,至此我们已经把今天示例代码基本分析完了,同时也对smail的基本语法有了一些初步的认识,基于以上这些知识点,我们基本上已经可以阅读smail源码了,但是为了更全面地学习smail的语法,我觉得还是有必要补充一些额外的知识点,比如条件语句的语法:

条件语句

语句 说明
if-eq vA, vB, :cond_** 如果vA等于vB则跳转到:cond_** #equal
if-ne vA, vB, :cond_** 如果vA不等于vB则跳转到:cond_** # not equal
if-lt vA, vB, :cond_** 如果vA小于vB则跳转到:cond_** #less than
if-ge vA, vB, :cond_** 如果vA大于等于vB则跳转到:cond_** # greater equal
if-gt vA, vB, :cond_** 如果vA大于vB则跳转到:cond_** # greater than
if-le vA, vB, :cond_** 如果vA小于等于vB则跳转到:cond_** # less equal
if-eqz vA, :cond_** 如果vA等于0则跳转到:cond_** #zero
if-nez vA, :cond_** 如果vA不等于0则跳转到:cond_**
if-ltz vA, :cond_** 如果vA小于0则跳转到:cond_**
if-gez vA, :cond_** 如果vA大于等于0则跳转到:cond_**
if-gtz vA, :cond_** 如果vA大于0则跳转到:cond_**
if-lez vA, :cond_** 如果vA小于等于0则跳转到:cond_**

其中,代码中:cond_**语句对应if条件中的:cond_**,表示该条件语句结束:

语法关键词

下面是一些常用的语法关键词

关键词 说明
.class 定义java类名
.super 定义父类名
.source 定义Java源文件名
.filed 定义字段
.method 定义方法开始
.end method 定义方法结束
.annotation 定义注解开始
.end annotation 定义注解结束
.implements 定义接口指令
.local 指定了方法内局部变量的个数
.registers 指定方法内使用寄存器的总数
.prologue 表示方法中代码的开始处
.line 表示java源文件中指定行
.paramter 指定了方法的参数
.param 和.paramter含义一致,但是表达格式不同

总结

至此,我们的smail语法学习之路暂时告一段落,但是还是要多实践,多探索,毕竟学语法就是为了更好的实践,还是建议各位小伙伴找几个安卓项目亲自动手实践下,好了,今天的内容就到这里吧,感谢各位小伙伴的支持!!!

在梳理这些知识点的时候,我发现有位大佬的笔记写的很完整,感兴趣的小伙伴可以去看下:

Android逆向开发之smali语言的学习

posted @ 2022-12-07 21:04  云中志  阅读(1999)  评论(0编辑  收藏  举报