乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

smali基本语法

发表于 2020-11-06

注释

1
2


类声明

1
2


方法声明

1
2


字段声明

1
2


字段取值赋值

1
2


方法调用

1
2


方法取值

1
2


包名和签名

发表于 2020-11-06

包名作用?

1
唯一标识

一台机器能否同时运行两个相同包名的不同应用?

1
2
3
不可以
原因: 一个包名代表一个应用, 相同包名系统认为是同一个应用, 会进行覆盖安装
原理: 签名不一致

一台机器能否同时运行两个相同签名的不同应用?

1
可以

历史问题:反编译为什么必须要修改包名?

1
成为一款独立的应用, 方便用户下载安装

Smali语言从入门到精通视频教程-胖薯出品

发表于 2020-11-06

适用人群

  • 对Smali语言以及逆向反编译有兴趣的同学
  • 安卓逆向从业者

课程要求

有任意一门编程语言的同学均可学习, 有Java或者安卓基础最佳

课程概述

安卓逆向必备基础知识 学完后快速掌握smali语法

课程特色

23课时 帮你快速入门Smali语言

课程亮点

  • 从0到1快速入门 基础语法开始 循序渐进

  • 内容清晰流畅 不废话不拖沓

  • 配套工具 快速上手 一边学习一边实战

课程大纲

  • 第一部分: Smali语言和Java语言的对比分析

  • 第二部分: 类和各种方法以及属性的定义

  • 第三部分: 寄存器的声明和定义

  • 第四部分: 手写Smali代码打印输出

课程观看

  • 网易云课堂
  • 51CTO课堂
  • 面包多付费观看
  • Udemy课堂
  • B站免费观看
  • YouTube观看
  • 淘宝/拼多多赞助观看

作者介绍

移动端讲师、作家、开发者、独立音乐人

《smali语言从门到精通》《安卓进阶之逆向安卓反编译从0到1》《硬件进阶之三大架构汇编语言入门》系列视频作者 九年移动开发经验 旨在将复杂的事情简单化

欢迎大家来学习 一块进步~

image-20240409102103326

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

smali语言之locals和registers的区别

发表于 2020-11-04

介绍

对于dalviks字节码寄存器都是32位的,它能够表示任何类型,2个寄存器用于表示64位的类型(Long and Double)。

作用

声明于方法内部(必须)

1
2
3
4
5
.method public getName()V
.registers 6

return-void
.end method

.registers和locals基本区别

在一个方法(method)中有两中方式指定有多少个可用的寄存器。指令.registers指令指定了在这个方法中有多少个可用的寄存器,

指令.locals指明了在这个方法中非参(non-parameter)寄存器的数量。然而寄存器的总数也包括保存方法参数的寄存器。

参数是如何传递的?

1.如果是非静态方法

例如,你写了一个非静态方法LMyObject;->callMe(II)V。这个方法有2个int参数,但在这两个整型参数前面还有一个隐藏的参数LMyObject;也就是当前对象的引用,所以这个方法总共有3个参数。
假如在一个方法中包含了五个寄存器(V0-V4),如下:

1
2
3
4
5
6
.method public callMe(II)V
const-string v0,"1"
const-string v1,"1"

return-void
.end method

那么只需用.register指令指定5个,或者使用.locals指令指定2个(2个local寄存器+3个参数寄存器)。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.method public callMe(II)V
.registers 5
const-string v0,"1"
const-string v1,"1"
v3==>p0
V4==>P1
V5==>P2

return-void
.end method

或者
.method public callMe(II)V
.locals 2
const-string v0,"1"
const-string v1,"1"
return-void
.end method

该方法被调用的时候,调用方法的对象(即this引用)会保存在V2中,第一个参数在V3中,第二个参数在v4中。

2.如果是静态方法

那么参数少了对象引用,除此之外和非静态原理相同,registers为4 locals依然是2

关于寄存器命名规则

v命名法

上面的例子中我们使用的是v命名法,也就是在本地寄存器后面依次添加参数寄存器,

但是这种命名方式存在一种问题:假如我后期想要修改方法体的内容,涉及到增加或者删除寄存器,由于v命名法需要排序的局限性,那么会造成大量代码的改动,有没有一种办法让我们只改动registers或者locals的值就可以了呢, 答案是:有的

除v命名法之外,还有一种命名法叫做p命名法

p命名法

p命名法只能给方法参数命名,不能给本地变量命名

假如有一个非静态方法如下:

1
.method public print(Ljava/lang/String;Ljava/lang/String;I)V

