旧操作系统内存空间保护——真的那么糟糕吗?

信息安全 操作系统 记忆 C
2021-08-13 12:30:02

在他的《安全工程》一书中,安德森真正关注了 90 年代和 2000 年代初期的程序如何需要访问不属于他们自己的内存,而程序员在编程时假设程序将以管理权限运行:

最新的 Microsoft 系统 Vista 正试图摆脱以管理员权限运行所有代码,而这三种现代技术各自独立地试图实现相同的目标——如果所有应用程序都拥有以用户权限而不是以管理员身份运行。这或多或少是 1970 年代计算的情况,当时人们将代码作为非特权进程在分时共享的小型计算机和大型机上运行。只有时间会证明我们是否能够重新夺回失去的秩序和控制的伊甸园……并摆脱当今混乱的现实……但肯定值得一试。

特别是这个报价

PC 有一个不幸的遗留问题,旧的单用户操作系统(如 DOS 和 Win95/98)允许任何进程修改任何数据;因此,许多应用程序将无法运行,除非它们以管理员权限运行并因此可以访问整个机器

这是否意味着诸如此类的想法,您可以将任何地址分配给指针并可以访问内存,这些想法可以在 Windows 95/98 上运行?他们为什么要这样设计它,因为读取其他程序内存(或写入它!)会有什么好处?

int * firefoxmemory = (char*) 0x11111111 //this is just an example of address.
*firefoxmemory = 200;//screw firefox
4个回答

内存隔离

您的示例不适用于 Windows 95,但它确实适用于 DOS 和 Windows 3.11(不是 Windows NT)。

PC 体系结构和 Microsoft 系列操作系统始于Intel 8086 处理器和旨在一次运行单个程序的操作系统 ( DOS )。你会运行一个程序,当你完成它时,你会退出它,所以覆盖另一个进程的数据并不是真正的问题。(您可能有退出后仍保留在内存中的程序;它们本质上是操作系统的一部分。)

英特尔80386 处理器改变了交易方式,成为其家族中第一个拥有内存管理单元 (MMU)的处理器。MMU 是一个硬件组件,它通过将虚拟地址(程序中的指针)转换为物理地址(RAM 中的实际位置¹)来提供虚拟内存。Windows 95 是其血统中第一个利用它的:每个 Windows 95 应用程序都使用自己的 MMU 配置运行,因此它*firefoxmemory要么指向程序自己的地址空间(在这种情况下,它所做的任何坏事都只会影响该程序) 或根本不指向任何地方(在这种情况下,程序——而不是整个系统——会崩溃)。

Windows 3.0–3.11 利用了 386 的一些新功能,但它们在同一地址空间中运行所有应用程序。这是由于几个因素:

  • 保持旧的 Windows 1.x/2.x 应用程序运行的要求(它们被设计为在单地址空间硬件上运行,许多人利用这一点来深入操作系统内部以实现通过记录无法实现的事情接口);
  • 缺乏基于完全不同架构重新设计整个操作系统的开发时间;
  • 在 RAM 很少的计算机上运行的要求:保持应用程序隔离确实会花费 2 MB 或 4 MB 的更多内存(因为必须在程序之间复制数据,并且因为操作系统必须跟踪所有程序交互允许应用程序进行通信并规范通信)。

与其说操作系统被明确设计为允许程序访问彼此的内存,不如说是没有办法阻止它。当防止它的方法变得负担得起时,需要几个操作系统版本来利用它们。

例如,让我们考虑一个功能,例如程序间复制粘贴(剪贴板)。如果你没有内存隔离,这可以通过让源程序在自己的内存中保存一份数据的副本来实现;当数据粘贴到另一个程序中时,该其他程序直接从源程序复制数据。操作系统唯一需要跟踪的是当前拥有剪贴板的人。如果应用程序的内存是隔离的,则操作系统需要安排数据复制,并可能独立于源应用程序存储数据的副本。这需要更多的开发工作来编写此代码,并且需要更多的资源来将此代码存储在内存中并运行它。

我在这里简化了很多事情;有关操作系统如何使用 MMU 隔离应用程序的更多信息,您可以阅读内核如何防止恶意程序运行?,两个相同的虚拟地址怎么会指向不同的物理地址呢?,不支持虚拟内存是否可以支持多进程?(它们是面向 Unix 的,但这些原则适用于 Windows 或您可能在 PC 上遇到的任何操作系统)。

特权隔离

在这篇文章中,Anderson 实际上不是在讨论内存隔离,而是特权隔离,即防止正在运行的程序影响系统的某些部分。内存隔离是必要的,但还不够。操作系统还必须控制进程交互的方式,控制它们打开的文件等。即使每个应用程序都在自己的内存空间中运行,应用程序也可以交互。内存隔离仅强制应用程序使用操作系统服务(通过系统调用可以访问所有内存的内核)进行交互。

Windows 1.x/3.x/9x/ME 系列操作系统设计为单用户操作系统,没有隔离应用程序。Windows 95 中添加了内存隔离,但只是为了提高稳定性,而不是为了实施安全限制。这些操作系统没有安全限制:如果你在你的机器上运行一个程序,它就可以做任何事情。(这对病毒编写者当然有帮助。)内存空间是隔离的,但文件空间不是。

Windows NT/2000/XP/...系列操作系统,和Unix家族的操作系统一样,被设计成一个多用户操作系统,它引入了一个基本的安全目标:一个用户所做的事情不应该对其他用户的事情产生不利影响可以做。因此,操作系统必须强制执行,例如,Alice 执行的程序不能与 Bob 执行的程序交互,不能修改 Bob 的文件等。作为操作系统一部分的程序和数据涉及每个用户,因此应该受到保护全部用户。(当然,某些交互是通过显式权限允许的,例如,网络服务器确实处理它从网络接收到的请求,Bob 可以将文件的权限更改为其他人可写,一些用户被授予管理权限,因此可以修改操作系统等)

