汇编讲解(上)--逆向开发

Emiter0 2019-12-28

今天进入逆向开发的另一个部分--汇编知识的讲解,分为上下篇,希望通过两篇博客的讲解让大家对汇编知识有个大致的了解!!!

汇编语言发展

机器语言

机器语言:有0和1共同组成的机器语言

  • 加: 0100 0000
  • 减: 0100 1000
  • 乘: 1111 0111 1110 0000
  • 除: 1111 0111 1111 0000

汇编语言(assembly language)

汇编语言:使用助记符来代替机器语言

  • 加:INC EAX 通过编译器 0100 0000
  • 减:DEC EAX 通过编译器 0100 1000
  • 乘:MUL EAX 通过编译器 1111 0111 1110 0000
  • 除:DIV EAX 通过编译器 1111 0111 1111 0000

高级语言(High-level programming language)

高级语言:比如Java,Objective-C

  • 加:A+B 通过编译器 0100 0000
  • 减:A-B 通过编译器 0100 1000
  • 乘:A*B 通过编译器 1111 0111 1110 0000
  • 除:A/B 通过编译器 1111 0111 1111 0000

代码在手机终端上运行过程

汇编讲解(上)--逆向开发

  •  汇编语言与机器语言是一一对应,每条机器指令都是有相对应的汇编指令
  • 汇编语言是可以通过编译得到机器语言,机器语言也是可以通过反汇编来得到汇编语言
  • 高级语言是可以通过编译得到汇编语言和机器语言;但是汇编语言,机器语言几乎是不可能还原成高级语言

汇编语言的特点与语言种类

特点

  • 不区分大小写,DIV和div是一样的
  • 汇编指令是机器指令的助记符,是和机器指令是一一对应的,每一种的CPU都是有自己的机器指令集/汇编指令集
  • 目标代码短、占位内存少以及执行速度快
  • 能够不受编译器的控制,对生成的二进制代码是完全控制的
  • 能够直接访问,控制住硬件设备。

语言种类

iPhone使用的是ARM汇编,但是不同的手机设备以及架构是不同的

汇编讲解(上)--逆向开发

APP/程序执行过程

汇编讲解(上)--逆向开发

如果想深入了解汇编,下面将讲解几个插曲。

硬件最重要就是CPU和内存

插曲

总线

总线是一根根导线的集合

总线分为地址总线、数据总线、控制总线三种

汇编讲解(上)--逆向开发

  •  地址总线

地址总线宽度决定了CPU的寻址能力

8086地址总线是20,其寻址能力是2^20 = 1M

  • 数据总线

数据总线宽度决定了CPU单次传递的数据量,换句话说也是数据的传送速度

8086的数据宽度为16,单次最大传送数据量为2个字节

  • 控制总线

控制总线宽度决定了CPU对其他控件的控制能力

数据宽度  

  • 位(bit):就是二进制位,就是0或者1
  • 字节(Byte):1个字节是由8个bit(8位)组成。内存中最小的存储单元为Byte
  • 字(word):1个字由两个字节Byte组成,两个字节是高字节和低字节
  • 双字:1个双字由2个字组成

在存储数据时,分为有符号和无符号数

汇编讲解(上)--逆向开发

 寄存器

内部部件之间是由总线来相连

汇编讲解(上)--逆向开发

CPU最重要的部件是寄存器,日常工作中,通过改变寄存器的值来实现对CPU的值。

通用寄存器 

ARM64有31个64位通用寄存器X0-X30,通常用来存储一般性的数据,有时候也会有特定用途,称之为通用寄存器。

W0-W28是32位,是X0-X32的低28位,而64位CPU是兼容32位的CPU,所以使用64位的低32位。W0就是X0。

汇编讲解(上)--逆向开发

 CPU一般先将内存的数据存储到通用寄存器中,然后再对此进行运算。

pc寄存器

  • 指向了CPU当前要执行的指令地址
  • 在内存或者磁盘上,指令和数据无区别,都是二进制信息
  • CPU将pc指向的内存单元的内容看做指令,如果内存中的某段内容曾经被执行过,其内存单元必被pc指向过。

汇编讲解(上)--逆向开发

CPU的补充

CPU由计算器,控制器以及寄存器组成,其中,寄存器的作用将数据进行临行存储起来.

CPU的运行速度非常快, 为了性能的提高, CPU会在内部开辟小块的临时存储区域, 并在运算时先将数据从内存复制到这小块临时存储区域中, 在这小块区域进行运算,这小块临时区域就称作寄存器.

对于ARM64架构的CPU, 以 X 开头的就是64位的寄存器, 以 W 开头的就是32位的寄存器, 其中32位寄存器就是64位寄存器的低32位部分

汇编讲解(上)--逆向开发

 汇编讲解(上)--逆向开发

 系统中没有提供16位 和 8位的寄存器供以访问(注意: 查看时要使用真机调试, 模拟器使用的X86的架构)

高速缓存

iPhoneX搭载的ARM64处理器A11, 它的一级缓存容量是64KB, 二级缓存容量是8M (现在普遍也只有二级缓存)

CPU每执行一条指令都需要从内存中将指令读取到CPU中执行, 而寄存器运行速度要比内存的读写速度快很多, CPU还集成了一个高速缓存存储区域来提高性能. 当程序运行时, 先将要执行的指令代码存储到高速缓存存储区域中(由操作系统完成), CPU直接从高速缓存中依次读取指令来执行.

寄存器的补充

数据地址存储器

数据地址存储器通常作为数据计算的临时存储, 做累加, 计数地址保存等功能. 这些寄存器的作用主要用于在CPU指令中保存操作数, 在CPU中当做常规变量使用.
ARM64中
  • 64位: X0 - X30 , XZR(零寄存器)
  • 32位: W0 - W30 , WZR(零寄存器)