以下是p命名法参数对应表:

p0 this
p1 第一个参数Ljava/lang/String;
p2 第二个参数Ljava/lang/String;
p3 第三个参数I

如前面提到的,long和double类型都是64位,需要2个寄存器。当你引用参数的时候一定要记住,例如:你有一个非静态方法

1
LMyObject;->MyMethod(IJZ)V

方法的参数为int、long、bool。所以这个方法的所有参数需要5个寄存器。

p0 this
p1 I
p2, p3 J
p4 Z

另外当你调用方法后,你必须在寄存器列表,调用指令中指明,两个寄存器保存了double-wide宽度的参数。

注意:在默认的baksmali中,参数寄存器将使用P命名方式,如果出于某种原因你要禁用P命名方式,而要强制使用V命名方式,应当使用-p/–no-parameter-registers选项。

总结

  • locals和registers都可以表示寄存器数量,locals指定本地局部变量寄存器个数,registers是locals和参数寄存器数量的总数,两者使用任选其一
  • 同时,寄存器命名一共分两种,一种是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

smali语言之const const/4 const/16 const/high16

发表于 2020-11-03

数据的定义

分三大类

1
2
3
字符串类型数据
字节码数据
数值型数据

数值类型数据拆分

1
2
3
4
5
6
7
8
9
10
第一种 const开头 占用一个容器(寄存器) 32位/容器
const v0,30
* const/4 最大只允许存放4位数值(4个二进制位) 1 111 7
* const/16 最大值允许存放16位数值 第一位默认为符号位 所以计算后15位的数值
* const 32位 最大32位
* const/high16 v0,0xFF7f0000


第二种 const-wide 占用两个容器 64位
const-wide v0,30 #占用v0和v1

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const-string  v0 , "hello"# 定义字符串 将字符串hello赋值给v0

const-class v0,LGoActivity; # 定义字节码对象 将GoActivity.class对象赋值给v0

# 以下数据定义高位默认为符号位
const/4 v0,0x2 # 定义一个容器 最大只允许存放半字节4位数据 取值范围为 -8 and 7
const/16 v0 , 0xABCD # 定义定义一个容器 最大只允许存放16位数据 比如short类型数据 取值范围为-32768~32767
const v0 , 0xA# 定义一个容器 最大只允许存放32位数据,比如int类型数据 将数字10赋值给v0 取值范围-2147483647~2147483647
const/high16 #定义一个容器 最大只允许存放高16位数值 比如0xFFFF0000末四位补0 存入高四位0XFFFF

# const-wide 占用两个寄存器vx和vx+1, 数值必须以L结尾 否则编译不通过
const-wide/16 # 定义两个相连容器 最大只允许存放16位数据
const-wide/32 # 定义两个相连容器 最大只允许存放32位数据
const-wide # 定义两个相连容器 最大只允许存放64位数据
const-wide/high16 # 定义两个相连容器 只允许存放高16位数据

详情参考:Android逆向开发之smali语言的学习

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

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

发表于 2020-11-03

smali和java基本数据类型对比

smali java
B byte
S short
I int
J long
F float
D double
C char
Z boolean
V void
[ 数组
L+全类名路径用/分割 object

注释

在smali语言中注释使用#表示

1
# 我是注释

类声明

1
.class +权限修饰符 +类名;

比如以下java代码:

1
2
3
public class Test
{
}

用smali代码表示为:

1
2
3
4
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)
.implements Ljava/lang/CharSequence; #如果实现了接口 则添加接口代码
.source "Test.java" # 源码文件 (非必须)

关于分号;

凡是L开头全包名路径结尾都需要加分号

字段声明(成员/全局变量)

1
.field 权限修饰符+静态修饰符 +变量名:变量全类名路径;

比如以下java代码:

1
2
3
4
public class Test
{
private static String a;
}

用smali代码表示为:

1
2
3
4
5
6
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)
.source "Test.java" # 源码文件 (非必须)

# 如果是非静态,只需将static去掉即可
.field private static a:Ljava/lang/String;

补充:

1
2
3
4
5
6
7
8
9
10
基本数据类型示例:
.method public final pubFinalMethod()V //返回值
.field private boType:Z // boolean
.field private byteType:B // byte
.field private shortType:S // short
.field private charType:C // char
.field private intType:I // int
.field private longType:J //long
.field private floatType:F // float
.field private doubleType:D // double

常量声明

1
.field 权限修饰符+静态修饰符 +final+变量名:变量全类名路径;=常量值

