麻省理工学院。 讲座课程#6.858。 “计算机系统的安全性。” Nikolai Zeldovich,James Mickens。 2014年
计算机系统安全是一门有关开发和实施安全计算机系统的课程。 讲座涵盖了威胁模型,危害安全性的攻击以及基于最新科学研究的安全技术。 主题包括操作系统(OS)安全性,功能,信息流管理,语言安全性,网络协议,硬件安全性和Web应用程序安全性。
第1课:“简介:威胁模型”
第1 部分 /
第2 部分 /
第3部分第2课:“控制黑客攻击”,
第1 部分 /
第2 部分 /
第3部分第3讲:“缓冲区溢出:漏洞利用和保护”
第1 部分 /
第2 部分 /
第3部分 有趣的是,尽管我们主要使用硬编码地址,但攻击者无法跳转到特定地址。 他所做的被称为“堆攻击”,如果您是坏人,那对您来说将很有趣。 有了这样的攻击,黑客开始动态分配大量的Shell代码,然后简单地将其随机输入到内存中。 如果您使用动态高级语言(例如JavaScript),这将特别有效。 因此,标签读取器处于狭窄的循环中,仅生成大量的外壳代码行,然后填充其中的一堆代码。
攻击者无法确定这些行的确切位置,他只是选择10 MB的Shell代码行并进行任意跳转。 而且,如果他能以某种方式控制
ret指针之一,那么他就有机会“登陆” shell代码。

您可以使用一种称为
NOP slide ,
NOP sled或
NOP ramp的技巧,其中
NOP是
无操作指令或空的,空闲的命令。 这意味着只要程序转到幻灯片上任何位置的内存地址,处理器命令执行流程就会“滑至”其最终的所需目标。
想象一下,如果您有一行外壳程序代码,并且转到该行上的随机位置,则此方法可能行不通,因为它不允许您以正确的方式部署攻击。
但是,也许您放入堆中的内容基本上只是一吨
NOP ,到最后,您有了shell代码。 这实际上很聪明,因为这意味着现在您可以真正到达要跳跃的正确位置。 因为如果您跳入这些
NOP之一 ,它只会发生“动臂,动臂,动臂,动臂,动臂,动臂,动臂”,然后进入Shell代码。
似乎人们想出了这一点,您可能在我们的团队中看到过。 他们发明了类似的东西,这就是问题所在。 因此,这是另一种绕过某些随机事件的方法,只需使代码的随机化变得健壮(如果有道理)。
因此,我们讨论了可以使用的某些类型的随机性。 人们也出现了一些愚蠢的想法。 因此,现在您知道,当您要进行系统调用时,例如,使用
syscall libc函数,您基本上传入了代表您要进行的系统调用的唯一数字。 因此,也许
fork函数是7,
sleep是8,或者类似的东西。
这意味着,如果攻击者可以弄清楚该
syscall指令的地址并以某种方式到达该地址,则他或她实际上可以仅替换他们要直接使用的系统调用号。 您可以想象,每次程序运行时,实际上都会
为有效的
syscall创建一个
syscall号的动态分配,以使攻击者的捕获更加复杂。

