麻省理工学院。 讲座课程#6.858。 “计算机系统的安全性。” Nikolai Zeldovich,James Mickens。 2014年
计算机系统安全是一门有关开发和实施安全计算机系统的课程。 讲座涵盖了威胁模型,危害安全性的攻击以及基于最新科学研究的安全技术。 主题包括操作系统(OS)安全性,功能,信息流管理,语言安全性,网络协议,硬件安全性和Web应用程序安全性。
第1课:“简介:威胁模型”
第1 部分 /
第2 部分 /
第3部分第2课:“控制黑客攻击”,
第1 部分 /
第2 部分 /
第3部分第3讲:“缓冲区溢出:漏洞利用和保护”
第1 部分 /
第2 部分 /
第3部分讲座4:“特权分离”,
第1 部分 /
第2 部分 /
第3部分讲座5:“安全系统从何而来?”
第1 部分 /
第2部分讲座6:“机会”
第1 部分 /
第2 部分 /
第3部分讲座7:“本地客户端沙箱”
第1 部分 /
第2 部分 /
第3部分 规则
C4有一个警告。 您不能在程序末尾“跳转”。 您可以跳到的最后一件事是最后一条指令。 因此,该规则可确保在“引擎”流程中执行程序时不会出现差异。
规则
C5指出,不能有大于32个字节的指令。 在讨论指令大小到32个字节的多重性时,我们考虑了该规则的特定版本,否则您可以跳入指令的中间并造成系统调用问题,系统调用可以“隐藏”在那里。
规则
C6指出,所有可用的指令都可以从一开始就进行反汇编。 因此,这确保了我们看到每条指令,并且可以检查程序运行时运行的所有指令。
规则
C7指出,所有直接跳跃都是正确的。 例如,您直接跳转到指示目标的那部分指令,尽管它不是32的倍数,但它仍然是从左到右应用反汇编的正确指令。
观众: C5和
C3有什么区别?
教授:我认为
C5表示如果我有一个多字节指令,它就不能越过相邻地址的边界。 假设我有一条指令流,并且有一个地址32和一个地址64。因此,一条指令不能越过32字节的边界倍数,也就是说,它一定不能以小于64的地址开头并以大于64的地址结尾。

这就是规则
C5所说的。 因为否则,在完成乘法运算的跳转32之后,您可以进入另一条指令的中间,在那儿不知道发生了什么。
规则
C3与跳跃这一方面的禁止类似。 它指出,每当您跳跃时,跳跃的长度必须是32的倍数。
C5还声称,地址范围中的任何值(是32的倍数)都是安全的指令。
在阅读了这些规则的清单之后,我感到mixed贬不一,因为我无法评估这些规则是否足够,也就是说,清单是最小的还是完整的。
因此,让我们考虑一下您必须完成的作业。 我认为,实际上,在沙箱中执行一些复杂的指令时,
Native Client的操作有错误。 我相信他们没有正确的长度编码,这可能会导致某些错误,但是我不记得确切的错误是什么。
假设沙盒验证器错误地获取某种指令的长度。 在这种情况下会发生什么不好的事情? 您将如何使用此单据?
受众:例如,您可以隐藏系统调用或return语句
ret 。
教授:是的。 假设您写下了一些漂亮的
AND语句版本。 验证程序有可能被误认为它的长度为6个字节,而实际长度为5个字节。

