我听说过缓冲区溢出,我想知道如何导致缓冲区溢出。
有人可以给我看一个小的缓冲区溢出示例吗?新的(它们的用途是什么?)
缓冲区溢出的经典示例:
// noone will ever have the time to type more than 64 characters...
char buf[64];
gets(buf); // let user put his name
单独的缓冲区溢出通常不会故意发生。它最常发生是因为所谓的“一个接一个”错误。这意味着您将数组大小错误地计算了一个 - 可能是因为您忘记考虑终止空字符,或者因为其他一些东西。
但它也可以用于一些邪恶的东西。事实上,用户早就知道这个漏洞,然后插入说 70 个字符,最后一个字符包含一些特殊字节,这些字节会覆盖一些堆栈槽 - 如果用户真的很棘手,他/她会点击堆栈中的返回地址槽, 并覆盖它,使其向前跳转到刚刚插入的缓冲区:因为用户输入的不是他的名字,而是他之前编译并转储的 shell 代码。然后那个将被执行。有一些问题。例如,您必须安排在该二进制代码中不包含“\n”(因为gets 会停止读取那里)。对于其他与危险字符串函数混淆的方式,二进制零是有问题的,因为字符串函数停止复制到缓冲区。人们用过xor
两次相同的值也可以产生零,而无需明确写入零字节。
这是经典的做法。但是有一些安全块可以告诉我们发生了这样的事情,以及其他使堆栈不可执行的东西。但我想有比我刚才解释的更好的技巧。一些汇编人员现在可能会告诉你关于那个的长篇大论:)
如果您不能100%确定缓冲区是否足够大,请始终使用带有最大长度参数的函数。不要玩“哦,数量不会超过5个字符”之类的游戏——它总有一天会失败。请记住,科学家曾说过一枚火箭的数量不会超过某个数量级,因为火箭永远不会那么快。但是有一天,它实际上更快了,结果是整数溢出和火箭坠毁(这是关于Ariane 5中的一个错误,这是历史上最昂贵的计算机错误之一)。
例如,而不是使用fgets
. 而不是在合适和可用的地方sprintf
使用snprintf
(或者只是像 istream 之类的 C++ 风格的东西)
缓冲区溢出基本上是指内存的精心设计的部分(或缓冲区)被写入其预期范围之外。如果攻击者能够设法从程序外部实现这一点,它可能会导致安全问题,因为它可能允许他们操纵任意内存位置,尽管许多现代操作系统可以防止最坏的情况发生。
虽然超出预期范围的读取和写入通常被认为是一个坏主意,但术语“缓冲区溢出”通常保留用于超出范围的写入,因为这可能导致攻击者轻松修改代码运行方式。Wikipedia 上有一篇关于缓冲区溢出以及它们可用于漏洞利用的各种方式的好文章。
就您如何自己编程而言,这将是一个简单的问题:
char a[4];
strcpy(a,"a string longer than 4 characters"); // write past end of buffer (buffer overflow)
printf("%s\n",a[6]); // read past end of buffer (also not a good idea)
是否编译以及运行时会发生什么可能取决于您的操作系统和编译器。
在现代 linux 操作系统中,如果没有一些额外的实验,你就无法利用缓冲区溢出。为什么 ?因为您将被这个现代 GNU C 编译器中的ASLR(地址堆栈层随机化)和堆栈保护器阻止。你不会轻易定位内存,因为内存会落入由ASLR引起的随机内存。如果您尝试溢出程序,您将被堆栈保护器阻止。
首先,您需要将 ASLR 设置为 0 默认值为 2
root@bt:~# cat /proc/sys/kernel/randomize_va_space
2
root@bt:~# echo 0 > /proc/sys/kernel/randomize_va_space
root@bt:~# cat /proc/sys/kernel/randomize_va_space
0
root@bt:~#
在这种情况下,不是关于您可能从互联网上获得的 OLD STYLE 缓冲区溢出教程。或 aleph one 教程现在将不再在您的系统中工作。
现在让我们制作一个缓冲区溢出场景的程序漏洞
---------------------bof.c--------------------------
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv)
{
char buffer[400];
strcpy(buffer, argv[1]);
return 0;
}
---------------------EOF-----------------------------
看 strcpy 函数在没有堆栈保护器的情况下是危险的,因为函数没有检查我们将输入多少字节。使用额外选项-fno-stack-protector dan -mpreferred-stack-boundary=2编译以在 C 程序中取消堆栈保护器
root@bt:~# gcc -g -o bof -fno-stack-protector -mpreferred-stack-boundary=2 bof.c
root@bt:~# chown root:root bof
root@bt:~# chmod 4755 bof
SUID root 访问场景的缓冲区溢出 C 程序现在我们已经完成了。现在让我们搜索我们需要将多少字节放入缓冲区以造成程序分段错误
root@bt:~# ./bof `perl -e 'print "A" x 400'`
root@bt:~# ./bof `perl -e 'print "A" x 403'`
root@bt:~# ./bof `perl -e 'print "A" x 404'`
Segmentation fault
root@bt:~#
你看我们需要 404 字节来制造程序分段错误(崩溃)现在我们需要多少字节来覆盖EIP?EIP是指令将在之后执行。所以黑客确实会在程序的二进制 SUID 中将EIP覆盖到他们想要的邪恶指令中。如果程序在 SUID 根目录下,该指令将在根目录下运行。
root@bt:~# gdb -q bof
(gdb) list
1 #include <stdio.h>
2 #include <string.h>
3
4 int main(int argc, char** argv)
5 {
6 char buffer[400];
7 strcpy(buffer, argv[1]);
8
9 return 0;
10 }
(gdb) run `perl -e 'print "A" x 404'`
Starting program: /root/bof `perl -e 'print "A" x 404'`
Program received signal SIGSEGV, Segmentation fault.
0xb7e86606 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
(gdb) run `perl -e 'print "A" x 405'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 405'`
Program received signal SIGSEGV, Segmentation fault.
0xb7e800a9 in ?? () from /lib/tls/i686/cmov/libc.so.6
(gdb)
程序 GOT 分段错误返回码。让我们输入更多字节并查看 EIP 寄存器。
(gdb) run `perl -e 'print "A" x 406'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 406'`
Program received signal SIGSEGV, Segmentation fault.
0xb7004141 in ?? ()
(gdb)
(gdb) run `perl -e 'print "A" x 407'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 407'`
Program received signal SIGSEGV, Segmentation fault.
0x00414141 in ?? ()
(gdb)
再多一点
(gdb) run `perl -e 'print "A" x 408'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 408'`
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)
(gdb) i r
eax 0x0 0
ecx 0xbffff0b7 -1073745737
edx 0x199 409
ebx 0xb7fc9ff4 -1208180748
esp 0xbffff250 0xbffff250
ebp 0x41414141 0x41414141
esi 0x8048400 134513664
edi 0x8048310 134513424
eip 0x41414141 0x41414141 <-- overwriten !!
eflags 0x210246 [ PF ZF IF RF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb)
现在你可以做你的下一步...
缓冲区溢出只是写到缓冲区的末尾:
int main(int argc, const char* argv[])
{
char buf[10];
memset(buf, 0, 11);
return 0;
}
除了已经说过的内容之外,请记住,当发生缓冲区溢出时,您的程序可能会“崩溃”,也可能不会“崩溃”。它应该会崩溃,你应该希望它会崩溃——但是如果缓冲区溢出“溢出”到你的应用程序也分配的另一个地址——你的应用程序可能会在更长的时间内正常运行。
如果您使用的是 Microsoft Visual Studio 的更高版本 - 我建议在 stdlib 中使用新的安全对应版本,例如 sprintf_s insted of sprintf 等...
这应该足以重现它:
void buffer_overflow()
{
char * foo = "foo";
char buffer[10];
for(int it = 0; it < 1000; it++) {
buffer[it] = '*';
}
char accessViolation = foo[0];
}
“经典”缓冲区溢出示例是:
int main(int argc, char *argv[])
{
char buffer[10];
strcpy(buffer, argv[1]);
}
这使您可以使用缓冲区溢出参数并将它们调整为您的心满意足。“黑客 - 剥削的艺术”一书(链接到亚马逊)非常详细地介绍了如何解决缓冲区溢出问题(显然纯粹是一种智力练习)。
如果你想检查你的程序是否有缓冲区溢出,你可以使用Valgrind之类的工具来运行它。他们会为您找到一些内存管理错误。
这是对您收到的答案的一般性评论。例如:
int main(int argc, char *argv[]) { char buffer[10]; strcpy(buffer, argv[1]); }
和:
int main(int argc, const char* argv[]) { char buf[10]; memset(buf, 0, 11); return 0; }
在现代 Linux 平台上,这可能无法按预期或预期工作。由于 FORTIFY_SOURCE 安全功能,它可能不起作用。
memcpy
FORTIFY_SOURCE 使用和等高风险函数的“更安全”变体strcpy
。当编译器可以推断目标缓冲区大小时,它会使用更安全的变体。如果副本超出目标缓冲区大小,则程序调用abort()
.
要为您的测试禁用 FORTIFY_SOURCE,您应该使用-U_FORTIFY_SOURCE
或编译程序-D_FORTIFY_SOURCE=0
。
在这种情况下,缓冲区是为特定目的留出的一部分内存,缓冲区溢出是当对缓冲区的写操作不断超过末尾时发生的(写入具有不同目的的内存)。这始终是一个错误。
缓冲区溢出攻击是使用此错误来完成程序作者不打算实现的事情。
给出正确答案:要深入了解该主题,您可能需要收听 Podcast Security Now。在第 39 集(不久前)中,他们深入讨论了这个问题。这是一种无需消化整本书即可获得更深入理解的快速方法。
(在链接中,如果您比较注重视觉,您会找到具有多种尺寸版本的存档以及成绩单)。音频不是这个主题的完美媒介,但史蒂夫正在努力解决这个问题。
缓冲区溢出是插入超出分配的内存可以容纳的字符。