基于 Windows® 10 的英特尔® 内存保护扩展:教程

简介

自第六代智能英特尔® 酷睿™ 处理器起,英特尔公司开始推出英特尔® 内存保护扩展(英特尔® MPX),它是一种针对指令集架构的全新扩展,旨在通过帮助抵御缓冲区溢出攻击,增强软件安全性。 在本文中,我们将介绍缓冲区溢出,并提供详细的步骤指导应用开发人员如何保护 Windows® 10 上的应用免受缓冲区溢出攻击。 英特尔 MPX 适用于传统桌面应用和 Universal Windows Platform* 应用。

前提条件

为运行本文提供的示例,您需要准备以下软硬件:

  • 采用第六代智能英特尔® 酷睿™ 处理器和 Microsoft Windows 10 操作系统(2015 年 11 月更新版或更高版本,首选 Windows 10 版本 1607)的计算机(台式机、笔记本电脑或其他外形的计算机)
  • 在 UEFI 中启用英特尔 MPX(如果该选项可用)
  • 安装正确的英特尔 MPX 驱动程序
  • Microsoft Visual Studio* 2015(更新 1 或更高版本 IDE;首选 Visual Studio 2015 更新 3)

缓冲区溢出

从本质上来说,C/C++ 代码更容易遭受缓冲区溢出。 例如,在以下代码中,main() 中的字符串操作函数 “strcpy” 将会导致程序遭受缓冲区溢出攻击的风险。

#include "stdafx.h"
#include <iostream>
#include <time.h>
#include <stdlib.h> 

using namespace std;

void GenRandomUname(char* uname_string, const int uname_len)
{
	srand(time(NULL));
	for (int i = 0; i < uname_len; i++)
	{
		uname_string[i] = (rand() % ('9' - '0' + 1)) + '0';
	}
	uname_string[uname_len] = '\0';
}

int main(int argnum, char** args)
{
	char user_name[16];
	GenRandomUname(user_name, 15);
	cout << "random gentd user name: " << user_name << endl;

	char config[10] = { '\0' };
	strcpy(config, args[1]);

	cout << "config mem addr: " << &config << endl;
	cout << "user_name mem addr: " << &user_name << endl;

	if (0 == strcmp("ROOT", user_name))
	{
		cout << "Buffer Overflow Attacked!" << endl;
		cout << "Uname changed to: " << user_name << endl;
	}
	else 
	{
		cout << "Uname OK: " << user_name << endl;
	}
	return 0;
}

为了提高准确性,如果我们以 C++ 控制台应用的形式编译并运行以上示例,将 CUSM_CFG 当作参数,那么该程序将正常运行,并且控制台将显示以下输出:

Figure 1 Buffer Overflow

但如果我们重新运行该程序,将 CUSTOM_CONFIGUREROOT 当作参数,输出将“出乎意料”,且控制台显示以下消息:

Figure 2 Buffer Overflow

这一简单示例说明了缓冲区溢出攻击的工作原理。 出现意外输出的原因是 strcpy 函数调用无法检查目标数组的联系。 尽管编译器通常为数组提供额外字节以达到内存对齐,但如果源数组足够长,还是会发生缓冲区溢出。 在这种情况下,程序的一部分运行时内存布局将如下所示(结果可能因编译器或编译选项的不同而有所差异):

Figure 3

英特尔内存保护扩展

借助英特尔 MPX,只需为 Visual Studio C++ 编译器添加编译选项 /d2MPX,就可避免缓冲区溢出这一安全问题。

Figure 4

借助英特尔 MPX 选项进行编译后,该程序将能够抵御缓冲区溢出攻击。 如果我们尝试运行重新进行编译,且包含 CUSTOM_CONFIGUREROOT 参数的程序,运行时例外情况将会增加,并导致程序退出。

Figure 5

我们来深入了解一下生成的汇编代码,看看英特尔 MPX 对该程序有何作用。 从结果来看,原始指令中插入了许多有关英特尔 MPX 的指令,以检测运行时的缓冲区溢出。

Figure 6

现在我们来详细了解一下有关英特尔 MXP 的指令:

bndmk: 在界限寄存器 (%bnd0) 中创建 LowerBound (LB) 和 UpperBound (UB),如上述代码快照所示。
bndmov: 从内存中获取(上下)界限信息并将其放在界限寄存器中。
bndcl: 根据上述代码快照中的参数 (%rax) 检查下界限。
bndcu: 根据上述代码快照中的参数 (%rax) 检查上界限。

故障排除

如果 MPX 无法正常工作。

  1. 复查 CPU、操作系统和 Visual Studio 2015 的版本。 将 PC 启动至 UEFI 设置,检查是否有英特尔 MPX 开关,如有必要打开开关。
  2. 确认英特尔 MPX 驱动程序安装正确并在 Windows* Device Manager 中正常运行。 Figure 7
  3. 检查编译的可执行文件是否包含有关英特尔 MPX 的指令。 插入一个断点,然后运行程序。 如果命中断点,右击鼠标,然后单击Go To Disassembly。 将显示一个新的窗口以供查看汇编代码。

Figure 8

结论

英特尔 MPX 是一款全新的硬件解决方案,有助于抵御缓冲区溢出攻击。 从应用开发人员的角度来看,相比于 AddressSanitizer (https://code.google.com/p/address-sanitizer/) 等软件解决方案,英特尔 MPX 拥有多项优势,包括:

  • 检测指针点在对象之外,但仍然指向有效内存。
  • 英特尔 MPX 更加灵活,可用于许多模块,但不影响其他模块。
  • 与传统代码的兼容性更高,适用于用英特尔 MPX 控制的代码。
  • 由于特殊的指令编码,因此仍然可以发布单一版本的二进制。 在不支持的硬件或操作系统上,与英特尔 MPX 相关的指令将以 NOP(空指令)的形式执行。

在第六代智能英特尔® 酷睿™ 处理器和 Windows 10 上,只需添加编译器选项,即可受益于面向应用的英特尔 MPX,从而帮助增强应用安全性,且丝毫不影响应用的后向兼容性。

相关文章

英特尔® 内存保护扩展启用指南:

https://software.intel.com/zh-cn/articles/intel-memory-protection-extensions-enabling-guide

参考资料

[1] AddressSanitizer: https://code.google.com/p/address-sanitizer/

关于作者

Fanjiang Pei 是软件和解决方案事业部 (SSG) 开发人员关系部门客户端计算移动支持团队的一名应用工程师, 负责为英特尔 MPX、英特尔® Software Guard 扩展等英特尔安全技术提供支持。

Для получения подробной информации о возможностях оптимизации компилятора обратитесь к нашему Уведомлению об оптимизации.
Возможность комментирования русскоязычного контента была отключена. Узнать подробнее.