操作系统和虚拟化安全知识

2023-05-30 11:57阚哲
计算机与网络 2023年3期
关键词:控制流堆栈内核

阚哲

控制流限制

正交防线是调节操作系统的控制流。通过确保攻击者无法将控制权转移到他们选择的代码上,即使不删除内存错误,也会使利用内存错误变得更加困难。最好的例子被称为控制流完整性(CFI),现在许多编译器工具链(如LLVM和微软的VisualStudio),于2017年以ControlFlowGuard的名义并入Windows内核。

从概念上讲,CFI非常简单:确保代码中的控制流始终遵循静态控制流图。例如,一个函数的返回指令应该只允许返回到它的调用站点,而使用C中的函数指针或C++中的虚函数的间接调用应该只能够定位它应该能够调用的合法函数的入口点。为了实现这种保护,可以标记间接控制转移指令的所有合法目标(返回、间接调用和间接跳转),并将这些标签添加到特定于此指令的集合。在运行时,检查指令即将进行的控制转移是否为集合中的目标。否则,CFI会发出警报。

与ASLR一样,CFI有多种“口味”,从粗粒度到细粒度,从上下文敏感到上下文不敏感。就像在ASLR中一样,今天的大多数实现只采用最简单、最粗粒度的保护。粗粒度CFI意味着为了性能而稍微放宽规则。例如,它不是将函数的返回指令限制为可能调用此函数的仅目标合法调用站点,而是可以针对任何调用站点。虽然不如细粒度CFI安全,但它仍然极大地限制了攻击者的回旋余地,并且可以更快地在运行时检查。

在现代机器上,某些形式的CFI甚至(或将要)得到硬件的支持。例如,英特尔控制流强制技术(CET)支持影子堆栈和间接分支跟踪,以帮助强制实施退货和前向控制传输的完整性(以非常粗粒度的方式)。ARM也不甘示弱,它提供了指针身份验证,以防止对指针值进行非法修改———主要是通过使用指针的上位来存储指针身份验证代码(PAC),其功能类似于加密指针值上的签名(除非获得正确的PAC,否则指针无效)。

遗憾的是,CFI只能通过破坏返回地址、函数指针和跳转目标等控制数据来帮助抵御改变控制流的攻击,但对非控制数据攻击却无能为力。例如,它无法阻止覆盖当前进程的权限级别并将其设置为“root”的内存损坏(例如通过将有效用户ID设置为root用户的ID)。但是,如果对控制流的限制在实践中如此成功,您可能想知道数据流是否也可以进行类似的限制。事实上,它们确实如此,这被称为数据流完整性(DFI)。在DFI中,静态地确定每个加载指令(即从内存中读取的指令)存储指令可能合法地产生了数据,并且标记这些指令并将这些标签保存在一组中。在运行时,对于内存中的每个字节,记住该位置最后一个存储的标签。当遇到加载指令时,会检查该地址的最后一个存储是否在合法存储集中,如果不是,会发出警报。与CFI不同,DFI在实践中并未被广泛采用,可能是因为其显著的性能开销。

信息隐藏

大多数当前操作系统的主要防线之一是隐藏攻击者可能感兴趣的任何内容。具体来说,通过随机化所有相关内存区域(在代码、堆、全局数据和堆栈中)的位置,攻击者将不知道在哪里转移控制流,也无法发现哪个地址包含敏感数据等。术语地址空间布局随机化(ASLR)是在PaX安全補丁发布时创造的,该补丁在2001年为Linux内核实现了这种随机化。很快,类似的努力出现在其他操作系统中,第一个默认启用ASLR的主流操作系统是2003年的OpenBSD和2005年的Linux。Windows和MacOS在2007年紧随其后。然而,这些早期的实现只是随机化了用户程序中的地址空间,随机化直到大约10年后才到达主要操作系统的内核。

如今,大多数实现都采用粗粒度随机化:它们随机化代码、堆或堆栈的基本位置,但在这些区域中,每个元素都与基本元素处于固定偏移量。这很简单,而且非常快,然而,一旦攻击者设法通过信息泄漏获得哪怕一个代码指针,他们就会知道每条指令的地址。对于堆、堆栈等,也是如此,因此,毫不奇怪,这些信息泄漏是当今攻击者高度重视的目标。

