返回介绍

10.1 x86

发布于 2025-02-22 14:00:44 字数 7426 浏览 0 评论 0 收藏 0

10.1.1 x86 + MSVC

f_signed() 函数:

Listing 10.1: 非优化 MSVC 2010

#!bash
_a$ = 8
_b$ = 12
_f_signed   PROC
        push    ebp
        mov     ebp, esp
        mov     eax, DWORD PTR _a$[ebp]
        cmp     eax, DWORD PTR _b$[ebp]
        jle     SHORT $LN3@f_signed
        push    OFFSET $SG737       ; ’a>b’
        call    _printf
        add     esp, 4
$LN3@f_signed:
        mov     ecx, DWORD PTR _a$[ebp]
        cmp     ecx, DWORD PTR _b$[ebp]
        jne     SHORT $LN2@f_signed
        push    OFFSET $SG739       ; ’a==b’
        call    _printf
        add     esp, 4
$LN2@f_signed:
        mov     edx, DWORD PTR _a$[ebp]
        cmp     edx, DWORD PTR _b$[ebp]
        jge     SHORT $LN4@f_signed
        push    OFFSET $SG741       ; ’a<b’
        call    _printf
        add     esp, 4
        $LN4@f_signed:
        pop     ebp
        ret     0
_f_signed   ENDP

第一个指令 JLE 意味如果小于等于则跳转。换句话说,第二个操作数大于或者等于第一个操作数,控制流将传递到指定地址或者标签。否则(第二个操作数小于第一个操作数)第一个 printf() 将被调用。第二个检测 JNE:如果不相等则跳转。如果两个操作数相等控制流则不变。第三个检测 JGE:大于等于跳转,当第一个操作数大于或者等于第二个操作数时跳转。如果三种情况都没有发生则无 printf() 被调用,事实上,如果没有特殊干预,这种情况几乎不会发生。

f_unsigned() 函数类似,只是 JBE 和 JAE 替代了 JLE 和 JGE,我们来看 f_unsigned() 函数

Listing 10.2: GCC

#!bash
_a$ = 8                                 ; size = 4
_b$ = 12                                ; size = 4
_f_unsigned     PROC
        push    ebp
        mov     ebp, esp
        mov     eax, DWORD PTR _a$[ebp]
        cmp     eax, DWORD PTR _b$[ebp]
        jbe     SHORT $LN3@f_unsigned
        push    OFFSET $SG2761 ; ’a>b’
        call    _printf
        add     esp, 4
$LN3@f_unsigned:
        mov     ecx, DWORD PTR _a$[ebp]
        cmp     ecx, DWORD PTR _b$[ebp]
        jne     SHORT $LN2@f_unsigned
        push    OFFSET $SG2763 ; ’a==b’
        call    _printf
        add     esp, 4
$LN2@f_unsigned:
        mov     edx, DWORD PTR _a$[ebp]
        cmp     edx, DWORD PTR _b$[ebp]
        jae     SHORT $LN4@f_unsigned
        push    OFFSET $SG2765 ; ’a<b’
        call    _printf
        add     esp, 4
$LN4@f_unsigned:
        pop     ebp
        ret     0
_f_unsigned     ENDP

几乎是相同的,不同的是:JBE-小于等于跳转和 JAE-大于等于跳转。这些指令(JA/JAE/JBE/JBE) 不同于 JG/JGE/JL/JLE,它们使用无符号值。

我们也可以看到有符号值的表示(35)。因此我们看 JG/JL 代替 JA/JBE 的用法或者相反,我们几乎可以确定变量的有符号或者无符号类型。

main() 函数没有什么新的内容:

Listing 10.3: main()

#!bash
_main   PROC
        push    ebp
        mov     ebp, esp
        push    2
        push    1
        call    _f_signed
        add     esp, 8
        push    2
        push    1
        call    _f_unsigned
        add     esp, 8
        xor     eax, eax
        pop     ebp
        ret     0
_main   ENDP

10.1.2 x86 + MSVC + OllyDbg

我们在 OD 里允许例子来查看标志寄存器。我们从 f_unsigned() 函数开始。CMP 执行了三次,每次的参数都相同,所以标志位也相同。

第一次比较的结果:fig. 10.1.标志位:C=1, P=1, A=1, Z=0, S=1, T=0, D=0, O=0.标志位名称为 OD 对其的简称。

当 CF=1 or ZF=1 时 JBE 将被触发,此时将跳转。

接下来的条件跳转:fig. 10.2.当 ZF=0(zero flag)时 JNZ 则被触发

第三个条件跳转:fig. 10.3.我们可以发现 14 当 CF=0 (carry flag) 时,JNB 将被触发。在该例中条件不为真,所以第三个 printf() 将被执行。

enter image description here

Figure 10.1: OllyDbg: f_unsigned(): 第一个条件跳转

enter image description here

