【问题标题】:Call stack memory architecture调用栈内存架构
【发布时间】:2011-01-05 14:28:02
【问题描述】:

my question yesterday 之后,我尝试更多地了解调用堆栈的架构。在线和 SO 搜索没有产生我正在寻找的答案,这可能是因为我不确切知道要使用哪些关键字。无论如何,我相信这里有人可以帮助我......

首先,让我们从 Wikipedia 中stack buffer overflow 条目的摘录开始:

在软件中,当程序写入预期数据结构之外的程序调用堆栈上的内存地址时,会发生堆栈缓冲区溢出;通常是一个固定长度的缓冲区。

一位同事告诉我,他记得了解到,在 Linux 上,堆栈位于进程虚拟内存的最后,并根据需要向后增长——因此它不是“固定长度缓冲区”。但是,我无法证实这一点。所以我的问题是:

  1. 在 Windows 和 Linux 上是调用 堆栈总是一个固定大小的缓冲区?如果 不是,它是如何生长的?怎么做的 管理共享虚拟内存 堆?
  2. 堆栈的架构取决于 使用的编译语言?在 操作系统?在硬件上?
  3. 是大小 堆栈的确定在 编译时或可以更改 后验?
  4. 如何以及在哪里 单个线程的调用堆栈 分配?

【问题讨论】:

    标签: architecture memory multithreading stack


    【解决方案1】:

    堆栈缓冲区溢出

    堆栈缓冲区溢出是指程序意外或恶意写入堆栈上特定数据项的范围之外,例如 c 字符串。这具有修改堆栈(而不是堆)上附近控件或数据结构的值的效果,这可能导致不良的程序行为,例如崩溃、错误或更改控制流。

    这通常不是指在堆栈本身的范围之外写入,通常由guard pages 保护以防止意外溢出或欠载。

    | start of stack |
    | data           |
    | parameters     |
    | return address |
    | data           |
    | parameters     |
    | return address |
    | parameters     |
    | return address | <- might overflow into this region or above
    | string data    | <- writes to this region ... (look up)
     stack head
      |
      V direction of growth for pushes
     ...
    | end of stack   |
    | guard page     | <- writes to this region cause a segfault
     ...
    | heap           |
    

    Linux 上的调用栈

    调用堆栈是固定大小的,实际堆栈本身在此限制内根据需要增长和缩小。

    堆和堆栈不重叠或共享内存 - 它们通常在虚拟地址空间的不同区域中管理。

    进程的主堆栈的大小由程序运行时存在的环境决定。有关设置此功能的 c 函数,请参阅 man 3 ulimit 并从 bash 查看/设置它,请参阅 ulimit -s 了解详细信息。

    > ulimit -s
    8192
    

    如果您创建自己的线程,您可以负责创建它们的堆栈(请参阅man pthread_attr),您可以使用系统推荐的大小或设置自己的大小。

    【讨论】:

    • 所以,“堆栈缓冲区溢出”与我在填充堆栈时在 Windows 中遇到的“堆栈溢出”错误指的概念不同,即使用永无止境的递归调用? (Windows 称其为“堆栈溢出”,而不是“段错误”。)
    • 正确。保护页面中的段错误被捕获并更改为堆栈溢出。
    【解决方案2】:

    对于 Windows:

    1. 对于用户模式应用程序,默认情况下,堆栈的内存最初保留为 1 MB。保留意味着地址范围不能用于任何其他目的,但实际上并未分配内存。这允许堆栈在内存中是连续的,但不需要默认分配所有堆栈(即使大多数将未使用)。在实际提交堆栈的末尾有一个保护页面 - 每当访问此页面时,Windows 都会为堆栈分配更多内存。如果您尝试使用超出为堆栈保留的空间,则会出现堆栈溢出异常。 VirtualAlloc 的 MSDN 页面更详细地介绍了保留与提交。

    2. x86 对堆栈提出了一些严格的要求(例如必须向下增长)。其他架构更灵活。几乎所有基于 x86 的操作系统都类似地使用堆栈。可以使用不同的堆栈架构。您不能使用任何 x86 的堆栈支持,您必须自己手动完成,但是当您调用任何 OS API 时,您必须转换为传统堆栈。

    3. 信息存储在 .exe 中。您可以使用linker flag 对其进行调整。或者,CreateThread API 允许您更改堆栈大小。

    4. 线程的堆栈是在创建线程时创建的,可以使用 .exe 中的默认值,也可以使用 CreateThread 调用中指定的值。

    当您超出存储在堆栈上的固定长度缓冲区并覆盖堆栈上的其他控制数据(如返回地址)时,会发生堆栈缓冲区溢出。

    例如,假设您调用函数,它有一个大小为 16 字节的本地缓冲区。调用堆栈可能如下所示(为清楚起见,省略了其他细节:

    0x1000 - Return address
    0x990 - Buffer
    

    如果您的代码存在错误并超出 0x990 处的缓冲区,您将覆盖返回地址。如果攻击者可以导致缓冲区溢出,他们可以将一些代码放入缓冲区,并覆盖返回地址以指向注入的代码。当你的函数返回时,它会跳转到攻击者的代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-30
      • 1970-01-01
      • 1970-01-01
      • 2014-01-19
      • 2023-04-03
      • 2015-09-08
      相关资源
      最近更新 更多