注意: 8086汇编中有一种寄存器, 段寄存器: CS, ES, DS, SS四个寄存器用来保存段的基地址, 这属于Intel CPU中, 在ARM中没有.

浮点和向量寄存器

汇编讲解(上)--逆向开发

 现在CPU支持向量运算(向量运算在图形处理中使用比较广泛), 为支持向量运算, 故提供了更多向量寄存器.

  • 向量寄存器: 128位 v0 - v128

汇编讲解(上)--逆向开发

 因为浮点数以及其运算的特殊性, CPU专门提供浮点寄存器来计算浮点数

sp, fp寄存器

  • sp寄存器会在任意时刻保存栈顶的地址
  • fp寄存器也成为x29寄存器属于通用寄存器, 但是在某些时刻我们会用它保存栈底的地址

注意: ARM64开始取消32位的LDM, STM, PUSH, POP指令. 与之替代的是 ldr/ ldp, str/ stp
ARM64里面 对栈的操作都是16进制对齐的!!!

汇编讲解(上)--逆向开发

内存的读写指令

内存的读写指令

str(store register)写入指令
将数据从寄存器中读出来, 写入到内存中

ldr(load register)读取指令
将数据从内存中读取出来, 存到寄存器中

ldp/stp 是 ldr/str 的衍生, 可以同时读/写两个寄存器, ldr/str只能读写一个

sub sp, sp, #0x20 ; 拉伸栈空间32(20 = 2*16)个字节
stp x0 , x1, [sp, #0x10] ; sp往上加16(10 = 1 * 16)个字节,存放x0 和 x1
ldp x1 , x0, [sp, #0x10] ; 将sp偏移16个字节的值取出来,放入x1 和 x0

注意: 拉伸栈空间是往低地址拉伸, 拉伸的字节数只能是16的倍数, 否则会崩溃(对照上面 16进制对齐理解 )

bl 和 ret 指令

bl: 跳转指令
1. 将下一条指令的地址存放到 lr(x30) 寄存器中
2. 跳转到对应函数中执行指令

汇编讲解(上)--逆向开发

ret: 返回指令
默认使用 lr(x30) 寄存器的值, 通过底层指令提示CPU这是下一条指令的地址.
ARM64架构中的特色指令, 面向硬件做了优化处理

汇编讲解(上)--逆向开发

lr (x30) 寄存器

  • x30寄存器存放的是函数返回的地址, 当ret执行指令时, 会寻找这个寄存器中保存的地址
注意: 在函数嵌套调用中, 需要将x30入栈, 否则可能会造成死循环.

状态寄存器

汇编讲解(上)--逆向开发
CPU内部的寄存器中, 有一种特殊的寄存器(不同的处理器, 结构和寄存器个数都可能不同). 这种寄存器在ARM中, 称为 CPSR (current program status register) 状态寄存器.
CPSR和其他寄存器不一样, 其他寄存器都是用来存放数据的, 一个寄存器对应一个含义 ; 而CPSR寄存器是按位起作用的, 每一位都有专门的含义,记录特定的信息.
注意: CPSR 寄存器是32位的
  • CPSR的低8位(包括 I , F, T 和 M[4-0])称为控制位, 程序无法修改, 除非CPU运行于特权模式下, 才能修改控制位.

  • N Z C V均为条件码标志位, 它们的内容可被算术或逻辑运算的结果所改变, 并且决定某条指令是否被执行, 意义重

汇编讲解(上)--逆向开发

N (negative) 标志

CPSR 第31位是N, 符号标志位. 它记录相关指令执行后, 其结果是否为负, 如果为负,则 N 为1; 如果非负则N为0.

Z (zero) 标志

CPSR 第30位是Z, 0标志位. 它记录相关指令执行后, 其结果是否为0, 如果为0,则Z为1; 如果非0则Z为0.

C (carry) 标志

CPSR 第29位是C, 进位标志位. 一般情况下, 进行无符号的运算.
加法运算: 当运算结果产生了进位时(无符号位溢出), 则 C = 1, 否则C=0.
减法运算(包括CMP): 当运算结果产生了借位时(无符号数溢出), 则C=0,否则C=1.

对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N - 1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。如下图所示:

汇编讲解(上)--逆向开发

进位

当两个数据相加的时候,有可能产生从最高有效位想更高位的进位。比如两个32位数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在32位中无法保存,我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM下就用C位来记录这个进位值。比如,下面的指令

mov  w0,#0xaaaaaaaa;0xa 的二进制是 1010
adds w0,w0,w0; 执行后 相当于 1010 << 1 进位1(无符号溢出) 所以C标记 为 1
adds w0,w0,w0; 执行后 相当于 0101 << 1 进位0(无符号没溢出) 所以C标记 为 0
adds w0,w0,w0; 重复上面操作
adds w0,w0,w0

借位

当两个数据做减法的时候,有可能向更高位借位。再比如,两个32位数据:0x00000000 - 0x000000ff,将产生借位,借位后,相当于计算0x100000000 - 0x000000ff。得到0xffffff01 这个值。由于借了一位,所以C位 用来标记借位。C = 0.比如下面指令:

mov  w0,#0x0
subs w0,w0,#0xff ;
subs w0,w0,#0xff
subs w0,w0,#0xff

V(overflow)溢出标志

CPSR的第28位, 溢出标志位. 在进行有符号数运算的时候, 如果超出了机器所能识别的范围, 称为溢出.
  • 正数 + 正数 为 负数, 溢出
  • 负数 + 负数 为 正数, 溢出
  • 正数 + 负数 不可能溢出

以上就是关于汇编知识点内容,希望对大家有所帮助,进入了解技术语言的底层知识!!!




相关推荐