利用缓冲区溢出漏洞破解简易登录程序

1,383 阅读3分钟

如何登陆一个无法登陆的系统?

首先我们来看一个非常简易的登录系统

#include<bits/stdc++.h>
using namespace std;

int main()
{
    char *buf=(char *)malloc(3);
    char *password=(char *)malloc(6);
    strcpy(password,"123456");
    memset(buf,0,3);
    scanf("%s",buf);
    if(strcmp(password,"666666")==0){
        puts("login success");
    }
    return 0;
}

里面最核心的逻辑是通过strcmp函数进行了密码校验,只有密码为666666的时候才能登陆成功。 貌似这是一个永远无法登录的系统?但是事实上并非如此,在不安全的环境下,我们可以通过很简单的技术手段实现登录,这种技术手段通常被称作缓冲区溢出

如何实现登录?

直接切入主题,通过实验可以发现,只需要输入38个6,就可以成功登录系统。

2018-05-03-13-24-29
效果如上图所示,看似不可思议?实际上只要了解了C++的内存模型就可以很容易的理解为什么只要输入38个6就可以成功登陆。

关于C++的缓冲区溢出

我们这里不探讨内存对齐等C++知识,只针对缓冲区溢出有关的内存模型部分进行调试和讲解。
在这里,调试工具我采用了Codeblocks16+GDB的配置。调试环境基于Windows10系统。

2018-05-03-13-33-50
首先,我们需要在scanf这一句设置断点,以便于后面的内存分析。然后启动调试环境。 首先我们需要启动两个窗口,分别是内存和变量监视窗口。这里为了节约文章篇幅,直接展示了断点处两个监视窗口的状态。
2018-05-03-13-35-04
在变量监视器窗口,我们注意到两个变量的内存地址非常相近。buf的内存地址为0x2c83520,password的内存地址为0x2c83540。 我们将内存监视器跳转到对应的位置,结果如下图所示。
2018-05-03-13-36-35
可以看到,0x2c83540内存地址所对应的字符为123456。而我们的目的就是将123456更改为666666,从而完成校验。由于C++个scanf函数并没有对安全性做出较强的校验,因此我们可以直接通过输入远超过分配内存空间大小的输入字符串来实现对后序内存空间值的覆盖。
在实现覆盖之前,我们需要简单计算一下,0x2c83540-0x2c83520=32,因此字符从0x2c83520开始,到0x2c8353F结束,一共需要32个字符。而0x2c83540到0x2c83545还需要6个字符才能完全覆盖123456这六个字符,因此我们需要尝试输入38个字符。 经过了上述分析以后,我们输入38个字符'6',发现成功完成了校验,实现了登录。结果在文章开头已经展示,这里不再进行重复展示。

总结

缓冲区溢出利用了系统内存模型的漏洞,通过输入过量的字符实现对后续变量的覆盖,从而达到了破坏程序正常运行的目的。如果想要防止缓冲区溢出漏洞,最佳的方案是使用更安全的输入输出函数.(例如VC++实现了scanf_s函数来避免缓冲区攻击)