比如以下java代码:

1
2
3
4
public class Test
{
private static final String a=”hello“;
}

用smali代码表示为:

1
2
3
4
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)

.field public static final a:Ljava/lang/String; = "hello"

成员方法/函数声明

1
2
3
.method 权限修饰符+静态修饰符 +方法名(参数类型)返回值类型
#方法体
.end method #方法结尾标志

比如以下java代码:

1
2
3
4
5

public class Test
{
public static void getName(){}
}

用smali代码表示为:

1
2
3
4
5
6
7
8
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)

# 如果是非静态,只需将static去掉即可
.method public static getName()V

return-void
.end method

如果是带参并且带有返回值的方法

比如以下java代码:

1
2
3
4
5
6
public class Test
{
public String getName(String p){
return "hello";
}
}

用smali代码表示为:

1
2
3
4
5
6
.method public getName(Ljava/lang/String;)Ljava/lang/String;

const-string v0, "hello"

return-object v0
.end method

关于方法返回关键字

主要有以下四种

1
2
3
4
return-void
return-object
return
return-wide

数据类型对应关系表如下:

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

构造方法/构造函数声明

1
2
3
.method 权限修饰符 +constructor <init>(参数类型)返回值类型
#方法体
.end method #方法结尾标志

比如以下java代码:

1
2
3
4
5
public class Test
{
public Test(String a){
}
}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)


.method public constructor <init>(Ljava/lang/String;)V

invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法

return-void
.end method

静态代码块的声明

1
2
3
.method static +constructor <clinit>()V
#方法体
.end method #方法结尾标志

比如以下java代码:

1
2
3
4
5
6
7
8
public class Test
{
public static String a="a";

static{

}
}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)


.method public static constructor <clinit>()V


return-void
.end method

方法调用

关键字

1
2
3
4
5
invoke-virtual //用于非私有实例方法的调用
invoke-direct //用于构造方法以及私有方法的调用
invoke-static //调用静态方法
invoke-super //调用父类的方法
invoke-interface //调用接口方法

非私有实例方法的调用

1
invoke-virtual {参数}, 方法所属类名;->方法名(参数类型)返回值类型;

比如以下java代码:

1
2
3
4
5
6
7
8
9
public class Test
{
public Test(String a){
getName();
}
public String getName(){
return "hello";
}
}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)


.method public constructor <init>( Ljava/lang/String;)V

invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法
invoke-virtual {p0}, LTest;->getName()Ljava/lang/String;# 调用普通成员getName方法

return-void
.end method

#声明getName方法
.method public getName()Ljava/lang/String;

const-string v0, "hello"# 定义局部字符串常量

return-object v0 # 返回常量
.end method

私有方法或者构造方法的调用

1
invoke-direct {参数}, 方法所属类名;->方法名(参数类型)返回值类型;

私有方法调用:

比如以下java代码:

1
2
3
4
5
6
7
8
9
10
public class Test
{
public Test(String a){
getName();
}
//私有方法
private String getName(){
return "hello";
}
}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)


.method public constructor <init>(Ljava/lang/String;)V

invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法
invoke-direct {p0}, LTest;->getName()Ljava/lang/String;# 调用私有getName方法

return-void
.end method

#声明getName方法
.method private getName()Ljava/lang/String;

const-string v0, "hello"# 定义局部字符串常量

return-object v0 # 返回常量
.end method

构造方法调用:

比如以下java代码:

1
2
3
4
5
6
7
8
9
10
11
public class Test
{
public Test(String a){
new Test2("hello");
}
public class Test2
{
public Test2(String a){
}
}
}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)

# 匿名内部类的声明
.annotation system Ldalvik/annotation/MemberClasses;
value = {
LTest$Test2;
}
.end annotation


# 构造方法
.method public constructor <init>(Ljava/lang/String;)V
# 初始化父类构造方法
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
# 创建对象
new-instance v0, LTest$Test2;
# 定义常量
const-string v1, "hello"
# 调用构造方法
invoke-direct {v0, p0, v1}, LTest$Test2;-><init>(LTest;Ljava/lang/String;)V

return-void
.end method

静态方法的调用并获取返回值(不区分私有公有 静态优先)

1
invoke-static {参数}, 方法所属类名;->方法名(参数类型)返回值类型;

比如以下java代码:

1
2
3
4
5
6
7
8
9
10
11
public class Test
{
public Test(String a){
String b=getName();
System.out.print(b);
}
private static String getName(){
return "hello";
}

}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)