甚至有一些前卫的建议来更改硬件,以使设备包含用于
异或动态功能的
异或加密密钥。 想象一下,每次编译程序时,所有指令代码都会获得一个特定的
xor键。 最初下载程序时,此密钥存储在设备寄存器中,之后,无论何时执行指令,设备都会在继续执行该指令之前自动对其执行
异或运算。 这种方法的好处是,即使攻击者可以生成Shell代码,他也不会识别此密钥。 因此,他很难弄清楚到底该把什么存储在内存中。
听众:但是,如果他可以获取代码,那么他也可以使用
xor将代码转换成指令。
教授:是的,这是规范的问题,对。 这有点类似于在
BROP攻击期间会发生的情况,当时我们似乎随机选择了代码的位置,但是攻击者可以“感觉”它并找出正在发生的情况。 可以想象,例如,如果攻击者知道他希望在二进制文件中找到的某些子代码序列,他将尝试对该文件使用
xor操作以提取密钥。
本质上,我们讨论了今天我想告诉您的各种随机攻击。 在进行编程之前,值得讨论一下在实践中使用了哪种保护方法。 事实证明,
默认情况下 ,
GCC和Visual Studio都包括
堆栈金丝雀方法。 这是一个非常受欢迎且非常有名的社区。 如果您查看Linux和Windows,它们还将利用不可执行的内存和地址空间随机化等优势。 的确,
宽松边界系统在它们中并不那么受欢迎,这可能是由于我们已经讨论过的内存,处理器,错误警报等方面的成本。 因此,基本上我们已经研究了如何防止缓冲区溢出问题。
现在,我们将讨论
ROP (反向编程)。 今天,我已经告诉您了在随机分配地址空间和防止数据执行方面的含义-它是读取,写入和执行。 这些实际上是非常强大的东西。 因为随机化可以防止攻击者了解我们的硬编码地址在哪里。 阻止数据执行的能力确保即使将shell代码放在堆栈上,攻击者也不能只是跳转到它并执行它。
所有这些看起来都是渐进的,但是黑客正在不断开发针对这种渐进式防御解决方案的攻击方法。
那么反向编程的本质是什么?
如果攻击者不仅可以在攻击过程中创建新代码,还可以组合现有代码段,然后以异常方式将它们组合在一起,该怎么办? 毕竟,我们知道该程序包含大量此类代码。

因此,幸运或不幸的是,这完全取决于您站在哪一边。 如果您可以找到一些有趣的代码片段并将它们组合在一起,则可以得到
图灵语言之类的内容,攻击者可以在其中进行所需的操作。
让我们看一个非常简单的示例,起初您似乎很熟悉,但是很快就会变成疯狂的事情。
假设我们有以下程序。 因此,让我们拥有某种功能,对于攻击者来说很方便,这是一个不错的
运行shell函数。 因此,这只是对系统的调用,它将执行
bin / bash命令,并且此操作将结束。 接下来,我们有一个规范的缓冲区溢出过程,或者对不起,该函数将宣布缓冲区的创建,然后使用这些不安全的函数之一用字节填充缓冲区。

因此,我们知道这里发生了缓冲区溢出而没有问题。 但是有趣的是,我们具有
运行外壳程序功能,但是很难基于缓冲区溢出来获得它。 攻击者如何调用此
运行Shell命令?
首先,攻击者可以反汇编程序,启动
GDB ,然后在可执行文件中找到该地址。 您可能熟悉实验室工作中的这些方法。 然后,在缓冲区溢出期间,攻击者可以获取该地址,并将其放入生成的缓冲区溢出中,并验证函数是否返回到
run shell 。
为了清楚起见,我将其绘制。 因此,您有一个如下所示的堆栈:在底部有一个溢出的缓冲区,在其上方是一个已保存的间隙指示器,在其上方是
prosess_msg的返回地址。 在左下方,我们有一个新的堆栈指针来启动该函数,在其上方有一个新的中断指针,然后是将要使用的堆栈指针,甚至更高的是前一帧的中断指针。 看起来都很熟悉。

就像我说的那样,在攻击过程中,使用
GDB来查找
运行外壳程序的地址是什么。 因此,当缓冲区溢出时,我们可以简单地将
运行外壳程序的地址放在右边。 这实际上是我们已经知道如何做的相当简单的扩展。 本质上,这意味着如果我们有一个启动外壳程序的命令,并且如果我们可以反汇编二进制文件以找出该地址在哪里,则只需将其放入位于堆栈底部的此溢出数组中即可。 这很简单。
因此,这是一个极其琐碎的示例,因为程序员出于某种疯狂的原因将此功能放在此处,从而为攻击者提供了一份真正的礼物。
现在假设不是调用
run_shell ,而是调用
run_boring ,然后简单地运行
/ bin / ls命令 。 但是,我们并没有丢失任何东西,因为我们将在字符串的
最上面放置char * bash_path ,它将告诉我们该
bin / bash的路径。