更细粒度的随机化也是可能的。例如,可以在页面级别或功能级别进行随机化。如果在内存区域中打乱函数的顺序,即使知道内核代码的基础对于攻击者来说也是不够的。事实上,我们可以更细粒度地洗牌基本块、指令(可能带有从不执行或无效的垃圾指令)甚至寄存器分配。许多细粒度随机化技术都是以空间和时间开销为代价的,例如,由于局部性和碎片化减少。

除了代码之外,还可以对数据进行细粒度随机化。例如,研究表明,堆栈上的堆分配、全局变量甚至变量都可以分散在内存中。当然,这样做会产生性能和内存方面的成本。

考虑到KASLR,尤其是粗粒度的KASLR,作为抵御内存错误攻击的第一道防线,这不会离目标太远。不幸的是,它也是一个非常薄弱的防御,许多出版物表明,通过从内存、侧信道等泄漏数据和(或)代码指针,KASLR可以相当容易地被破解。

保护环

Multics引入的最具革命性的想法之一是保护环的概念———一种特权的分层,其中内环(环0)是最有特权的,并且外环的特权最低。因此,不受信任的用户进程在外环中执行,而直接与硬件交互的受信任和特权内核在环0中执行,其他环可用于或多或少特权的系统进程。

保护环通常采用硬件支持,这是当今大多数通用处理器提供的功能,尽管保护环的数量可能有所不同。例如,霍尼韦尔6180支持多达8个环,英特尔的x86支持4个,ARMv7支持3个(加上TrustZone的额外1个)和PowerPC的2个。但是,故事变得有点混乱,因为一些现代处理器也引入了更多不同的处理器模式;大多数常规操作系统只使用2个环:一个用于操作系统,一个用于用户进程。

每当权限较低的代码需要更多权限的函数时,它就会“调用”下环以请求将此函数作为服务执行。因此,只有受信任的特权代码才能执行最敏感的指令或操作最敏感的数据。除非具有较少权限的进程诱骗更多特权的代码执行它不应该做的事情(作为困惑的副手),否则环会提供强大的保护。Multics的最初想法是,环之间的转换将通过实施严格控制和调解的特殊呼叫门进行。例如,外环中的代码不能调用内环中的任何指令,而只能调用第一个调用的预定义入口点经过审查,看它及其参数是否违反任何安全策略。

虽然像x86这样的处理器仍然支持呼叫门,但很少有操作系统使用它们,因为它们相对较慢。相反,用户进程通过执行操作系统处理的软件中断(陷阱),或更常见的是通过特殊的高效操作过渡到操作系统内核(系统调用)。系统调用指令(如SYSCALL、SYSENTER、SVC、SCALL等,具体取决于体系结构)。许多操作系统将系统调用的参数放在一组预定义的寄存器中。与调用门一样,陷阱和系统调用指令也确保在操作系统中的预定义地址继续执行,代码在其中检查参数,然后调用相应的系统调用函数。

除了用户进程调用操作系统外,大多数操作系统还允许内核调用用户进程。例如,基于UNIX的系统支持操作系统用来通知用户程序“有趣的事情”的信号:错误、过期的计时器、中断、来自另一个的消息工艺等,如果用户进程为信号注册了一个处理程序,操作系统将停止进程的当前执行,将其所有处理器状态存储在进程堆栈上,形成所谓的信号,并在信号处理程序上继续执行。当信号处理程序返回时,进程执行sigreturn系统调用,使操作系统接管,还原堆栈上的处理器状态并继续执行进程。

安全域(如操作系统内核和用户空间进程)之间的边界是检查系统调用本身及其安全性参数的好地方。例如,在基于功能的操作系统中,内核将验证功能,而在MINIX3等操作系统中,只允许特定进程进行特定调用,因此任何尝试拨打不在预先批准列表中的呼叫将被标记为违规。同样,基于Windows和UNIX的操作系统必须检查许多系统调用的参数。例如,考虑常见的读写系统调用,通过这些调用,用户请求将数据从文件或套接字读取到缓冲区中,或者从缓冲区分别到文件或套接字中。在执行此操作之前,操作系统应检查要写入或读取的内存是否实际归进程所有。

