一道有趣的面试题,题目是这样的:
问:下面是一个简单的密码保护功能,你能在不知道密码的情况下将其破解吗?
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int flag = 0;
char passwd[10];
memset(passwd,0,sizeof(passwd));
strcpy(passwd, argv[1]);
if(0 == strcmp("LinuxGeek", passwd))
{
flag = 1;
}
if(flag)
{
printf("\n Password cracked \n");
}
else
{
printf("\n Incorrect passwd \n");
}
return 0;
}
coder的愿意是想验证用户输入的密码,正确的密码应该是”LinuxGeek”,但是这不是个鲁棒的设计,以至于给了他人以“攻破”密码的机会。
事实上只要用户输入的密码超过”一定位数”,那么“LinuxGeek”将形同虚设。
[root@localhost project]# g++ -g strcpy.cpp -o run
[root@localhost project]# ./run 0123456789
Incorrect passwd
[root@localhost project]# ./run 01234567890
Password cracked
[root@localhost project]# ./run 0123456789a
Password cracked
[root@localhost project]# ./run 0123456789ab
Password cracked
问题的根源在于:
1.strcpy并不是一个安全的拷贝方式
//把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间
char strcpy(char dest, const char *src);
char passwd[10];
strcpy(passwd, argv[1]);
将输入的密码拷贝到以passwd的地址空间,
如果argv[1]内容的长度超过10位了?argv[1]内容的内容将继续接着原地址写,直到遇到argv[1]的结束符!
如果flag的地址紧随passwd地址之后了?那么flag地址指向的内容将被argv[1]的内容“侵占”!
如果“被侵占”的的内容不为0,那么if(flag)将为true,即使argv[1]内容不是”LinuxGeek”
2.如果argv[1]内容长度足够长,flag一定会被“侵占”吗?或者说flag的地址一定在passwd地址之后吗?
是的,一定!因为 flag和passwd是在栈上分配的!而且栈的地址生长方向是由高地址到低地址!
不妨用gdb来看下吧!
在第10行设置断点,打印flag和passwd的地址
Breakpoint 1, main (argc=2, argv=0xbfffe9f4) at strcpy.cpp:10
10 strcpy(passwd, argv[1]);
(gdb) print &flag
$1 = (int *) 0xbfffe948
(gdb) print &passwd
$2 = (char (*)[10]) 0xbfffe93e
3.这里还有另一个问题?在本题中,输入的passwd的长度到底要为多长时,才能cracked密码了?或者passwd的地址一定紧随flag吗?
cracked密码的长度当然取决于passwd和flag的地址;而passwd的地址并不一定紧随flag,因为还受到“内存对齐”的影响!
4.为什么这种“溢出”,还能正常编译并运行了?
取决于编译器,在我的gcc 4.1.2,编译和运行都不会有异常提示,但是在vs2010上运行时,则给出了提示。
5.为什么“./run 01234567890”将“0”写到flag地址上也可以了?
其实并不是把“0”写给flag,而是ASCII码 48
那么请使用strncpy吧!
//把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回dest。
char *strncpy(char *dest, const char *src, int n);