逆向学习笔记 1: 寄存器

反编译是逆向工程中无法绕开的关键步骤。要掌握逆向工程,必须深入理解反编译,而这又要求对汇编语言有足够的了解。学习汇编的第一步是熟悉计算机中各个寄存器的作用和功能。

1. 通用寄存器扩展

最初的 x86 架构是 16 位的,后来随着实际运用的需要, 寄存器的位数不断增加, 在现代的 CPU 架构中,寄存器的位宽从 16 位扩展到 32 位,再扩展到 64 位时,新寄存器通常是基于原有寄存器扩展而来。

以通用64位寄存器RAX为例:

图片

名称 位宽 描述
RAX 64 全部64位寄存器
EAX 32 RAX的低32位
AX 16 EAX的低16位
AH 8 AX的高8位
AL 8 AX的低8位

如果所示, 寄存器扩展是基于复用低位的基础实现的, 例如EAX 包含了 AX,RAX 包含了 EAX。寄存器低位的访问(如 AX、AH、AL)会影响到对应的高位寄存器

这种设计充分利用了硬件资源,保证了向后兼容性,同时支持更高的运算位宽。

2. 指令指针寄存器 (EIP)

指令指针寄存器(EIP)指向下一条即将执行的指令, 通常情况下来说, 程序的执行从入口点开始, 并且按顺序执行. 当碰到条件, 循环语句时, 顺序可能被破坏. 在汇编中比较常见的有: CALL, RET, JE, JNE, JMP等。

3. 标志寄存器

标志寄存器是 x86 架构中一个重要的寄存器,它用于存储处理器的状态信息和控制特定操作行为。其每一位都有独特的含义,分为状态标志、控制标志和系统标志。

图片

本节将主要解释其中的两位标志位的含义: CF(Carry Flag)和OF(Overflow Flag)。

3.1 CF和OF的定义

CF 用于描述无符号数加法中是否发生了溢出:当最高位向更高位进行了进位或者借位时, CF将置为1, 否则为0。这一点非常容易理解, 因为多出来的进位或者借位已经造成了信息丢失, 这显然已经溢出。

OF 用于描述有符号数加法中是否发生了溢出:当两个符号位相同的数相加后,其结果的符号位发生了改变时置1,否则为0。这个概念乍一看比较抽象,最好结合二进制加法进行理解。

3.2 二进制数的表示和二进制加法

寄存器存放和操作二进制数时,实际上是对该数的补码进行操作。对于正数,其补码就是本身,而对于一个负数,其补码为对应的正数的原码取反再加1获得。

例如,BYTE -1的补码为1111 1111, -128的补码和原码都为1000 0000

在对寄存器中的二进制数据进行加法时,机器对于该数据是否有符号是无意识的,机器不知道也不关心寄存器中的数据是否有符号, 机器只需要将二者的补码相加即可得到结果,这也是补码运算的巧妙之处。

3.3 一个例子

假设要相加的两个寄存器 AH 和 AL 中存放的数据最高位分别是:

  • 均为 0: 运算结果最高位为 0,则正常, CF = 0, OF = 0;
  • 均为 0: 运算结果最高位为 1,对于有符号数,就相当于两个整数相加得到了一个负数,这显然已经发生了溢出,此时 CF = 0,OF = 1。
  • 0 和 1: 对于有符号数来说,这相当于相同数据类型的一个正数减去了另一个正数,这显然是不可能发生溢出的;而对于无符号数,这取决于最高位是否发生了进位。此时始终有 OF = 0。
  • 均为 1: 运算结果最高位为 1, 说明最高位的更高位发生了进位,而如果这是一个有符号数,则符号未变, CF = 1, OF = 0。
  • 均为 1: 运算结果最高位为 0, 首先最高位的更高位一定发生进位;对于有符号数, 相当于两个负数相加得到了一个正数, 这显然已经发生了溢出, 此时 CF = 0,OF = 1。

这就是 OF 定义规则的由来。总结:CF 与是否发生最高位向更高位进位有关,而 OF 是与符号溢出有关。

图片