假设我有一个程序声明char buffer[size]
和另一个变量,并使用gets(buffer);
将数据写入buffer
。如果gets
提供了太长的输入,那么它将从缓冲区溢出到下一个变量中(假设该变量位于buffer
之后的下一个地址中):
void f(){
char str[12] = "hello_world";
char buffer[1];
gets(buffer); // provided with a random char and then "hello_kitty"
printf("str = %s\n", str); // no crash. Just prints "hello_kitty" as expected
}
使用"合法输入"运行时(意思是 - 没有溢出第二个缓冲区)这没关系。即使我稍微溢出缓冲区也没关系,但进入太多程序后程序崩溃了。
据我了解,这(意思是 - 没有溢出第二个缓冲区)不应该导致任何崩溃。可能导致崩溃的是破坏保存指令指针的内存,因此它现在将指向一个无效的地址(这是一个页面错误吗?)。
这是对的吗?一个不会影响堆栈/帧/指令指针的错误写入会导致崩溃吗?
答案 0 :(得分:4)
简洁地回答你的问题,没有。继续阅读以获得更长的答案。
缓冲区溢出导致分段错误的最典型方式是溢出缓冲区驻留在堆栈上,溢出会覆盖返回指针。当函数返回时返回指针弹回指令指针时,通常会发生分段错误,因为处理器尝试读取您无权访问的内存。
最后一句话非常重要。覆盖返回指针只是分段错误发生的一种方式。实际上,任何覆盖以后用于访问内存的内存地址的缓冲区溢出都可能导致分段错误,或者,如果是尝试写入内存,则会导致访问冲突。
例如,假设您在堆上分配了一个结构。该结构采用以下形式:
struct sample_struct {
char bytes[20];
struct sample_struct *next;
};
如果在没有正确验证边界的情况下将数据复制到bytes
成员中,则内存中的下一项将是next
指针。如果此指针被覆盖并且随后尝试从中读取,则很可能发生分段错误,假设现在的值表示控制之外的存储器地址。如果碰巧在内存空间中找到了一个地址,结果将是尝试将那里的字节解释为struct sample_struct
,可能会导致其他问题。
正如注意,不要假设覆盖上面的示例结构中的指针只需要21到24个字节;除非您指示编译器打包结构,否则该结构的内存分配可能包含用于对齐目的的附加字节。
答案 1 :(得分:2)
缓冲区溢出是否仅在重要指针出现时才会导致segfault 覆盖?
没必要,让我们从头开始。在大多数处理器中,内存可以分为大块或小块。大块通常称为段。一小块通常称为页面。
如果你正在写缓冲区(缓冲区溢出)和"事情"在此缓冲区后面覆盖的属于同一进程,不会立即发生故障。
link
当溢出的缓冲区在内存段的末尾结束时,会发生SEGFAULT 。除此之外,该处理器的当前状态还有什么不确定的。因此,该空隙中的存储器访问将导致总线故障。没有可用于处理器的内存内容。它不知道去哪里。因此,在这种情况下,错误是一个直接的灾难。
在 x86 或 ARM 上,在写入不在任何映射区域的页面或内存中的页面时,会得到 SEGMENT n | SEGMENT n
buffer1 | buffer2
+-----+-----+-----+-----+-----+-----+-----+-----+
| 'a' | 'a' | 'a' | 'a' | 'a' | 'a' | 'a' | 'a' |
+-----+-----+-----+-----+-----+-----+-----+-----+
^-----^-----^-----^-----^-----^-----^-----^-------- Writing to variables of same process (UB)
以只读方式映射的区域,或从不在任何映射区域中的地址读取的区域。
SIGSEGV
SEGFAULT 通常是在程序计数器返回地址(在堆栈后面的参数上)被覆盖时引起的,并且在弹出后继续执行后,程序计数器跳转到某处(取决于具有的值)已写入指针)并且无法在那里阅读。
即使我稍微溢出缓冲区也没关系
您已在同一段中覆盖属于您的进程的仅内存(无返回地址),但仍为UB。
但进入太多后,程序崩溃了。
是的,你有可能写入另一个段或覆盖的返回地址。
答案 2 :(得分:1)
如果您要阅读Aleph One的smashing the stack for fun and profit,您就会知道计算机被溢出缓冲区(并覆盖重要指针)攻击。
...正如文章所述,没有 segfault ,因为部分漏洞利用代码通常是exit(0);
,前提是攻击者无法以更好的方式恢复程序。
所以回答你的问题:
这是一个页面错误吗?
不一定。这种概念不一定存在于C世界中。
这是对的吗?一个不会影响堆栈/帧/指令指针的错误写入会导致崩溃吗?
当然可以,但这不是必须依赖的。这就是为什么人们认为未定义的行为,例如缓冲区溢出会导致真正发生的事情(例如龙和核浩劫)。
我可以从其他评论中看到这个问题背后的意图:
在这种情况下,你走在正确的轨道上...你遗漏的一件事是你提到的那些随机字符是机器码,而指令指针< / em>需要被覆盖以指向该机器代码。 Aleph One更详细地介绍了这一点,尽管你的里程肯定会因为它是古老的文件而有所不同。
如果没有针对此问题的许多未定义行为,C的专家所有可能会拦截信号以便在没有问题的情况下恢复其软件,但唉,C不是Java。 ..你最好遵循专家的例子,避免未定义的行为 ......
假设一个体面的指南会尽可能地帮助您避免未定义的行为。至少有一本体面的指南以书的形式出现,我们称之为形式的“圣经”,部分原因是它是由一些权威人士撰写的。它上面有一个大的蓝色“C”,现在“SECOND EDITION”用红色写成......
你们找到那本书是一种练习。我有信心。