试图执行堆栈破坏导致了段错误

26

我正在尝试在C语言中完成Smashing the Stack for Fun and Profit的一个示例,但目前遇到了一些困难,以下是代码(我的机器是64位Ubuntu):

int main()
{
    int x;

    x = 0;
    func(1,2,3);
    x = 1;
    printf("x is : %d\n", x);
}

void func(int a, int b, int c)
{
    char buffer[1];
    int *ret;

    ret = buffer + 17;
    (*ret) += 7;
}

上述代码运行良好,返回时x=1行未被执行,但我不理解ret = buffer + 17;的逻辑,难道不应该是ret = buffer + 16;即8个字节用于缓冲区,8个字节用于堆栈上保存的基指针。
其次,我的理解是char buffer[1]占用了8个字节(因为64位架构),如果我将这个缓冲区增加到比如buffer[2],那么同样的代码应该能正常工作,但实际上并非如此,它开始出现段错误。
谢谢, Numan

2
你真的在这里寻求帮助来创建缓冲区溢出漏洞吗? - Lawrence Dol
4
了解机器的工作原理并不意味着你想要进行有害的攻击。 - falstro
3
理解漏洞利用对于保护自己免受漏洞攻击是必要的,而理解的最好方式是为针对自己的软件编写漏洞利用,这是完全合法的。 - Kent Fredric
9
为什么会有人投票关闭这个问题,我不理解。学习缓冲区溢出、漏洞等内容没有错。想要避免常见的安全错误,又怎能不了解它们的工作原理呢? - mmcdole
2
如果您正在尝试遵循Alepth One的原始论文,那么自其发表以来已经发生了很多变化。您可以阅读以下优秀的博客文章,详细了解所有变化- http://paulmakowski.wordpress.com/2011/01/25/smashing-the-stack-in-2011/ - Raminder
显示剩余4条评论
5个回答

13

在我使用过的各种架构上,“char”都是8位宽,无论是8位微处理器、16位微处理器、32位个人电脑还是64位新个人电脑。而“int”通常是字长。

局部变量在堆栈上的顺序可能会因具体实现而异。我的猜测是你的编译器将“int *ret”放在“char buffer1”之前放入堆栈。因此,要到达返回地址,我们必须通过“char buffer1”(1字节)、“int *ret”(8字节)和保存的基指针(8字节),共计17字节。

以下是x86 64位堆栈帧的描述: http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-035-computer-language-engineering-spring-2010/projects/x86-64


谢谢您的答复。但这应该意味着如果我声明一个char缓冲区[2],它将占用2个字节,我应该能够通过ret = buffer + 18来覆盖返回地址;即2个字节用于缓冲区+8个字节用于*ret+8个字节保存基指针。但是这仍然无法正常工作,并且会导致段错误。 - user60103
随机小知识:在我看过的某种嵌入式系统架构中,char类型占用32位,因为处理器无法引用更小的存储单元。此外,在一些架构中(例如Itanium),返回地址存储在不同于参数和本地变量的堆栈中。 - Doug
1
@numanahsan - 实际上,char buffer[2]; 将会是一个数组,因此 buffer 的大小将会是一个指针的大小,可能是 4 而不是 2 个字节。它将包含一个 2 字节内存区域的地址。 - tvanfosson

9

使用gdb逐步执行反汇编代码(disassemble,stepi,nexti),并在每个步骤中查看寄存器内容(info registers)。

以下是如何逐步执行反汇编代码的方法:

gdb ./myprogram
break main
run
display/4i $eip
stepi
stepi
...
info registers
...

您也应该知道(您可能已经知道了,因为您已经部分地让它工作),在许多发行版上,gcc中的堆栈保护器默认是启用的。 您可以使用-fno-stack-protector手动禁用它。


$eip适用于x86。在x86_64上,它将是$rip。 - A B

3

在处理大量的堆栈溢出问题时,你最好的朋友是gdb。由于你已经发生了段错误,说明你已经写入了不该写的内存(这是一个好迹象)。更有效的方法是将返回地址改为其他有效地址(例如func的地址或一些你拥有的shellcode)。我推荐一个很棒的资源——Shellcoder's Handbook,但由于你使用的是64位架构,许多示例需要进行一些修改才能运行。


0

0

除了(或更好的是,除了)运行调试器之外,您还可以使用printf“%p”结构来打印变量的地址,例如:

printf("buf: %p\n", buffer); //&buffer[0] works too; &buffer works for an array
printf("ret: %p\n", &ret):
printf("a:   %p\n", &a);

打印各种事物的地址可以深入了解编译器/实现在后台如何安排事物。而且你也可以直接从C代码中执行它!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接