android逆向分析之smali语法
一 、smali数据类型
1.Dalvik字节码
Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个连续的寄存器表示;
Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)
原始类型:
# direct methods 定义静态方法的标记
# virtual methods 定义非静态方法的标记
三、控制条件
"if-eq vA, vB, :cond_**" 如果vA等于vB则跳转到:cond_**
"if-ne vA, vB, :cond_**" 如果vA不等于vB则跳转到:cond_**
"if-lt vA, vB, :cond_**" 如果vA小于vB则跳转到:cond_**
"if-ge vA, vB, :cond_**" 如果vA大于等于vB则跳转到:cond_**
"if-gt vA, vB, :cond_**" 如果vA大于vB则跳转到:cond_**
"if-le vA, vB, :cond_**" 如果vA小于等于vB则跳转到:cond_**
"if-eqz vA, :cond_**" 如果vA等于0则跳转到:cond_**
"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_**
z 既可以表示zero(0) 也可以是null、或者false;具体看逻辑。
四、switch分支语句
.method private packedSwitch(I)Ljava/lang/String;
.locals 1
.parameter "i"
.prologue
.line 21
const/4 v0, 0x0
.line 22
.local v0, str:Ljava/lang/String; #v0为字符串,0表示null
packed-switch p1, :pswitch_data_0 #packed-switch分支,pswitch_data_0指定case区域
.line 36
const-string v0, "she is a person" #default分支
.line 39
:goto_0 #所有case的出口
return-object v0 #返回字符串v0
.line 24
:pswitch_0 #case 0
const-string v0, "she is a baby"
.line 25
goto :goto_0 #跳转到goto_0标号处
.line 27
:pswitch_1 #case 1
const-string v0, "she is a girl"
.line 28
goto :goto_0 #跳转到goto_0标号处
.line 30
:pswitch_2 #case 2
const-string v0, "she is a woman"
.line 31
goto :goto_0 #跳转到goto_0标号处
.line 33
:pswitch_3 #case 3
const-string v0, "she is an obasan"
.line 34
goto :goto_0 #跳转到goto_0标号处
.line 22
nop
:pswitch_data_0
.packed-switch 0x0 #case 区域,从0开始,依次递增
:pswitch_0 #case 0
:pswitch_1 #case 1
:pswitch_2 #case 2
:pswitch_3 #case 3
.end packed-switch
.end method
packed-switch 指令。p1为传递进来的 int 类型的数值,pswitch_data_0 为case 区域,在 case 区域中,第一条指令“.packed-switch”指定了比较的初始值为0 ,pswitch_0~ pswitch_3分别是比较结果为“case 0 ”到“case 3 ”时要跳转到的地址。可以发现,标号的命名采用 pswitch_ 开关,后面的数值为 case 分支需要判断的值,并且它的值依次递增。再来看看这些标号处的代码,每个标号处都使用v0 寄存器初始化一个字符串,然后跳转到了goto_0 标号处,可见goto_0 是所有的 case 分支的出口。另外,“.packed-switch”区域指定的case 分支共有4 条,对于没有被判断的 default 分支,会在代码的 packed-switch指令下面给出。
至此,有规律递增的 switch 分支就算是搞明白了。最后,将这段 smali 代码整理为Java代码如下。
private String packedSwitch(int i) {
String str = null;
switch (i) {
case 0:
str = "she is a baby";
break;
case 1:
str = "she is a girl";
break;
case 2:
str = "she is a woman";
break;
case 3:
str = "she is an obasan";
break;
default:
str = "she is a person";
break;
}
return str;
}
现在我们来看看无规律的case 分支语句代码会有什么不同
.method private sparseSwitch(I)Ljava/lang/String;
.locals 1
.parameter "age"
.prologue
.line 43
const/4 v0, 0x0
.line 44
.local v0, str:Ljava/lang/String;
sparse-switch p1, :sswitch_data_0 # sparse-switch分支,sswitch_data_0指定case区域
.line 58
const-string v0, "he is a person" #case default
.line 61
:goto_0 #case 出口
return-object v0 #返回字符串
.line 46
:sswitch_0 #case 5
const-string v0, "he is a baby"
.line 47
goto :goto_0 #跳转到goto_0标号处
.line 49
:sswitch_1 #case 15
const-string v0, "he is a student"
.line 50
goto :goto_0 #跳转到goto_0标号处
.line 52
:sswitch_2 #case 35
const-string v0, "he is a father"
.line 53
goto :goto_0 #跳转到goto_0标号处
.line 55
:sswitch_3 #case 65
const-string v0, "he is a grandpa"
.line 56
goto :goto_0 #跳转到goto_0标号处
.line 44
nop
:sswitch_data_0
.sparse-switch #case 区域
0x5 -> :sswitch_0 #case 5(0x5)
0xf -> :sswitch_1 #case 15(0xf)
0x23 -> :sswitch_2 #case 35(0x23)
0x41 -> :sswitch_3 #case 65(0x41)
.end sparse-switch
.end method
按照分析packed-switch 的方法,我们直接查看 sswitch_data_0 标号处的内容。可以看到“.sparse-switch ”指令没有给出初始case 的值,所有的case 值都使用“case 值 -> case 标号”的形式给出。此处共有4 个case ,它们的内容都是构造一个字符串,然后跳转到goto_0 标号处,代码架构上与packed-switch 方式的 switch 分支一样。
最后,将这段smali 代码整理为Java 代码如下。
private String sparseSwitch(int age) {
String str = null;
switch (age) {
case 5:
str = "he is a baby";
break;
case 15:
str = "he is a student";
break;
case 35:
str = "he is a father";
break;
case 65:
str = "he is a grandpa";
break;
default:
str = "he is a person";
break;
}
return str;
}
五、try/catch 语句
.method private tryCatch(ILjava/lang/String;)V
.locals 10
.parameter "drumsticks"
.parameter "peple"
.prologue
const/4 v9, 0x0
.line 19
try_start_0 # 第1个try开始
invoke-static {p2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I #将第2个参数转换为int 型
:try_end_0 # 第1个try结束
.catch Ljava/lang/NumberFormatException; {:try_start_0 .. :try_end_0} : catch_1 # catch_1
move-result v1 #如果出现异常这里不会执行,会跳转到catch_1标号处
.line 21
.local v1, i:I #.local声明的变量作用域在.local声明与.end local 之间
:try_start_1 #第2个try 开始
div-int v2, p1, v1 # 第1个参数除以第2个参数
.line 22
.local v2, m:I
mul-int v5, v2, v1 #m * i
sub-int v3, p1, v5 #v3 = p1 - v5
.line 23
.local v3, n:I
const-string v5, "\u5171\u6709%d\u53ea\u9e21\u817f\uff0c%d
\u4e2a\u4eba\u5e73\u5206\uff0c\u6bcf\u4eba\u53ef\u5206\u5f97%d
\u53ea\uff0c\u8fd8\u5269\u4e0b%d\u53ea" # 格式化字符串
const/4 v6, 0x4
new-array v6, v6, [Ljava/lang/Object;
const/4 v7, 0x0
.line 24
invoke-static {p1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v8
aput-object v8, v6, v7
const/4 v7, 0x1
invoke-static {v1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v8
aput-object v8, v6, v7
const/4 v7, 0x2
invoke-static {v2}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v8
aput-object v8, v6, v7
const/4 v7, 0x3
invoke-static {v3}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v8 aput-object v8, v6, v7
.line 23
invoke-static {v5, v6}, Ljava/lang/String;
->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
move-result-object v4
.line 25
.local v4, str:Ljava/lang/String;
const/4 v5, 0x0
invoke-static {p0, v4, v5}, Landroid/widget/Toast;
->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
Landroid/widget/Toast;
move-result-object v5
invoke-virtual {v5}, Landroid/widget/Toast;->show()V # 使用Toast 显示格式化后的结果
:try_end_1 #第2个try 结束
.catch Ljava/lang/ArithmeticException; {:try_start_1 .. :try_end_1} : catch_0 # catch_0
.catch Ljava/lang/NumberFormatException; {:try_start_1 .. :try_end_1} : catch_1 # catch_1
.line 33
.end local v1 #i:I
.end local v2 #m:I
.end local v3 #n:I
.end local v4 #str:Ljava/lang/String;
:goto_0
return-void # 方法返回
.line 26
.restart local v1 #i:I
:catch_0
move-exception v0
.line 27
.local v0, e:Ljava/lang/ArithmeticException;
:try_start_2 #第3个try 开始
const-string v5, "\u4eba\u6570\u4e0d\u80fd\u4e3a0" #“人数不能为0”
const/4 v6, 0x0
invoke-static {p0, v5, v6}, Landroid/widget/Toast;
->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
Landroid/widget/Toast;
move-result-object v5
invoke-virtual {v5}, Landroid/widget/Toast;->show()V #使用Toast 显示异常原因
:try_end_2 #第3个try 结束
.catch Ljava/lang/NumberFormatException; {:try_start_2 .. :try_end_2} :catch_1
goto :goto_0 #返回
.line 29
.end local v0 #e:Ljava/lang/ArithmeticException;
.end local v1 #i:I
:catch_1
move-exception v0
.line 30
.local v0, e:Ljava/lang/NumberFormatException;
const-string v5, "\u65e0\u6548\u7684\u6570\u503c\u5b57\u7b26\u4e32"
#“无效的数值字符串”
invoke-static {p0, v5, v9}, Landroid/widget/Toast;
->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
Landroid/widget/Toast;
move-result-object v5
invoke-virtual {v5}, Landroid/widget/Toast;->show()V # 使用Toast 显示异 常原因
goto :goto_0 #返回
.end method
在try_end_0 标号下面使用“.catch”指令指定处理到的异常类型与catch的标号,格式如下。
.catch < 异常类型> {<try起始标号> .. <try 结束标号>} <catch标号>
查看catch_1标号处的代码发现,当转换 String 到int 时发生异常会弹出“无效的数值字符串”的提示。对于代码中的汉字,baksmali 在反编译时将其使用Unicode进行编码,因此,在阅读前需要使用相关的编码转换工具进行转换。
仔细阅读代码会发现在try_end_1标号下面使用“.catch”指令定义了 catch_0与catch_1两个catch。catch_0标号的代码开头又有一个标号为try_start_2的try 语句块,其实这个try语句块是虚构的,假如下面的代码。
private void a() {
try {
……
try {
……
} catch (XXX) {
……
}
} catch (YYY) {
……
}
}
private void tryCatch(int drumsticks, String peple) {
try {
int i = Integer.parseInt(peple);
try {
int m = drumsticks / i;
int n = drumsticks - m * i;
String str = String.format("共有%d只鸡腿,%d个人平分,每人可分得%d只,还剩下%d只",drumsticks, i, m, n);
Toast.makeText(MainActivity.this, str,Toast.LENGTH_SHORT).show();
} catch (ArithmeticException e) {
Toast.makeText(MainActivity.this, " 人数不能为0",Toast.LENGTH_SHORT).show();
}
} catch (NumberFormatException e) {
Toast.makeText(MainActivity.this, " 无效的数值字符串",Toast.LENGTH_SHORT).show();
}
}
try {
ServerSocket serverSocket= new ServerSocket(10000);
Socket socket=serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}finally{
int abc=5;
Toast.makeText(this, "sssss ", Toast.LENGTH_SHORT).show();
}
finally 语句块作用:执行一些必要代码。即不管出现异常与否,在finally中的代码都会被执行
执行时机:针对所有catch语句之后,退出方法之前将被执行(即先执行catch里面的代码,但在throw之前将转向finally)。finally中返回的结果将可以覆盖catch中返回的结果
对应的smail代码如下:
:try_start_0
new-instance v2, Ljava/net/ServerSocket; #ServerSocket v2 = null;
const/16 v3, 0x2710 # v3 = 10000;
invoke-direct {v2, v3}, Ljava/net/ServerSocket;-><init>(I)V # v2 = new ServerSocket(v3);
.line 21
.local v2, serverSocket:Ljava/net/ServerSocket;
invoke-virtual {v2}, Ljava/net/ServerSocket;->accept()Ljava/net/Socket; # v2.accept( );
:try_end_0
.catchall {:try_start_0 .. :try_end_0} :catchall_0
//上一句处理start_0对应的异常块是catchall_0 也就是finally
.catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0
//上一句处理start_0对应的异常块是catch_0,catch_0异常块先执行,之后再执行catchall_0
相对应的smali代码为:
:try_start_0
new-instance v2, Ljava/net/ServerSocket;
const/16 v3, 0x2710
invoke-direct {v2, v3}, Ljava/net/ServerSocket;-><init>(I)V
.line 21
.local v2, serverSocket:Ljava/net/ServerSocket;
invoke-virtual {v2}, Ljava/net/ServerSocket;->accept()Ljava/net/Socket;
:try_end_0 .catchall {:try_start_0 .. :try_end_0} :catchall_0
.catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0
.line 27
const/4 v0, 0x5 #正常流程 即未发生异常
.line 28
.local v0, abc:I
const-string v3, "sssss "
invoke-static {p0, v3, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v3
invoke-virtual {v3}, Landroid/widget/Toast;->show()V
.line 32
.end local v2 #serverSocket:Ljava/net/ServerSocket;
:goto_0
return-void
.line 22
.end local v0 #abc:I :catch_0 #当发生异常时执行
move-exception v1
.line 24
.local v1, e:Ljava/io/IOException; :try_start_1
invoke-virtual {v1}, Ljava/io/IOException;->printStackTrace()V
:try_end_1
.catchall {:try_start_1 .. :try_end_1} :catchall_0 #异常部分执行完毕,转而执行finally .line 27
const/4 v0, 0x5
.line 28
.restart local v0 #abc:I
const-string v3, "sssss "
invoke-static {p0, v3, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v3
invoke-virtual {v3}, Landroid/widget/Toast;->show()V
goto :goto_0
.line 25
.end local v0 #abc:I
.end local v1 #e:Ljava/io/IOException; #finally代码定义部分
:catchall_0
move-exception v3
.line 27
const/4 v0, 0x5
.line 28
.restart local v0 #abc:I
const-string v4, "sssss "
invoke-static {p0, v4, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v4
invoke-virtual {v4}, Landroid/widget/Toast;->show()V
.line 30
throw v3
六、for循环
# virtual methods
.method public onClick(Landroid/view/View;)V
.locals 9
.parameter “v” .prologue
.line 36
invoke-virtual {p1}, Landroid/view/View;->getId()I # 非静态方法参数中隐含的第一个参数p0为this指针, p1为第一个参数, 即View对象 move-result v6 # 把上次的计算结果给第七个寄存器,v6=p1.getId(), v6中为View对象的id packed-switch v6, :pswitch_data_0 # switch(v6) # —————– 程序出口开始 ——————
.line 58
:goto_0 # for循环出口
return-void # return;
# —————– 程序出口结束 —————— # —————– 获取控件内容开始 ——————
.line 39
:pswitch_0
iget-object v6, p0, Lcom/sanwho/crackdemo/ForActivity$1;->this$0:Lcom/sanwho/crackdemo/ForActivity; # v6保存this指针 const v7, 0x7f080001 # v7 = txtValue1, 该id保存在public.xml中 invoke-virtual {v6, v7}, Lcom/sanwho/crackdemo/ForActivity;->findViewById(I)Landroid/view/View; # findViewById(txtValue1) move-result-object v4 # v4为txtValue1对应的View对象 check-cast v4, Landroid/widget/EditText; # 将View对象转换成EditText, 完成后v4中是txtValue1对象, 失败会抛出ClassCastException异常 .line 40
.local v4, txtValue1:Landroid/widget/EditText;
iget-object v6, p0, Lcom/sanwho/crackdemo/ForActivity$1;->this$0:Lcom/sanwho/crackdemo/ForActivity; const v7, 0x7f080003 # v7 = txtValue2 invoke-virtual {v6, v7}, Lcom/sanwho/crackdemo/ForActivity;->findViewById(I)Landroid/view/View; move-result-object v5 # v5为txtValue2对应的View对象 check-cast v5, Landroid/widget/EditText; # 将View对象转换成EditText, 完成后v5中是txtValue2对象 .line 41
.local v5, txtValue2:Landroid/widget/EditText;
invoke-virtual {v4}, Landroid/widget/EditText;->getText()Landroid/text/Editable; # 根据.line 39处可知,v4中为txtValue1对象 move-result-object v6 # v6 = txtValue1.getText(); invoke-interface {v6}, Landroid/text/Editable;->toString()Ljava/lang/String; move-result-object v6 # v6 = txtValue1.getText().toString(); invoke-static {v6}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I move-result v1 # v1 = Integer.parseInt(v6); 也就是起始数值 .line 42
.local v1, from:I
invoke-virtual {v5}, Landroid/widget/EditText;->getText()Landroid/text/Editable; # 根据.line 40处可知,v5中为txtValue2对象 move-result-object v6 # v6 = txtValue2.getText(); invoke-interface {v6}, Landroid/text/Editable;->toString()Ljava/lang/String; move-result-object v6 # v6 = txtValue2.getText().toString(); invoke-static {v6}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I move-result v0 # v0 = Integer.parseInt(v6); 也就是结束数值 # —————– 获取控件内容结束 —————— .line 43
.local v0, end:I
if-le v1, v0, :cond_0 # if v1 <= v0, 即起始数值 <= 结束数值, 则跳到cond_0 # —————– 起始数值 > 结束数值时开始 ——————
.line 45
iget-object v6, p0, Lcom/sanwho/crackdemo/ForActivity$1;->this$0:Lcom/sanwho/crackdemo/ForActivity; const-string v7, “\u8d77\u59cb\u6570\u503c\u4e0d\u80fd\u5927\u4e8e\u7ed3\u675f\u6570\u503c!” # 起始数值不能大于结束数值 #calls: Lcom/sanwho/crackdemo/ForActivity;->MessageBox(Ljava/lang/String;)V
invoke-static {v6, v7}, Lcom/sanwho/crackdemo/ForActivity;->access$0(Lcom/sanwho/crackdemo/ForActivity;Ljava/lang/String;)V goto :goto_0 # —————– 起始数值 > 结束数值时结束 —————— # —————– 起始数值 <= 结束数值时开始 —————–
.line 49
:cond_0
const/4 v3, 0x0 # v3 = 0, 即int sum = 0; .line 50
.local v3, sum:I
move v2, v1 # v2 = v1, v2即源码中的i变量 .local v2, i:I
:goto_1 # for循环主要入口
if-le v2, v0, :cond_1 # if 当前数值 <= 结束数值, 跳到cond_1; 否则循环结束, 显示累加结果 .line 54
iget-object v6, p0, Lcom/sanwho/crackdemo/ForActivity$1;->this$0:Lcom/sanwho/crackdemo/ForActivity; # v6指向MessageBox方法 new-instance v7, Ljava/lang/StringBuilder; # v7为StringBuilder对象 const-string v8, “\u7d2f\u52a0\u7ed3\u679c\uff1a” # v8 = “累加结果:” invoke-direct {v7, v8}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V # 以v8为参数调用StringBuilder构造函数 invoke-static {v3}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; # 把int型的sum值转成字符串 move-result-object v8 # v8 = Integer.toString(v3); 此时v8中为sum的值 invoke-virtual {v7, v8}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; # 把累加结果和sum的值进行追加 move-result-object v7 # v7 为 “累加结果:” + Integer.toString(sum)的StringBuilder对象; invoke-virtual {v7}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; # 将v7转为字符串对象 move-result-object v7 # v7 = “累加结果:” + Integer.toString(sum); #calls: Lcom/sanwho/crackdemo/ForActivity;->MessageBox(Ljava/lang/String;)V
invoke-static {v6, v7}, Lcom/sanwho/crackdemo/ForActivity;->access$0(Lcom/sanwho/crackdemo/ForActivity;Ljava/lang/String;)V # 调用MessageBox显示字符串 goto :goto_0 # 跳到goto_0
# —————– 起始数值 <= 结束数值时结束 —————– .line 52
:cond_1 # 加1操作入口
add-int/2addr v3, v2 # v3 = v3 + v2, 即sum += i .line 50
add-int/lit8 v2, v2, 0x1 # v2 = v2 + 1, , 即i = i + 1 goto :goto_1 # 跳到for循环入口继续比对 .line 36
nop :pswitch_data_0
.packed-switch 0x7f080004
:pswitch_0
.end packed-switch
.end method
源码解释
Button.OnClickListener onClickListener = new Button.OnClickListener()
{
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.btnSubmit:
EditText txtValue1 = (EditText) findViewById(R.id.txtValue1);
EditText txtValue2 = (EditText) findViewById(R.id.txtValue2);
int from = Integer.parseInt(txtValue1.getText().toString());
int end = Integer.parseInt(txtValue2.getText().toString());
if (from > end){
MessageBox("起始数值不能大于结束数值!");
}
else
{
int sum = 0;
for (int i = from; i <= end; i++){ sum += i;
}
MessageBox("累加结果:" + Integer.toString(sum));
}
break;
}
}
}; private void MessageBox(String str)
{
Toast.makeText(this, str, Toast.LENGTH_LONG).show();
}
如果看不懂access$0或者this$0等请看下一章节
八、内部类
Java 语言允许在一个类的内部定义另一个类,这种在类中定义的类被称为内部类(Inner Class)。内部类可分为成员内部类、静态嵌套类、方法内部类、匿名内部类。前面我们曾经说过,baksmali 在反编译dex 文件的时候,会为每个类单独生成了一个 smali 文件,内部类作为一个独立的类,它也拥有自己独立的smali 文件,只是内部类的文件名形式为“[外部类]$[内部类].smali ”,例如下面的类。
class Outer {
class Inner{}
}
baksmali 反编译上述代码后会在同一目录生成两个文件:Outer.smali 与Outer$Inner.smali。
public class MainActivity extends Activity {
private Button btnAnno;
private Button btnCheckSN;
private EditText edtSN;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnAnno = (Button) findViewById(R.id.btn_annotation);
btnCheckSN = (Button) findViewById(R.id.btn_checksn);
edtSN = (EditText) findViewById(R.id.edt_sn);
btnAnno.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
getAnnotations();
}
}); btnCheckSN.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SNChecker checker = new SNChecker(edtSN.getText().toString());
String str = checker.isRegistered() ? "注册码正确" : "注册码错误";
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
}
});
} private void getAnnotations() {
try {
Class<?> anno = Class.forName("com.droider.anno.MyAnno");
if (anno.isAnnotationPresent(MyAnnoClass.class)) {
MyAnnoClass myAnno = anno.getAnnotation(MyAnnoClass.class);
Toast.makeText(this, myAnno.value(), Toast.LENGTH_SHORT).show();
}
Method method = anno.getMethod("outputInfo", (Class[])null);
if (method.isAnnotationPresent(MyAnnoMethod.class)) {
MyAnnoMethod myMethod = method.getAnnotation(MyAnnoMethod.class);
String str = myMethod.name() + " is " + myMethod.age() + " years old.";
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
Field field = anno.getField("sayWhat");
if (field.isAnnotationPresent(MyAnnoField.class)) {
MyAnnoField myField = field.getAnnotation(MyAnnoField.class);
Toast.makeText(this, myField.info(), Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
e.printStackTrace();
}
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
} public class SNChecker {
private String sn;
public SNChecker(String sn) {
this.sn = sn;
} public boolean isRegistered() {
boolean result = false;
char ch = '\0';
int sum = 0;
if (sn == null || (sn.length() < 8)) return result;
int len = sn.length();
if (len == 8) {
ch = sn.charAt(0);
switch (ch) {
case 'a':
case 'f':
result = true;
break;
default:
result = false;
break;
}
if (result) {
ch = sn.charAt(3);
switch (ch) {
case '1':
case '2':
case '3':
case '4':
case '5':
result = true;
break;
default:
result = false;
break;
}
}
} else if (len == 16) {
for (int i = 0; i < len; i++) {
char chPlus = sn.charAt(i);
sum += (int) chPlus;
}
result = ((sum % 6) == 0) ? true : false;
}
return result;
}
}
}
MainActivity$ SNChecker.smali 文件,这个SNChecker 就是MainActivity的一个内部类。打开这个文件,代码结构如下。
.class public Lcom/droider/crackme0502/MainActivity$SNChecker;
.super Ljava/lang/Object;
.source "MainActivity.java" # annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lcom/droider/crackme0502/MainActivity;
.end annotation .annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x1
name = "SNChecker"
.end annotation # instance fields
.field private sn:Ljava/lang/String; .field final synthetic this$0:Lcom/droider/crackme0502/MainActivity; # direct methods
.method public constructor <init>(Lcom/droider/crackme0502/MainActivity;Ljava/lang/String;)V
.locals 0
.parameter
.parameter "sn" .prologue
.line 83
iput-object p1, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->this$0:Lcom/droider/crackme0502/MainActivity; invoke-direct {p0}, Ljava/lang/Object;-><init>()V .line 84
iput-object p2, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; .line 85
return-void
.end method # virtual methods
.method public isRegistered()Z
.locals 10 .prologue
const/16 v9, 0x8 const/4 v7, 0x0 .line 88
const/4 v4, 0x0 .line 89
.local v4, result:Z
const/4 v0, 0x0 .line 90
.local v0, ch:C
const/4 v6, 0x0 .line 91
.local v6, sum:I
iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; if-eqz v8, :cond_0 iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; invoke-virtual {v8}, Ljava/lang/String;->length()I move-result v8 if-ge v8, v9, :cond_1 :cond_0
move v5, v4 .line 126
.end local v4 #result:Z
.local v5, result:I
:goto_0
return v5 .line 92
.end local v5 #result:I
.restart local v4 #result:Z
:cond_1
iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; invoke-virtual {v8}, Ljava/lang/String;->length()I move-result v3 .line 93
.local v3, len:I
if-ne v3, v9, :cond_3 .line 94
iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; invoke-virtual {v8, v7}, Ljava/lang/String;->charAt(I)C move-result v0 .line 95
sparse-switch v0, :sswitch_data_0 .line 101
const/4 v4, 0x0 .line 104
:goto_1
if-eqz v4, :cond_2 .line 105
iget-object v7, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; const/4 v8, 0x3 invoke-virtual {v7, v8}, Ljava/lang/String;->charAt(I)C move-result v0 .line 106
packed-switch v0, :pswitch_data_0 .line 115
const/4 v4, 0x0 :cond_2
:goto_2
move v5, v4 .line 126
.restart local v5 #result:I
goto :goto_0 .line 98
.end local v5 #result:I
:sswitch_0
const/4 v4, 0x1 .line 99
goto :goto_1 .line 112
:pswitch_0
const/4 v4, 0x1 .line 113
goto :goto_2 .line 119
:cond_3
const/16 v8, 0x10 if-ne v3, v8, :cond_2 .line 120
const/4 v2, 0x0 .local v2, i:I
:goto_3
if-lt v2, v3, :cond_4 .line 124
rem-int/lit8 v8, v6, 0x6 if-nez v8, :cond_5 const/4 v4, 0x1 :goto_4
goto :goto_2 .line 121
:cond_4
iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; invoke-virtual {v8, v2}, Ljava/lang/String;->charAt(I)C move-result v1 .line 122
.local v1, chPlus:C
add-int/2addr v6, v1 .line 120
add-int/lit8 v2, v2, 0x1 goto :goto_3 .end local v1 #chPlus:C
:cond_5
move v4, v7 .line 124
goto :goto_4 .line 95
:sswitch_data_0
.sparse-switch
0x61 -> :sswitch_0
0x66 -> :sswitch_0
.end sparse-switch .line 106
:pswitch_data_0
.packed-switch 0x31
:pswitch_0
:pswitch_0
:pswitch_0
:pswitch_0
:pswitch_0
.end packed-switch
.end method
发现它有两个注解定义块“Ldalvik/annotation/EnclosingClass;”与“Ldalvik/annotation/ InnerClass; ”、两个实例字段sn 与this$0 、一个直接方法 init()、一个虚方法isRegistered() 。注解定义块我们稍后进行讲解。先看它的实例字段,sn 是字符串类型,this$0 是MainActivity类型,synthetic 关键字表明它是“合成”的,那 this$0 到底是个什么东西呢?
其实this$0 是内部类自动保留的一个指向所在外部类的引用。左边的 this 表示为父类的引用,右边的数值0 表示引用的层数。我们看下面的类。
public class Outer { //this$0
public class FirstInner { //this$1
public class SecondInner { //this$2
public class ThirdInner {
}
}
}
}
每往里一层右边的数值就加一,如 ThirdInner类访问 FirstInner 类的引用为this$1 。在生成的反汇编代码中,this$X 型字段都被指定了synthetic 属性,表明它们是被编译器合成的、虚构的,代码的作者并没有声明该字段。
我们再看看MainActivity$SNChecker的构造函数,看它是如何初始化的。代码如下。
# direct methods
.method public constructor <init>(Lcom/droider/crackme0502/MainActivity;Ljava/lang/String;)V
.locals 0
.parameter #第一个参数MainActivity引用
.parameter "sn" #第二个参数字符串sn .prologue
.line 83
#将MainActivity引用赋值给this$0
iput-object p1, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->this$0:Lcom/droider/crackme0502/MainActivity; #调用默认的构造函数
invoke-direct {p0}, Ljava/lang/Object;-><init>()V .line 84
#将sn字符串的值赋给sn字段
iput-object p2, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; .line 85
return-void
.end method
对于一个非静态的方法而言,会隐含的使用p0寄存器当作类的this 引用。因此,这里的确是使用了3 个寄存器:p0表示MainActivity$SNChecker自身的引用,p1表示MainActivity的引用,p2表示sn 字符串。另外,从 MainActivity$SNChecker的构造函数可以看出,内部类的初始化共有以下 3 个步骤:首先是保存外部类的引用到本类的一个 synthetic字段中,以便内部类的其它方法使用,然后是调用内部类的父类的构造函数来初始化父类,最后是对内部类自身进行初始化。
一个方法中指定的寄存器个
在一个方法(method)中有两中方式指定有多少个可用的寄存器。指令.registers指令指定了在这个方法中有多少个可用的寄存器,指令.locals指明了在这个方法中非参(non-parameter)寄存器的数量。然而寄存器的总数也包括保存方法参数的寄存器。
参数是如何传递的?
当一个方法被调用时,该方法的参数被保存在最后N个寄存器中。如果一个方法有2个参数和5个寄存器(V0-V4),参数将被保存在最后的2个寄存器内V3和V4.
非静态方法的第一个参数,总是被方法调用的对象。
例如,你写了一个非静态方法LMyObject;->callMe(II)V。这个方法有2个int参数,但在这两个整型参数前面还有一个隐藏的参数LMyObject;所以这个方法总共有3个参数。
比如说,在方法中指定有5个寄存器(V0-V4),只用.register指令指定5个,或者使用.locals指令指定2个(2个local寄存器+3个参数寄存器)。该方法被调用的时候,调用方法的对象(即this引用)会保存在V2中,第一个参数在V3中,第二个参数在v4中。
除了不包含this隐藏参数,对于静态方法都是相同的。
寄存器名称
有两种寄存器的命名方式,对于参数寄存器有普通的V命名方式和P命名方式。在方法(method)中第一个参数寄存器,是使用P方式命名的第一个寄存器,让我们回到前面的例子中,有三个参数和5个寄存器,下面的这个表显示了对每个寄存器的普通V命名方式,后面是P方式命名的参数寄存器。
v0 | the first local register | |
v1 | the second local register | |
v2 | p0 | the first parameter register |
v3 | p1 | the second parameter register |
v4 | p2 | the third parameter register |
You can reference parameter registers by either name - it makes no difference.
你可以使用名称引用参数寄存器,他们没有区别。
引入参数寄存器的目的
P命名方式被引入去解决,在编辑smail代码时候共同的烦恼。
假设你有一个方法(mehtod),这个方法带有一些参数,并且你需要添加一些代码到这个方法中,这时发现需要一些额外的寄存器,你会想“没有什么大不了的。我只需要使用.registers指令添加寄存器数量就可以了。”
不幸的是没有想象的那么容易,请记住,方法中方法的参数被保存在最后的寄存器里。如果你增加了寄存器的数量,达到让寄存器中的参数被传入的目的。所以你不得不使用.registers指令重新分配参数寄存器的编号。
但如果在方法中P命名方式,被用来引用参数寄存器。你将很容易的在方法中去修改寄存器数量,而不用去担心现有寄存器的编号。
注意:在默认的baksmali中,参数寄存器将使用P命名方式,如果出于某种原因你要禁用P命名方式,而要强制使用V命名方式,应当使用-p/--no-parameter-registers选项。
Long/Double values
正如前面提到的,long和double类型都是64位,需要2个寄存器。当你引用参数的时候一定要记住,例如:你有一个非静态方法LMyObject;->MyMethod(IJZ)V,LMyObject方法的参数为int、long、bool。所以这个方法的所有参数需要5个寄存器。
p0 | this |
p1 | I |
p2, p3 | J |
p4 | Z |
另外当你调用方法后,你必须在寄存器列表,调用指令中指明,两个寄存器保存了double-wide宽度的参数。
关于几个调用方法指令: invoke-virtual、invoke-direct、invoke-super介绍。
涉及到Java强大的动态扩展能力,这一特性使得可以在类运行期间才能确定某些目标方法的实际引用,称为动态连接;也有一部分方法的符号引用在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。
在Java语言中,符合“编译器可知,运行期不可变”这个要求的方法主要有静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法都不可能通过继承或别的方式重写出其他的版本,因此它们都适合在类加载阶段进行解析。
- invoke-static 是类静态方法的调用,编译时,静态确定的;
- invoke-virtual 虚方法调用,调用的方法运行时确认实际调用,和实例引用的实际对象有关,动态确认的,一般是带有修饰符protected或public的方法;
- invoke-direct 没有被覆盖方法的调用,即不用动态根据实例所引用的调用,编译时,静态确认的,一般是private或<init>方法;
- invoke-super 直接调用父类的虚方法,编译时,静态确认的。
- invokeinterface 调用接口方法,调用的方法运行时确认实际调用,即会在运行时才确定一个实现此接口的对象。
参考:
http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
http://www.cnblogs.com/Fang3s/p/3782903.html
http://www.52pojie.cn/thread-233852-1-1.html
http://book.2cto.com/201212/12474.html
http://book.2cto.com/201212/12475.html
android逆向分析之smali语法的更多相关文章
- Android逆向利器和smali代码修改出错举例-入参类型
当smali修改代码出错举例1,log如下: 虚拟机层次: 1.本身做出了预测,寄存器v2是符合要求入参,暗示你这个也许是你想要的.VFY: register1 v2 type 17, wanted ...
- Android逆向分析工具表
逆向分析工具表 工具 描述 网址 androidterm Android Terminal Emulator http://code.google.com/p/androidterm/ droidbo ...
- Android逆向分析(2) APK的打包与安装背后的故事
前言 上一次我们反编译了手Q,并遇到了Apktool反编译直接crash的问题,虽然笔者很想在这次解决这个问题,但在解决途中,发现该保护依赖于很多知识,所以本次先插入一下,正所谓知其然知其所以然,授之 ...
- Android逆向分析(2) APK的打包与安装
http://blog.zhaiyifan.cn/2016/02/13/android-reverse-2/ 2/18日增加对aidl和java编译的描述. 前言 上一次我们反编译了手Q,并遇到了Ap ...
- [摘]Android逆向分析常用网站
androidterm: Android Terminal Emulator http://code.google.com/p/androidterm/ droidbox: Andro ...
- Android逆向基础知识Smali
什么是Smali: 我们用工具反编译一些APP的时候,会看到一个smali文件夹,里面其实就是每个Java类所对应的smali文件.Android虚拟机Dalvik并不是执行java虚拟机JVM编译后 ...
- Android逆向之静态分析
想必打过CTF的小伙伴多多少少都触过Android逆向,所以斗哥将给大家整一期关于Android逆向的静态分析与动态分析.本期先带来Android逆向的静态分析,包括逆向工具使用.文件说明.例题解析等 ...
- 【转】Android逆向入门流程
原文:https://www.jianshu.com/p/71fb7ccc05ff 0.写在前面 本文是笔者自学笔记,以破解某目标apk的方式进行学习,中间辅以原理性知识,方便面试需求. 参考文章的原 ...
- android逆向学习小结--CrackMe_1
断断续续的总算的把android开发和逆向的这两本书看完了,虽然没有java,和android开发的基础,但总体感觉起来还是比较能接收的,毕竟都是触类旁通的.当然要深入的话还需要对这门语言的细节特性和 ...
随机推荐
- iOS:多线程的详细介绍
多线程: 一.概念 1.什么是进程? 程序的一次性执行就是进程.进程占独立的内存空间. 2.什么是线程? 进程中的代码的执行路径. 3.进程与线程之间的关系? 每个进 ...
- vue中watch的用法总结以及报错处理Error in callback for watcher "checkList"
首先确认 watch是一个对象,一定要当成对象来用. 对象就有键,有值. 键:就是你要监控的那个家伙,比如说$route,这个就是要监控路由的变化,或者是data中的某个变量. 值可以是函数:就是当你 ...
- HDU1505(HDU1506的加强版)
昨天打 CF又跪了.近期睡不好睡不好睡不好-感觉整个人都累傻了,根本无办法写下去,只写了一题签到题就跪了orz..从未试过这么悲剧. 今天早上凭着我的意念("怨念").七点又起来了 ...
- R简易安装
post={"title":"my Blog post","content":"Here's my blog post" ...
- Silverlight 安装失败 提示 消息 ID 1603 的解决方法
消息 ID: 1603 安装过程中出现错误.请执行以下步骤 原因是在以前安装过silverlight,没有安装成功或者没有彻底卸载干净,遗留了一些文件,尤其是安装时突然中断的时候会出现这个问题. 解决 ...
- redis学习笔记——命令执行流程
基础知识部分 如果需要掌握Redis的整个命令的执行过程,那么必须掌握一些基本的概念!否则根本看不懂,下面我就一些在我看来必备的基础知识进行总结,希望能为后面命令的整个执行过程做铺垫. 事件 Redi ...
- 深入理解C#中的泛型(一)
为什么要有泛型? 请大家思考一个问题:由你来实现一个最简单的冒泡排序算法.假设没有使用泛型的经验.可能会毫不犹豫的写出下面代码: public class SortHelper { //參数为int数 ...
- Rails 枚举
Rails Model中使用枚举有两种方案,一种是rails内置的enum,一种使用enumerize这个gem,不管哪种都能达到相同的目的. 首先介绍第一种: 一. enum 基本使用方法,以一个案 ...
- windows下安装UNO,配置AEROO_REPORT (Openoffice4已经升级为Python2.7.5版)
来自:http://shine-it.net/index.php?topic=8019.msg22007 最近单位要上一个OE,但OE7一天一个新更新,不知何年到头. 闲着没事写一点心得,不敢称为教程 ...
- linux中的两个很重要的信号:SIGALRM信号和SIGCHID信号
在进行堵塞式系统调用时.为避免进程陷入无限期的等待,能够为这些堵塞式系统调用设置定时器.Linux提供了alarm系统调用和SIGALRM信号实现这个功能. 要使用定时器.首先要安装S ...