安全函数的缓冲区溢出的攻击
大家有应该已经了解了缓冲区溢出攻击的方法和原理,一般而言,在普通的安全编程建议中,都可以看见推荐大家使用安全函数来替换掉可能被缓冲区溢出攻击的不安全函数,如用strncpy()替代strcpy()等等,大家重一些开放式的操作系统的源代码或者反汇编的WINDOWS操作系统的代码中也可以看出,基本上是使用了这些安全函数。那么真的对操作系统的溢出攻击就可以因为使用了这些安全函数就绝迹了吗?我们可以依赖于这些安全函数就保证我们开发的程序不再受缓冲区溢出的攻击了吗?答案是否定的。安全函数的使用在某种方面只是降低了缓冲区溢出的攻击,但是安全函数在特定情况下本身也是可能被达到缓冲区溢出攻击的,那么安全函数的缓冲区溢出攻击的原理又是什么呢?
我们知道普通的如strcpy()类函数的缓冲区溢出攻击在于这类函数不检查输入的长度,导致第一个参数在堆栈中分配的大小不足够,导致后面堆栈的地址被覆盖掉。由此达到修改后面地址代表的变量和堆栈中返回的程序调用地址达到修改程序流程的目的。strncpy()类函数加强了对第二个参数的长度检测,使得其在第一个参数里只保持拷贝指定数字长度的内容,来保证不发生缓冲区溢出的攻击。但是我们同时又知道一个事实,就是在一些函数中对给定地址的字符串的处理和长度计算并非以其数组或结构的大小计算,而是以NULL字符为计算的,那么在strncpy()中,如果第二个参数大于第一个字符串的大小,虽然拷贝到第一个字符串的长度只有指定的数目,但是有些函数对其的引用可能就不是这样看待了,如:
char a<2>;
strncpy(a,argv<1>,2);
如果argv<1>为"1234"这此时的缓冲区的内容为
SP-》
char<1>: "1"
char<2>: "2"
argv<1><1>: "1"
argv<1><2>: "2"
argv<1><3>: "3"
argv<1><4>: "4"
argv<1><5>: NULL
len: 2
ret addr: 0X....(指向strncpy下一条指令的地址)
那么此时你以a的地址做应用的话,会有什么结果了?如:
printf("%s",a);
这是会打印出”121234"这样的字串出来。当然,这还没实现溢出的攻击,但是,我们可以看一个FreeBSD 3.2-RELEASE的syslogd.c中摘出来的一段源代码中的例子,来体会一下可能的攻击:
int
validate(sin, hname)
struct sockaddr_in *sin;
const char *hname;
{
int i;
size_t l1, l2;
char *cp, name;
struct allowedpeer *ap;
if (NumAllowed == 0)
/* traditional behaviour, allow everything */
return 1;
strncpy(name, hname, sizeof name);
if (strchr(name, '.') == NULL) {
strncat(name, ".", sizeof name - strlen(name) - 1);
strncat(name, LocalDomain, sizeof name - strlen(name) - 1);
}
...
}
如果我们输入的hname大于MAXHOSTNAMELEN并不包含'.',那么根据上面的介绍我们知道strlen(name)就是应该是:hname的长度加上MAXHOSTNAMELEN的长度,假设MAXHOSTNAMELEN=256,输入的hname长度为300,则strlen(name)返回的长度是556,那么在sizeof name - strlen(name) - 1计算中则成为了-301,我们知道strncat类函数中长度的值为无符号的整数,-301其实就是一个很大的数字,于是在strncat(name, ".", sizeof name - strlen(name) - 1);,由于sizeof name - strlen(name) - 1远远大于name的实际长度,于是,溢出就产生了。
(注意:此处溢出的关键点在于:通过对strncpy()的攻击,虽然没有发生溢出,但是导致其依赖于字符串方式对长度计算的函数产生错误的值,导致在另外的安全函数中使用了超过长度的值来进行再次的溢出)。
那么除此以外,还可能发生溢出的情况有:当这个字符串地址发送给另一个涉及到字符串操作的非安全字符串操作函数,也能发生溢出。如下代码:
int
main(int argc, char **argv)
{
char buf1<1024>;
char buf2<256>;
strncpy(buf, argv<1>, 1024);
strncpy(buf2, argv<2>, 256); /* 这里可能导致buf2未中断 */
...
if(somecondition)
print_error(buf2); /* 报错 */
}
void print_error(char *p)
{
char mybuf<263>;
sprintf(mybuf, "error: %s", p);
}
由于main()函数中使用了strncpy(),程序员假设数据在达到print_error()是"干净"的。因此print_error()没有检查就直接调用了sprintf().不幸地是,既然p指向buf2,而buf2又没有正确地被中断,sprintf()就会持续的拷贝数据一直到发现buf1的末尾的NULL为止。
剩下的攻击则主要看对方代码和缓冲区的可控制性方面了,发生溢出也不一定就完全会导致攻击,如例1的:
strncat(name, ".", sizeof name - strlen(name) - 1);虽然会发生溢出但是拷贝多个"."后未知的内容,除非我们能控制在内存中"."后面的内存内容,否则是无法达到攻击目的的。
但是strncat(name, LocalDomain, sizeof name - strlen(name) - 1)中如果我们能控制LocalDomain中的内容和长度,就可以达到攻击目的了。
因此,在分析可能的溢出缓冲区攻击的时候,千万不要因为对方使用了字符串安全操作函数就放过了他。
阅读(1339) | 评论(0) | 转发(0) |