C 一种预期的缓冲区溢出,它并不总是导致程序崩溃
考虑以下最小C程序: 案例编号1:C 一种预期的缓冲区溢出,它并不总是导致程序崩溃,c,memory,stack,buffer-overflow,C,Memory,Stack,Buffer Overflow,考虑以下最小C程序: 案例编号1: #include <stdio.h> #include <string.h> void foo(char* s) { char buffer[10]; strcpy(buffer,s); } int main(void) { foo("01234567890134567"); } void main() { foo("012345678901345678");
#include <stdio.h>
#include <string.h>
void foo(char* s)
{
char buffer[10];
strcpy(buffer,s);
}
int main(void)
{
foo("01234567890134567");
}
void main()
{
foo("012345678901345678");
^
}
程序因分段错误而崩溃
看起来除了堆栈中保留的10个字符之外,还有一个额外的空间容纳8个字符。因此,第一个程序不会崩溃。但是,如果再添加一个字符,则会开始访问无效内存。我的问题是:
Cygwin
通用条款4.8.3
Windows 7操作系统 编辑:
#include <stdio.h>
#include <string.h>
void foo(char* s)
{
char buffer[10];
strcpy(buffer,s);
}
int main(void)
{
foo("01234567890134567");
}
void main()
{
foo("012345678901345678");
^
}
这是从生成的程序集,但是使用GCC4.8.2,我在可用的编译器中看不到GCC4.8.3。但我想生成的代码应该是类似的。我构建的代码没有任何标志。我希望具有汇编专业知识的人能够解释foo函数中发生了什么,以及为什么额外的字符会导致seg错误
foo(char*):
pushq %rbp
movq %rsp, %rbp
subq $48, %rsp
movq %rdi, -40(%rbp)
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movq -40(%rbp), %rdx
leaq -32(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call strcpy
movq -8(%rbp), %rax
xorq %fs:40, %rax
je .L2
call __stack_chk_fail
.L2:
leave
ret
.LC0:
.string "01234567890134567"
main:
pushq %rbp
movq %rsp, %rbp
movl $.LC0, %edi
call foo(char*)
movl $0, %eax
popq %rbp
ret
堆栈中的下一个条目是64位系统中的函数地址开关必须与8对齐,这样就有足够的空间容纳16个字符
您可以通过在数组后声明一个int变量来验证这一点。Int将与4对齐,字符空间将减少,所以程序将在较小的数字上崩溃 我相信您理解您已经实施了一些导致未定义行为的措施。因此,很难回答为什么它使用额外的字符串失败,而不使用原始字符串。它可能与受编译标志(如对齐、优化等)影响的内部编译器实现有关 您可以尝试反汇编二进制代码或创建汇编代码,并查看缓冲区在堆栈上的确切位置。您可以对不同的优化级别执行相同的操作,以检查汇编代码和行为中的更改 操作系统(本例中为Windows)如何检测坏内存访问? 通常根据Windows文档,默认堆栈大小为 1MB堆栈大小。所以我看不出操作系统是如何检测到地址 被访问在进程内存之外,特别是当 最小页面大小通常为4k。在这种情况下,操作系统是否使用SP 要查地址吗 操作系统不会监视您执行的代码。硬件(CPU)执行(因为它执行此代码)。一旦您的代码尝试访问未分配给您的进程(不是为您的程序)的地址,操作系统将得到指示,因为硬件将触发#PF(页面错误)异常。另一种情况是,您试图访问分配给您的地址,但权限不正确(例如,您试图从没有“执行”权限的数据页执行二进制数据),或者转到代码页,但偏移量错误,并且您读取的指令不存在,或者(甚至更糟)它存在并解码成你不期望的东西(我们以前说过未定义的行为吗?) 通常,您的代码很可能不会在strcpy上失败(如果您写入足够的数据以访问某些禁止的地址,则可能会失败,但很可能不是这样)-当它从
foo
函数返回时失败strcpy
只需重写下一条指令指针,该指针指向foo
函数之后的下一条指令。因此,指令指针被“012345678901345678”字符串中的数据填充,并尝试从“junky”地址获取下一条指令,但由于上述原因失败
这种“方法”/“bug”被称为“”,并在黑客中广泛使用,以使您的代码(通常是以更高权限执行的OS/BIOS/VMM/SMM代码)执行黑客提供的恶意代码。只需确保用事先准备好的代码地址覆盖指令指针。官方的系统无关答案是: 您的代码将数据写入目标阵列的末尾之外,行为未定义,任何事情都可能发生,包括什么也不发生或太空探测器在火星表面坠毁。您观察到的缓冲区结束后最多8个字节的无明显影响,以及超出缓冲区的分段错误导致的崩溃都是未定义行为的可能影响,在预期结果范围内 您感兴趣的额外实施细节: 实际行为将取决于许多情况,例如您使用的编译器、操作系统和ABI(应用程序二进制接口)等 您的程序是在64位Windows环境中编译和执行的。在此环境中,堆栈在64位边界或16字节边界上保持对齐,以允许从堆栈位置直接加载和存储MMX寄存器。数组
缓冲区[10]
占用堆栈上的16个字节。给定堆栈是如何在此处理器上建立的,它将位于函数foo
用于将任何保存的寄存器和返回地址存储到调用函数main
中的位置的正下方。额外的6个字节是在数组之前还是之后是编译器的选择。它可以将这个空间用于其他局部变量,也可以忽略它
如果填充在数组之后,超过缓冲区结尾写入最多6个字节可能是无害的,另外8个字节可能不会产生任何明显的影响(关闭保存的rbp
寄存器,