4

我在 linux 5.4.18-1-MANJARO 上运行 gcc 版本 9.2.0 (GCC)

文件名:ac

#include<stdio.h>

int
main(void) {
    int a;

    scanf("%d", &a);

    if (a < 5 || a > 6)
        puts("fail");
    else
        puts("succeed");
}

然后我运行:

gcc a.c -O0 -o a.out
gcc a.c -O1 -o b.out

我用r2反编译a.out,我得到了这个

undefined8 main(void)
{
    undefined8 uVar1;
    int64_t in_FS_OFFSET;
    int32_t var_ch;
    int64_t canary;

    canary = *(int64_t *)(in_FS_OFFSET + 0x28);
    sym.imp.__isoc99_scanf(0x2004, &var_ch);
    if ((var_ch < 5) || (6 < var_ch)) {
        sym.imp.puts("fail");
    } else {
        sym.imp.puts("succeed");
    }
    uVar1 = 0;
    if (canary != *(int64_t *)(in_FS_OFFSET + 0x28)) {
        uVar1 = sym.imp.__stack_chk_fail();
    }
    return uVar1;
}

这是我所期望的。

但是当我反编译 b.out 时,我得到了这个

undefined8 main(void)
{
    undefined8 uVar1;
    undefined8 extraout_RDX;
    int64_t iVar2;
    int32_t *piVar3;
    uint32_t uVar4;
    int64_t in_FS_OFFSET;
    int32_t iStack20;
    int64_t iStack16;

    iStack16 = *(int64_t *)(in_FS_OFFSET + 0x28);
    piVar3 = &iStack20;
    sym.imp.__isoc99_scanf(0x2004, piVar3);
    if (iStack20 - 5U < 2) {
        uVar4 = 0x200c;
        sym.imp.puts("succeed");
    } else {
        uVar4 = 0x2007;
        sym.imp.puts("fail");
    }
    if (iStack16 != *(int64_t *)(in_FS_OFFSET + 0x28)) {
        sym.imp.__stack_chk_fail();
        sym._init();
        iVar2 = 0;
        do {
            uVar1 = (**(code **)(segment.LOAD3 + iVar2 * 8))((uint64_t)uVar4, piVar3, extraout_RDX);
            iVar2 = iVar2 + 1;
        } while (iVar2 != 1);
        return uVar1;
    }
    return 0;
}

这似乎只检查 iStack20 < 7,并且 b.out 运行良好。

我不明白它是如何工作的。

4

1 回答 1

4

这里利用了整数下溢。操作

iStack20 - 5U

当两个操作数的类型不同时,由于隐式转换规则而执行无符号(C 标准 6.3.1.8(1)):

否则[signedness different],如果无符号整数类型的操作数的秩大于或等于另一个操作数类型的秩,则将有符号整数类型的操作数转换为无符号整数类型的操作数的类型。

所以如果iStack20< 5,那么结果会非常大(接近UINT_MAX),因此比较

iStack20 - 5U < 2

将是错误的。如果iStack20大于 6,那么结果无论如何都会大于 2。因此保留了程序逻辑。

于 2020-02-18T12:01:35.013 回答