gcc在编译时是否会重新排列局部变量顺序?

10

我目前正在第二次阅读《黑客攻防技术宝典》并遇到了一些问题。

这本书提出了两种不同的方法来利用这两个相似的程序:auth_overflowauth_overflow2

在第一个程序中,有一个密码检查函数的布局如下:

int check_authentication(char *password) {
    int auth_flag = 0;
    char password_buffer[16];

    strcpy(password_buffer, password);
    ...
}

输入超过16个ASCII字符将会导致auth_flag的值变为大于0的某个值,从而绕过检查,正如此gdb输出所示:

gdb$ x/12x $esp
0xbffff400: 0xffffffff  0x0000002f  0xb7e0fd24  0x41414141
0xbffff410: 0x41414141  0x41414141  0x41414141  0x00000001
0xbffff420: 0x00000002  0xbffff4f4  0xbffff448  0x08048556

password_buffer @ 0xbffff40c
auth_flag @ 0xbffff41c

第二个程序颠倒了两个变量:

int check_authentication(char *password) {
    char password_buffer[16];
    int auth_flag = 0;

    strcpy(password_buffer, password);
    ...
}

作者随后提出,不可能将数据溢出到auth_flag中,这一点我本以为是正确的。然后我尝试进行缓冲区溢出,令我惊讶的是,它仍然起作用。正如您在此gdb输出中看到的那样,auth_flag变量仍然位于缓冲区之后:

gdb$ x/12x $esp
0xbffff400: 0xffffffff  0x0000002f  0xb7e0fd24  0x41414141
0xbffff410: 0x41414141  0x41414141  0x41414141  0x00000001
0xbffff420: 0x00000002  0xbffff4f4  0xbffff448  0x08048556

password_buffer @ 0xbffff40c
auth_flag @ 0xbffff41c

我想知道gcc是否会为了对齐/优化而重新排序局部变量。

我尝试使用-O0标志编译,但结果仍然相同。

你们中有人知道为什么会发生这种情况吗?

谢谢事先。


3
“重新排序”这个词似乎暗示你预期有一个最初的“排序”。但 C++ 实际上并没有指定本地变量存储的任何顺序,它们的存储被称为“自动”的——也就是不要问,不要告诉。 - Kerrek SB
2
很抱歉,您的期望与语言规则不相关联。(对不起,您说的是C而不是C ++,但同样的观点适用。)“现实与个人期望不同”可能是您经历的合适总结 :-) 自动存储只是“自动存在”,具有非常少的保证细节。 - Kerrek SB
你说得完全正确。我觉得这本书的第一个例子基于一些不准确的东西,这让我感到很奇怪。虽然我设法按照他们的期望使其正常工作了,但如果它不是一个普遍规则,那么就不应该这样呈现。 - rgehan
2
可能甚至没有顺序:在某些系统上,“auth_flag”可能甚至不在内存中,它可能在CPU寄存器中,使得攻击变得不可能。似乎这本书的作者正在使用一个非常特定的系统和编译器,具有非常特定的行为。 - user694733
1
这非常有趣。出于好奇,是否有任何方法可以让编译器使用寄存器来实现这一点?我很想看到同一个函数可以实现的不同方式。也许有特定的编译器标志吗? - rgehan
显示剩余2条评论
3个回答

12
编译器的作者完全可以为具有自动存储的局部变量实现任何分配方案。在堆栈上,password_buffer 之前或之后,它可以在寄存器中,如果代码的适当分析允许的话,它甚至可以被省略掉... 标准给你的唯一保证是:

strcpy(password_buffer, password); 如果源字符串(包括其空终止符)比目标数组 password_buffer 更长,则会引发未定义行为。无论这个未定义的行为是否符合您的需求,都完全超出了语言规范。

事实上,在某些情况下,一些实现者会故意使类似于发布的代码的行为随机化,以增加黑客攻击难度。


这就是Kerrek SB和user694733指出的。我没有考虑到编译器的输出可能与另一个编译器完全不同。这完全有道理。谢谢 :) - rgehan

0

我知道这是一个老问题。

但是在我的情况下,-fno-stack-protector标志解决了这个问题。 因此,如果我使用-fno-stack-protector编译,本地变量按照期望的顺序排序(至少对于这个简单程序是如此)。

我想知道,重新排序可能是某种保护措施。 在这里我找到了相关链接


我使用了“-fno-stack-protector”来防止代码检测溢出攻击,但它并不影响我的变量排序。 - polmonroig

0

我遇到了同样的问题。为了解决这个问题,将两个变量放在一个结构体中。在结构体中,字段总是按照结构体中定义的顺序进行定位。请注意顺序是相反的。

struct myStruct {
       int auth_flag;
       char password_buffer[16];
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接