缓冲区溢出和返回至libc攻击的区别

10
我想了解这两种攻击之间的确切区别。根据我所了解的:
缓冲区溢出:它覆盖栈上的ret地址,使其指向代码的另一部分,其中插入了恶意代码。因此,我们需要修改程序的源代码才能实际执行攻击。
返回到Libc:这里不是修改源代码,而是使用由C库提供的运行时函数调用(比如打开一个shell)。在这里,用于函数调用的参数也会传递到覆盖缓冲区中,在栈的ret部分之后结束。
以上描述准确吗?
另外一个相关的问题是 - 是否可能在不修改原始程序的源代码的情况下发生缓冲区溢出攻击?可能是,如果我们编写一个新程序并允许它修改某些内存部分(即原始程序中的已损坏栈的新ret地址)。但是,我认为这可能是不可能的,因为内核中存在进程间提供的内存保护。
4个回答

12
在经典的缓冲区溢出攻击中,被溢出的栈缓冲区通常会被填充上要执行的机器码(称为shellcode,因为它通常会调用一个shell进程)和新的返回地址。新的返回地址将被精心构造,指向溢出的栈缓冲区本身内部的某个位置。显然,这需要知道或猜测被攻击进程中该栈缓冲区的地址。
在那个时代,进程的内存布局通常高度确定性,攻击者通常可以相当准确地预测栈缓冲区的位置(特别是如果他们确切地知道正在被攻击的目标软件的版本)。为了提高在有些猜测工作参与的情况下的成功率,活动的shellcode通常会在前面跟着大量无用操作的可执行机器码,称为"NOP sled"或"NOP slide",其中"NOP"是执行“无操作”的典型机器码指令的名称。返回到NOP sled中的任何位置都会产生所需的效果。
另一方面,“return-to-libc”攻击并不直接导致被劫持的进程返回到shellcode。相反,它会使进程逐个返回到一系列库函数的起始点。这些库函数可能直接执行攻击者想要的操作,但更常见的是它们会被用于间接执行攻击者的shellcode。

4
在这两种攻击中,覆盖返回地址的部分是共享的。正如上面的回复所示,你曾经只需返回到你编写的汇编代码即可。然后,这个汇编代码会生成一个root用户shell。
在任何一种攻击中,你都不需要“覆盖”源代码。从汇编的角度来看,源代码位于.text段中,并且始终(或者至少我知道的所有时间)受到写保护。你过去利用的方法是编写之前已经汇编成内存段的代码,然后跳转到这个代码。这个代码通常位于或靠近“堆栈段”,通过溢出你选择要溢出的内容,你可以将流量重定向到ret地址(例如)。其他攻击方式包括你先前创建并填充了shellcode的环境变量;或者也可以使用堆、函数指针或PLT。插入的代码通常会使用system()调用来执行所需的操作-但是为此,你需要能够在内存区域上“执行”(或者具有你打算使用的重定位条目声明为可写)。
这两种攻击的区别在于,一旦内存被大量设置为不可执行,攻击类型a(堆栈溢出)就基本上无法进行。然后,作为你所写的一种尝试来绕过这种攻击,是直接访问共享库函数-也就是说,你不再需要先将你的代码写入堆栈段或其他地方并在那里执行它。然而,我相信libc类型的攻击现在也被大部分修补了;我手头没有详细信息;也许我错了。
如果你想看看这些攻击如何被挫败,或者至少了解一些关键思想,请搜索“2011年(或2010年)破坏堆栈”来开始。

你能解释一下什么叫做使内存可执行(或不可执行)吗?我的疑惑之一是,我认为C代码无法访问主内存的所有部分。那么如何确定恶意汇编代码放置在内存中的位置?因为我认为对于大多数内存区域-返回时只会标记一个分段错误。 - Hari
你插入的漏洞代码核心通常使用exec()家族风格函数。这意味着,通过提供适当的参数,您可以让它生成一个作为root用户的/bin/sh shell。这是执行程序,但现在你不能了。有许多不同的安全措施,但其中一个将被描述,例如: - gnometorule
抱歉评论有些混乱 - 我的浏览器出了点问题。如果你使用Ubuntu,这里展示了当前已实现的内容:https://wiki.ubuntu.com/Security/Features - gnometorule
你写道你怀疑C代码可以访问所有的内存部分。这也是正确的,但只是你面临的措施整个包裹的一部分。其中一个例子是(我正在Ubuntu下工作,但在其他地方也会类似)设置一个canary值(你也可以谷歌一下)。 - gnometorule
如果您使用gcc,请先使用-fno-stack-protector编译一次,再不使用该选项编译一次,并查看反汇编。您会注意到在ebp附近有一个对gs:14的引用,它设置了canary值,您无法从中读取或写入-至少我所知道的没有任何技巧可以做到。您是正确的,分段错误的标记是检测到攻击企图的一般响应。但是,这并不意味着您一定尝试覆盖返回地址,尽管通常确实如此。 - gnometorule
如果你想知道现在人们如何仍然进行黑客攻击(他们到底将代码放置在哪里,使这仍然有效),我不知道。正如我所说,即使libc攻击现在也已经不再成功——至少第一代攻击已经不行了(正如我上面所说的那样,它们是不同的,因为程序的执行在共享库中而不是在堆栈上)。 - gnometorule

4
我认为“缓冲区溢出”是一种编程错误,而“返回libc”是一种利用技术。最好不要混淆这两个概念。
例如,您可以使用“返回libc”来利用“缓冲区溢出”漏洞。或者您还可以使用其他技术,如“返回.text”或“返回shellcode”。反过来,您也可以使用“返回libc”来利用其他漏洞,如“格式化字符串”。

0

实际上,在缓冲区溢出攻击中,您在覆盖ret指针的同时插入恶意代码。对于此操作,您无需修改任何内容,因此我无法看出所提到的两种攻击之间的区别。

例如:

char* str[5];
cin << str;

这是可以被利用的代码,如果用户插入的字符串大于5个字符,则会覆盖其后面的堆栈上的所有内容。由于ret-ptr在堆栈上“较低”,因此如果距离正确,您可以覆盖它。您在覆盖时的意图是让它指向您输入的开头,其中插入了恶意(汇编)代码,该代码将在调用ret-ptr并执行“跳转”时立即执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接