【发布时间】:2016-01-05 22:08:22
【问题描述】:
考虑以下最小的 C 程序:
案例编号 1:
#include <stdio.h>
#include <string.h>
void foo(char* s)
{
char buffer[10];
strcpy(buffer,s);
}
int main(void)
{
foo("01234567890134567");
}
这不会导致崩溃转储
如果只添加一个字符,那么新的主要是:
案例编号 2:
void main()
{
foo("012345678901345678");
^
}
程序因分段错误而崩溃。
看起来除了堆栈中保留的 10 个字符之外,还有一个额外的空间可容纳 8 个额外的字符。因此第一个程序不会崩溃。但是,如果您再添加一个字符,您将开始访问无效内存。我的问题是:
- 为什么我们在堆栈中保留了这 8 个额外的字符?
- 这是否与内存中的 char 数据类型对齐有关?
在这种情况下我还有一个疑问是操作系统(在这种情况下是 Windows)如何检测到错误的内存访问?通常,根据 Windows 文档,默认堆栈大小为 1MB Stack Size。所以我看不到操作系统如何检测到正在访问的地址在进程内存之外,特别是当最小页面大小通常为 4k 时。操作系统在这种情况下是否使用SP来检查地址?
PD:我正在使用以下环境进行测试
赛格温
GCC 4.8.3
视窗 7 操作系统
编辑:
这是从http://gcc.godbolt.org/# 生成的程序集,但使用 GCC 4.8.2,我在可用的编译器中看不到 GCC 4.8.3。但我猜生成的代码应该是相似的。我构建了没有任何标志的代码。我希望具有汇编专业知识的人可以阐明 foo 函数中发生了什么以及为什么额外的 char 会导致 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
【问题讨论】:
-
访问冲突通常由虚拟内存系统和MMU/MPU硬件处理。
-
我认为它会因机器而异,甚至可能因编译器而异。
-
在任何一种情况下,请注意写入未初始化的内存是未定义的行为,尤其是不保证会产生运行时错误。
-
是的,我知道 :) .. 我正在询问详细信息这是如何执行的。页面大小通常为 4K,而 TMP 只知道页面,因此如何在字节级别检测到错误访问。正如您从问题中看到的那样,由于某种我不明白的原因,没有检测到第一个案例。
-
您假设 segv 是在写入溢出期间的某个时刻直接引起的。这可能是真的,也可能不是(可能不是)。溢出更有可能成功地覆盖了随后用于有效地址计算的堆栈部分 - 例如返回地址。然后在从这个无效的有效地址加载期间发生 segv。分析堆栈框架布局将更准确地了解发生的情况。
标签: c memory stack buffer-overflow