文章目录
前言
本节介绍移动安全相关知识中的Smali语法,并自主动手完成一个小练习
一、Smali简介
Smali是Android虚拟机Davlik的寄存器语言,语法上和汇编语言类似. Davlik是基于寄存器的,就是说smali里的所有操作都必须经过寄存器来进行.
简单来说就是高级语言Java和机器语言的一个中间层代码语言,执行效率相比高级语言更加高效,更加贴近底层,通过我们用Apktool反编译APK出来的结果中便会含有Smali文件夹,文件夹下的内容就是Smali语言
本节主要针对基于Java编写的Android应用反编译出的Smali文件进行分析。
二、Smali语法简介
1.基本类型
B—byte
C—char
D—double
F—float
I—int
S—short
V—void
J—long
Z—boolean
2.引用类型
- [XXX->X代表类型的数组
例:
[B -->Byte[]
[I -->Int[] - Lxx/yyy -->Object对象
例如:
String str–>str Ljava/lang/String;
subobjectName objectName->objectName LpackageaName/objectName s u b o b j e c t N a m e 注 : 这 里 的 subobjectName 注:这里的 subobjectName注:这里的符号代表内部类,内部类的含义我就不用解释了吧
3.方法体定义
方法体格式:
Func-Name (Para-Type1Para-Type2Para-Type3…)Return-Type 注意: 参数之间没有任何分隔符,返回值在最后
例:
void hello() —> hello()V
boolean hello(int,int,int) —>hello(III)Z
String fun(boolean, int[], int[], String, long)
—>fun(Z[I[ILjava/lang/String,J)Ljava/lang/String
4.关键字
- .field private isFlag:z 定义变量
- .method 方法
- .parameter 方法参数
- .prologue 方法开始
- .line123 此方法开始于123行
- invoke-super 调用父函数
- const/high16 v0,0x7fox 把0x7fox的值赋值给v0
- invoke-direct 调用函数
- return-void 函数返回void
- .end method 函数结束
- new-instance 创建实例
- iput-object 对象赋值
- iget-object 调用对象
- invoke-static 调用静态函数
- .class public Lcom/disney/WMW/WMWActivty; 类名
- .super Lcom/XXX/XXX/XXX; 父类名
- .source “XXX.java” 源文件名
- .implements Lcom/XXX/XXX/XXX; 实现了接口
- .annotation 内部类
5.寄存器类别区分
-
寄存器v,本地寄存器 (local register, 非参寄存器)
常用v开头数字结尾的符号表示 v0,v1,v2…
可以看到这里的locals后面的参数为2,表明使用两个v开头的本地寄存器,若修改数量为1,可以编译通过,但是运行时,会发生错误 -
寄存器p,参数寄存器 (parameter regisgter)
常用p开头数字结尾的符号来表示 p0,p1,p2,p3…
# direct methods
.method public constructor <init>()V
.locals 0
.line 18
invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;-><init>()V
return-void
.end method
p0为非静态方法自动创建的寄存器,存储着this,也就是该方法所属类的实例本身,例如这段函数中,一个Activity invoke-direct调用其父类AppCompatActivity的init()方法,传入的参数是这个实例本身的this
6.条件跳转
- 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_**
7.Smali针对函数返回结果的操作
在Java代码中调用函数和返回函数结果可以只用一条语句来表示,如:
A=getPoint();
但在Smali里则需要分开完成,在使用上述指令后,如果调用的函数返回非void, 那么还需要用到move-result(返回基本函数类型)和move-result-object(返回对象指令);
例:
const-string v0,"Eric"
invoke-static {v0},Lcmb/pbi;->t(Ljava/lang/String)LJava/lang/String;
move-result-object v2
此时v2保存的就是调用t方法得到的String字符串
8.Smali变量操作权限
字段操作指令表示对对象字段进行设值和取值操作,就像是你在代码中长些的set和get方法.基本指令是iput-type,iget-type,sput-type,sget-type.type表示数据类型.
普通字段读写操作
前缀是i的iput-type和iget-type指令用于字段的读写操作.
指令 描述
- iget-object vAA,vBB,filed_id 读取vAA寄存器中的对象中的filed_id对象的引用值给vBB寄存器
- iget-boolean vAA,vBB,filed_id 读取vAA寄存器中的对象中的filed_id的值给vBB寄存器
- iget-wide vAA,vBB,filed_id 读取vAA寄存器中的对象中的filed_id的值给vBB寄存器
- iget vAA,vBB,filed_id 读取vAA寄存器中的对象中的filed_id的值给vBB寄存器
- iput-object vAA,vBB,filed_id 把vAA寄存器指向的对象的引用赋值给vBB寄存器中的filed_id对象
- iput-boolean vAA,vBB,filed_id 把vAA寄存器的值给vBB寄存器中的boolean类型
- iput-wide vAA,vBB,filed_id 把vAA寄存器的值给vBB寄存器中的wide类型
- iput vAA,vBB,filed_id 把vAA寄存器的值给vBB寄存器中的int类型
静态字段读写操作
前缀是s的sput-type和sget-type指令用于静态字段的读写操作
指令 描述
- sget-object vAA,vBB,filed_id 读取vAA寄存器中的对象中的filed_id对象的引用值给vBB寄存器
- sget-boolean vAA,vBB,filed_id 读取vAA寄存器中的对象中的filed_id的值给vBB寄存器
- sget-wide vAA,vBB,filed_id 读取vAA寄存器中的对象中的filed_id的值给vBB寄存器
- sget vAA,vBB,filed_id 读取vAA寄存器中的对象中的filed_id的值给vBB寄存器
- sput-object vAA,vBB,filed_id 把vAA寄存器指向的对象的引用赋值给vBB寄存器中的filed_id对象
- sput-boolean vAA,vBB,filed_id 把vAA寄存器的值给vBB寄存器中的boolean类型
- sput-wide vAA,vBB,filed_id 把vAA寄存器的值给vBB寄存器中的wide类型
- sput vAA,vBB,filed_id 把vAA寄存器的值给vBB寄存器中的int类型
三、阅读部分
我们这里采取自己动手打包一个登录验证的APK进行阅读和练习
APK效果
登录界面
登陆后的界面
打包和反编译
打包用AS一键打包
反编译用APKTool对APK进行反编译出相关Smali代码
点击Smali用VS Code工具进行阅读
阅读
MainActivity.smali
.class public Lcom/example/androidsafetest01/MainActivity;//包名+类名
.super Landroidx/appcompat/app/AppCompatActivity;//父类名称
.source "MainActivity.java"//.source 源文件名称
# instance fields
.field private btn:Landroid/widget/Button;
.field private editText:Landroid/widget/EditText;//实例变量 变量名称:包名
# direct methods
.method public constructor <init>()V//方法开始
.locals 0
.line 12//这个方法对应Java代码中的12行
invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;-><init>()V//调用父类中的构造函数,返回值为空
return-void
.end method//方法结束
.method static synthetic access$000(Lcom/example/androidsafetest01/MainActivity;)Landroid/widget/EditText;
.locals 0
.line 12
iget-object p0, p0, Lcom/example/androidsafetest01/MainActivity;->editText:Landroid/widget/EditText;//将MainActivity的id值送给EditText,初步猜测是在给权限
return-object p0
.end method
.method private initEvent()V
.locals 2//使用了两个寄存器
.line 26
iget-object v0, p0, Lcom/example/androidsafetest01/MainActivity;->btn:Landroid/widget/Button;
new-instance v1, Lcom/example/androidsafetest01/MainActivity$1;
invoke-direct {v1, p0}, Lcom/example/androidsafetest01/MainActivity$1;-><init>(Lcom/example/androidsafetest01/MainActivity;)V
invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V//调用实例的虚方法
return-void
.end method
.method private initView()V
.locals 1
const v0, 0x7f08008f
.line 44
invoke-virtual {p0, v0}, Lcom/example/androidsafetest01/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/EditText;
iput-object v0, p0, Lcom/example/androidsafetest01/MainActivity;->editText:Landroid/widget/EditText;
const v0, 0x7f080057
.line 45
invoke-virtual {p0, v0}, Lcom/example/androidsafetest01/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
iput-object v0, p0, Lcom/example/androidsafetest01/MainActivity;->btn:Landroid/widget/Button;
return-void
.end method
# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
.locals 0
.annotation system Ldalvik/annotation/MethodParameters;
accessFlags = {
0x0
}
names = {
"savedInstanceState"
}
.end annotation
.line 19
invoke-super {p0, p1}, Landroidx/appcompat/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
const p1, 0x7f0b001d
.line 20
invoke-virtual {p0, p1}, Lcom/example/androidsafetest01/MainActivity;->setContentView(I)V
.line 21
invoke-direct {p0}, Lcom/example/androidsafetest01/MainActivity;->initView()V
.line 22
invoke-direct {p0}, Lcom/example/androidsafetest01/MainActivity;->initEvent()V
return-void
.end method
MainActivity$1.smali
.class Lcom/example/androidsafetest01/MainActivity$1;
.super Ljava/lang/Object;
.source "MainActivity.java"
# interfaces
.implements Landroid/view/View$OnClickListener;
# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
value = Lcom/example/androidsafetest01/MainActivity;->initEvent()V
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x0
name = null
.end annotation
# instance fields
.field final synthetic this$0:Lcom/example/androidsafetest01/MainActivity;
# direct methods
.method constructor <init>(Lcom/example/androidsafetest01/MainActivity;)V
.locals 0
.annotation system Ldalvik/annotation/MethodParameters;
accessFlags = {
0x8010
}
names = {
"this$0"
}
.end annotation
.line 26
iput-object p1, p0, Lcom/example/androidsafetest01/MainActivity$1;->this$0:Lcom/example/androidsafetest01/MainActivity;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public onClick(Landroid/view/View;)V
.locals 2
.annotation system Ldalvik/annotation/MethodParameters;
accessFlags = {
0x0
}
names = {
"view"
}
.end annotation
.line 29
iget-object p1, p0, Lcom/example/androidsafetest01/MainActivity$1;->this$0:Lcom/example/androidsafetest01/MainActivity;
invoke-static {p1}, Lcom/example/androidsafetest01/MainActivity;->access$000(Lcom/example/androidsafetest01/MainActivity;)Landroid/widget/EditText;
move-result-object p1
invoke-virtual {p1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object p1
invoke-virtual {p1}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object p1
if-eqz p1, :cond_0
.line 30
invoke-virtual {p1}, Ljava/lang/String;->length()I
move-result v0
if-eqz v0, :cond_0
const-string v0, "123456"
.line 31
invoke-virtual {p1, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result p1
if-eqz p1, :cond_1
.line 32
new-instance p1, Landroid/content/Intent;
iget-object v0, p0, Lcom/example/androidsafetest01/MainActivity$1;->this$0:Lcom/example/androidsafetest01/MainActivity;
invoke-virtual {v0}, Lcom/example/androidsafetest01/MainActivity;->getApplicationContext()Landroid/content/Context;
move-result-object v0
const-class v1, Lcom/example/androidsafetest01/EndActivity;
invoke-direct {p1, v0, v1}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V
.line 33
iget-object v0, p0, Lcom/example/androidsafetest01/MainActivity$1;->this$0:Lcom/example/androidsafetest01/MainActivity;
invoke-virtual {v0, p1}, Lcom/example/androidsafetest01/MainActivity;->startActivity(Landroid/content/Intent;)V
goto :goto_0
.line 36
:cond_0
iget-object p1, p0, Lcom/example/androidsafetest01/MainActivity$1;->this$0:Lcom/example/androidsafetest01/MainActivity;
invoke-virtual {p1}, Lcom/example/androidsafetest01/MainActivity;->getApplicationContext()Landroid/content/Context;
move-result-object p1
const/4 v0, 0x0
const-string v1, "\u8f93\u5165\u5bc6\u7801\u6709\u8bef"//这里Smali采用的是Unicode编码
invoke-static {p1, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object p1
invoke-virtual {p1}, Landroid/widget/Toast;->show()V
:cond_1
:goto_0
return-void
.end method
四、实际操作部分
这里通过修改其中的明文密码判定部分,改变其登录逻辑为,密码等于123456789时,进行登录行为
改变后
再次打包APK,进行重签名发送到手机上进行安装
重签名,这里的签名方式采用的是Java中的jarsigner命令,jarsigner命令只可以针对V1的签名方式进行签名,后期应该会进行更改签名方式
jarsigner -verbose -keystore D:\2022\Names\AndroidSafeSign.jks D:\2022\移动安全\apks\release\AndroidSafeTest02.apk -signedjar D:\2022\移动安全\apks\AfterRenamed\AndroidSafeTest01.apk key0
执行效果:
但是这种修改Smali的方法基于静态调试,实际反编译APK过程中基本不会有打静态补丁的机会,这里也是仅供练习使用。
总结
以上是Smali语法学习部分,欢迎大家指正