数据的定义
分三大类
1 | 字符串类型数据 |
数值类型数据拆分
1 | 第一种 const开头 占用一个容器(寄存器) 32位/容器 |
总结
1 | const-string v0 , "hello"# 定义字符串 将字符串hello赋值给v0 |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
乱码三千 – 码出一个新世界
1 | 字符串类型数据 |
1 | 第一种 const开头 占用一个容器(寄存器) 32位/容器 |
1 | const-string v0 , "hello"# 定义字符串 将字符串hello赋值给v0 |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
| 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 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
凡是L开头全包名路径结尾都需要加分号
1 | .field 权限修饰符+静态修饰符 +变量名:变量全类名路径; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
补充:
1 | 基本数据类型示例: |
1 | .field 权限修饰符+静态修饰符 +final+变量名:变量全类名路径;=常量值 |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
1 | .method 权限修饰符+静态修饰符 +方法名(参数类型)返回值类型 |
比如以下java代码:
1 |
|
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .method public getName(Ljava/lang/String;)Ljava/lang/String; |
1 | return-void |
| 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 | .method 权限修饰符 +constructor <init>(参数类型)返回值类型 |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
1 | .method static +constructor <clinit>()V |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
1 | invoke-virtual //用于非私有实例方法的调用 |
1 | invoke-virtual {参数}, 方法所属类名;->方法名(参数类型)返回值类型; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
1 | invoke-direct {参数}, 方法所属类名;->方法名(参数类型)返回值类型; |
私有方法调用:
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
构造方法调用:
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
1 | invoke-static {参数}, 方法所属类名;->方法名(参数类型)返回值类型; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
1 | invoke-super |
比如以下java代码
1 | @Override |
用smali代码表示为
1 | .method protected onCreate(Landroid/os/Bundle;)V |
1 | invoke-interface {参数}, 方法所属类名;->方法名(参数类型)返回值类型; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
对象的创建分多步进行:
1 | # 声明实例 |
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-data1 | 字符串类型数据 |
1 | 第一种 const开头 占用一个容器(寄存器) 32位/容器 |
1 | const-string v0 , "hello"# 定义字符串 将字符串hello赋值给v0 |
1 | 1000 → -8; |
算法:正数的符号位是0,负数的符号位是1。正数的反码、补码与原码一样。负数的反码是让符号位不变,数据位按位取反;补码是将反码加1。
分多步进行 关键代码:
1 | sput-object # s代指static |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
分多步进行 关键代码:
1 | iput-object # i代表instance |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
关键代码
1 | sget-object # s代指static |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
关键代码:
1 | iget-object # i代表instance |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
值定义
1 | const/4 v0, 0x1 # 实例变量值内容定义 值皆为十六进制 |
取值:
1 | iget #实例变量int型取值 |
赋值
1 | iput #实例变量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 | public class Test |
$$
$$
1 | public class Test |
1 | "if-eq vA, vB, :cond_**" 如果vA等于vB则跳转到:cond_** #equal |
比如以下java代码
1 | public class Test { |
对应的smali代码为:
1 | .method public static main([Ljava/lang/String;)V |
如果将int改成long, 结果又不一样,这里使用到了比较运算符cmp(comparable)
1 | .method public static main([Ljava/lang/String;)V |
表示与java源文件代码的映射关系,比如:
1 | .line 3 # 代表以下代码还原成java代码在源文件第三行 |
删除该关键字不影响程序执行,该关键字在反编译时能很好地帮助我们阅读smali代码,以该关键字当作代码块的分割线,方便快速阅读执行内容
条件分支,配合if使用
表示程序的开始 可省略
goto跳转分支,配合goto关键字使用
显示局部变量别名信息,作用等同.line
1 | move-result-object v0 # 调用方法后结果储存在v0中 |
注意这个和上面local的区别多加了一个s
标明了你在这个函数中最少要用到的本地寄存器的个数 也即是指明了在这个方法中非参(non-parameter)寄存器的数量
locals和registers具体区别参见:点击跳转
在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double
声明可使用的寄存器数量的方式为:.registers N,N代表需要的寄存器的总个数
示例:
1 | .method private test(I)V |
那么,如何确定需要使用的寄存器的个数?
由于非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
如果方法体内含有常量、变量等定义,则需要根据情况增加寄存器个数,数量只要满足需求,保证需要获取的值不被后面的赋值冲掉即可,方法有:存入类中的字段中(存入后,寄存器可被重新赋值),或者长期占用一个寄存器
寄存器数量只能多不能少
如果需要使用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 | java.lang.VerifyError: Rejecting class com.pangshu.SLog because it failed compile-time verification (declaration of 'com.pangshu.SLog' appears in /sdcard/ex.dex) |
这种错误一般很难定位,因为没有提示具体原因或者具体的行数,有可能是静态方法调用你写成了虚方法的调用,或者是构造函数调用没有加尖括号, 甚至是寄存器数量过少 等等
为什么方法中包括参数在内需要3个寄存器,但是在定义的时候只写了两个却也不报错呢?
如:
1 | .method public static print(Ljava/lang/String;)V |
答案是:系统会更具最大寄存器的位置进行判断,从v0到vN,数量必须大于N,
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
进制转换属于计算机基础,虽然是基础,但是想要熟练计算,需要花点时间
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进阶之逆向安全反编译视频教程-胖薯出品》的配套工具包
该压缩包包含以下工具:
本文为作者原创 转载时请注明出处 谢谢

mov ax, ffffh 应该写成 mov ax, 0ffffh 否则编译报错## BX存在的意义
1. asm编译器无法识别中括号,mov ax,[0]编译时会默认去除中括号.使用bx替代可以解决这个问题,如果在debug模式下使用-a命令输入中括号则没有问题,可以正常识别
1 | ;错误写法:编译器无法识别[0],会直接取值为0 而不是偏移地址0所对应的内容 |
问题: 那如果非要以带中括号的方式进行编写可以吗? 答案是:需要带上段地址ds,如下:
1 | assumme cs:code |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
在搭建好了8086汇编的开发环境后,接下来介绍8086的debug模式。执行debug.exe以进入debug调试模式,在dos中通过输入命令的方式进行交互

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

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

上面为(段地址:偏移地址)查看方式。D命令默认会显示寻址地址开始的后128个内存单元的内容,以16进制的方式显示(每个内存单元8位,一行最多16个内存单元),而最右边会将内存单元中的二进制数据以ascll码的形式翻译展示
但有时,我们只想聚焦于某一部分内存地址的内容,而默认展示的内存视图不是很方便。
D命令提供了另外一种访问内存的方式(段地址:偏移起始地址 偏移终止地址),其能够展示(段地址:偏移起始地址 至 段地址:偏移终止地址)的内存信息,范围两端均为闭区间

E命令的作用是改变内存中的内容。
和对CPU中寄存器的查看,修改不同,对内存进行查看和修改较为复杂,为此debug设计了两个不同的命令分别进行控制(E命令修改内存、D命令查看内存)。
通过(E 起始地址 数据1 数据2 数据3…)命令可以修改内存中以起始地址开始,顺序的N个内存单元的值(N为实际参数传递的数量)
也可以和R命令修改CPU中寄存器值类似的,通过提示来修改特定内存单元的值。00.12 00代表内存单元在修改前的值,12是我们手动输入的、需要修改的新值

U命令的作用是将内存中的二进制数据转换为汇编指令展示(反汇编)
D命令能够将内存中的数据以16进制或ascll码的形式展现出来,但有时我们需要观察的是内存中的机器指令时,D命令的视图过于抽象,不利于理解。debug提供了U命令来解决这个问题。
对于前面我们在1000:0处输入的机器指令,使用 U 1000:0 命令(u 内存地址)可以将内存中的数据以汇编语言指令的方式进行展示


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命令的作用是进行单步机器指令的调试
G命令的作用是进行Debug程序断点调试
1 | -g 代码地址(cs+ip)地址 |
p命令的作用是断点跳过执行 ,可用于循环调试
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
数据段可以通俗理解为数据容器指针
比如:
1 | MOV AX 0220H |
代码段可以通俗理解为汇编代码指针
比如:
代码从 MOV AX 0220H 开始,那么代码段指向这行代码地址, 如果想要跳过这行代码的执行,那么进行代码段偏移
在通过debug模式配合-u指令查看汇编代码时,可以根据CS进行范围查看:
比如:
1 | #以下模拟控制台输出 |
栈段可以通俗理解为栈指针
首先内存并没有分段,段的划分来自CPU,来自我们自己对内存的操作。由8086CPU
1 | (段地址+偏移地址=“物理地址”) |
的方式给出内存单元的物理地址,使得我们用分段的方式管理内存
可以将段通俗理解为小区的一栋楼,偏移地址为这栋楼的住户门牌号.比如五号楼101房,那么形象比喻:
1 | 五号楼---->段地址 101房---->偏移地址 |
直接使用一个物理地址岂不是更简单,何必拆分成段地址+物理地址?
这是由于8086cpu16位寄存器局限性造成的, 由于16位的寄存器最大只能存放0xFFFF 如果存放超过五位的地址比如0xFFFFA 则无法存放, 为了解决这个问题, cup设计者想出了 段地址*16+偏移地址的方法完美解决这个问题
物理地址=段地址*16+偏移地址
一个物理地址可以有四种写法, 比如0xFFFFA:
1 | 0xFFFFA=0xFFFF0*16+0x000A |
代码段CS 数据段DS 栈段SS 不能直接赋值, 必须通过通用寄存器中转赋值
偏移地址可以直接赋值
1 | mov ax ,2000H |
本文为作者原创 转载时请注明出处 谢谢

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

https://bitbucket.org/JesusFreke/smali/downloads/
1 | dx.bat --dex --output=./aa.dex Test.class |
1 | java -jar baksmali-2.4.0.jar d test.dex |
1 | java -jar smali-2.4.0.jar a smali文件或目录 -o 输出目录/xxx.dex |
1 | d2j-dex2jar.bat xxx.dex |
如果你嫌麻烦 可以直接使用dex2jar内部的工具 里面进行了相应的封装
android studio自带的.class转smali就非常好用,如图:

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

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
以10举例:(满10进一位)
以2F0举例:(满16进一位)
以1010举例:(满2进一位)
本文为作者原创 转载时请注明出处 谢谢

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