缓冲区溢出攻击:为什么“jmp esp”需要位于DLL中?

8
我试图理解经典的缓冲区溢出漏洞,即输入缓冲区覆盖堆栈、保存在堆栈上的函数返回地址和上部内存区域(通常用于放置shell代码)的情况。互联网上有许多这方面的例子,我认为我已经理解得很好了:
1. 输入数据超过开发人员设定的固定大小的输入缓冲区。 2. 溢出的输入将覆盖堆栈上调用函数的函数参数和返回地址。 3. 当操作系统尝试从发生溢出的函数返回时,该地址将被加载到EIP中,这就允许您将受控制的数据加载到EIP寄存器中(如果不仔细阅读一些文章,您会得出可以覆盖CPU寄存器的印象。当然,事实并非如此,您只能覆盖堆栈,但CPU将从堆栈中加载地址到其寄存器中)。 4. 如果攻击设计得很好,那么加载到EIP中的值将使程序跳转到shell代码的开头(参见第5点)。 5. 我认为我已经很好地理解了“JMP ESP”机制。在您的实验环境中复制崩溃时,您要查找内存中包含“JMP ESP”指令的位置,并用该地址覆盖EIP(现在我的措辞不太准确,我知道...)。无论您运行此操作多少次、在何时、哪个线程执行您的操作等,该地址都需要相同,至少对于相同的操作系统版本,且该地址不能包含任何禁止使用的字节。代码将跳转到该地址(同时堆栈减少4个字节),因此“jmp esp”会跳转到溢出缓冲区中我设置用于覆盖EIP的值之后的下一个地址,通常这是shellcode所在的地方,可能附加了NOP。
现在来谈问题。
所有我读过的文章都在DLL中寻找“JMP ESP”指令的地址(必须不可重定位,未使用ASLR等)。为什么不在exe本身中寻找“jmp esp”?为什么它必须在DLL中?
我在Immunity Debugger中运行了“!mona modules”命令,唯一显示满足所有这些条件的模块是exe本身。当我查看流行的漏洞数据库时,地址总是在已加载的DLL中。
我看不出任何明显的原因。exe也可以在内存中以与DLL相同的方式位于相同的地址。有什么区别吗?

3
仔细思考一下。哪种攻击最有可能成功?你希望的确切版本的单个EXE,还是用于许多进程、存在一段时间且不容易被EXE所有者更改的DLL? - Hans Passant
好的,基本上你的意思是,在DLL中使用地址更有可能在多个易受攻击程序版本中成功,而如果我在EXE中使用地址,这只对该程序的特定实例可靠,而不适用于其他版本?但是,从技术上讲,我为什么不能在同一个EXE中使用地址呢? - kaidentity
我再次查看我的示例,我认为答案比我最初想象的要简单得多。exe加载在基地址0x004...处,并延伸到0x009...。这意味着每个地址都将包含一个0x00,这对于每个类似C的程序来说可能是一个停止符... - kaidentity
3个回答

4

我在这里找到了另一个有用的资源:

http://resources.infosecinstitute.com/in-depth-seh-exploit-writing-tutorial-using-ollydbg/#commands

你可以考虑使用的一个模块是主可执行文件本身,特别是当应用程序由第三方开发人员而不是Microsoft编写时,通常不会受到任何基于编译器的漏洞保护。然而,使用主可执行文件有一个主要缺陷,几乎总是以零字节开头。这是一个问题,因为在C/C++中,零字节是字符串终止符。如果将零字节作为溢出缓冲区的一部分使用,并且在该点处终止字符串,则可能导致无法适当地溢出缓冲区并破坏漏洞。


Corlan也是一个很好的参考资料。它指出:“我们不能仅仅用像000ff730这样的直接内存地址覆盖EIP。这不是一个好主意,因为它不可靠,而且包含了一个空字节。”正如答案中所述,空字节似乎是个问题。 - Goaler444

2
位置无关代码中,跳转地址相对于程序计数器,而在不可重定位代码中,它们是绝对的。Windows中的DLL通常不使用位置无关代码。依赖于二进制文件中可执行代码偏移量的漏洞需要非可重定位代码。

投反对票的人最好解释一下这个答案的问题所在。我们怀着善意发布帖子,除非我们知道为什么它不是一个有用的答案,否则惩罚性的反对票对SO社区毫无意义。 - Clifford
我没有给你的回答点踩,但是我也看不到它有任何帮助,因为它根本没有回答我的问题。我的问题是关于为什么我不能在exe文件本身中查找jmp esp。 - kaidentity
1
@kaidentity:你文本中带有问号的句子(即问题)是“为什么需要放在DLL中?”exe文件是位置无关的,跳转地址是相对而非绝对的。 - Clifford

0

简短回答:地址不需要在DLL中。

详细回答:

如果指令寄存器设置为其地址,则进程中的任何映射的未受保护的可执行内存都将被执行。

映射:意味着操作系统将内存映射到您的进程中,某些地址可能没有映射,任何访问都会导致操作系统内存故障信号被触发。

可执行:映射内存通常具有权限设置,有时仅需内存可读,但对于具有NX位的新处理器,可能需要将其映射为可执行。

未受保护:表示该内存未映射为守卫页。由守卫页保护的内存页面将引发处理器中断。这些的处理取决于您的操作系统,并且可以用于在不实现NX位的处理器上实现非可执行页面。

如果您的可执行文件中的内存符合这些要求,则可以将其用于利用。是否能够在运行时找到此地址是另一个问题,并且对于真实世界的利用来说可能非常困难。对于初学者来说,坚持使用非可重定位的DLL和EXE是一个好主意。

至于您的评论:

我再次查看我的示例,我认为答案比我最初想的要简单得多。exe文件加载在基地址0x004...处,并延伸到0x009...。这意味着每个地址都将包含一个0x00,这对于每个类似C的程序可能是一个停止符号。
试图用包含'\0'字符的新地址覆盖先前地址可能会对使用字符串函数(例如strcpy或gets)中的溢出的漏洞造成问题,因为这些函数会停在'\0'字符上。
如果您可以使用不受'\0'字符限制的逻辑来溢出缓冲区(例如memcpy),则也可以使用这些地址。

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