翻译自:https://medium.com/@danielabloom/binary-exploitation-eli5-part-3-d1872eef71b3

“To err is human… to really foul up requires the root password.”
0×00 - 前言
本文是白话二进制漏洞攻击方式第三部分。
在整篇文章中,我们将介绍:
0x01 – 必备知识:寄存器
0x02 – 必备知识:堆和内存的分配
0x03 - 攻击:面向返回编程(ROP)
0x04 - 防御:地址空间布局随机化
0x05 - 攻击:Heap Spraying(堆喷射)
让我们开始吧!
0x01 - 必备知识:寄存器
在前面的文章中,我们讨论了诸如堆栈和汇编调用规范之类的概念。在本节中,我想扩展讨论一个程序存储数据的功能,并为您提供一些重要的汇编命令和更详细的概述。
如前文所述,堆栈是每个程序的地址空间内的存储区域,用于存储数据(即变量,存储位置等)。虽然堆栈可以看作程序的背包,每个程序也都有自己的口袋称为寄存器。寄存器是存储器的小区域,可以根据寄存器存储结构化或任意信息。存储结构化或任意信息的意思是某些寄存器存储特定类型的信息,例如ESP寄存器,它被称为堆栈指针并且是专有的在任何时刻都存储堆栈顶部的地址,而其他如EBX寄存器可以存储任意信息。当存储数据到寄存器中时,你必须通过MOV组件命令移动数据到寄存器中:

除了简单地存储数据之外,寄存器也可用于执行一般操作。我们来看一个例子:

对于那些可能还没有完全理解寄存器的人来说,让我们来看看我在本节前面提到的关于寄存器是程序口袋的这个概念。
想象一下,你准备好休假一天。你穿着牛仔裤,T恤衬衫,背着背包。在你走之前,你需要收拾你的东西。你把钱包放在一个口袋里,把你的钥匙放在一个口袋里,把你的手机放在一个口袋里,把你的太阳镜放在一个口袋里,然后把不能放入口袋里的所有东西放入背包里。
程序与此非常相似,虽然它们可以将任何数据存储在堆栈(背包)上,但寄存器(口袋)只能存储适合它们的数据。但是,程序可以更有效地从寄存器中检索数据而不是堆栈(尽管在现代计算机中,堆栈和寄存器之间的速度差异可以忽略不计)。


0x02 - 必备知识:堆和内存分配
我们现在讨论了两种存储数据的方法,堆栈和寄存器。这两个内存区域都非常适合存储普通变量,内存地址等。但是,如果你需要存储一些非常大的内容呢?或者如果你的程序需要比为堆栈分配的内存更多的内存呢?嗯,这就是堆的作用。堆只是内核为程序分配的一大块内存。如果堆栈是我们的背包,寄存器是我们的口袋,堆可以看作我们把东西放在晚上的房间里,你可能只需要房间的一个小角落来放置你的东西,但以防万一你需要更多空间时就可以随时分配到(更多的内存)。要在堆上分配内存,我们使用malloc函数。Malloc是一个C函数,可以分配所需的内存。例如,如果调用malloc(8),程序将在堆上分配8个字节。malloc最常见的实现方式是Doug Lea实现,它将每个分配块的块大小存储在块之前的字节中。
让我们看一下内存分配,举个简单的例子:
鲍勃在一家工厂工作。鲍勃被要求为A部分留出3个螺丝,为B部分留出4个螺丝.鲍勃在一张纸上写下“3-A”和“4-B”,然后在纸上放下7个螺丝。现在,鲍勃已在同一区域为这两个部件分配了正确数量的螺钉。
看到了吗?很简单吧!如果我想在堆上分配8个字节的内存,我使用malloc(8)将0x08放在位于分配区域的内存位置之前的空间中。这样,如果我们以后需要分配更多内存,程序只需获取堆的基地址并向其添加先前分配的空间0x08!十分简单!
0x03 - 攻击:面向返回编程(ROP)
在本系列文章的前面,我们讨论了Return-to-libc(Ret2libc)攻击,其中攻击者覆盖了一个返回变量并强制程序通过libc库执行系统命令。在本节中,我们将讨论面向返回的编程,它可以被视为ret2libc的更高级版本(尽管它通常根本不涉及libc)。在ROP攻击中,攻击者操纵返回调用以返回汇编代码中的不同段(称为gadgets)允许攻击者基本上完全控制程序的执行。例如,攻击者可以不断定位到不同的gadgets,以便在程序中创建后门,而无需实际操作任何代码或内存区域(不在堆栈或堆上直接执行)!
在我们进一步讨论如何完成此攻击之前,我们需要深入了解实际的gadgets。gadgets是在应用程序中找到的汇编代码段,以return语句结尾。

