所有可写和可执行的程序内存段类型

3

在《C和C++安全编码》一书中,作者提到:

“W^X策略允许内存段可写或可执行,但不能同时具备。该策略无法防止覆盖atexit()等需要在运行时既可写又可执行的目标。”

我的两个问题是:

  1. atexit需要通过函数指针注册函数作为参数。由函数指针指向的函数可能已在当前程序中定义,链接器将找到该定义,或者运行时加载器将查找函数体。在任一情况下,我们都将知道函数定义。然后它只需要是可执行的。那么为什么atexit()的内存段需要在运行时既可写又可执行?

  2. 有没有C/C++专家能告诉我还有哪些其他类型的API具有此属性(在linux上限制范围)(在运行时可写且可执行)?


懒惰的JIT编译器可能会使用W|X段。他们“不应该”,但他们很懒。 - Ignacio Vazquez-Abrams
3
我同意你关于退出处理程序的看法。指向退出处理程序函数的指针列表需要是可写的,但不需要是可执行的。指向的函数需要是可执行的,但它们不需要在可写段中。 - John Bollinger
1个回答

3
基本上,可以写入并执行的内存非常容易被篡改,因此可能更容易导致利用,因为不需要使用ROP或其他花哨的方法,您可以在段中的任何地方编写要执行和分支到的代码。
在您的引用中,目标在此上下文中的意思很可能是称为退出时的函数指针列表。根据C API,列表本身需要可写/可变。这些函数所指向的代码只需可执行即可。在这里,因为列表是可变的,您可以通过插入指向您的代码的指针并强制程序退出来简单地修改此列表,从而利用程序,这将执行您的代码。在此上下文中,保持所有内存段均可写入或可执行都无法保存您,因为此处使用了2个不同的段(一个可写入带有函数指针列表,另一个可执行带有代码)。

任何在运行时动态生成代码的软件,例如JIT、内核、可执行文件解包器等都需要可写和可执行的内存段。对于每个软件,技术上没有要求这些内存段同时拥有两种属性。内存可以先被分配为可写状态,之后代码会被复制或生成,并使用mprotect()函数调用来将内存段转为可执行状态(并去除可写属性)。唯一我能想到同时拥有两种属性有益于内存受限的环境下(例如: 在原地解包一个可执行文件)。

请注意,有些平台不支持在用户空间分配可执行内存: 例如Xbox360和PS3不支持JIT(内核/ API支持它,但你无法发布你的软件,因为微软和索尼会拒绝你的提交,这样的功能只能在开发中使用)。


同时,如果Linux(Unix)允许您调用mprotect()来更改当前标志,则可以轻松地将任何内存区域设置为可执行。当然,对于黑客来说,首先生成有效的mprotect()调用更加困难... 如果您的编译器在执行时是安全的,则没问题。为此,我想很多人都不应该编写这样的软件,永远不要。 :-) - Alexis Wilke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接