有意的缓冲区溢出并不总是导致程序崩溃

2024-02-12

考虑以下最小 C 程序:

案例编号1:

#include <stdio.h>
#include <string.h>

void foo(char* s)
{
    char buffer[10];
    strcpy(buffer,s);
}

int main(void)
{
    foo("01234567890134567");
}

这不会导致崩溃转储

如果只添加一个字符,则新的 main 为:

案例编号 2:

void main()
{
    foo("012345678901345678");
                          ^   
}

程序因分段错误而崩溃。

看起来除了堆栈中保留的 10 个字符之外,还有额外的空间可容纳 8 个额外的字符。因此第一个程序不会崩溃。但是,如果您再添加一个字符,您就会开始访问无效内存。我的问题是:

  1. 为什么我们要在堆栈中保留这额外的 8 个字符?
  2. 这是否与内存中的 char 数据类型对齐有关?

在这种情况下,我的另一个疑问是操作系统(在本例中为 Windows)如何检测错误的内存访问?通常根据 Windows 文档,默认堆栈大小为 1MB堆栈大小 https://msdn.microsoft.com/en-us/library/windows/desktop/ms686774(v=vs.85).aspx。所以我不明白操作系统如何检测到正在访问的地址位于进程内存之外,特别是当最小页面大小通常为 4k 时。在这种情况下,操作系统是否使用 SP 来检查地址?

PD:我使用以下环境进行测试
Cygwin
海湾合作委员会 4.8.3
Windows 7操作系统

EDIT:

这是生成的程序集http://gcc.godbolt.org/# http://gcc.godbolt.org/#但使用 GCC 4.8.2,我在可用编译器中看不到 GCC 4.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

我相信您明白您已经实施了一些导致未定义行为的事情。因此很难回答为什么它会因额外的字符串而失败而不会因原始字符串而失败。它可能与内部编译器实现有关+受编译标志(如对齐、优化等)的影响。

您可以尝试反汇编二进制文件或创建汇编代码,然后查看缓冲区到底放置在堆栈上的位置。您可以对不同的优化级别执行相同的操作,以检查汇编代码和行为的更改。

操作系统(在本例中为 Windows)如何检测错误的内存访问? 通常,根据 Windows 文档,默认堆栈大小为 1MB 堆栈大小。所以我不明白操作系统如何检测该地址 被访问的是进程内存之外,特别是当 最小页面大小通常为 4k。在这种情况下操作系统是否使用 SP 检查地址?

操作系统不会监视您执行的代码。硬件(CPU)执行此操作(因为它执行此代码)。一旦您的代码尝试访问未分配给您的进程的地址(未分配给您的进程)由操作系统映射 https://en.wikipedia.org/wiki/Virtual_address_space对于您的程序)操作系统将收到指示,因为硬件将触发#PF(页面错误)异常。另一种情况是,您尝试访问为您分配的地址,但权限不正确(例如,您尝试从没有“执行”权限的数据页执行二进制数据)或转到代码页,但权限错误偏移量和您读取的指令不存在,或者(更糟糕)它存在并解码为您不期望的东西(我们之前说过未定义的行为吗?)。

一般来说,您的代码很可能不会失败strcpy(如果您写入足够的数据来访问某些禁止的地址,则可以,但很可能情况并非如此) - 当它从foo功能。strcpy只是覆盖了指向下一条指令的下一条指令指针foo功能。因此指令指针填充了“012345678901345678”字符串中的数据,并尝试从“垃圾”地址获取下一条指令,但由于上述原因而失败。

这个“方法”/错误被称为“缓冲区溢出攻击 https://en.wikipedia.org/wiki/Buffer_overflow”并在黑客中广泛使用,使您的代码(更常见的是以更高权限执行的 OS/BIOS/VMM/SMM 代码)执行黑客提供的恶意代码。只需确保使用指令的地址覆盖指令指针即可您提前准备的代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

有意的缓冲区溢出并不总是导致程序崩溃 的相关文章

随机推荐