因此,最有趣的是,想要运行
ls的攻击者可以“解析”程序并找到
run_boring的位置,这一点都不好玩。 但是实际上,我们在内存中有一条线指向shell的路径,此外,我们还知道其他有趣的东西。 这是即使程序不使用
/ bin / ls参数调用系统,它仍然会进行某种调用。
因此,我们知道系统必须以某种方式与该程序
系统(“ / bin / ls”)连接 。 因此,我们可以使用这两个
void操作将系统与此
char * bash_path参数实际关联。 我们要做的第一件事是进入
GDB ,找出该
系统(“ / bin / ls”)在二进制过程映像中的位置。 因此,您只需转到
GDB ,只需键入
print_system并获取有关其偏移量的信息。 这非常简单,您可以对
bash_path进行相同的
操作 。 也就是说,您只需要使用
GDB来查找该事物的住处。
完成后,您需要做其他事情。 因为现在我们确实需要以某种方式弄清楚如何使用我们选择的参数来调用系统。 我们执行此操作的方法实质上包括伪造系统的调用框架。 记住,框架是编译器和硬件用来实现堆栈调用的框架。
我们想在堆栈上安排一些类似于我在此图中所描绘的内容。 实际上,我们将伪造一个应该在堆栈上的系统,但要在它实际执行其代码之前。
因此,这里有系统的参数,这是我们要执行的行。 在底部的一行中,当带有参数的行完成时,系统应在其中返回。 系统希望堆栈在执行开始之前就具有这种外观。

我们过去一直假设传递函数时没有参数,但是现在看起来有些不同。 我们只需要确保
参数在我们正在创建的溢出代码中即可。 我们只需要确保此伪造的
调用帧在此数组中即可。 因此,我们的工作如下。 回想一下,堆栈溢出是从底部到顶部。

首先,我们将把系统地址放在这里。 在顶部,我们将放置一些
垃圾返回地址 。 这是系统完成后将返回的位置。 该地址将是一个随机字节组。 在它上面,我们将放置地址
bash_path 。 现在缓冲区溢出时会发生什么?
在
prosess_msg到达终点后,他会说:“好,这是我应该返回的地方”! 系统代码继续运行,它移至更高位置并看到我们创建的假调用框架。 对于该系统,什么都不会发生,它会说:“是的,我要执行的参数是
bin / bash ”,它执行并完成了-攻击者已经捕获了外壳!
我们现在做了什么? 我想说,我们利用了
调用约定 (
calling Convention)的知识,将其作为创建伪堆栈框架或伪框架名称的平台。 使用这些伪造的
调用框架 ,我们可以执行已引用的应用程序已经定义的任何功能。
我们应该问的下一个问题是:如果程序根本没有此
char * bash_path行,
该怎么办? 我注意到该行几乎总是存在于程序中。 但是,假设我们生活在一个倒置的世界中,现在还不存在。 那么我们该怎么做才能将该行放入程序中?
为此,您可以做的第一件事是为
bash_path指定正确的地址,并将其放在更高的位置,这里是我们堆栈的该部分,在其中插入三个元素,每个元素的大小为4个字节:
/ 0
/拍
/箱
但是无论如何,我们的指针就会来到这里-繁荣! -事情完成了。 这样,您现在可以通过简单地将参数放在外壳程序代码中来调用参数。 恐怖,不是吗? 而所有这些都是在全面的
BROP攻击之前
构建的 。 但是在指出完整的
BROP攻击之前,您需要了解如何将代码中已经存在的内容简单地链接在一起。 当我在这里有这个转储的返回地址时,我们只想访问外壳程序。 但是,如果您是攻击者,则可以将此返回地址或返回地址定向到可以真正使用的地址。 如果执行了此操作,则可以将多个函数连续地串成一行,并把一个函数的多个符号串成一行。 这确实是一个非常强大的选择。
因为如果仅设置跳转的返回地址,则此后程序通常会崩溃,而我们可能不希望这样做。 因此,有必要将其中一些东西链接在一起,以便使用该程序做更多有趣的事情。
假设我们的目标是我们想要多次调用该系统。 我们不只是想做一次,我们会做任意次。 那怎么办呢?
为此,我们使用了两条我们已经知道如何获取的信息。 我们知道如何获取系统地址-您只需要查看
GDB并在此处找到它。 我们还知道如何找到该行的地址
bin / bash 。 现在,要使用对系统的多次调用来发起这种攻击,我们需要使用小工具。 这使我们更接近
BROP中发生的事情。
因此,我们现在需要找到这两个代码操作的地址:
pop%eax和
ret 。 第一个删除堆栈的顶部,并将其放入
eax寄存器,第二个放在堆栈的
eip指令
指针中 。 这就是我们所说的小工具。 攻击者似乎可以使用一小组汇编指令来进行更具野心的攻击。

