处理器执行一个操作数为 2 个寄存器的指令时,无法判别这是有符号还是无符号数的运算。读者会知道是因为可以从后面那个条件跳转指令来判断。所以处理器同时考虑这 2 种情况,并且触发对应的标志。
使用 IDA 调试 crackme.exe 程序,运行到程序入口暂停。
如果使用 keypatch 修改指令后,发现汇编指令变成 db 形式了,记得先把修改的那段 Undefine,然后再转成 Code。
CF(CARRY FLAG)
测试一
随便可以触发到的地方打个断点,让程序运行到断点处。
使用 Keypatch 插件将第一句指令改为 ADD EAX, 1
。
在 EAX 寄存器右键单击,选择 MODIFY VALUE,输入 0xffffffff。
按 F8 键单步执行,观察 CF 标志位变化。可以看到计算结果超过了数值上限,因而触发了 CF 标志。
测试二
再将下一条指令改为 SUB EAX, EDX
。
如果两个无符号数相减结果为负这种错误,同样也会触发 CF。修改寄存器使得 EAX=0x25,EDX=0x40,同样可以看到,CF 还是为 1。
测试三
将下一条指令修改为 SUB EAX, EDX
这条指令,并设置 EAX=0x100,EDX=0x40。
按 F8 键单步执行。结果没有触发 CF 标志。
那么结论是:
在 ARM64 架构中,CF(Carry Flag,进位标志)是一个用于表示算术运算结果的状态标志。CF 的主要用途包括:
- 加法运算:如果在加法运算中发生了进位(即结果超出了寄存器的位数),CF 会被设置为 1;如果没有进位,CF 会被清零(0)。
- 减法运算:在减法运算中,CF 表示是否发生了借位。如果发生借位,CF 会被设置为 1;否则为 0。
- 无符号比较:在进行无符号整数比较时,CF 用于指示一个值是否小于另一个值。例如,使用
CMP
指令进行比较时,如果第一个操作数小于第二个操作数,CF 会被设置为 1。
CF 在许多指令中都会受到影响,包括加法、减法、位操作等,通常用于控制条件分支和处理复杂的算术逻辑。
OF(OVERFLOW FLAG)
OF 标志类似于 CF,但它是针对有符号数的。
测试一
将下一条指令改为 ADD EAX,1
指令,并使 EAX=0x7fffffff。
按 F8 键单步执行。
因为最大正数 0x7fffffff 加上 1 之后变成 0x80000000 是一个负数,产生了错误,所以触发了 OF。
测试二
同样再将 EAX(0x80000000)减去 EDX(0x40)的值。
按 F8 键执行,再次触发 OF,因为最小负数 0x80000000 减去 0x40 之后结果是一个很大正数,所以产生了错误。
OF 标志位的设置规则:
-
加法运算:
- 当两个有符号数相加的结果超出了其表示范围时,OF 会被设置为 1。
- 例如,对于 8 位有符号数,最大值是 127(0x7F),如果计算
100 + 50
,结果是 150,但 150 超出了 8 位有符号数的范围,因此 OF 被设置为 1。
-
减法运算:
- 当从一个有符号数中减去另一个有符号数时,如果结果超出了其表示范围,OF 也会被设置为 1。
- 例如,对于 8 位有符号数,计算
100 - 50
时,结果为 -150,超出了范围,因此 OF 被设置为 1。
OF 的常见用途:
- 条件跳转:在执行算术运算后,可以根据 OF 的值来决定是否进行条件跳转。例如,如果在加法操作后 OF 被设置,可以跳转到处理溢出的代码块。
- 错误检测:通过检查 OF 标志位,可以检测是否发生了溢出,这在处理有符号数时非常重要。
SF(SIGNED FLAG)
SF 标志比较简单。任何操作的结果如果是一个负数就会触发 SF。SF 只表示结果的符号,并不表示结果是对还是错。
例子一
0x8000000+0x1 结果是一个负数,结果是 0x8000001,并且触发 SF。
CF 和 OF 都没有触发,因为无论是有符号或者无符号,计算的结果都是正确的。
ZF(ZERO FLAG)
ZF 在以下条件下将会触发:在内部实际是减法的比较指令中,两个操作数相同;增或减导致结果为 0;相减结果为 0。
例子一
下一条指令改为 ADD EAX, 1
,将 EAX 的值改为 0xffffffff,再加上 1 会发生什么。
触发了 ZF,因为结果是 0。不考虑符号运算超过数值上限触发了 CF。考虑符号-1 + 1 = 0 没有错误,所以没有触发 OF。结果 0 是个非负数,所以 SF 也没有触发。
ZF(Zero Flag,零标志位)是计算机体系结构中的一个状态标志,用于指示算术或逻辑运算的结果是否为零。ZF 的设置规则和用途如下:
ZF 标志位的设置规则:
-
算术运算:
- 当执行加法、减法等算术运算后,如果结果为零,则 ZF 会被设置为 1;如果结果不为零,ZF 会被清零(0)。
- 例如,执行
ADD EAX, EBX
后,如果EAX
和EBX
的和为 0,则 ZF 被设置为 1。
-
比较运算:
- 如果两个操作数相等,ZF 设置为 1。
- 如果不相等,ZF 设置为 0。
- 在执行比较指令(如
CMP
)时,ZF 会反映比较的结果:
-
逻辑运算:
- 对于逻辑运算(如
AND
、OR
),如果结果为零,ZF 也会被设置为 1。
- 对于逻辑运算(如
ZF 的常见用途:
- 条件跳转:ZF 常用于控制流,程序可以根据 ZF 的值来决定是否进行条件跳转。例如,
JE
(Jump if Equal)指令在 ZF 设置为 1 时跳转。 - 错误检测:在执行某些运算后,可以使用 ZF 来检测是否得到了预期的结果,尤其是在处理状态或结果时。
条件跳转与标志位
再介绍一下条件跳转。
例子一
使用 keypatch 写两条指令:
SUB EAX, EDX
JB 0x401018
结果如下:
CODE:0040110C sub eax, edxCODE:0040110E nopCODE:0040110F nopCODE:00401110 nopCODE:00401111 jb loc_401018
因为指令是变长的,所以中间补了一些 nop 指令。
设置 EAX=0x40,EDX=0x2,然后按 F8 键执行 SUB 指令。
因为 EAX 的值比 EDX 的值大,所以红色的箭头会闪烁,没有执行跳转。
上面是 SUB 执行后的标志寄存器状态。JB 是一个无符号条件跳转指令,如果触发 CF 标志,跳转将会执行。由于指令对两个正数进行操作,结果也是正数,这表示第一个数大于第二个数,所以没有触发 CF 标志,也没有引发跳转。
例子二
将 EAX 设置为 0x40,EDX 设置为 0x80,然后再执行这条减法指令。
由于 EAX 小于 EDX,程序会沿着绿色箭头执行跳转。
由于 JB 是否执行取决于 CF 标志,因为这两个无符号数相减的结果是个负数产生了错误,导致 CF 触发,所以跳转被执行。SF 也被触发,因为结果是个负数。而 OF 没有触发,因为作为有符号数 0x40 – 0x80 的结果就是负数,结果是正确的。
例子三
现在将 JB 改成 JL。
执行 SUB 之后,程序跟着绿色箭头执行跳转,因为第一个数比第二个数小(less),但是决定 JL 的是哪一个标志呢。
当 SF 不等于 OF 的时候执行 JL 跳转。
本例中 SF=1,OF=0 所以执行了跳转。
因为 SUB 与 CMP 指令类似,除了它会保存计算的结果,只要第一个数比第二个数小(less)就会触发 SF 执行跳转。
这部分内容的目的是作者不希望读者完全通过标志来判断条件跳转。还是要理解它本身的含义。也就是当 2 个操作数相同,JZ 指令会执行跳转。如果第一个无符号操作数比第二个小,JB 指令会执行跳转。如果第一个有符号操作数比第二个小,JL 指令会执行跳转。一般只需要看无符号条件跳转和有符号条件跳转表格就行。当然深入研究也没有问题。
条件跳转
在 x86 汇编语言中,条件跳转指令用于根据某个条件的真值来决定程序的控制流。以下是常见的条件跳转指令及其对应的条件:
指令 | 条件 | 描述 |
---|---|---|
JE (Jump if Equal) | ZF = 1 | 如果两个值相等,跳转 |
JNE (Jump if Not Equal) | ZF = 0 | 如果两个值不相等,跳转 |
JG (Jump if Greater) | ZF = 0 and SF = 0 | 如果第一个值大于第二个值 |
JGE (Jump if Greater or Equal) | SF = 0 | 如果第一个值大于或等于第二个值 |
JL (Jump if Less) | SF ≠ OF | 如果第一个值小于第二个值 |
JLE (Jump if Less or Equal) | ZF = 1 or SF ≠ OF | 如果第一个值小于或等于第二个值 |
JA (Jump if Above) | ZF = 0 and CF = 0 | 对于无符号数,如果第一个值大于第二个值 |
JAE (Jump if Above or Equal) | CF = 0 | 对于无符号数,如果第一个值大于或等于第二个值 |
JB (Jump if Below) | CF = 1 | 对于无符号数,如果第一个值小于第二个值 |
JBE (Jump if Below or Equal) | ZF = 1 or CF = 1 | 对于无符号数,如果第一个值小于或等于第二个值 |
JO (Jump if Overflow) | OF = 1 | 如果发生了溢出,跳转 |
JNO (Jump if Not Overflow) | OF = 0 | 如果没有发生溢出,跳转 |
JS (Jump if Sign) | SF = 1 | 如果结果为负,跳转 |
JNS (Jump if Not Sign) | SF = 0 | 如果结果为非负,跳转 |
关注我的微信公众号:二手的程序员。