会发生什么? 验证器认为该指令的长度为6个字节,并且后面还有另一条有效指令。 但是处理器在启动代码时使用指令的实际长度,即5个字节。 结果,我们在
AND语句的末尾有一个空闲字节,我们可以在其中插入系统调用并利用它来发挥我们的优势。 如果我们在此处插入一个
CD字节,它将就像另一条指令的开头。 接下来,我们将在下一个6字节的跨度中放入一些内容,它就像一条以
CD字节开头的指令,尽管实际上它是
AND指令的一部分。 之后,我们可以进行系统调用并从沙箱中“退出”。
因此,本
机客户端验证器必须将其操作与
CPU的操作同步,即准确地“猜测”处理器将如何解释每个指令。 而且这应该在沙箱的每个级别上,这都是很难实现的。
实际上,本
机客户端中还存在其他有趣的错误。 其中之一是在跳入“
可信服务运行时”时错误地清理了处理器环境。 我想我们稍后再讨论。 但是,
可信服务运行时将基本上与旨在运行不可信模块的同一组
CPU寄存器一起工作。 因此,如果处理器忘记清除某些内容或重新启动,则可以通过将不可靠的模块视为受信任的应用程序,并执行不应执行的操作或开发人员不打算执行的操作,来欺骗运行时。
那么我们现在在哪里? 目前,我们了解了如何分解所有指令以及如何防止执行禁止的指令。 现在,让我们看看我们如何在
Native Client模块中存储代码以及数据的内存和链接。
出于性能方面的考虑,
Native Client家伙开始使用硬件支持来确保存储内存和链接实际上不会造成很多开销。 但是在考虑他们使用的硬件支持之前,我想听听建议,如果没有硬件支持,我该怎么做? 我们能否仅提供对机器先前设置的范围内所有内存进程的访问?
受众:您可以使用指令清除所有高位。
教授:是的,没错。 实际上,我们看到这里有此
AND指令,例如,每次跳转到某个地方时,它都会清除低位。 但是,如果我们想将所有可能的代码保持在256 MB以下,我们可以简单地将第一个属性
f替换为
0并获得
$ 0x0fffffe0而不是
$ 0xffffffe0 。 这将清除低位并设置256 MB的上限。
因此,这正是您所提供的功能,确保每次跳转时您的内存都在256 MB之内。 而且,我们正在进行拆卸的事实也使我们有可能验证所有直接跳跃均在可及范围之内。
他们之所以不对自己的代码执行此操作,是因为在
x86平台上,您可以非常有效地对
AND进行编码,其中所有高位均为1。这将导致存在一个3字节的
AND指令和一个2字节的指令为跳跃。 因此,我们有3字节的额外开销。 但是,如果您需要一个非单位的高位,例如
0而不是
f ,那么您突然会有一个5字节的指令。 因此,我认为在这种情况下,他们担心开销。
受众:是否存在一些指令来增加您要获取的版本的说明? 也就是说,您可以说您的指令可能存在持续的偏差或类似现象?
教授:我是这样认为的。 您可能会禁止跳转到某个复杂地址公式的指令,并且仅支持直接跳转到该值的指令,并且该值始终为
AND 。
受众:与...相比,访问内存更重要。
教授:是的,因为那只是代码。 对于在
x86平台上访问内存,有许多奇怪的方法可以访问特定的内存位置。 通常,您必须首先计算内存位置,然后添加一个附加的
AND,然后才进行访问。 我认为这是他们担心由于使用此工具包而导致性能下降的真正原因。
在
x86平台上,或至少在本文所述的32位平台上,它们使用硬件支持,而不是限制引用不可信模块的代码和地址数据。
在弄清楚如何在沙箱中使用
NaCl模块之前,让我们看一下它的外观。 这种硬件称为分段。 它甚至在
x86平台获得交换文件之前就出现了。 在
x86平台上,在此过程中存在受支持的硬件表。 我们称其为段描述符表。 它是一束段,从0到任意大小的表的末尾。 这类似于
Unix上的文件描述符,不同之处在于每个条目包含2个值:基数
基数和长度。
该表告诉我们,我们有一对段,每当引用一个特定段时,从某种意义上讲,这意味着我们正在谈论的是一段内存,该内存开始于
基址的
基址,并持续整个长度
length 。

这有助于我们在
x86平台上保持内存的边界,因为访问内存的每条指令均引用该表中的特定段。
例如,当我们执行
mov(%eax),(%ebx)时 ,也就是说,我们将内存值从
EAX寄存器中存储的指针移动到
EBX寄存器中存储的另一个指针,程序知道起始和结束地址是什么鉴于,并将该值保存在第二个地址中。
但是实际上,在
x86平台上,当我们谈论内存时,有一个隐含的东西称为段描述符,类似于
Unix中的文件描述符。 这只是描述符表中的索引,除非另有说明,否则每个操作代码都包含一个默认段。
因此,执行
mov(%eax)时 ,它引用
%ds或数据段寄存器,该段是处理器中的特殊寄存器。 如果我没记错的话,它是一个指向此描述符表的16位整数。
同样,
(%ebx) -引用相同的
%ds段选择器。 实际上,在
x86中,我们有6个代码选择器组:
CS,DS,ES,FS,GS和
SS 。
CS调用选择器隐式用于接收指令。 因此,如果您的指令指针指向某物,则它指的是选择
CS段选择器的那个。

