缓冲区溢出导致安全漏洞的示例

15

我读了许多关于像strcpy、memcpy等不安全函数的文章。当处理外部数据时,例如文件内容或来自套接字的数据,这些函数可能会导致安全问题。这听起来可能很愚蠢,但我写了一个容易受攻击的程序,但我没有成功“黑进”它。

我理解缓冲区溢出的问题。看看这个示例代码:

int main() {
   char buffer[1];
   int var = 0;

   scan("%s", &buffer);
   printf("var = 0x%x\n", var);
   return 0;
}
当我执行程序并输入"abcde"时,程序输出0x65646362,这是十六进制+小端形式下的"edcb"。然而,我读到过可以修改被压入堆栈的eip值,以便在调用system()函数之前执行一些不需要的代码。但是,该函数的汇编从这里开始:
push %ebp
mov %ebp, %esp
and $0xfffffff0, %esp
sub $0x20, %esp

由于函数开始时%esp的值是随机的,加上此处的"and"操作符,似乎没有可靠的方法将精确值写入推送的eip值中。

而且,我读到过可以在缓冲区中执行你编写的代码(这里缓冲区只有1个字节长,但实际上它足够大以存储一些代码),但考虑到缓冲区的位置是随机的,你会给eip赋什么值来执行它呢?

那么,为什么开发者会如此担心安全问题呢(除了程序可能会崩溃)?你有一个易受攻击的程序示例和如何"黑掉"它以执行不需要的代码吗?我在Linux上尝试过这个,Windows是否更安全?

6个回答

15

请纠正我,但是尽管这篇文章很有开创性,我不记得它涉及任何堆栈保护机制。 - torak
@Moron:鉴于OP特别提到and $0xfffffff0, %esp指令会将偏移量渲染为EIP变量,因此他们似乎掌握了基本的溢出机制。 - torak
@Moron:它并没有随机化ESP,只是变化了buffer的第一个字节和返回EIP存储地址之间的字节数。然而,我刚刚意识到这个额外偏移量可能的值只有0、4、8或12。这些都是四个字节(地址大小)的倍数。因此,将要写入返回EIP的值重复即可。 - torak
函数开始时ESP的值是随机的,并且“and”操作码在推送EIP之后,因此EIP和你溢出的缓冲区之间的偏移量是随机的。 - Tomaka17
仅供参考,这个“and”是出于性能原因而存在的(为了保持堆栈帧(实际上是参数和局部变量)具有良好的对齐方式,以获得更好的性能表现)。 - ninjalj
显示剩余4条评论

4
首先,不要低估在 EIP 中无法可靠放置值所带来的危害。如果攻击成功的概率为 1/16,并且被攻击的服务会自动重启(像许多 Web 应用程序一样),那么试图获取访问权限的攻击者总是可以再试一次。
此外,在许多情况下,ESP 的值比您想象的更少随机。首先,在 32 位系统上,它几乎总是四的倍数。这意味着指令 and $0xfffffff0, %esp 提供的额外填充将是 0、4、8 或 12 字节。这意味着可以将要写入返回 EIP 的值重复四次,以涵盖到返回 EIP 地址的所有可能偏移量。
实际上,还有更具攻击性的堆栈保护/缓冲区溢出检测机制。然而,即使对于这些机制,也有方法和手段可以绕过它们。
此外,考虑以下玩具示例中变量 var 的值对您的逻辑是否很重要,就可以看出这种情况会危险。
int main() {
  char buffer[1];
  int var = 0;

  var = SecurityCheck();

  scan("%s", &buffer);
  if (var != 0)
    GrantAccess();
  else
    DenyAccess()
}

1
我还想提一下 NOP 滑铁卢。简而言之,您可以在 shellcode 之前放置许多 NOP,这样猜测 shellcode 的“起点”就更容易了。 - ninjalj
与此示例密切相关的是一种技术,即通过提供一个过长的“name”来破坏像struct user_account {char name[16]; int group; int permissions;};这样的结构,以便覆盖“group”和“permissions”成员。关键在于能够预测数组后面的字节中存储的内容。 - bta

4

此外,您不必使用指向字符串中某个内容的指针来覆盖EIP。例如,您可以使用指向system()的指针来覆盖它,并在程序镜像的固定位置上用指向/bin/sh的指针覆盖下一个单词。

编辑:请注意,system使用PATH(实际上是通过shell运行命令),因此"sh"同样有效;因此,在字符串末尾以"sh"结尾的任何英文单词都提供了所需的参数。


1

基于缓冲区溢出的一个实际漏洞的典型例子是1988年的莫里斯蠕虫


1
实际上,莫里斯蠕虫利用了几个漏洞,其中之一是 fingerd 中的缓冲区溢出。 - ninjalj
@ninjalj:是的,那似乎很准确。 - Aryabhatta

1
如其他答案所提到的,攻击并不总是需要绝对的可靠性才能成功。能够自动重启的应用程序就是一个例子。suid程序上的本地可利用缓冲区溢出则是另一个例子。还有NOP sled技术可增加成功利用的机会,在shellcode之前放置大量的NOP,从而更有可能正确猜测shellcode的“起点”。
有许多提高攻击可靠性的技巧。在Windows上,过去很多漏洞都使用了在程序中某个位置上具有“jmp %esp”地址的返回地址覆盖技术(蹦床)。
《通过示例进行不安全编程》介绍了Linux的一个好技巧。清除你的环境并将你的shellcode放在一个环境变量中。过去,这会导致栈顶附近的一个可预测的地址。
还有像return-into-libc和return-oriented programming这样的变体。
甚至在Phrack上有一篇关于如何利用1字节栈溢出(意味着缓冲区仅被一字节溢出)的文章(顺便说一下,1字节堆溢出在绝大多数情况下也是可利用的,除非有保护措施)。
总之,开发人员并不是偏执狂,即使在最奇怪的情况下也有很多利用方式,请记住:
  • 一个程序的质量很好,当它能完成它应该完成的任务。
  • 一个程序是安全的,当它只能完成它应该完成的任务,没有多余操作

0
这是一个Windows版本和教程:

http://www.codeproject.com/KB/winsdk/CodeInject.aspx

我一直被警告的一般情况是:

printf( string );

因为用户可以在其中提供一个"%n",这允许您将任何内容插入内存中。你需要做的就是找到系统调用的内存偏移量,传递几个"%n"和垃圾字符,从而将内存地址插入返回向量通常会在堆栈上。Voila--插入任何你喜欢的代码。

2
这被称为格式化字符串漏洞,而不是缓冲区溢出 :) - ninjalj
@ninjali: 哎,它们就像亲戚一样。 这个溢出了栈缓冲区。 :-) - eruciform

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