作者:Tang Zheming
strcpy 是C语言中最常用的函数之一,今天我们将要看到的就是如何利用strcpy的错误而导致的一次匪夷所思的函数调用。
实验
例子如下:
void copyString(char* s)
{
char buf[10];
strcpy(buf, s);
}
作为一个合格的C语言程序员,当然不应该出现这样的代码,因为这样的函数可能会导致数据溢出,但是很遗憾在现实中我们还是经常能看到这样的没有保护的字符串复制函数。如果这样的代码被黑客发现那将会是一个灾难,而不仅仅是是一个代码错误了,他将可以随心所欲的在你的代码中植入一段他自己的代码。
如何才能做到这样的事件,看如下例子:
void my_testfuntion(void)
{
printf("bad word\n");
}
int main(int argc,char* argv[])
{
char badStr[] ="000011112222"
"333344445555";
dword *pEIP = (dword*)&badStr[16];
*pEIP = (dword)my_testfuntion;
copyString(badStr);
return 0;
}
运行结果如下,测试环境是vc6.0++
很奇怪的结果,不是吗?
原理
那么为什么会发生这样的事情,首先我们需要弄清楚栈和缓冲区,以及函数调用的一些过程。
栈: 每次函数调用时,系统会把函数的返回地址(函数调用指令后紧跟指令的地址),函数的实际参数和局部变量(包括数据、结构体、对象等)也会保存在栈内。这些数据统称为函数调用的栈帧,而且是每次函数调用都会有个独立的栈帧,这提供了函数的调用及推出的可能。
缓冲区: 高级语言定义的变量、数组、结构体等在运行时可以说都是保存在缓冲区内的,因此所谓缓冲区可以更抽象地理解为一段可读写的内存区域。
我的理解是缓冲区应该是栈的一部分。
函数调用: 每次调用前push函数返回地址,每次调用后pop当前函数地址,返回上一函数桢。
理解了这3个概念之后我们就很好理解这次攻击行为了,它的本质就是通过缓冲区溢出,修改了函数return之后的返回地址。下面是调试步骤,我们看它是怎么一步一步走向深渊的。
(1)在main函数中我们把数组badstr的一部分赋值成了my_testfuntion的地址
(2)在函数执行到my_strcpy的时候也很正常(因为strcpy是系统函数,无法调试,所以用了一个my_strcpy代替)
调用函数my_strcpy之前epb=0x0018fed4,执行完成之后将会返回到上图39行之上的位置。因为断点设在my_strcpy所以0x0018FED0是压入栈中的参数,后面0x004011d0是函数return地址
(3) 当函数执行完my_strcpy之后的情况
可以看到0x0018FED4的位置已经被修改成了my_testfuntion的位子,因此当my_strcpy执行完之后就会执行my_testfuntion了。
说明:
本文的例子只能在VC6.0下实现。
较高级版本的VS、gcc、linux都做了相关的防御机制,不会出现本文中的效果