Figure 10.2: OllyDbg: f_unsigned(): 第二个条件跳转

enter image description here

Figure 10.3: OllyDbg: f_unsigned(): 第三个条件跳转

现在我们在 OD 中看 f_signed() 函数使用有符号值。

可以看到标志寄存器:C=1, P=1, A=1, Z=0, S=1, T=0, D=0, O=0.

第一种条件跳转 JLE 将被触发 fig. 10.4.我们可以发现 14 ,当 ZF=1 or SF≠OF。该例中 SF≠OF,所以跳转将被触发。

下一个条件跳转将被触发:如果 ZF=0 (zero flag): fig. 10.5.

第三个条件跳转将不会被触发,因为仅有 SF=OF,该例中不为真: fig. 10.6.

enter image description here

Figure 10.4: OllyDbg: f_signed(): 第一个条件跳转

enter image description here

Figure 10.5: OllyDbg: f_signed(): 第二个条件跳转

enter image description here

Figure 10.6: OllyDbg: f_signed(): 第三个条件跳转

10.1.3 x86 + MSVC + Hiew

我们可以修改这个可执行文件,使其无论输入的什么值 f_unsigned() 函数都会打印“a==b”。

在 Hiew 中查看:fig. 10.7.

我们要完成以下 3 个任务:

1\. 使第一个跳转一直被触发;
2\. 使第二个跳转从不被触发;
3\. 使第三个跳转一直被触发。

我们需要使代码流进入第二个 printf(),这样才一直打印“a==b”。

三个指令(或字节)应该被修改:

1\. 第一个跳转修改为 JMP,但跳转偏移值不变。
2\. 第二个跳转有时可能被触发,我们修改跳转偏移值为 0 后,无论何种情况,程序总是跳向下一条指令。跳转地址等于跳转偏移值加上下一条指令地址,当跳转偏移值为 0 时,跳转地址就为下一条指令地址,所以无论如何下一条指令总被执行。
3\. 第三个跳转我们也修改为 JMP,这样跳转总被触发。

修改后:fig. 10.8.

如果忘了这些跳转,printf() 可能会被多次调用,这种行为可能是我们不需要的。

enter image description here

Figure 10.7: Hiew: f_unsigned() 函数

enter image description here

Figure 10.8: Hiew:我们修改 f_unsigned() 函数

10.1.4 Non-optimizing GCC

GCC 4.4.1 非优化状态产生的代码几乎一样,只是用 puts() (2.3.3) 替代 printf()。

10.1.5 Optimizing GCC

细心的读者可能会问,为什么要多次执行 CMP,如果标志寄存器每次都相同呢?可能 MSVC 不会做这样的优化,但是 GCC 4.8.1 可以做这样的深度优化:

Listing 10.4: GCC 4.8.1 f_signed()

#!bash
f_signed:
        mov     eax, DWORD PTR [esp+8]
        cmp     DWORD PTR [esp+4], eax
        jg      .L6
        je      .L7
        jge     .L1
        mov     DWORD PTR [esp+4], OFFSET FLAT:.LC2 ; "a<b"
        jmp     puts
.L6:
        mov     DWORD PTR [esp+4], OFFSET FLAT:.LC0 ; "a>b"
        jmp     puts
.L1:
        rep     ret
.L7:
        mov     DWORD PTR [esp+4], OFFSET FLAT:.LC1 ; "a==b"
        jmp     puts

我们可以看到 JMP puts 替代了 CALL puts/RETN。稍后我们介绍这种情况 11.1.1.。

不用说,这种类型的 x86 代码是很少见的。MSVC2012 似乎不会这样做。其他情况下,汇编程序能意识到此类使用。如果你在其它地方看到此类代码,更可能是手工构造的。

f_unsigned() 函数代码:

Listing 10.5: GCC 4.8.1 f_unsigned()

#!bash
f_unsigned:
        push    esi
        push    ebx
        sub     esp, 20
        mov     esi, DWORD PTR [esp+32]
        mov     ebx, DWORD PTR [esp+36]
        cmp     esi, ebx
        ja      .L13
        cmp     esi, ebx ; instruction may be removed
        je      .L14
.L10:
        jb      .L15
        add     esp, 20
        pop     ebx
        pop     esi
        ret
.L15:
        mov     DWORD PTR [esp+32], OFFSET FLAT:.LC2 ; "a<b"
        add     esp, 20
        pop     ebx
        pop     esi
        jmp     puts
.L13:
        mov     DWORD PTR [esp], OFFSET FLAT:.LC0 ; "a>b"
        call    puts
        cmp     esi, ebx
        jne     .L10
.L14:
        mov     DWORD PTR [esp+32], OFFSET FLAT:.LC1 ; "a==b"
        add     esp, 20
        pop     ebx
        pop     esi
        jmp     puts

因此,GCC 4.8.1 的优化算法并不总是完美的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文