大多数数据引用隐式使用
DS或
ES ,
FS和
GS表示一些特殊的东西,并且
SS始终用于堆栈操作。 而且,如果您执行
push&pop ,那么它们隐式地来自此段选择器。 这是一个相当古老的机制,但是在这种特殊情况下却非常有用。
如果访问某个地址,例如在选择器
%ds:addr中 ,则硬件会将其重定向到使用表
adrr + T [%ds] .base的操作 。 这意味着它将从同一张表中获取模块长度地址。 因此,每次访问内存时,它都有一个以描述符表条目形式存在的段选择器数据库,并且它将使用您指定的地址并将其与相应段的长度进行匹配。
听众:那么为什么不使用它来保护缓冲区?
教授:是的,这是一个好问题! 我们可以用它来防止缓冲区溢出吗? 例如,对于我们拥有的每个缓冲区,您可以在此处放置缓冲区基数,并在此处放置缓冲区大小。
听众:如果您不需要在编写表格之前将其放在桌子上怎么办? 您不需要经常出现。
教授:是的。 因此,我认为这种方法不经常用于防止缓冲区溢出的原因是,此表中的条目数在第16度不能超过2,因为描述符长16位,但实际上实际上,还有其他一些位用于其他用途。 因此,实际上您只能在该表的记录的13次幂中放置2。 因此,如果您的代码中的数据数组的大小大于2
13 ,则该表可能会溢出。
另外,对于编译器直接管理该表会很奇怪,因为通常使用系统调用来操纵它。 您无法直接写入此表,首先需要对操作系统进行系统调用,然后操作系统会将记录放入此表中。 因此,我认为大多数编译器将根本不想处理这种复杂的内存缓冲区管理系统。

顺便说一句,
Multex使用了这种方法:它有2
18个不同段的记录,2
18个可能的偏移量的记录。 每个公共库片段或内存片段都是单独的段。 他们都检查范围,因此不能在可变的水平上使用。
受众:据推测,不断需要使用内核会减慢该过程。
教授:是的,没错。 因此,由于在堆栈上突然创建新缓冲区时,我们需要进行系统调用以添加它,因此会产生开销。
那么这些元素中有多少实际上使用了细分机制? 您可以猜测它是如何工作的。 我认为,默认情况下,
x86中的所有这些段的基数均等于0,长度为2到32。因此,您可以访问所需的整个内存范围。 因此,对于
NaCl,它们将基数编码为0,并将长度设置为256 MB。 然后,它们指向此记录中256 MB区域的6个段选择器的所有寄存器。 因此,无论何时设备访问内存,它都会以256 MB的偏移量对其进行修改。 因此,更改模块的能力将限制为256 MB。
我认为您现在已经了解了该硬件的支持方式及其工作方式,因此最终可以使用这些段选择器。
那么,如果我们仅执行该计划,会出什么问题呢? 我们可以跳出不受信任模块中的细分选择器吗? 我认为需要注意的一件事是这些寄存器就像常规寄存器一样,您可以将值移入和移出它们。 因此,必须确保不受信任的模块不会使这些段选择器寄存器失真。 因为描述符表中的某处可能有一条记录,它也是基数为0且长度最多为2
32的进程的源段描述符。