.method public constructor <init>(Ljava/lang/String;)V

invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法
invoke-static {p0}, LTest;->getName()Ljava/lang/String;# 调用普通成员getName方法
move-result-object v0 #将返回值赋给v0
return-void
.end method

#声明getName方法
.method public getName()Ljava/lang/String;

const-string v0, "hello"# 定义局部字符串常量

return-object v0 # 返回常量
.end method

父类成员的方法调用

1
invoke-super

比如以下java代码

1
2
3
4
5
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

}

用smali代码表示为

1
2
3
4
5
6
.method protected onCreate(Landroid/os/Bundle;)V
.registers 2

invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
return-void
.end method

接口的调用

1
invoke-interface  {参数}, 方法所属类名;->方法名(参数类型)返回值类型;

比如以下java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test
{
private InterTest a=new Test2();
public Test(String a){
}
public void setAa(){
InterTest aa=a;
# 调用接口方法
aa.est2();
}
public class Test2 implements InterTest
{
public Test2(){}

public void est2(){}
}
interface InterTest
{
public void est2();
}
}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)


.method public constructor <init>(Ljava/lang/String;)V

invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法
invoke-static {p0}, LTest;->getName()Ljava/lang/String;# 调用普通成员getName方法

return-void
.end method

#声明setAagetName方法
.method public setAa()V
.registers 2


iget-object v0, p0, LTest;->a:LTest$InterTest;
# 调用接口方法
invoke-interface {v0}, LTest$InterTest;->est2()V

return-void
.end method

创建对象

对象的创建分多步进行:

1
2
3
4
# 声明实例
new-instance +变量名, 对象全包名路径;
# 调用构造方法 (如果构造方法内还定义了成员变量,那么在调用之前需要提前声明,然后在invoke的时候当作参数一并传入)
invoke-direct {变量名}, 对象全包名路径;-><init>(参数)返回类型
数组的创建
const/4 v0, 0x4
new-array v0, v0, [I

fill-array-data v0, :array_a

  :array_a
    .array-data 4 # 表示占用四个字节
        0x0
        0x1
        0x2
        0x3
    .end array-data

数据的定义

分三大类

1
2
3
字符串类型数据
字节码数据
数值型数据

数值类型数据拆分

1
2
3
4
5
6
7
8
9
10
第一种 const开头 占用一个容器(寄存器) 32位/容器
const v0,30
* const/4 最大只允许存放4位数值(4个二进制位) 1 111 7
* const/16 最大值允许存放16位数值 第一位默认为符号位 所以计算后15位的数值
* const 32位 最大32位
* const/high16 v0,0xFF7f0000


第二种 const-wide 占用两个容器 64位
const-wide v0,30 #占用v0和v1

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const-string  v0 , "hello"# 定义字符串 将字符串hello赋值给v0

const-class v0,LGoActivity; # 定义字节码对象 将GoActivity.class对象赋值给v0

# 以下数据定义高位默认为符号位
const/4 v0,0x2 # 定义一个容器 最大只允许存放半字节4位数据 取值范围为 -8 and 7
const/16 v0 , 0xABCD # 定义定义一个容器 最大只允许存放16位数据 比如short类型数据 取值范围为-32768~32767
const v0 , 0xA# 定义一个容器 最大只允许存放32位数据,比如int类型数据 将数字10赋值给v0 取值范围-2147483647~2147483647
const/high16 #定义一个容器 最大只允许存放高16位数值 比如0xFFFF0000末四位补0 存入高四位0XFFFF

# const-wide 占用两个寄存器vx和vx+1, 数值必须以L结尾 否则编译不通过
const-wide/16 # 定义两个相连容器 最大只允许存放16位数据
const-wide/32 # 定义两个相连容器 最大只允许存放32位数据
const-wide # 定义两个相连容器 最大只允许存放64位数据
const-wide/high16 # 定义两个相连容器 只允许存放高16位数据

数据取值范围算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1000 → -8;
1001 → -7;
1010 → -6;
1011 → -5;
1100 → -4;
1101 → -3;
1110 → -2;
1111 → -1;

0000 → 0;
0001 → 1;
0010 → 2;
0011 → 3;
0100 → 4;
0101 → 5;
0110 → 6;
0111 → 7。

算法:正数的符号位是0,负数的符号位是1。正数的反码、补码与原码一样。负数的反码是让符号位不变,数据位按位取反;补码是将反码加1。

静态字段赋值

分多步进行 关键代码:

1
sput-object # s代指static

比如以下java代码:

1
2
3
4
public class Test
{
private static String a=”hello“;
}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)
.source "Test.java" # 源码文件 (非必须)

