如何触发缓冲区溢出?

35

我得到了一个作业任务,要求使用缓冲区溢出来调用一个函数而不需要显式地调用它。代码基本上是这样的:

#include <stdio.h>
#include <stdlib.h>

void g()
{
    printf("now inside g()!\n");
}


void f()
{   
    printf("now inside f()!\n");
    // can only modify this section
    // cant call g(), maybe use g (pointer to function)
}

int main (int argc, char *argv[])
{
    f();
    return 0;
}

虽然我不确定如何继续下去。我考虑修改程序计数器的返回地址,以便它直接跳转到g()的地址,但我不知道如何访问它。无论如何,提示将是很好的。


12
一个作业问题获得了4个赞!而提问者甚至都没有想出这个问题...哇,有些人真容易满足。 - Lazarus
@Lazarus,我赞同了您的评论。哦哦! :-) - Alok Singhal
24
@Lazarus,这是一道作业问题并不影响我对它的兴趣。我还点了赞,因为我想鼓励有趣的作业问题,而不是那些简单的“我关闭了文件缓存,现在当我尝试从文件中读取时它不能工作。为什么?”(换句话说,我会点赞那些我不知道答案但想要了解的问题)。 - Yacoby
@Alok,LOL-这些都是我自己的话...这有助于安抚你的良心吗?;) - Lazarus
4
哇,那是一道家庭作业问题吗?我已经爱上你的老师了 :D - Andreas Grech
显示剩余2条评论
5个回答

14
基本思路是修改函数的返回地址,使函数返回时继续执行一个新的被黑客篡改的地址。就像 Nils 在其中一个答案中所做的那样,您可以声明一块内存(通常是数组)并以这样的方式溢出它,以便覆盖返回地址。
我建议您不要盲目地采用此处提供的任何程序,而是应该先了解它们的工作原理。这篇文章写得非常好,对您会很有帮助: 一步步了解缓冲区溢出漏洞

13

那取决于编译器,因此无法给出单一的答案。

以下代码可以在gcc 4.4.1中实现您想要的功能。请禁用优化进行编译(很重要!)

#include <stdio.h>
#include <stdlib.h>

void g()
{
    printf("now inside g()!\n");
}


void f()
{   
  int i;
  void * buffer[1];
  printf("now inside f()!\n");

  // can only modify this section
  // cant call g(), maybe use g (pointer to function)

  // place the address of g all over the stack:
  for (i=0; i<10; i++)
     buffer[i] = (void*) g;

  // and goodbye..
}

int main (int argc, char *argv[])
{
    f();
    return 0;
}

输出:

nils@doofnase:~$ gcc overflow.c
nils@doofnase:~$ ./a.out
now inside f()!
now inside g()!
now inside g()!
now inside g()!
now inside g()!
now inside g()!
now inside g()!
Segmentation fault

我正在使用gcc 4.4.1,不确定如何关闭优化:尝试了 gcc -O0 -o buff buff.c(这是零而不是字母o) 以及 gcc -O1 -fno-defer-pop -fno-thread-jumps -fno-branch-probabilities -fno-cprop-registers -fno-guess-branch-probability -fno-omit-frame-pointer -o buff buff.c 都没有起作用。 - sa125
1
在'g()'函数内使应用程序退出,以避免分段错误=) - Kieveli
sa125,也许gcc尝试优化到不同的CPU架构。据我所知,默认情况下它会使用您正在运行的系统的CPU。这可能会改变f()的堆栈帧的外观,并可能防止溢出发生。 - Nils Pipenbrinck
Nils - 也许今天早上我有点慢 -- 但你是如何将 "now inside g()" 打印出来的呢?我看到你正在存储指向 g() 的指针,但在你的示例代码中,我没有看到你解引用指针并调用 g()。 - Dan
1
Dan,在main函数中调用了f()。在调用过程中,编译器会将返回地址放入堆栈中,以便f()知道完成后要跳转到哪里。然而,在f()内部,我使用g()的地址覆盖了堆栈的大部分内容。很可能我也覆盖了返回地址。因此,当f()退出时,它不会返回到main函数,而是跳转到g()。这真的很糟糕,但这就是问题所在。 - Nils Pipenbrinck
@Nils -- 非常好的解释,我完全理解了,现在我回想起来那就是问题的关键...我一直在想“缓冲区溢出”,这已经通过越界数组写入实现了,但我忘记了“意外”调用g()。感谢您花时间解释! - Dan

8

4
试试这个:
void f()
{   
    void *x[1];
    printf("now inside f()!\n");
    // can only modify this section
    // cant call g(), maybe use g (pointer to function)
    x[-1]=&g;
}

或者这个:

void f()
{   
    void *x[1];
    printf("now inside f()!\n");
    // can only modify this section
    // cant call g(), maybe use g (pointer to function)
    x[1]=&g;
}

2
x 是一个局部变量,它位于栈上。由于 x 的大小为1,因此只有 x[0] 是有效的。通过将 g 的地址写入 x[-1] 或 x[1],有可能会覆盖返回地址。这取决于栈的组织方式,哪个版本可以正常工作。 - Erich Kitzmueller

4
虽然此解决方案未使用溢出技术来覆盖函数在堆栈上的返回地址,但仍通过修改f()而非直接调用g(),使得g()在返回到main()时从f()中被调用。
类似于函数结尾的内联汇编添加到f()中,以修改堆栈上返回地址的值,使得f()将通过g()返回。
#include <stdio.h>

void g()
{
    printf("now inside g()!\n");
}

void f()
{   
    printf("now inside f()!\n");
    // can only modify this section
    // cant call g(), maybe use g (pointer to function)

    /* x86 function epilogue-like inline assembly */
    /* Causes f() to return to g() on its way back to main() */
    asm(
        "mov %%ebp,%%esp;"
        "pop %%ebp;"
        "push %0;"
        "ret"
        : /* no output registers */
        : "r" (&g)
        : "%ebp", "%esp"
       );
}

int main (int argc, char *argv[])
{
    f();
    return 0;
}

了解这段代码的工作原理可以更好地理解函数在特定架构下如何设置其堆栈帧,这是缓冲区溢出技术的基础。

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