我有这样的代码:
#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
编译它。 然后我用 gdb 打开溢出文件并反汇编 overflow_me 函数。
endbr32
0x00001211 <+4>: push %ebp
0x00001212 <+5>: mov %esp,%ebp
0x00001214 <+7>: push %ebx
0x00001215 <+8>: sub $0x6c,%esp
我想知道为什么堆栈分配 108 个字节。我预计这将是0x64而不是0x6c。
整体拆卸功能:
0x0000120d <+0>: endbr32
0x00001211 <+4>: push %ebp
0x00001212 <+5>: mov %esp,%ebp
0x00001214 <+7>: push %ebx
0x00001215 <+8>: sub $0x6c,%esp
0x00001218 <+11>: call 0x1110 <__x86.get_pc_thunk.bx>
0x0000121d <+16>: add $0x2db3,%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 $0x8,%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 $0x8,%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
看起来额外的空间用于堆栈cookie(默认情况下-fstack-protector=strong
打开,-fpie
也使代码复杂化),以及无缘无故地将堆栈参数从EBP上方复制到下方。
使用-fno-stack-protector -fno-pie
简化 asm。 它们在 Godbolt 上默认处于关闭状态,较新的 GCC 不会浪费从 EBP+8 复制到另一个本地的指令,因此 https://godbolt.org/z/7bMzxGKsd 说明当您使用较新的 GCC 进行不同编译时,您确实只保留了 100 字节的堆栈空间。 我还使用-fverbose-asm
用 var 名称注释 asm。 32 位 PIE 代码很糟糕(PC 相对寻址在 x86-64 64 位模式下是新的),所以很难阅读,这就是为什么我提到-fno-pie
,即使它不影响堆栈使用。 (除了调用 thunk 将当前 EIP 放入整数寄存器时的片刻。
我通过使用 |n|> 0x6c 查找n(%ebp)
找到了发生了什么,所以我发现了mov %eax,-0x70(%ebp)
(前面是来自8(%ebp)
的负载,即 arg)和pushl -0x70(%ebp)
,这也清楚地表明这是dizi
被推送为 printf 参数的副本。
另外,mov %gs:0x14,%eax
和call 0x1320 <__stack_chk_fail_local>
很明显这是用某种形式的-fstack-protector
编译的,所以我看了看,发现它存储了堆栈cookie到mov %eax,-0x8(%ebp)
。 (在保存的 EBX 正下方,它本身位于保存的 EBP 下方,EBP 帧指针在费心设置后指向该 EBP。
Comments 提到了 EBX,但保存的 EBP 和 EBX 的空间由push %ebp
和push %ebx
分配,它们本身会修改 ESP,而不是sub $0x6c,%esp
的一部分。
请注意,GCC有时会分配比局部变量 + 对齐所需的更多的堆栈空间(为什么 GCC 在堆栈上分配的空间超过了对齐所需的空间?)但这不是它在这里所做的:它正在使用它保留的堆栈空间的每个字节。 没有用,但是您要求它不要优化,因此它制作了愚蠢的代码。:P
查看反汇编的代码,它似乎正在检查strcpy
函数中的数据溢出。在 <+28> 和 <+34> 时,它会将一个幻数gs:0x14
到刚过buff_array
末尾的地址。然后在strcpy
返回后,在 <+74>-<+84>,它会检查这个幻数是否未被覆盖。如果有,它知道复制了 100 多个字节,并表示__stack_chk_fail_local
错误。