# 声明静态字段
.field private static a:Ljava/lang/String;

#类初始化方法 被jvm执行 优先于构造方法
.method static constructor <clinit>()V

const-string v0, "hello"# 定义常量值

sput-object v0, LTest;->a:Ljava/lang/String;#常量赋值

return-void
.end method

类非静态字段赋值

分多步进行 关键代码:

1
iput-object  # i代表instance

比如以下java代码:

1
2
3
4
5
6
7
8
9
10
11
public class Test
{
private String a="g";
public Test(String a){

}
public void setAa(){
a="b";
}

}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)
.source "Test.java" # 源码文件 (非必须)

# 声明字段
.field private a:Ljava/lang/String;

# 构造方法初始化值a="g"
.method public constructor <init>(Ljava/lang/String;)V
.registers 3
# 初始化父类构造方法
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
# 声明字符串内容
const-string v0, "g"
# 赋值
iput-object v0, p0, LTest;->a:Ljava/lang/String;


return-void
.end method

# 成员方法修改变量a="b"
.method public setAa()V
.registers 2

.prologue

const-string v0, "b"

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

return-void
.end method

静态字段取值

关键代码

1
sget-object # s代指static

比如以下java代码:

1
2
3
4
5
6
7
8
9
10
public class Test
{
private static String a="hello";
public Test(String a){
}
public void getA(){
String aa=a;
}

}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)
.source "Test.java" # 源码文件 (非必须)

# 声明静态字段
.field private static a:Ljava/lang/String;

#类初始化方法 被jvm执行 优先于构造方法
.method static constructor <clinit>()V

const-string v0, "hello"# 定义常量值

sput-object v0, LTest;->a:Ljava/lang/String;#常量赋值

return-void
.end method

# 取值方法
.method public getA()V
.registers 2

# 静态字段取值
sget-object v0, LTest;->a:Ljava/lang/String;

return-void
.end method

类非静态字段取值

关键代码:

1
iget-object  # i代表instance

比如以下java代码:

1
2
3
4
5
6
7
8
9
10
public class Test
{
private String a="hello";
public Test(String a){
}
public void getA(){
String aa=a;
}

}

用smali代码表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)
.source "Test.java" # 源码文件 (非必须)

# 声明静态字段
.field private static a:Ljava/lang/String;

#构造方法
.method public constructor <init>(Ljava/lang/String;)V
.registers 3

.prologue

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

const-string v0, "hello"
# 初始化成员变量
iput-object v0, p0, LTest;->a:Ljava/lang/String;

return-void
.end method


# 取值方法
.method public getA()V
.registers 2

# 类非静态字段取值
iget-object v0, LTest;->a:Ljava/lang/String;

return-void
.end method

注意:以上取值赋值方法都是以String对象举例,如果是基本数据类型,那么按照如下表处理:

值定义

1
const/4 v0, 0x1 # 实例变量值内容定义 值皆为十六进制

取值:

1
2
iget #实例变量int型取值
sget #静态变量int型取值

赋值

1
2
iput #实例变量int型赋值
sput #静态变量int型赋值

下表以实例变量举例:

smali取值赋值和值定义关键字 java
iget-byte
iput-byte
const/4
byte
iget-short
iput-short
const/4
short
iget
iput
const/4
int
iget-wide
iput-wide
const-wide/16
long
iget-
iput
const/high16
float
iget-wide-
iput-wide
const/high16
double
iget-char-
iput-char
const/16
char
iget-boolean-
iput-boolean
const/4
boolean
#### 如果是基本数据类型,那么按照如下表处理:
smali取值赋值和值定义关键字 java
iget-object-
iput-object
new-array v0, v0, [数据类型签名

fill-array-data v0, :array_c
数组
iget-object-
iput-object

以下两步为类对象定义
new-instance v0, 全包名类路径;

invoke-direct #调用构造方法
类和接口
iget-object-
iput-object
sget-object
枚举
iget-object-
iput-object
const-string
String

以上表结果示例java代码如下,可自行试验:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test
{
private Test2 a=Test2.a;
public Test(String a){
}
public void setAa(){
Test2 aa=a;
}
public enum Test2
{
a,b;
}
}

$$

$$

1
2
3
4
5
6
7
8
9
10
public class Test
{
private String a="a";
public Test(String a){
}
public void setAa(){
String aa=a;
}

}

逻辑语句之条件跳转分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"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_**

逻辑语句之循环

比如以下java代码

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {

for(int i=0; i<3;i++){
}
}
}

