1

我对 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 搜索并没有产生我能够使用的示例。

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

4

3 回答 3

0

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

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

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

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

无论如何,希望这会有所帮助。

于 2016-04-11T01:05:46.087 回答
0

这是一个简单的示例,覆盖堆栈上的返回地址以执行另一个函数(然后会立即崩溃)。适用于 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");
}
于 2016-04-11T21:30:23.007 回答
0

这是另一个简单的例子(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");
}
于 2016-04-12T02:36:50.140 回答