这些小工具是黑客用来查找二进制文件之类的标准工具。 假设您拥有二进制文件的副本,那么找到这些小工具之一也很容易,而且我们也不会为随机化而烦恼。 这些东西很容易找到,以及系统地址也很容易找到,依此类推。
那么,如果我们有这些小工具之一,为什么还要使用它呢? 当然要做恶! 为此,您可以执行以下操作。
假设我们更改堆栈,使其看起来像这样,则漏洞利用与以前一样是自下而上的。 我们要做的第一件事是在此处放置系统地址,并在其上方放置
pop / ret小工具的地址。 甚至更高,我们放置
bash_path的地址,然后重复所有操作:从上方再次放置系统地址,小工具
pop / ret的地址和
bash_path的地址。

现在这里会发生什么? 这会有些复杂,因此可以在Internet上获得本讲座的笔记,现在您可以只听这里发生的事,但是当我第一次理解这一点时,就像在了解圣诞老人不存在!
我们将从入口
条目所在的地方开始,回到
ret语句将使用
pop命令从堆栈中删除项目的系统,因此现在堆栈指针的顶部在此处。 因此,我们使用
pop删除元素,然后返回
ret过程,该过程将控制权转移到从堆栈中选择的返回地址,并将此返回地址与
call命令一起放置在那里。 因此,我们再次调用该系统,并且可以一次又一次地重复此过程。

显然,我们可以将此序列与执行任意数量的事情相关联。 本质上,内核获得所谓的反向编程。 请注意,我们在此堆栈上未执行任何操作。 我们做了能够防止数据执行而又不破坏任何事情的事情。 我们只是做了一些意外的跳跃以完成我们想要的。 实际上,它非常非常非常聪明。
有趣的是,在较高的层次上,我们已经确定了这种新的计算模型。 , , , . , , . , - . , , . , . . , . , ,
stack canaries., «» , . , , «»
ret address saved %ebp , - , «». ,
ret , , «», , - .
stack canaries .
, «». , . , «»?
, , , .
, , , «» , «» «».
, , , «» , , .
, - , «» , . , ? ?
,
fork . ,
fork . , , ,
fork , , , , «» . ,
stack canaries .
«»? . , , , «». «» . .

, , – , «». , , 0. , «», . , :
«, «»! , 0. «»! 1 – «», 2 – . , 2- . , , «».

, , , .
«», , , . , , «».
57:10
:
麻省理工学院的课程“计算机系统安全”。 第3讲:缓冲区溢出:漏洞利用和保护,第3部分该课程的完整版本可在此处获得。感谢您与我们在一起。 你喜欢我们的文章吗? 想看更多有趣的资料吗? 通过下订单或将其推荐给您的朋友来支持我们,为我们为您开发
的入门级服务器的独特模拟,为Habr用户提供
30%的折扣: 关于VPS(KVM)E5-2650 v4(6核)的全部真相10GB DDR4 240GB SSD 1Gbps从$ 20还是如何划分服务器? (RAID1和RAID10提供选件,最多24个内核和最大40GB DDR4)。
戴尔R730xd便宜2倍? 仅
在荷兰和美国,我们有
2台Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100电视(249美元起) ! 阅读有关
如何构建基础架构大厦的信息。 使用价格为9000欧元的Dell R730xd E5-2650 v4服务器的上等课程?