对应的smali代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.method public static main([Ljava/lang/String;)V

const/4 v0, 0x0

:goto_1
const/4 v1, 0x3

if-ge v0, v1, :cond_7

add-int/lit8 v0, v0, 0x1 # 加法运算符 v0=v0+0x1

goto :goto_1

:cond_7
return-void
.end method

如果将int改成long, 结果又不一样,这里使用到了比较运算符cmp(comparable)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.method public static main([Ljava/lang/String;)V
.registers 5

.prologue
.line 4
const-wide/16 v0, 0x0

:goto_2
const-wide/16 v2, 0x3

cmp-long v2, v0, v2 # cmp-long为固定写法 如果v0大于v2 则返回1 赋值给v2 等于为0 小于则为-1

if-gez v2, :cond_c

const-wide/16 v2, 0x1

add-long/2addr v0, v2

goto :goto_2

.line 6
:cond_c
return-void
.end method

smali语法关键字

.line

表示与java源文件代码的映射关系,比如:

1
2
3
4
.line 3  # 代表以下代码还原成java代码在源文件第三行 
const/4 v0, 0x1

iput v0, p0, LTest;->a:I

删除该关键字不影响程序执行,该关键字在反编译时能很好地帮助我们阅读smali代码,以该关键字当作代码块的分割线,方便快速阅读执行内容

:cond_0

条件分支,配合if使用

.prologue

表示程序的开始 可省略

:goto_0

goto跳转分支,配合goto关键字使用

.local

显示局部变量别名信息,作用等同.line

1
2
move-result-object v0 # 调用方法后结果储存在v0中
.local v0, "b":Ljava/lang/String; # 局部变量v0别名为b 是一个String类型 也就是 String b=v0

.locals N

注意这个和上面local的区别多加了一个s

标明了你在这个函数中最少要用到的本地寄存器的个数 也即是指明了在这个方法中非参(non-parameter)寄存器的数量

locals和registers具体区别参见:点击跳转

.registers N

在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double

声明可使用的寄存器数量的方式为:.registers N,N代表需要的寄存器的总个数

示例:

1
2
3
4
5
6
7
8
9
.method private test(I)V
.registers 4 # 声明总共需要使用4个寄存器

const-string v0, "LOG" # 将v0寄存器赋值为字符串常量"LOG"

move v1, p1 # 将int型参数的值赋给v1寄存器

return-void
.end method

那么,如何确定需要使用的寄存器的个数?

由于非static方法,需要占用一个寄存器以保存this指针,那么这类方法的寄存器个数,最低就为1,如果还需要处理传入的参数,则需要再次叠加,此时还需要考虑Double和Float这种需要占用两个寄存器的参数类型,举例来看:

如果一个Java方法声明如下:

1
myMethod(int p1, float p2, boolean p3)1

那么对应的Smali则为:

1
method LMyObject;->myMethod(IJZ)V1

此时,寄存器的对应情况如下:

寄存器名称 对应的引用
p0 this
p1 int型的p1参数
p2, p3 float型的p2参数
p4 boolean型的p3参数

那么最少需要的寄存器个数则为:5

如果方法体内含有常量、变量等定义,则需要根据情况增加寄存器个数,数量只要满足需求,保证需要获取的值不被后面的赋值冲掉即可,方法有:存入类中的字段中(存入后,寄存器可被重新赋值),或者长期占用一个寄存器

寄存器数量只能多不能少

Dalvik指令集

如果需要使用Smali编写程序,还需要掌握常用的Dalvik虚拟机指令,其合集称为Dalvik指令集。这些指令有点类似x86汇编的指令,但指令更多,使用也非常简单方便。最详尽的介绍,可以参考Android官方的Dalvik相关文档:

https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions

一般的指令格式为:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选),比如:move v1,v2,move-wide/from16 v1,v2

这里也列举一些常用的指令,并结合Smali进行说明:

  • 移位操作:

此类操作常用于赋值

指令 说明
move v1,v2 将v2中的值移入到v1寄存器中(4位,支持int型)
move/from16 v1,v2 将16位的v2寄存器中的值移入到8位的v1寄存器中
move/16 v1,v2 将16位的v2寄存器中的值移入到16位的v1寄存器中
move-wide v1,v2 将寄存器对(一组,用于支持双字型)v2中的值移入到v1寄存器对中(4位,猜测支持float、double型)
move-wide/from16 v1,v2 将16位的v2寄存器对(一组)中的值移入到8位的v1寄存器中
move-wide/16 v1,v2 将16位的v2寄存器对(一组)中的值移入到16位的v1寄存器中
move-object v1,v2 将v2中的对象指针移入到v1寄存器中
move-object/from16 v1,v2 将16位的v2寄存器中的对象指针移入到v1(8位)寄存器中
move-object/16 v1,v2 将16位的v2寄存器中的对象指针移入到v1(16位)寄存器中
move-result v1 将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用)
move-result-object v1 将上条计算结果的对象指针移入v1寄存器
move-result-wide v1 将上条计算结果(双字)的对象指针移入v1寄存器
move-exception v1 将异常移入v1寄存器,用于捕获try-catch语句中的异常
  • 返回操作:

