我不了解以下内容:在一个非常简单的虚拟C函数中:

当我使用gdb进行拆装时,我得到:

void vulnerable_function(char* string) { 
    char buffer[100];
}


我真的不明白为什么堆栈指针减0x88。

我相信它将是0x64而不是0x88。您能解释一下吗?

非常感谢!

评论

您可以添加有关可执行文件的更多信息吗,例如文件./executable

#1 楼

编译器为进程运行时堆栈上的函数的堆栈框架分配多少空间涉及几个因素:


将参数的副本保存到堆栈中所需的空间框架
将局部变量存储在堆栈中所需的空间框架
将堆栈对齐到16字节边界(i386体系结构的GCC默认值)

背景

i386 ABI

堆栈框架规范

在System V应用程序的第3章“低级系统信息”中给出了x86机器上的堆栈框架的规范。二进制接口Intel386体系结构处理器补充,第四版,标题为“函数调用序列”部分。

注意:是16位对象,术语word是指32位对象,术语doubleword是指64位对象。


以下是相关摘录:


堆栈是字对齐的。尽管体系结构不需要堆栈的任何
对齐方式,但是软件约定和操作系统要求堆栈在单词边界上对齐。


参数的大小会增加,如有必要,将其改为多个单词。根据参数的大小,这可能需要填充尾部。


其他区域取决于编译器和正在编译的代码。标准调用序列没有定义最大堆栈框架大小,也没有限制语言系统如何使用标准堆栈框架的“未指定”区域。


堆栈框架中的“未指定”区域是为局部变量创建的空间,该函数的参数将复制到该空间。该空间由编译器管理。

这是ABI的图:


对齐方式

是由编译器管理堆栈框架,并且为了使堆栈框架对齐,还必须知道堆栈框架内变量的对齐方式。

变量的对齐方式取决于它们的类型和CPU的体系结构。
这也在ABI中指定:


有约定专门与数组,结构和联合的对齐有关的内容:


集合(结构和数组)和联合假定其最严格对齐的组件的对齐。任何对象的大小(包括聚合和并集)始终是对象对齐方式的倍数。数组使用与元素相同的对齐方式。结构和联合对象可能需要填充以符合大小和对齐约束。任何填充的内容都是不确定的。


但是,在i386体系结构系统上,默认情况下,GCC会将堆栈对齐到16字节边界:


-mpreferred-stack-boundary=num
尝试保持堆栈边界与2对齐到num字节边界。如果未指定-mpreferred-stack-boundary,则默认值为4(16字节或128位)。


这意味着编译器在堆栈帧上为类型大小小于16个字节的变量分配16个字节的空间。例如,即使在i386系统上int是4字节,编译器仍会为其在堆栈框架上分配16字节的空间。

脆弱性函数()的堆栈框架

让我们通过2个简单的示例分析编译器如何在函数的堆栈框架上分配空间:带有char指针局部变量的函数和带有100字节char数组的函数。

A带有pointer_test指针局部变量的char函数:

void pointer_test(void) {
    char *i = "test";
}


gcc + as生成的汇编代码:

Dump of assembler code for function pointer_test:
   0x080483db <+0>:     push   %ebp
   0x080483dc <+1>:     mov    %esp,%ebp
   0x080483de <+3>:     sub    
void char_array_test(void) {
    char buffer[100];
}
x10,%esp <-- 16 bytes of space created for 4-byte pointer 0x080483e1 <+6>: movl
Dump of assembler code for function char_array_test:
   0x0804844b <+0>:     push   %ebp
   0x0804844c <+1>:     mov    %esp,%ebp
   0x0804844e <+3>:     sub    q4312078qx78,%esp  <-- 120 bytes of space created for 100-byte array
   0x08048451 <+6>:     mov    %gs:0x14,%eax
   0x08048457 <+12>:    mov    %eax,-0xc(%ebp)
   0x0804845a <+15>:    xor    %eax,%eax
   0x0804845c <+17>:    nop
   0x0804845d <+18>:    mov    -0xc(%ebp),%eax
   0x08048460 <+21>:    xor    %gs:0x14,%eax
   0x08048467 <+28>:    je     0x804846e <char_array_test+35>
   0x08048469 <+30>:    call   0x8048310 <__stack_chk_fail@plt>
   0x0804846e <+35>:    leave  
   0x0804846f <+36>:    ret
x8048480,-0x4(%ebp) 0x080483e8 <+13>: nop 0x080483e9 <+14>: leave 0x080483ea <+15>: ret


在这里,我们看到为4字节的指针分配了16字节的空间。

带有char数组局部变量的名为char_array_test的函数: br />这里我们看到为100个字节的数组分配了120个字节的空间。

对于gcc,必须由as为4字节指针和100字节数组分配堆栈帧中的空间。


正如我们在void vulnerable_function(char *string)中观察到的那样,由于gcc默认情况下将分配的空间对齐到16个字节的边界,因此还在堆栈帧上为4个字节的指针pointer_test()分配了16个字节的空间。函数的参数。
我们在上面的gcc中观察到char *string为100字节数组分配了120个字节的空间(120不是16的倍数,因此不与16字节边界对齐。)不知道为什么编译器会这样做)。同样,编译器在char_array_test()中为gcc分配120个字节的空间。

char buffer[100]的0x10字节+ vulnerable_function()的0x78字节= 0x88


资源

Compiler Explorer是在您的计算机上运行的交互式编译器浏览器。与不断地重新编译和分解代码相比,使用它要快得多。 GCC的64个选项

string和x86调用约定讨论了x86编译器中的调用约定

Poke-a-hole,朋友是一篇
文章,讨论了结构如何进行填充以维护对齐以及跨架构的变化。

相关的SO问题

堆栈分配,填充和对齐

什么是“堆栈对齐”?

#2 楼

正如SYS_V在其答案中正确引用的那样,GCC文档指出,GCC默认情况下将使堆栈指针与16字节边界对齐。


尝试使堆栈边界与2对齐,以提高到num byte
边界。如果未指定-mpreferred-stack-boundary=num
默认值为4(16字节或128位)。在64位体系结构中,必须进行16字节对齐。库)以未对齐的堆栈调用。在这种情况下,SSE指令可能会导致未对齐的内存访问陷阱[并且]对于16字节对齐的对象,变量参数的处理不正确[...]您必须使用相同的值构建所有模块。其中包括系统库和启动模块。


但是,请注意,这主要是关于堆栈框架(边界)的,不一定是堆栈上的单个对象。这种框架对齐不是在函数内部发生的,而是在调用站点上您会看到类似这样的内容(请注意-mpreferred-stack-boundary的额外减法):也可以使(某些)对象保持对齐。

在您的示例中,您遇到了分配给100字节缓冲区的0x88(= 136)字节,而SYS_V却获得了0x78(= 120)的字节。请注意,这两个值均为8模16。之所以选择此值,是因为此时您的堆栈帧已经包括两个4字节值:返回地址和保存的帧指针。结合这些,您将在分配后最终以16字节对齐。