利用strcpy攻击服务器

2021-03-24 11:15:06 浏览数 (1)


char* strcpy(char * destination, const char * source)

strcpy函数将source所指向的字符串拷贝到destination,拷贝内容是从source所指向的地址开始,直到遇到为止。这就意味着,拷贝的内容有可能超过destination的空间。如果destination是通过new在堆中分配内存,将导致堆破坏。如果destination是局部变量,将导致栈破坏。不管是堆破坏还是栈破坏,最终大概率将导致程序异常崩溃,这种情况有可能会在程序运行一段时间后才出现崩溃,所以排查相当困难。强烈建议使用微软提供的安全版本strcpy_s,指定destination的空间长度。sprintf也存在同样的问题。

由于strcpy会导致栈破坏,如果source是来源于用户输入的数据,用户就可以构造特殊的数据让服务端执行,进行恶意攻击,攻击的原理是:程序在调用函数时,call指令会将下一条指令先存入栈,然后执行函数,函数执行完后,ret指令会取出之前call指令存放的下一条指令,继续执行,于是通过栈破坏,替换下一条指令为自己构造的恶意代码(比如调起cmd,执行文件删除)。

接下来,我将演示如何通过strcpy的漏洞代码,改变程序的执行流程。先看一段简单的服务端代码。

代码语言:javascript复制
void ProcessData(const char data[])
{
    char buf[4];
    strcpy(buf, data);    
}

int main()
{    
    const char* data = GetDataFromUserInput();
    ProcessData(data);
    return 0;
}

此代码片段,演示程序通过GetDataFromUserInput()函数从用户输入获得数据(可以是通过网络从前端发送过来),然后处理数据ProcessData(),该函数通过strcpy拷贝data到局部变量buf。如前面所说,strcpy调用前未对data长度进行校验,可能会导致栈破坏。

程序调用ProcessData函数返回后,要继续执行这行代码return 0; 所以在进入ProcessData()函数前需要先将return 0; 的指令存在栈上(这是由汇编指令call所做的)。进入ProcessData()后,buf是局部变量占4个字节也是在栈上,通过计算return 0的指令所在的栈上地址与buf的地址之间的偏移后,在data对应的位置填入攻击函数的地址,等strcpy执行完,下一条指令return 0;就会变成攻击函数的地址,完成拦截。

return 0的指令所在的栈上地址与buf的地址之间的偏移,怎么计算呢?对汇编代码熟悉的牛人,一眼就能计算出来。接下来,我演示下如何通过汇编代码,计算他们之间的偏移值。本人使用Visual Studio 2013调试。

这是main函数的汇编代码,我在调用ProcessData()这行代码设置断点,如下图所示。

此时,esp寄存器(指向栈顶地址)通过监视器知道是0x0034fa30

mov eax, dword ptr [data] 将data地址类型const char* 转成dword ptr,存在寄存器eax

push eax 将eax存在栈上,也就是将data地址存在栈上(函数调用前都需要将参数入栈,data是ProcessData的参数,所以得入栈),此时栈顶0x0034fa30的值是data的地址,esp变成0x0034fa2c(栈地址是从高到低,所以esp要减4)

call ProcessData 调用ProcessData,可以按F11调试进入ProcessData,call下一条指令地址是00E51725 add esp, 4

按F11进入ProcessData(),如下是ProcessData()的汇编代码。

此时通过监视器看到esp地址是0x0034fa28,比上面push eax指令执行后esp值0x0034fa2c又少了4个字节,就是因为call指令将返回指令00E51725 add esp, 4的地址00E51725存入栈上。通过内存查看器可以证实。

从上面调试可知道,函数调用返回地址存在栈0x0034fa28上。

继续调试,断点设置在call dword ptr ds:[0E54100h]这行(也就是strcpy(buf,data)这行代码),按F5执行。鼠标移到buf变量上,知道buf地址是0x0034fa20。

由此可知函数调用返回地址在栈上的位置与buf变量的位置偏移值是8(0x0034fa28 - 0x0034fa20)。于是就可以构造用户数据,完整代码如下。

代码语言:javascript复制
#include <iostream>
#include<windows.h>
using namespace std;

void Attack()
{
    while (true)
    {
        cout << "You have been attacked" << endl;
        Sleep(1000);
    }
}

void ProcessData(const char data[])
{
    char buf[4];
    strcpy(buf, data);    
}

const char* GetDataFromUserInput()
{
    static char data[13];
    int attackAddress = (int)&Attack;
    memset(data, 'a', sizeof(data));
    memcpy(data   8, &attackAddress, 4);
    data[12] = 0;
    return data;
}

int main()
{    
    const char* data = GetDataFromUserInput();
    ProcessData(data);
    return 0;
}

GetDataFromUserInput()里面构造一个13字节的数据,内存初始化为字母a,最后一个字节设置为0(要让strcpy拷贝这13个字节,只能最后一个字节为0)。然后在偏移值8位置保存攻击函数的地址&Attack。

本演示是在Visual Studio 2013,Release配置下,关闭“启用内部函数”设置(如果不关闭,strcpy函数的汇编指令会直接内联到ProcessData()内,为了简化汇编指令,特意关闭该设置),运行结果如下图所示。

0 人点赞