用于返回值,对应Java中的return语句

指令 说明
return-void 返回void,即直接返回
return v1 返回v1寄存器中的值
return-object v1 返回v1寄存器中的对象指针
return-wide v1 返回双字型结果给v1寄存器

另外

android studio自带的.class转smali就非常好用,如图:

从错误总学习

1
SLog.smali[24,4] Invalid register: v-1. Must be between v0 and v15, inclusive.

寄存器命名从v0-v15 一共15个

1
SLog.smali[17,0] A .registers or .locals directive must be present for a non-abstract/non-final method

.registers或者.locals必须存在, 除非是抽象方法或者final方法

1
2
java.lang.VerifyError: Rejecting class com.pangshu.SLog because it failed compile-time verification (declaration of 'com.pangshu.SLog' appears in /sdcard/ex.dex)
at com.pangshu.HelloTest.main(HelloTest.java)

这种错误一般很难定位,因为没有提示具体原因或者具体的行数,有可能是静态方法调用你写成了虚方法的调用,或者是构造函数调用没有加尖括号, 甚至是寄存器数量过少 等等

思考

为什么方法中包括参数在内需要3个寄存器,但是在定义的时候只写了两个却也不报错呢?

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.method public static print(Ljava/lang/String;)V
.registers 2 #不报错
.prologue

invoke-static {p0},Lcom/pangshu/SLog;->wrapTag(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1 #如果这个地方改成v2以上那么报错

# log---System.out.print()
sget-object v0,Ljava/lang/System;->out:Ljava/io/PrintStream;


# 方法调用
invoke-virtual {v0,v1},Ljava/io/PrintStream;->print(Ljava/lang/String;)V

return-void
.end method

答案是:系统会更具最大寄存器的位置进行判断,从v0到vN,数量必须大于N,

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

二进制与十六进制和八进制的快速转换

发表于 2020-11-02

进制转换属于计算机基础,虽然是基础,但是想要熟练计算,需要花点时间

二进制举例

1
0101001001001101    //转成十进制值是2^0+2^2+2^3+2^6+2^9+2^12+2^14=21069

二进制转十六进制

从低到高每四位进行分割,也就是:

1
0101 0010 0100 1101   //结果 524D

二进制转八进制

从低到高每三位进行分割,也就是:

1
0 101 001 001 001 101

不足三位的补0, 也就是:

1
000 101 001 001 001 101 结果051115

思考

二进制每四位(1111)最大值是15, 按照四位分割转成十六进制

二进制每三位(111)最大值是7,按照三位分割转八进制

那么延伸:

二进制每两位(11)最大值是3,按照两位分割转成四进制

二进制每五位(11111)最大值是31,按照五位分割转成三十二进制

二进制每六位(111111)最大值是63,按照六位分割转成六十四进制

二进制每七位(1111111)最大值是127,按照七位分割转成一百二十八进制

……..

如果快速转换

十六进制和二进制一一对应关系表:

十六进制 二进制
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

android逆向反编译工具包下载

发表于 2020-11-02

前言

该压缩包为《Android进阶之逆向安全反编译视频教程-胖薯出品》的配套工具包

工具包

该压缩包包含以下工具:

命令行工具

  • apktool2.4
  • dex2jar2.0

集成工具

  • jadx0.9
  • jd-gui.exe
  • apkIDE
  • 安卓逆向助手2.0

工具下载

点击进入

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

8086汇编语言之各个通用寄存器的作用

发表于 2020-11-01

在8086cpu通用寄存器

  • 寄存器AX: 用于存放数据 (源代码编写时不能以字母开头,可在开头补上0)
    • 比如 mov ax, ffffh 应该写成 mov ax, 0ffffh 否则编译报错
  • 寄存器BX: 另外还用于存放数据段偏移地址 初始值为源代码大小
  • 寄存器CX: 另外还用于存放loop循环计数
  • 寄存器DX: 另外还用于存放累加结果
  • 寄存器EX:备用寄存器

## BX存在的意义

1. asm编译器无法识别中括号,mov ax,[0]编译时会默认去除中括号.使用bx替代可以解决这个问题,如果在debug模式下使用-a命令输入中括号则没有问题,可以正常识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;错误写法:编译器无法识别[0],会直接取值为0 而不是偏移地址0所对应的内容
assumme cs:code
code segment
mov ax,2000h
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]

