GCC 分配的堆栈 space 比本地人需要的多,即使没有对齐。 space 有什么用?
GCC allocates more stack space than needed for locals, even without alignment. What's it using the space for?
我有这样的代码:
#include <stdio.h>
#include <string.h>
void overflow_me(char* dizi){
char buff_array[100];
strcpy(buff_array,dizi);
printf("Hosgeldin %s",buff_array);
}
int main(int argc, char *argv[]){
overflow_me(argv[1]);
return 0;
}
我使用gcc -g -o overflow overflow.c -m32 -mpreferred-stack-boundary=2
this 编译它。
然后我用gdb打开溢出文件并反汇编overflow_me函数。
endbr32
0x00001211 <+4>: push %ebp
0x00001212 <+5>: mov %esp,%ebp
0x00001214 <+7>: push %ebx
0x00001215 <+8>: sub [=13=]x6c,%esp
不知为什么栈分配了108字节。我预计那将是 0x64 而不是 0x6c。
整个反汇编函数:
0x0000120d <+0>: endbr32
0x00001211 <+4>: push %ebp
0x00001212 <+5>: mov %esp,%ebp
0x00001214 <+7>: push %ebx
0x00001215 <+8>: sub [=14=]x6c,%esp
0x00001218 <+11>: call 0x1110 <__x86.get_pc_thunk.bx>
0x0000121d <+16>: add [=14=]x2db3,%ebx
0x00001223 <+22>: mov 0x8(%ebp),%eax
0x00001226 <+25>: mov %eax,-0x70(%ebp)
0x00001229 <+28>: mov %gs:0x14,%eax
0x0000122f <+34>: mov %eax,-0x8(%ebp)
0x00001232 <+37>: xor %eax,%eax
0x00001234 <+39>: pushl -0x70(%ebp)
0x00001237 <+42>: lea -0x6c(%ebp),%eax
0x0000123a <+45>: push %eax
0x0000123b <+46>: call 0x10b0 <strcpy@plt>
0x00001240 <+51>: add [=14=]x8,%esp
0x00001243 <+54>: lea -0x6c(%ebp),%eax
0x00001246 <+57>: push %eax
0x00001247 <+58>: lea -0x1fc8(%ebx),%eax
0x0000124d <+64>: push %eax
0x0000124e <+65>: call 0x1090 <printf@plt>
0x00001253 <+70>: add [=14=]x8,%esp
0x00001256 <+73>: nop
0x00001257 <+74>: mov -0x8(%ebp),%eax
0x0000125a <+77>: xor %gs:0x14,%eax
0x00001261 <+84>: je 0x1268 <overflow_me+91>
0x00001263 <+86>: call 0x1320 <__stack_chk_fail_local>
0x00001268 <+91>: mov -0x4(%ebp),%ebx
0x0000126b <+94>: leave
0x0000126c <+95>: ret
看起来额外的 space 是用于堆栈 cookie(默认情况下 -fstack-protector=strong
,以及 -fpie
也使代码复杂化),并且从上面复制堆栈 arg EBP无缘无故低于。
使用-fno-stack-protector -fno-pie
简化asm。它们在 Godbolt 上默认关闭,较新的 GCC 不会浪费指令从 EBP+8 复制到另一个本地,所以 https://godbolt.org/z/7bMzxGKsd 说明你只得到 100 字节的堆栈 space 保留您使用较新的 GCC 进行不同的编译。我还使用 -fverbose-asm
来注释带有 var 名称的 asm。 32 位 PIE 代码很糟糕(PC 相对寻址是 x86-64 64 位模式中的新内容),因此很难阅读,这就是我提到 -fno-pie
的原因,即使它不影响堆栈使用。 (除了调用 thunk 将当前 EIP 放入整数寄存器时的片刻。)
我通过使用 |n| 查找 n(%ebp)
找到了发生了什么> 0x6c,所以我发现了 mov %eax,-0x70(%ebp)
(前面是来自 8(%ebp)
的加载,即 arg。)和 pushl -0x70(%ebp)
这也清楚地表明这是 [= 的副本19=] 作为 printf 的参数被推送。
此外,mov %gs:0x14,%eax
和 call 0x1320 <__stack_chk_fail_local>
很明显这是用某种形式的 -fstack-protector
编译的,所以我查看并发现它将堆栈 cookie 存储到 mov %eax,-0x8(%ebp)
. (在保存的 EBX 正下方,它本身在保存的 EBP 下方,EBP 帧指针在费心设置之后指向它。)
评论中提到了EBX,但是space 保存的EBP 和EBX 是由push %ebp
和push %ebx
分配的,它们本身修改了ESP,而不是sub [=26=]x6c,%esp
的一部分。
请注意,GCC 确实 有时分配的堆栈 space 比局部变量 + 对齐 (Why does GCC allocate more space than necessary on the stack, beyond what's needed for alignment?) 所需的更多,但这不是它在这里所做的:它正在使用它保留的堆栈 space 的每个字节。没有用,但你要求它不要优化所以它产生了愚蠢的代码。 :P
查看您的反汇编代码,它似乎正在检查 strcpy
函数中的数据溢出。在 <+28> 和 <+34> 处,它向刚好超过 buff_array
末尾的地址写入一个幻数 gs:0x14
。然后在 strcpy
returns 之后,在 <+74>-<+84>,它检查这个幻数没有被覆盖。如果有,它知道复制了超过 100 个字节,并用 __stack_chk_fail_local
.
发出错误信号
我有这样的代码:
#include <stdio.h>
#include <string.h>
void overflow_me(char* dizi){
char buff_array[100];
strcpy(buff_array,dizi);
printf("Hosgeldin %s",buff_array);
}
int main(int argc, char *argv[]){
overflow_me(argv[1]);
return 0;
}
我使用gcc -g -o overflow overflow.c -m32 -mpreferred-stack-boundary=2
this 编译它。
然后我用gdb打开溢出文件并反汇编overflow_me函数。
endbr32
0x00001211 <+4>: push %ebp
0x00001212 <+5>: mov %esp,%ebp
0x00001214 <+7>: push %ebx
0x00001215 <+8>: sub [=13=]x6c,%esp
不知为什么栈分配了108字节。我预计那将是 0x64 而不是 0x6c。
整个反汇编函数:
0x0000120d <+0>: endbr32
0x00001211 <+4>: push %ebp
0x00001212 <+5>: mov %esp,%ebp
0x00001214 <+7>: push %ebx
0x00001215 <+8>: sub [=14=]x6c,%esp
0x00001218 <+11>: call 0x1110 <__x86.get_pc_thunk.bx>
0x0000121d <+16>: add [=14=]x2db3,%ebx
0x00001223 <+22>: mov 0x8(%ebp),%eax
0x00001226 <+25>: mov %eax,-0x70(%ebp)
0x00001229 <+28>: mov %gs:0x14,%eax
0x0000122f <+34>: mov %eax,-0x8(%ebp)
0x00001232 <+37>: xor %eax,%eax
0x00001234 <+39>: pushl -0x70(%ebp)
0x00001237 <+42>: lea -0x6c(%ebp),%eax
0x0000123a <+45>: push %eax
0x0000123b <+46>: call 0x10b0 <strcpy@plt>
0x00001240 <+51>: add [=14=]x8,%esp
0x00001243 <+54>: lea -0x6c(%ebp),%eax
0x00001246 <+57>: push %eax
0x00001247 <+58>: lea -0x1fc8(%ebx),%eax
0x0000124d <+64>: push %eax
0x0000124e <+65>: call 0x1090 <printf@plt>
0x00001253 <+70>: add [=14=]x8,%esp
0x00001256 <+73>: nop
0x00001257 <+74>: mov -0x8(%ebp),%eax
0x0000125a <+77>: xor %gs:0x14,%eax
0x00001261 <+84>: je 0x1268 <overflow_me+91>
0x00001263 <+86>: call 0x1320 <__stack_chk_fail_local>
0x00001268 <+91>: mov -0x4(%ebp),%ebx
0x0000126b <+94>: leave
0x0000126c <+95>: ret
看起来额外的 space 是用于堆栈 cookie(默认情况下 -fstack-protector=strong
,以及 -fpie
也使代码复杂化),并且从上面复制堆栈 arg EBP无缘无故低于。
使用-fno-stack-protector -fno-pie
简化asm。它们在 Godbolt 上默认关闭,较新的 GCC 不会浪费指令从 EBP+8 复制到另一个本地,所以 https://godbolt.org/z/7bMzxGKsd 说明你只得到 100 字节的堆栈 space 保留您使用较新的 GCC 进行不同的编译。我还使用 -fverbose-asm
来注释带有 var 名称的 asm。 32 位 PIE 代码很糟糕(PC 相对寻址是 x86-64 64 位模式中的新内容),因此很难阅读,这就是我提到 -fno-pie
的原因,即使它不影响堆栈使用。 (除了调用 thunk 将当前 EIP 放入整数寄存器时的片刻。)
我通过使用 |n| 查找 n(%ebp)
找到了发生了什么> 0x6c,所以我发现了 mov %eax,-0x70(%ebp)
(前面是来自 8(%ebp)
的加载,即 arg。)和 pushl -0x70(%ebp)
这也清楚地表明这是 [= 的副本19=] 作为 printf 的参数被推送。
此外,mov %gs:0x14,%eax
和 call 0x1320 <__stack_chk_fail_local>
很明显这是用某种形式的 -fstack-protector
编译的,所以我查看并发现它将堆栈 cookie 存储到 mov %eax,-0x8(%ebp)
. (在保存的 EBX 正下方,它本身在保存的 EBP 下方,EBP 帧指针在费心设置之后指向它。)
评论中提到了EBX,但是space 保存的EBP 和EBX 是由push %ebp
和push %ebx
分配的,它们本身修改了ESP,而不是sub [=26=]x6c,%esp
的一部分。
请注意,GCC 确实 有时分配的堆栈 space 比局部变量 + 对齐 (Why does GCC allocate more space than necessary on the stack, beyond what's needed for alignment?) 所需的更多,但这不是它在这里所做的:它正在使用它保留的堆栈 space 的每个字节。没有用,但你要求它不要优化所以它产生了愚蠢的代码。 :P
查看您的反汇编代码,它似乎正在检查 strcpy
函数中的数据溢出。在 <+28> 和 <+34> 处,它向刚好超过 buff_array
末尾的地址写入一个幻数 gs:0x14
。然后在 strcpy
returns 之后,在 <+74>-<+84>,它检查这个幻数没有被覆盖。如果有,它知道复制了超过 100 个字节,并用 __stack_chk_fail_local
.