因此,如果不可靠的模块能够更改
CS或
DS或
ES或这些选择器中的任何一个,以便它们开始指向覆盖您所有地址空间的原始操作系统,则可以建立到该段的内存链接,并“跳出沙盒。
因此,本
机客户端必须向此禁止列表添加更多说明。 我认为他们禁止所有指令,例如
mov%ds,es等。 因此,一旦进入沙箱,就无法更改引用它的某些事物所引用的段。 在
x86平台上
,用于更改段描述符表的指令具有特权,但可以更改
ds,es本身等。 该表完全没有特权。
观众:您可以初始化表格,以便在所有未使用的插槽中放置零长度吗?
教授:是的。 您可以为没有闲置插槽的表设置长度。 事实证明,您确实需要这个包含0和2
32的附加插槽,因为
受信任的运行时环境应该在此段中启动并获得对整个内存范围的访问。 因此,此条目对于受信任的
运行时环境起作用是必需的。
受众:为了改变表格输出的长度,需要做什么?
教授:您必须具有root特权。
Linux实际上为本地描述符表提供了一个名为
Modify_ldt()的系统,该系统允许任何进程修改其自己的表,也就是说,实际上每个进程都有一个表。 但是在
x86平台上,这更加复杂,既有全局表又有本地表。 可以更改特定过程的本地表。
现在,让我们尝试找出如何从本
机客户端执行过程中跳转或跳出沙盒。 跳出我们意味着什么?

因此,我们需要运行此受信任的代码,并且该受信任的代码“存在”于256 MB限制之上。 要跳到那里,我们将必须撤消
Native Client已安装的所有保护。 基本上,他们归结为更改这六个选择器。 我认为我们的验证器不会对256 MB以上的限制应用相同的规则,因此这非常简单。
但是,然后我们需要以某种方式跳入
受信任的运行时运行时 ,并将段选择器重新安装到该巨型段的正确值,从而覆盖整个进程的地址空间-此范围为0到2
32 。 他们称
原生客户端中存在的此类机制为“蹦床”
蹦床和“跳板”
跳板 。 它们生活在低64k模块中。 最酷的是,这些“蹦床”和“跳跃”是位于过程空间下部64k中的代码段。 这意味着这个不可靠的模块可以跳转到那里,因为它是一个有效的代码地址,在32位的限制内和256 MB之内。 这样您就可以跳上蹦床。
Native Client «» - . ,
Native Client «», trampoline
trusted runtime . ,
DS, CS , .
, , -
malo , «», «» 32- .
, 4096 + 32 , . , ,
mov %ds, 7 ,
ds , 7 0 2
32 .
CS trusted service runtime , 256 .

, , ,
trusted service runtime , . , . DS , , , , - .
, ? , «»? , ?
: 64.
: , , . malo, 64, 32 . , , , .
, 32- , . , , 32 , 32- , . «»
trusted runtime 32 .

. , ,
DS, CS . , 256- ,
trusted runtime , . .
«»,
trusted runtime 256
Native Client . «»
DS , ,
mov %ds, 7 , ,
trusted runtime . . , «», - .
halt 32- «». «», .
trusted service runtime , 1 .
trusted service runtime , , .
: «» ?
: «» 0 256 . 64- , , «», - -.
Native Client .
: ?
: , ? , «»? ?
: , ?
: , -
%eax ,
trusted runtime : «, »!
EAX ,
mov , «»
EAX ,
trusted runtime . , «»?
: , , . …
: , , — , , 0 2
32 . . «», 256 .
, «», . , «» , . , «» .
: «» 256 ?
: , . ,
CS - . «»,
halt , mov,
CS , , 256 .
, , «». ,
DS , ,
CS跳到某个地方。也许,如果您尝试过,可以提出一些x86指令序列,这些指令可以在Native Client模块的地址空间范围之外执行此操作。因此,下周见,并讨论网络安全。该课程的完整版本可在此处获得。感谢您与我们在一起。你喜欢我们的文章吗? 想看更多有趣的资料吗? 通过下订单或将其推荐给您的朋友来支持我们,为我们为您开发
的入门级服务器的独特模拟,为Habr用户提供
30%的折扣: 关于VPS(KVM)E5-2650 v4(6核)的全部真相10GB DDR4 240GB SSD 1Gbps从$ 20还是如何划分服务器? (RAID1和RAID10提供选件,最多24个内核和最大40GB DDR4)。
VPS(KVM)E5-2650 v4(6核)10GB DDR4 240GB SSD 1Gbps至12月免费,在六个月内付款时,您可以在此处订购。戴尔R730xd便宜2倍? 仅
在荷兰和美国,我们有
2台Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100电视(249美元起) ! 阅读有关
如何构建基础架构大厦的信息。 使用价格为9000欧元的Dell R730xd E5-2650 v4服务器的上等课程?