mov ax,4c00h
int 21h
code ends
end

问题: 那如果非要以带中括号的方式进行编写可以吗? 答案是:需要带上段地址ds,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
assumme cs:code
code segment
mov ax,2000h
mov ds,ax
mov al,ds:[0] ;其中ds称作段前缀
mov bl,ds:[1]
mov cl,ds:[2]
mov dl,ds:[3]

mov ax,4c00h
int 21h
code ends
end

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

汇编语言之debug模式常用指令介绍

发表于 2020-11-01

在搭建好了8086汇编的开发环境后,接下来介绍8086的debug模式。执行debug.exe以进入debug调试模式,在dos中通过输入命令的方式进行交互

## -R命令

R命令的作用是查看和修改debug模式下CPU中寄存器的值

## -D命令

D命令的作用是查看内存中的内容

上面为(段地址:偏移地址)查看方式。D命令默认会显示寻址地址开始的后128个内存单元的内容,以16进制的方式显示(每个内存单元8位,一行最多16个内存单元),而最右边会将内存单元中的二进制数据以ascll码的形式翻译展示

  但有时,我们只想聚焦于某一部分内存地址的内容,而默认展示的内存视图不是很方便。

  D命令提供了另外一种访问内存的方式(段地址:偏移起始地址 偏移终止地址),其能够展示(段地址:偏移起始地址 至 段地址:偏移终止地址)的内存信息,范围两端均为闭区间

-E命令

E命令的作用是改变内存中的内容。

 和对CPU中寄存器的查看,修改不同,对内存进行查看和修改较为复杂,为此debug设计了两个不同的命令分别进行控制(E命令修改内存、D命令查看内存)。

  通过(E 起始地址 数据1 数据2 数据3…)命令可以修改内存中以起始地址开始,顺序的N个内存单元的值(N为实际参数传递的数量)

也可以和R命令修改CPU中寄存器值类似的,通过提示来修改特定内存单元的值。00.12 00代表内存单元在修改前的值,12是我们手动输入的、需要修改的新值

-U命令

U命令的作用是将内存中的二进制数据转换为汇编指令展示(反汇编)

D命令能够将内存中的数据以16进制或ascll码的形式展现出来,但有时我们需要观察的是内存中的机器指令时,D命令的视图过于抽象,不利于理解。debug提供了U命令来解决这个问题。

  对于前面我们在1000:0处输入的机器指令,使用 U 1000:0 命令(u 内存地址)可以将内存中的数据以汇编语言指令的方式进行展示

-A命令

A命令能够以汇编指令的形式向内存中写入内容

对于内存操作,D命令可以查看内存中的内容,但如果想查看的是程序指令,显然U命令更加方便;E命令可以向内存中写入数据,但对于程序指令的写入,直接操作二进制机器码的方式过于硬核。为此,debug提供了A命令,我们可以通过A命令以汇编指令的形式向内存中写入内容。

  通过A命令将(mov ax,0001,mov bx,0002,add ax,bx)三条指令写入内存1000:0处:

通过A命令进行指令的写入,和E命令达到的效果一样,但使用起来却更加便捷。A命令能够自动识别所输入汇编指令的长度,正确的在内存中写入程序指令。

  debug提供了D、E两种命令用于对内存进行通用的操作(纯二进制、十六进制数据的读、写)。

  对于程序指令,debug提供了U、A两种命令以更人性化的方式来读写内存中的指令内容

-T命令

 T命令的作用是进行单步机器指令的调试

-G命令

 G命令的作用是进行Debug程序断点调试

1
-g 代码地址(cs+ip)地址

-p命令

 p命令的作用是断点跳过执行 ,可用于循环调试

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

1…303132…48

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

479 日志
139 标签
RSS
© 2024 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%