许多应用程序都是针对没有安全限制的 Windows 9x 编写的。所以他们冒昧地写入操作系统目录(在 Windows 95 下,将文件复制到 Windows 目录是安装共享库的正常方式)。即使按照当时的标准,这些应用程序所做的大部分工作都被认为有些混乱,但它确实有效,所以人们这样做了。NT 系列的 Windows 版本(包括 XP 之后的所有版本)必然会破坏其中的一些应用程序,尽管 Microsoft 确实添加了许多变通方法(例如假装将文件写入共享目录成功,但实际上将其写入用户特定的目录)。

Windows XP 强制执行用户隔离,但大多数安装不会利用它:许多安装只是让用户成为管理员,因此它们运行的​​所有程序都有权执行几乎任何操作。主要原因有两个:

  • 它允许运行不良的遗留应用程序。虽然大多数此类应用程序仅由一小部分用户使用,但许多用户确实运行此类应用程序,并且需要很长时间和巨大的努力才能全部更新。
  • 引入特权意味着有时用户会被告知由于缺乏特权而无法完成某事。见证有关 Vista 的抱怨,它会继续显示那些特权升级提示。

应用程序隔离

如果您足够年轻,可以通过便携式设备发现计算机,那么您可能已经习惯了以应用程序而不是用户为中心的隔离模型。在 Unix 和 Windows 模型中,Alice 的数据受到 Bob 的保护。应用程序在安全模型中是中立的:它们只是在登录用户的身份下执行。这意味着必须信任应用程序代码不会出现不当行为。

传统上,Windows 一直通过尝试通过防病毒软件检测和控制行为不端的应用程序来应对这些应用程序这不太好。Unix 系统,尤其是 Linux,倾向于通过可追踪的渠道分发更多的软件来消除恶意软件。

为移动设备设计的操作系统,尤其是 iOS 和 Android,不信任应用程序开发人员,因此应用程序是孤立的;例如,每个应用程序都有自己的文件空间。这有一个安全优势,但也有很大的成本,因为它减少了应用程序可以做的事情。应用程序需要权限来修改操作系统行为(例如创建快捷方式和更普遍的自动化),您不能轻易地在不同的应用程序中操作同一个文件,您不能轻易地允许多个应用程序同时显示信息,等等。或者,举一个更生动的例子,您不能编写具有受限权限的调试器,因为调试器的全部目的是窥探并扰乱正在调试的应用程序的执行。

如果回溯到 Windows 之前的几年,几乎可以预料,任何在微型计算机上运行的程序都会“拥有”这台计算机。如果它想让某些服务保持可用,它就必须不理会系统的某些部分,否则就没有任何“保护”任何东西的必要。大型计算机足够大且价格昂贵,让一个用户独占机器是不切实际的,当多人同时使用一台机器时,它必须能够像几台独立的计算机一样运行,而不会干扰彼此的操作,但在微型计算机上这不是问题。与今天的计算机都具有硬盘驱动器或非易失性存储器不同,早期的微型计算机两者都没有。如果您关闭并备份机器,其内存状态将有效地重置为出厂默认设置。打开 Altair 680 的电源,它将等待从串行端口接收 S-record 格式的程序。给它一个这样的程序(很可能使用纸带阅读器),它就会运行它。如果出现问题,请关闭机器电源并从纸带重新加载。

当计算机添加软盘驱动器时,同样的原则继续适用。只是在个人计算机获得硬盘驱动器之后,安全问题才开始变得相关,但许多对软盘有意义的编程技术继续使用,因为它们有效。

一个简单的例子:DOS 屏幕编写功能还有很多不足之处。简单地直接写入视频内存是例行公事。您需要执行此操作以获得执行大量屏幕更新的任何操作的合理性能,如果您想编写视频屏幕的右下角单元格(例如,在您正在显示的内容周围绘制边框),这绝对是必不可少的。

在早期版本中,如果您想打开很多(超过 16 个??)文件,还必须写入程序前面的控制块的内存。我忘记了哪个版本添加了操作系统调用来完成同样的事情。

为了扩展 MMU 的答案,所有 80 年代早期的“个人计算机”芯片组一开始都没有该功能。它在芯片面积和内存延迟方面具有非零成本,这在当时的 3 µm 芯片工艺中成本要高得多。请记住,今天我们可以在当时的一个空间中安装大约 20,000 个晶体管。

8086 和 68000(用于 Mac 和 Amiga)没有 MMU。它们也只有最多 1 兆字节的内存,通常只有一个软盘驱动器(因此也没有基于磁盘的虚拟内存)。由于缺乏网络且只有一个用户,内存保护的意义何在?您只会保护用户免受他们自己的伤害,并防止一些可能有用的应用程序间数据共享。常见的使用模式是从磁盘加载程序,然后将整个机器交给该程序。DOS 甚至没有多任务处理;有一个“终止并保持驻留”模式,它允许程序留下代码以在按键或计时器上运行。

MMU 在大多数微控制器中仍然不存在,并且是 ARM 上的一个可选功能。

显存是直接访问的常见目标;如果您想编写游戏,它被认为是获得可接受性能的唯一方法。由于没有驱动程序,您还必须直接写入声音硬件(如果存在)。

在 Windows 获得 DirectX 子系统之前,还有一个奇怪的“DOS/4GW”中间时代。为了克服对 16 位程序施加的 1Mb 内存的硬件限制,您可以使用自己的小型 32 位操作系统来发布游戏。这要求您退出 Windows 3.0。

我仍然拥有这个时代的 16 位游戏代码。我一直想把它发布到 github。