执行系统调用后,操作系统将控制权返回给进程,操作系统还必须注意不要返回危及系统安全性的结果。例如,如果一个进程使用mmap系统调用请求操作系统将更多内存映射到其地址空间,则操作系统应确保它返回的内存页不再包含来自另一个进程的敏感数据(如首先将每个字节初始化为零)。

零初始化问题可能非常微妙,如编译器经常在数据结构中引入填充字节以对齐。由于这些填充字节在编程语言级别根本不可见,因此编译器可能认为没有理由将它们初始化为零。但是,当操作系统返回此类数据结构以响应系统调用并且单元化填充包含来自内核或其他进程的敏感数据时,就会发生安全违规。

即使是UNIX系统中的信令子系统也是一个有趣的安全案例。回想一下,sigreturn采用堆栈上的任何处理器状态并恢复该状态。假设攻击者能够破坏进程的堆栈并在堆栈上存储虚假信号帧,如果攻击者能够触发sigreturn,或可以设置整个处理器状态(包括所有寄存器值),这样在熟练的攻击者手中就提供了一个强大的基元,被称为sigreturn导向编程(SROP)。

如前所述,为此,添加了看起来像底部额外戒指的东西。由于在x86处理器上,术语ring0已成为“操作系统内核”的同义词(以及“环”与“用户进程”的同义词),因此这种新的虚拟机管理程序环通常称为ring-1。它还指示其各自虚拟机中的操作系统可以继续在本地执行环0指令。严格来说,它的目的与原始戒指大不相同,虽然环-1这个名字已经卡住了,但它可能有点像用词不当。

为了完整起见,应该提到的事情可能会变得更加复杂,因为一些现代处理器仍然具有其他模式。例如,x86提供所谓的系统管理模式(SMM)。当系统启动时,固件控制硬件,并為操作系统接管系统做好准备。但是,启用SMM后,固件会在向CPU发送特定中断时重新获得控制权。例如,固件可以指示每当按下电源按钮时它都希望接收中断,在这种情况下,常规执行将停止,固件将接管。例如,可以保存处理器状态,执行任何需要执行的操作,然后恢复操作系统以进行有序关闭。在某种程度上,SMM有时被视为比其他环(环-2)低的水平。

英特尔甚至以英特尔管理引擎(ME)的形式增加了环-3。ME是一个完全自主的系统,现在几乎存在于英特尔的所有芯片组中。它在单独的微处理器上运行一个秘密且完全独立的固件,并且始终处于活动状态:在启动过程中,当机器运行时,当它处于睡眠状态时,即断电。只要计算机连接到电源,就可以通过网络与ME通信,如安装更新。虽然非常强大,但它的功能在很大程度上是未知的,除了运行自己的小型操作系统,研究人员发现其中包含漏洞。主CPU附带的附加处理器(无论是ME还是Apple的T2和Google的Titan芯片等相关处理器)都提出了一个有趣的点———在主CPU上运行的操作系统是否能够满足当今的安全要求?至少,这种趋势似乎通过专用系统(硬件和软件)来增强它的安全性。

低端设备和物联网

上述许多功能都可以在大多数通用处理器体系结构中找到。然而,在物联网或一般的嵌入式系统中不一定如此,并且通常使用定制的操作系统。简单的微控制器通常没有MMU,有时甚至没有MPU、保护环或在常见操作系统中依赖的任何高级功能。系统通常很小(减少攻击面),应用程序受信任(并可能经过验证)。然而,设备的嵌入式性质使其很难检查甚至测试其安全性,并且无论它们在安全敏感活动中发挥作用的何处,安全性隔离/遏制和调解的手段应由环境在外部强制执行。更广泛的物联网问题在网络物理系统安全CyBOK知识领域得到解决。

猜你喜欢
控制流堆栈内核
强化『高新』内核 打造农业『硅谷』
抵御控制流分析的Python 程序混淆算法
工控系统中PLC安全漏洞及控制流完整性研究
抵御控制流分析的程序混淆算法
基于嵌入式Linux内核的自恢复设计
Linux内核mmap保护机制研究
嵌入式软件堆栈溢出的动态检测方案设计*
基于堆栈自编码降维的武器装备体系效能预测
微生物内核 生态型农资
基于控制流隐藏的代码迷惑