由于gadgets是非恶意的,编译器构建,并且作为您尝试利用的程序的一部分代码片段,攻击者必须做的就是找到它们(可以通过简单地拆解程序或通过像Ropper这样的小工具查找程序来完成)然后将它们的地址放在堆栈上。通过构建一系列gadgets,并小心地操纵堆栈中的数据,攻击者可以在程序中编写完美的后门,或者根据需要做任何事情。
我们来看一个例子:

正如您所看到的,如果攻击者能够操纵堆栈中的数据,他/她可以简单地压入变量并覆盖返回地址然后使用他/她想要的程序!在上面的例子中,我们所做的只是让程序把3和4相加,以便eax寄存器等于7,但是如果不是那么简单的相加,你使用整个gadgets链将不同的数据块压入到堆栈上,最终落地在具有网络调用的gadgets上,这些gadgets可以从计算机中提取数据并将其发送到攻击者持有的服务器。
0x04 - 防御:地址空间布局随机化(ASLR)
在本系列文章的前面部分,我们讨论了DEP/NX作为对存储区域(如堆栈和堆)中代码执行的可行防御。然而,对于像ret2libc和Return Oriented Programming这样的攻击,我们很快就能够击败这种防御机制。所以现在,如果我们无法抵御因ROP攻击而被执行的代码,并且由于格式字符串漏洞我们无法确保stack canaries的防御能力,我们如何防御二进制利用攻击呢?那么如果我们每次打开程序时随机化了内存段的内容呢?这正是地址空间布局随机化。每次执行程序时,堆栈,堆,.text部分(存储汇编代码的地方)和其他内存部分都以随机偏移的方式放入内存中,这样,程序本身可以完美运行,攻击者无法获取堆栈中重要数据的内存地址,也不能在每次尝试利用可执行文件时在同一位置找到相同的gadgets。此外,随着内核地址随机链接的进一步实现,攻击者甚至无法自信地定位像libc这样的链接库,因为每次初始化链接时,库将以随机偏移的加载到内存中。
这听起来像一个完美的解决方案,对吗?嗯,是也不是。虽然没错,ASLR使得开发漏洞攻击更加困难,但是恶意代理是聪明和坚韧的。在接下来的部分中,我们将讨论可以击败ASLR的攻击。
0x05 - 攻击:堆喷射
所以在我们进入Heap Spray之前,我需要先说我实际上跳过了本系列文章中的堆溢出攻击。这是因为它们非常类似于堆栈缓冲区溢出,在堆溢出中,攻击者获得了将任意数据写入堆的能力,并且只要DEP/NX未启用,攻击者就可以将恶意代码写入堆。但是,正如上面的ASLR部分所述,即使攻击者可以将恶意代码写入堆中,他/她也无法执行它,因为在内存中的代码会根据程序将恶意代码置于堆中的不同位置(通过ASLR的实现随机化)。那么,我们将如何寻找和执行此代码?
那么,如果不是只将恶意代码写入堆中的特定区域,我们将它复制到整个堆上,然后尝试在庞大的堆中的任何地方执行,那该怎么办呢?嗯,这正是堆喷射。这几乎是概率问题-向墙上扔箭,最后总有一些会插上去!
让我们把它分解成一个更基本的例子:
鲍勃是一个盲人飞镖球员。显然,如果鲍勃只是扔了一个飞镖,他几乎没有机会击中靶心(因为他甚至看不到靶!)但是,如果鲍勃投掷1000个飞镖,其中至少有一个必然会击中目标的中心。
如果我们将恶意代码放在堆中一次,那么在指向单个内存地址时我们就不太可能找到它,但是,如果堆完全充斥着我们的恶意代码,我们最终肯定会遇到它!
请注意,在64位系统上,堆喷射远不如32位系统有效,因为在32位系统上,您可以使用恶意代码完全填满堆,而在64位系统上,堆非常大,即使你试图喷射堆,你可能仍然无法找到你的恶意代码。
0x06 - 结论
在本文中,我们讨论了:
0×01. 寄存器
0x02. 堆
0x03. 内存的分配
0x04. 地址空间布局随机化
0x05. 内核地址随机链接
0x06. 面向返回编程
0x07. 堆喷射
我希望这篇文章很有帮助。

源链接

Hacking more

...