【问题标题】:How can code be launched with smashed stack?如何使用粉碎的堆栈启动代码?
【发布时间】:2016-04-10 23:08:16
【问题描述】:

我对 C 有非常基本的了解(尽管我确实了解一般的编程概念)。我有一个任务是创建一个缓冲区溢出,它会产生一些东西(比如访问未经授权的区域、免费资金等),而不仅仅是让程序崩溃。

我尝试了不同大小的缓冲区,并且总是可以“崩溃”程序,但我无法让它启动任何代码(即 /bin/su)。我是不是在错误地处理这个问题?

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <limits.h>
#include <string.h>
#define BUFSIZE 20

int main() {
    int month=12;
    int day=31;
    int year=2016;
    int confirm = 0;
    double dollars = 5.00;
    char *sitenum="97871";
    char acctnum[BUFSIZE];

    printf("Welcome to the Acme AP-AR System. This is the Accounts Receivable module. \n");
    /* Gathering date information */    
    printf("Please enter the month of transaction as an integer value (2 digits). \n");
    printf("For example, July would be 07, December would be 12. Please input the month: ");
    for (;;) { /* Start of month input validation loop */
    scanf("%d", &month); 
        if(month>=1 && month<=12) { 
            printf("Validated. \n");
            break;        
        }
        else { 
            printf("Please enter a value between 1 and 12! \n"); 
            continue; 
        }
    }   /* End of month input validation loop */
    printf("\nPlease enter the day of transaction as an integer value (2 digits). \n");
    printf("For example, the 3rd would be 03, the 25th would be 25. Please input the day: ");
    for (;;) { /* Start of day input validation loop */
    scanf("%d", &day); 
        if(day>=1 && day<=31) { 
            printf("Validated. \n");
            break;        
        }
        else { 
            printf("Please enter a value between 1 and 31! \n"); 
            continue; 
        }
    }   /* End of day input validation loop */

    /* Gathering sender account number  */
    printf("\nPlease enter the sender Account Number: ");
    scanf("%s", acctnum);

    /* Gathering transaction amount */
    printf("\nPlease enter the USD amount (including cents) received: $ ");
    scanf("%lf", &dollars); 

    /* Confirming data entry */
    printf("\nTransaction information.\n   Date: %d-%d-%d \n", month,day,year);
    printf("Account: %s-%s \n", sitenum, acctnum);
    printf(" Amount: $ %.2lf \n", dollars);
    printf("\nProcess transaction information? (Yes=1/No=0) ");
    for (;;) { /* Start of confirmation validation loop */
    scanf("%d", &confirm); 
        if(confirm==1) { 
            printf("Transaction processed. \n");
            break;        
        }
        else { 
            printf("Transaction voided! \n"); 
            break; 
        }
    }   /* End of confirmation validation loop */

    return (EXIT_SUCCESS);
}

执行时,如果输入 25 个字符作为月份的日期,程序将继续执行直到结束。只有在最后一个输入之后,它才会以堆栈粉碎错误而终止。恐怕我正在尝试做一些无法完成的事情,但是一天(字面意思是过去 8 小时)的 Google 搜索并没有产生我能够使用的示例。

有人可以将我推向一个不同的方向,让我接近我想要实现的目标吗?谢谢。

【问题讨论】:

  • 我在回答中添加了更多注释,以解决我认为是作业的实际意图的问题。希望对您有所帮助。

标签: c memory-management stack-smash


【解决方案1】:

您需要深入了解目标架构(x86、x86-64 等)才能完成此任务。一种典型的方法是仔细构建缓冲区溢出的内容,以便它 1) 包含您希望在输入数据被重新解释为机器指令时运行的代码,以及 2) 覆盖堆栈帧的返回地址以便它跳转进入您的代码,而不是返回到调用函数。

我不愿意提供实际执行此操作的代码,但确实可以这样做。

