发信人: kevintz(血仍未冷)
整理人: wenbobo(2003-08-23 11:08:38), 站内信件
|
------[ 2.2.1 Exploiting
数值溢出能被利用的最通常的方法是当计算结果给用来分配多大的缓冲区.通常一个程序必需分配空间
给一个数组对象,所以用malloc(3)或calloc(3)函数来保留空间并且通过用元素的数量乘以对象的尺寸来
计算需要多大的空间.前面已经提到,如果我们能控制这些操作数中的一个(元素的数量或对象的尺寸),
我们或许能够导致错误分配缓冲区,接下来的代码片段体现了这点:
int myfunction(int *array, int len){
int *myarray, i;
myarray = malloc(len * sizeof(int)); /* [1] */
if(myarray == NULL){
return -1;
}
for(i = 0; i < len; i++){ /* [2] */
myarray[i] = array[i];
}
return myarray;
}
这看起来无害的函数能带来系统的崩溃就因为没有检查len参数.在(1)处通过提供一个足够大的值
给len,乘法操作后能够被溢出,这样我们随意控制缓冲区的大小.通过选择一个合适的值给len,我们
能使得(2)处的循环写缓冲区的后面,这导致了一个heap溢出.通过改写malloc的控制结构能够在正
常的运行里面插入可执行的任意代码,但是这超出了本文的讨论范围.
另一个例子:
int catvars(char *buf1, char *buf2, unsigned int len1,
unsigned int len2){
char mybuf[256];
if((len1 + len2) > 256){ /* [3] */
return -1;
}
memcpy(mybuf, buf1, len1); /* [4] */
memcpy(mybuf + len1, buf2, len2);
do_some_stuff(mybuf);
return 0;
}
在这个例子中,通过给合适的值给len1和len2能够欺骗过(3)处的检查,那样将导致加法的溢出并且
包含了一个低值.举个例子,看下面的数值:
len1 = 0x104
len2 = 0xfffffffc
当加在一起时将导致一个包含了0x100的结果(整数 256),这能通过(3)处的检查,然后在(4)处的
memcpy(3)将拷贝数据到缓冲区的后面.
--[ 3 符号问题
当一个无符号的变量被看成有符号时,或者当一个有符号的变量被看成无符号时,符号问
题漏洞就发生了.这种类型的反应发生是因为在电脑内部有符号的变量和无符号的变量存储
时是没有区别的.最近,一些符号漏洞在FreeBSD和OpenBSD的内核中发生,因此我们可以很
容易的例举许多例子.
----[ 3.1 它们看起来像什么?
符号问题可以是多种多样的,但是应该留意一下以下的几点:
out for are:
有符号整数的比较
有符号整数的运算操作
无符号整数和有符号整数的对比.
这里有一个关于符号问题漏洞的典型例子:
int copy_something(char *buf, int len){
char kbuf[800];
if(len > sizeof(kbuf)){ /* [1] */
return -1;
}
return memcpy(kbuf, buf, len); /* [2] */
}
这里的问题在于memcpy 使用无符号整数作为len参数,但是在之前的数据越界检测使用了有符号
整数,我们可以提供一个负数的len,可以绕过[1]的检测,但是这个值同样被使用在[2]的memcpy函
数的参数里面,len可能被转换成一个非常大的正整数,导致kbuf缓冲区后面的数据被重写.
当进行运算操作时,有符号和无符号的混淆可能产生另一个问题,参考下面的这个例子:
int table[800];
int insert_in_table(int val, int pos){
if(pos > sizeof(table) / sizeof(int)){
return -1;
}
table[pos] = val;
return 0;
}
因为这行
table[pos] = val;
等于
*(table + (pos * sizeof(int))) = val;
我们可以看出,这里的问题是代码没有预计到在加法里会有一个负的操作数,它预计(table +
pos)比table大,因此提供一个负的pos值将引起程序的运行状况在预料之外,也就不能正
常运行下去了.
------[ 3.1.1 Exploiting
这种类型的漏洞想要被利用还是有问题的,是因为当把有符号的整数转换成无符号的整数,这
个值将会变的非常大,比如: -1 用16进制表示为 0xffffffff.当它被转换成无符号时,它的值
用整数来表示可能是(4,294,967,295),如果把这个值传递给类似memcpy的函数(memcpy (char*
dest, char *src, int len),memcpy这类的函数只会单一的执行从 src中拷贝len大小的数据
到dest中,而不会考虑dest是否能容纳,或者src是否能取出这么多数据).memcpy 函数将会尝试
拷贝近4GB的数据到目的缓冲区.很明显的,这将会导致段错误,就算不会,也只会使堆栈中充满垃
圾数据.有时,通过传递一个很小的值到源地址有可能解决这个问题,但是这不是总是可行的.
----[ 3.2 符号问题导致的缓冲区溢出
有时,可能溢出一个整数而导致环绕成一个负数.既然应用程序没有预料会得到这样一个值,于是
就可能引发一个如前所述的符号漏洞.
一个这种类型漏洞的例子:
int get_two_vars(int sock, char *out, int len){
char buf1[512], buf2[512];
unsigned int size1, size2;
int size;
if(recv(sock, buf1, sizeof(buf1), 0) < 0){
return -1;
}
if(recv(sock, buf2, sizeof(buf2), 0) < 0){
return -1;
}
/* packet begins with length information */
memcpy(&size1, buf1, sizeof(int));
memcpy(&size2, buf2, sizeof(int));
size = size1 + size2; /* [1] */
if(size > len){ /* [2] */
return -1;
}
memcpy(out, buf1, size1);
memcpy(out + size1, buf2, size2);
return size;
}
这个例子展示给我们在网络后台程序中有时会发生的事情,尤其,当length信息是从网络数据包的一部分传递而来
的(换句话说:可能是一个未知用户提交的数据),[1]中的加法,用于检查数据没有超出了out缓冲区的范围,在设置size1
和size2相加时可能会产生一个很大的值,这个值会引起size变量环绕成一个负数.例题值可能是:
size1 = 0x7fffffff
size2 = 0x7fffffff
(0x7fffffff + 0x7fffffff = 0xfffffffe (-2)).
当上面操作执行时,绕过了[2]的越界检测,而且导致很多out缓冲区的数据被故意改写(事实上:是任意的内存被改写,
在(out + size1)的目标参数中,是我们可以控制的,允许我们指向任意的内存区域).
这个漏洞只要精确的构造数据是可以被利用的,和符号类型问题的漏洞一样,同样也有困扰的问题存在 - 比如产生的负数值将会
解释成一个非常大的正整数,那么在执行memcpy的时候就会很容易的导致段错误,但是这类问题可以利用bsd memcpy反向拷贝的
原理来利用.
--[ 4 真实的例子
这里有很多真实的应用程序包含整数溢出和符号问题,包括网络后台程序,操作系统内核的
的例子.
----[ 4.1 整数溢出
这里有一个例子来自linux的内核的安全模块里(不可利用的),这个代码在内核中运行:
int rsbac_acl_sys_group(enum rsbac_acl_group_syscall_type_t call,
union rsbac_acl_group_syscall_arg_t arg)
{
...
switch(call)
{
case ACLGS_get_group_members:
if( (arg.get_group_members.maxnum <= 0) /* [A] */
|| !arg.get_group_members.group
)
{
...
rsbac_uid_t * user_array;
rsbac_time_t * ttl_array;
user_array = vmalloc(sizeof(*user_array) *
arg.get_group_members.maxnum); /* [B] */
if(!user_array)
return -RSBAC_ENOMEM;
ttl_array = vmalloc(sizeof(*ttl_array) *
arg.get_group_members.maxnum); /* [C] */
if(!ttl_array)
{
vfree(user_array);
return -RSBAC_ENOMEM;
}
err =
rsbac_acl_get_group_members(arg.get_group_members.group,
user_array,
ttl_array,
arg.get_group_members.max
num);
...
}
在这个例子中,[A]的数据越界检查部分不能足够的防止[B][C]能造成的整数溢出,绕过这个检查只需
提供一个足够高的(比如:高于 0xffffffff / 4 )的值给 arg.get_group_members.maxnum,我们能引
起[B]和[C]处和这个值相乘的整数溢出,并且强制ttl_array 和user_array分配的缓冲区要小于程序
中预期的大小.因此rsbac_acl_get_group_members函数拷贝我们能控制的数据到这个缓冲区里,可能
改写tty_array和user_array指向的缓冲区后面的一些数据.在这里,程序中使用了vmalloc 分配缓冲
区,所以尝试改写tty_array和user_array指向的缓冲区后面的一些数据仅仅只是造成一个错误,所以
并不能被利用,尽管如此,它提供了一个例子以提醒我们在现实生活中编写代码要注意这些问题.
另外一个例子是最近SUN rpc XDR库中所带的xdr_array()函数中存在一个整数溢出漏洞的问题(见 I
SS X-Force的安全公告).在这个例子里面,用户提交的数据用来计算动态分配缓冲区的大小,该缓冲区
用来存储用户提交的数据,看如下有问题的代码:
bool_t
xdr_array (xdrs, addrp, sizep, maxsize, elsize, elproc)
XDR *xdrs;
caddr_t *addrp; /* array pointer */
u_int *sizep; /* number of elements */
u_int maxsize; /* max numberof elements */
u_int elsize; /* size in bytes of each element */
xdrproc_t elproc; /* xdr routine to handle each element */
{
u_int i;
caddr_t target = *addrp;
u_int c; /* the actual element count */
bool_t stat = TRUE;
u_int nodesize;
...
c = *sizep;
if ((c > maxsize) && (xdrs->x_op != XDR_FREE))
{
return FALSE;
}
nodesize = c * elsize; /* [1] */
...
*addrp = target = mem_alloc (nodesize); /* [2] */
...
for (i = 0; (i < c) && stat; i++)
{
stat = (*elproc) (xdrs, target, LASTUNSIGNED); /* [3] */
target += elsize;
}
正如你看到的,当提供一个很大的值给 elsize 和 c (sizep),可能引发[1]乘法操作导致整数溢出,并且
使nodesize的值小于应用程序中所预期的值,既然nodesize被用于[2]的缓冲区分配操作,该缓冲区将会被
错误的定位大小,导致[3]的堆溢出.更多关于这个漏洞的信息,见参考资料中CERT安全咨询公告.
---add by Sam@sst
还有一个关于整数溢出导致堆溢出的很典型的例子,OpenSSH Challenge-Response SKEY/BSD_AUTH 远程缓冲区溢出漏洞.
下面这段有问题的代码摘自OpenSSH的代码中的auth2-chall.c中的input_userauth_info_response() 函数:
unsigned int nresp,
nresp = packet_get_int();
if (nresp > 0) {
response = xmalloc(nresp * sizeof(char*)); [1]
for (i = 0; i < nresp; i++)
response[i] = packet_get_string(NULL);
}
packet_get_int是从用户提交的数据中取得一个int值,保存在整数变量nresp中,由于没有考虑到[1]处的乘法会导致程序的异常
(在32位的系统环境中,unsigend int的最大值是0xffffffff,我们只要提供0xffffffff/4 的值,比如0x40000000).程序执行如下
操作.比如:
0x40000000 + 1 (nresp的值)
= 0x40000001 (满足 nresp > 0的要求)
0x40000001 * 4 (分配 nresp * 4 的缓冲区空间)
= 0x100000004 (计算结果已经超出了32位的值)
由于以上的值已经超出了32位的值,将被截断成0x00000004.也就是说其实 xmalloc 只在堆上分配了4个字节的缓冲区空间.接下来
的for 循环操作:
for (i = 0; i < 0x40000001; i++ )
response[i] = packet_get_string(NULL);
packet_get_string函数从用户数据中读取4个字节的内容写入 response + i的地址.经过0x40000001的循环,用户的数据早已覆盖
了xmalloc原先分配的4字节的空间以及后面的数据(如同样分配在heap区的函数指针等等来获得程序流程的控制权).
----[ 4.2 符号问题的漏洞
最近,几个freebsd 内核符号问题的漏洞给我们带来了一些光亮.这些漏洞存在于很多个系统调用里面,
当提供一个负数的length参数给这些系统调用,可以绕过读取大块内核内存的限制. getpeername (2)
函数就有这个问题,如下:
static int
getpeername1(p, uap, compat)
struct proc *p;
register struct getpeername_args /* {
int fdes;
caddr_t asa;
int *alen;
} */ *uap;
int compat;
{
struct file *fp;
register struct socket *so;
struct sockaddr *sa;
int len, error;
...
error = copyin((caddr_t)uap->alen, (caddr_t)&len, sizeof (len));
if (error) {
fdrop(fp, p);
return (error);
}
...
len = MIN(len, sa->sa_len); /* [1] */
error = copyout(sa, (caddr_t)uap->asa, (u_int)len);
if (error)
goto bad;
gotnothing:
error = copyout((caddr_t)&len, (caddr_t)uap->alen, sizeof (len));
bad:
if (sa)
FREE(sa, M_SONAME);
fdrop(fp, p);
return (error);
}
这是个关于符号漏洞的著名的例子 - 对(1)的检测没有把len可能是负数的事实考虑进去,在这个例子里,
MIN宏将会一直返回len.当这个负数的参数传递给copyout函数,它会被解释成一个巨大的正整数导致copyout
函数拷贝近4GB的内核内存到用户空间.
--[ 结论
整数溢出是非常危险的,部分原因是因为它在发生后不可能被发现.如果一个整数溢出发生了,应用程序并不知道它的
计算是错误的.因此应用程序在在假定它是正确的情况下,会继续运行下去.即便如此,整数溢出还是很难被利用的.通
常是几乎不可能被利用.它们能引起意想不到的结果,在安全的系统中,这种结果绝对不是一件好事.
--[ 参考资料
CERT advisory on the XDR bug:
http://www.cert.org/advisories/CA-2002-25.html
FreeBSD advisory:
http://online.securityfocus.com/advisories/4407
Phrack60 另外一篇关于整数溢出的文章:
http://www.phrack.org/phrack/60/p60-0x09.txt
Sshutup-theo.tar.gz
http://online.securityfocus.com/data/vulnerabilities/exploits/sshutup-theo.tar.gz
|=[ EOF ]=---------------------------------------------------------------=|
1999-2002版权归属于网络安全焦点,所有文章版权各属于其作者,如转载请务必注明作者!
|
|