1. 缓冲区溢出代码简单示例
缓冲区溢出一般是指的栈溢出,即在函数中处理内存时越界,改写了栈中的其他数据。下面是一份演示代码:
#include <iostream>
// DoAttack是攻击者的想要执行的代码
// 这里为了演示方便,直接一起编译了
// 实际上攻击者可以将此函数先转成机器码,再通过应用程序的对外接口放到buffer中传进来。
void DoAttack()
{
std::cout << "Do attack!\n";
exit(0);
}
// 正常业务代码,处理外部输入
void DoSomething(char* buffer, int len)
{
char dstBuffer[12] = {0};
// 调用memcpy,没有校验len的大小是否小于12。
// 实际len是20,比dsBuffer多了8个字节。
// 多的8个字节中,后面那4个字节(即: buffer+16),就是DoAttack的函数地址,写到了dstBuffer+16处。
memcpy(dstBuffer, buffer, len);
// 这里只是为了演示,简单打印一些内容。
std::cout << "Do something!\n";
}
// debug版本,需要设置工程属性:
// 1. c/c++->代码生成->基本运行时检查,选“默认值”,即关闭。
// 2. 链接器->增量链接,选“否”。
int main()
{
// buffer可以是通过gets或者restful等从外部获取的不可信数据
// 这里为了演示方便,直接在代码里写死。
char buffer[20] = {0};
// 最后4个字节放的DoAttack函数的首地址
*(int32_t*)(buffer + 16) = (int32_t)DoAttack;
// 将特殊构造的buffer传给业务函数
DoSomething(buffer, sizeof(buffer));
std::cout << "main end!\n";
}
输出结果:
Do something!
Do attack!
代码开源地址:https://gitee.com/ferrisyu/CodingSea/tree/master/Sample/BufferOverflow
2. 代码说明
main函数中的buffer[20]是模拟外部来源的二进制数据,传给业务函数DoSomething处理,攻击者可以构造特殊的buffer,造成缓冲区溢出,从而调用到自己的函数DoAttack。
下面分析一下,为什么DoAttack函数会被执行。
3. 为什么DoAttack能够被执行
重点是③处,”DoSomething”返回时,跳转到此地址继续执行,正常情况下,会回到call的下一行,即:③处保存的00CE54C6继续执行,最终打印”main end”。 然而,③处正好是dstBuffer + 16的位置。从图中堆栈可以看到,dstBuffer + 12是④处的位置,dstBuffer + 16就是③的位置。 由于memcpy没有校验长度,dstBuffer + 16的位置(即③处)会被写成DoAttack函数的地址。 所以”DoSomething”返回时,会从”DoAttack”继续执行,并没有回到00CE54C6处。 DoAttack中可以实现攻击者需要的任何逻辑,之后,攻击者可以选择恢复原来的寄存器状态,再次回到00CE54C6正常执行。这里为了演示方便,直接exit退出进程。
4. 缓冲区溢出的用途
利用具备高级权限的程序,执行自己本来没有权限执行的代码。
比如:攻击者没有root账号的情况,可以利用某个具有root权限的程序,调用它的功能,将特殊构造的buffer传给它,从而执行自己的代码。
破坏性攻击,使用程序崩溃,业务中断。
这也是为什么不建议代码中使用memcpy、strcpy等不安全函数的原因。