编辑:顺便说一句,我不认为该分配旨在要求实际运行任意代码。我根据您发布的代码猜测您应该只覆盖堆栈的一部分,以便看起来您正在访问不同的“sitenum”。这绝对是可能的,因为 sitenum 指针将存储在堆栈中的 acctnum 之后(至少通常是这样)。因此,如果您仔细设计缓冲区溢出,您可以将 sitenum 指针更改为指向其他位置。例如,(假设 sitenum 指针紧跟在堆栈中的 acctnum 之后),您可以在 acctnum 中输入 1 个额外字符,空终止字符将覆盖 sitenum 指针的最低有效字节,它很可能指向一个那么不同的位置。

在我看来,这是一个糟糕的分配,因为 1) 堆栈可以根据大量因素以不同方式排列,2) 大多数现代开发环境将默认添加运行时检查以防止这种堆栈损坏.例如,在 MS Visual C++ 中,您必须竭尽全力禁用基本运行时检查和缓冲区安全检查功能以避免异常。

无论如何,希望对您有所帮助。

【讨论】:

  • 如何处理溢出是我的选择。该作业应该模拟一些有用的东西(例如 ATM),并且溢出会产生一些意想不到的东西(免费资金)。我选择模拟特权访问,但访问另一个站点也可以。这应该在 32 位 Kali Linux 环境中运行。我无法让它在那里崩溃(仅在 Ubuntu 上),所以我怀疑 Kali 上有一些额外的堆栈保护(金丝雀等)。我认为这也是一项糟糕的任务。
  • 我的缓冲语句错了吗?我的愿望是输入一个太长的帐号,这会覆盖可以使用自定义代码访问的区域。但我认为要么是操作系统很好地保护了自己,要么我误解了缓冲区的流动方式。
  • 您发布的代码本身没有问题。但是仅仅用任意数据溢出来破坏堆栈是不够的。您需要真正了解堆栈的内容,以便知道要覆盖的内容。特别是,如果要利用该漏洞启动其他代码,则需要确切知道返回地址在堆栈帧中的存储位置,并且需要仔细构造一个覆盖返回地址的“帐号”。跨度>
【解决方案2】:

这是一个简单的例子,覆盖堆栈上的返回地址以执行另一个函数(然后会立即崩溃)。适用于 x86 上的 Windows VS2015。

#include "stdafx.h"

void hello()
{
    printf("hello world!\n");
}
void run(int a)
{
    int * ret = &a;
    --ret; // stack grows downward on x86
    *ret = (int)hello;
}
int main()
{
    int a = 42;
    run(a);
    printf("this won't print\n");
}

【讨论】:

  • 我编译了这个(带 32 位开关),它在打印 'hello world!' 后产生了分段错误——这是预期的结果吗?如果是这样,那么我认为我需要进行一些超出缓冲区的数学计算。
  • 是的,这就是我的意思,“然后会立即崩溃”。
【解决方案3】:

这里还有一个简单的例子(VS2015/x86),先保存返回地址,然后在hello()执行后,将main()的返回地址放回栈中。请注意,它首先从 run() 中声明的局部变量开始,而不是作为参数传入的变量。归结为了解返回地址的顺序、传递的参数、堆栈的方向以及当前堆栈帧的开始位置。执行后,您可能会在调试器环境中收到运行时检查失败的通知,但您应该会看到打印到控制台:

你好世界
主要的

#include "stdafx.h"

int saveret;

void hello()
{
    int a = 43; 
    printf("hello world!\n");
    // put saved return address to main() back on stack
    int * ret = &a;
    ret += 4;
    *ret = saveret;
}
void run()
{
    int a = 42; 
    int * ret = &a;
    ret += 4; // stack grows downward on x86
    saveret = (int)*ret;
    *ret = (int)hello; 
}
int main()
{
    run();
    printf("main\n");
}

【讨论】:

  • 所以这个没有崩溃,但它只打印了“main”(我在 Linux 上执行,必须用“stdio.h”替换“stadafx.h”,这可能就是原因) .您的两个示例都不需要输入,但我想我正在了解它是如何工作的。我需要让一些变量重复足够多次才能将堆栈向下推。
猜你喜欢
  • 1970-01-01
  • 2012-11-18
  • 2011-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-06
相关资源
最近更新 更多