我正在玩简单的缓冲区溢出。但是,我发现这样的编译器行为非常有趣:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(char *arg1) {
int authenticated = 0;
char buffer[4];
strcpy(buffer, arg1);
if(authenticated) {
printf("HACKED !\n");
} else {
printf("POOR !\n");
}
return;
}
int main() {
char* mystr = "abcdefghijkl";
func(mystr);
printf("THANK YOU!\n");
return 0;
}
让我怀疑的是,我需要为arg1分配13个元素的缓冲区,而不是5个元素,以覆盖认证变量。
GDB确认:
(gdb) print &authenticated
$1 = (int *) 0x7fffffffe75c
(gdb) print &buffer
$33 = (char (*)[4]) 0x7fffffffe750
地址之间的差异为12。 为什么在这种情况下编译不是最优的?
在重构这个函数的情况下,差异在变化,但为什么不总是差异为4,这似乎是最佳解决方案。
谢谢
答案 0 :(得分:3)
变量之间的距离比您预期的要大,因为它们是aligned到optimize performance。某些操作要求变量的内存位置是某个数字的整数倍(通常是变量的大小)。因此,例如,8字节double
可以放在内存中的0x1000位置,或0x1008,但不是0x1004。这里是你的堆栈最终看起来如何(没有优化等),数字表示从堆栈底部的偏移量:
-16: char[] buffer (4 bytes)
-12: padding (8 bytes)
-4: int authenticated (4 bytes)
int可以理解为以4个字节对齐,但为什么char缓冲区对齐为16个字节?为了能够利用SSE instructions进行字符串操作。这些需要16位存储器对齐。编译禁用SSE的程序(-mno-sse
与gcc)导致此布局:
-8: char[] buffer (4 bytes)
-4: int authenticated (4 bytes)
这样就可以确认额外的填充是由于SSE造成的。
答案 1 :(得分:1)
您正在调用未定义的行为。任何事情都可能发生。
优化编译器会注意到在strcpy之后没有使用缓冲区,因此可以删除strcpy操作。没有未定义的行为,它不会有任何可检测的副作用。
优化(或非优化)编译器会注意到&#34;经过身份验证的&#34;除非存在未定义的行为,否则始终为0并且永远不会更改,并且编译器始终可以假定没有未定义的行为。因此,总是打印出来是绝对没问题的!\ n&#34;。
因此,在存在未定义行为的情况下,您尝试从实验中得出的任何结论都是100%无根据的。