注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

且行且记录

点滴记录,行的更远!

 
 
 

日志

 
 

【转】整型溢出的原理及三个实例  

2013-10-14 16:20:33|  分类: 参考文章 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

整型溢出的原理及三个实例

一、前言
因为最近几年一直都在跟踪网络安全领域的新漏洞,所以特别强烈的感觉到出现的系统漏洞有越来越难利用的趋势。以IIS的溢出漏洞为例,从前几年的.htr映射的溢出漏洞、.printer映射的溢出以及后来的.ida/idq溢出,一直到去年的.asp溢出,真的是一次比一次难以利用了,攻击成功的概率越来越小。究其原因,就是因为软件厂商的安全意识越来越强,像用strcpy直接拷贝buffer造成溢出的这种低级漏洞几乎是再也不可能出现了。
即使是这样,也并不意味着以后再也不会出现漏洞了,因为攻击技术也是在不断发展的。像最近整型溢出又是比较热门的东西,因为最近一些比较严重的漏洞都是由于整型溢出造成的,例如IIS ASP溢出、Apache分块编码溢出、openssh响应机制溢出等等。虽然这种溢出本身并没有什么高深之处,但是也有必要进行总结,因为今后在这方面很可能会产生相当多的漏洞。

二、整型溢出与传统溢出的共性和区别
首先应该明确所谓“溢出”的概念,根据我的理解,应该是程序外部的数据大小超出了原数据类型所能表达的范围,造成某些错误的操作。这里所提到的数据类型不仅仅是字符串类型,也包括整型、无符号整型甚至布尔类型等一切由程序外部所提供的数据。从这个角度来说,格式化字符串漏洞应该不属于溢出的范围。
传统的溢出一般都是发生在字符串类型数据上面,用户提交的数据超出程序分配的字符串大小,造成覆盖系统数据结构,最终导致程序流程改变。而整型溢出顾名思义,就是由用户提交的整型数据超出程序内部对整型数的安全要求,造成违反原来的程序限制,导致其他类型的溢出。一般来说,整型溢出并不能直接导致改变程序流程,它是由整型溢出造成字符串类型的溢出,从而导致覆盖系统数据结构,改变程序流程。
值得注意的就是程序内部对整型数的安全数据范围要求,而不是仅仅字符串数据范围的要求,这是整型溢出的根本原因,下面将详细介绍。

三、什么是整型数溢出
1.计算机中的整数概念
在计算机中,整型的概念就是一个特定的变量类型。在不同的CPU系统上被编译处理后,整型和指针的尺寸一般是相同的。例如,在32位的系统中,比如x86,一个整数是32位;而在64位的系统中,比如SPARC,一个整数是64位长。本文中所谈到的例子是在32位的系统环境和32位的整数,并且用10进制来表示它。
但是仅仅是这样还不够,因为这样还无法表示负数,所以就需要一种机制仅仅用位来代表负数,通过一个变量的最高位来决定正负。如果最高位置1,这个变量就被解释为负数;如果置0,这个变量就解释为正整数。因此通常的高级语言中都有有符号整型(int)和无符号整型(unsigned int)之分,下面我们就要讨论这两种整型数所带来的安全问题。

2.对整型数的安全要求
一般程序员写程序,对于整型数仅仅考虑使用范围,而不考虑它的安全要求。对于不同用途的整型数,其安全要求也不相同。例如最容易出问题的是由用户提交的用作长度变量的整型数,对其他部分数据的安全要求往往集中在上面。用作长度的变量一般要求使用无符号整型数。在32位系统中,无符号整型数(unsigned int)的范围是从0-0xffffffff。不仅要保证用户提交的数据在此范围内,还要保证对用户数据进行运算并存储后仍然在此范围内。

四、整型数溢出的成因分类
整型数溢出从造成溢出原因的角度来说可以分为三大类:存储溢出、计算溢出和符号问题。下面就分别来谈谈他们之间的共性和区别:
1.存储溢出
存储溢出是最简单的一类,也很容易理解。简单说就是使用不同的数据类型来存储整型数造成的。例如下面程序所示:
int len1 = 0x10000;
short len2 = len1;
由于len1和len2的数据类型长度不一样,len1是32位,而len2是16位,因此进行赋值操作后,len2无法容纳len1的全部位,导致了与预期不一致的结果,即len2等于0。
看到这里读者可能会想,只要不把长类型的变量赋给短类型变量就行了。其实并不是那么简单,把短类型变量赋给长类型变量同样存在问题,例如如下代码:
short len2 = 1;
int len1 = len2;
上面代码的执行结果并非总是如预期的那样使len1等于1,在很多编译器编译的程序中结果是使len1等于0xffff0001,实际上就是一个负数。这是因为当len1的初始值等于0xffffffff,而把short类型的len2赋值给len1时只能覆盖掉其低16位,这就造成了安全隐患。
2.运算溢出
运算过程中造成的整型数溢出是一种最常见的情况,很多著名漏洞都是由这种整型数溢出所导致的。它的原理很简单,就是对整型数变量进行运算过程中没有考虑到其边界范围,造成运算后的数值超出了其存储空间,例如下面伪代码:

bool func(char *userdata, short datalength)
{
char *buff;
...
if(datalength != strlen(userdata))
return false;
datalength = datalength*2;
buff = malloc(datalength);
strncpy(buff, userdata, datalength)
...
}
其中userdata是用户提交的字符串,datalength是用户提交的字符串长度。func()函数所作的就是首先保证用户提交的字符串长度和字符串的实际长度一样,然后分配一块2倍于用户提交字符串大小的缓冲区,然后把用户字符串数据拷贝到这个缓冲区当中。这看起来应该是没有任何安全问题的,实际则不然,程序员仅仅考虑了对字符串数据的安全要求,却没有考虑对作为整型数的长度变量的数据类型的表示范围。
说的更明确点就是datalength*2以后可能会超出16位short整型数的表示范围,造成datalength*2<datalength。假如用户提交的datalength=0x8000,如果正常情况下0x8000×2=0x10000,但是当把0x10000赋值给short类型变量时,最高位的1会因为溢出而无法表示,这时的0x8000×2=0了。也就是说,在上面例子程序中,如果用户提交的datalength>=0x8000,那么就会发生溢出。

下面我们来看一个运算整型数溢出实例:IIS ASP映射溢出分析。
去年eEye发现的asp堆溢出应该算是比较典型的运行造成的整型数溢出。以前的IIS映射溢出都出现在对字符串操作使用错误的长度限制,这次的asp溢出虽然也不例外,但是与以往不同的是,这次是因为一个长度变量计算过程中的整型数溢出造成的。我们来看看对asp.dll反汇编的分析结果:
这是对asp的ecb->lpbData数据进行处理的函数的反汇编代码:
.text:7499DD63 sub_7499DD63 proc near ; CODE XREF:
.text:7499DD63 var_14 = byte ptr -14h
.text:7499DD63 var_8 = dword ptr -8
.text:7499DD63 var_4 = dword ptr -4

此时ebx指向ecb(EXTENSION_CONTROL_BLOCK)结构
.text:7499DD7F mov esi, [ebx+7Ch] ;ecb+0x7c是lpszContentType变量

.text:7499DE04 mov eax, [ebx+70h] ;ecb+0x70就是cbTotalBytes变量
cbTotalBytes就是用户提交的Content-length,也就是用于指明lpszContentType的长度,如果用户不提交这个变量,IIS则把它自动赋予为0xffffffff。
.text:7499DE07 mov edi, [ebp+var_8]
.text:7499DE0A lea ecx, [eax+eax+2] ;这里计算dwBytes = cbTotalBytes*2+2
因为把cbTotalBytes*2+2以后就可能超出32位数的表示范围,因此造成溢出
.text:7499DE0E mov eax, [edi+20h]
.text:7499DE11 mov edx, [eax+0B8h]
.text:7499DE17 test edx, edx
.text:7499DE19 jnz short loc_7499DE33
.text:7499DE1B push 0F60h
.text:7499DE20 push offset aFNtPrivateIn_7 ; "F:\\nt\\private\\inet\\iis\\svcs\\cmp\\asp\\req"...
.text:7499DE25 push ecx ; dwBytes
.text:7499DE26 mov [eax+0B8h], ecx
.text:7499DE2C call sub_74972561
这里会调用一个函数进行内存分配,分配的大小就是cbTotalBytes*2+2
.text:74972561
.text:74972561 push [esp+dwBytes] ; dwBytes
.text:74972565 push 0 ; dwFlags
.text:74972567 push hHeap ; hHeap
.text:7497256D call ds:HeapAlloc
.text:74972573 retn 0Ch
后面的函数就会把从网络接受的数据buffer内容拷到这个动态分配的堆内存当中,这时就出现了问题:
.text:7499DE75 mov dword ptr [eax+0A4h], 2
.text:7499DE7F mov ecx, [ebx+70h] ;cbTotalBytes
.text:7499DE82 mov eax, [ebx+74h] ;cbAvailable
.text:7499DE85 cmp ecx, eax
.text:7499DE87 ja short loc_7499DEA5
用C语言的伪代码来表示上面的代码就是这样的:
if(cbTotalBytes>cbAvailable) copylen = cbAvailable
else copylen = cbTotalBytes
然后下面会用repe movsd进行内存复制操作:

.text:7499DEA5 mov esi, [ebx+78h] ;这个是lpbData
.text:7499DEA8 mov ecx, eax
.text:7499DEAA mov eax, [edi+20h]
.text:7499DEAD mov edx, [ebp+var_8]
.text:7499DEB0 mov edi, [eax+0B4h] ;buff[0xB200]
.text:7499DEB6 mov eax, ecx ;ecx = cbAvailable = 0xc000
.text:7499DEB8 shr ecx, 2
.text:7499DEBB repe movsd
.text:7499DEBD mov ecx, eax
.text:7499DEBF and ecx, 3
.text:7499DEC2 repe movsb ;内存复制操作
在这里进行字符串复制的时候溢出,分配的heap buffer大小为cbTotalBytes*2+2,如果cbTotalBytes>cbAvailable,那么拷贝的长度是cbAvailable,也就是说拷贝长度取cbTotalBytes和cbAvailable中比较小的一个。cbAvailable的最大值是0xc000,lpbData可装载的数据最大也是0xc000。

因为整型数溢出:cbTotalBytes*2+2<cbTotalBytes,所以分配的heap buffer可能是很小的,造成复制0xc000 bytes的数据就会造成堆溢出,覆盖堆内存之间的管理结构,从而可能造成改变程序的流程。
这个溢出要在下一次进行内存分配的时候显现出来:

ext:7499576E push 8 ; dwBytes
.text:74995770 mov [ebp+var_4], esi
.text:74995773 mov [ebp+var_8], esi
.text:74995776 mov [ebp+ppv], esi
.text:74995779 call sub_74972561
这里分配了8个字节,上一次造成的溢出被显现出来了。
3.符号问题
正如前面所提到过的那样,整型数是分为有符号整型数和无符号整型数的,由于符号的问题也可能引发安全方面的隐患。一般对长度变量都要求使用无符号整型数,如果程序员忽略了符号的话,在进行安全检查判断的时候就可能出现意想不到的情况。由符号引起的溢出的最典型的例子是去年eEye发现的Apache HTTP Server分块编码漏洞。下面我们就来分析一下这个漏洞的成因。
分块编码(chunked encoding)传输方式是HTTP 1.1协议中定义的Web用户向服务器提交数据的一种方法,当服务器收到chunked编码方式的数据时会分配一个缓冲区存放之,如果提交的数据大小未知,客户端会以一个协商好的分块大小向服务器提交数据。
Apache服务器缺省也提供了对分块编码支持。Apache使用了一个有符号变量储存分块长度,同时分配了一个固定大小的堆栈缓冲区来储存分块数据。出于安全考虑,在将分块数据拷贝到缓冲区之前,Apache会对分块长度进行检查,如果分块长度大于缓冲区长度,Apache将最多只拷贝缓冲区长度的数据,否则,将根据分块长度进行数据拷贝。然而在进行上述检查时,没有将分块长度转换为无符号型进行比较。因此,如果攻击者将分块长度设置成一个负值,就会绕过上述安全检查,Apache会将一个超长(至少>0x80000000字节)的分块数据拷贝到缓冲区中,这会造成缓冲区溢出。

http_protocol.c中的具体引发漏洞的代码如下:
API_EXPORT(long) ap_get_client_block(request_rec *r, char *buffer, int bufsiz)
//漏洞就发生在这个函数里面,其中bufsiz是用户提交的buffer的长度,这里是一个有符号的整型变量
{
...
len_to_read = (r->remaining > bufsiz) ? bufsiz : r->remaining; //这里判断bufsiz和r->remaining哪个小,就用哪个作为拷贝字符串的长度,如果用户提交的buffer长度即bufsiz超过0x80000000的时候,由于bufsiz是有符号的整型数,因此它一定是一个负数,所以它就肯定会小于r->remaining,这样就绕过了安全检查。

len_read = ap_bread(r->connection->client, buffer, len_to_read);
if (len_read <= 0) {
r->connection->keepalive = -1;
return -1;
}
...
我们再来看看ap_bread()函数的处理:
API_EXPORT(int) ap_bread(BUFF *fb, void *buf, int nbyte)
{
...
memcpy(buf, fb->inptr, nbyte); //这里使用memcpy进行缓冲区拷贝,拷贝的长度就是用户提交的buffer的大小,因为前面已经绕过了长度安全检查,所以这里会发生缓冲区溢出。
这个漏洞在Windows下可以利用异常处理地址来进行利用,*BSD下则可以利用memcpy的反向拷贝机制来利用。关于漏洞利用的具体方法就不在这里讨论了,这里要说明的重点就是由于程序员忽略了整型数的符号问题而引起了安全漏洞。在新版本的Apache中使用了如下语句进行长度检查:
len_to_read = (r->remaining > (unsigned int)bufsiz) ? bufsiz : r->remaining;
这样以来把bufsiz当作无符号的数,这样就不会出现问题了。

五、总结
整型溢出并不是一种很新的漏洞形式,但是它却是非常危险的,部分原因是因为它在发生后不可能被发现。如果一个整型溢出发生了,应用程序往往并不知道它的计算是错误的,因此应用程序在假定它是正确的情况下继续运行下去。当然,正是由于其隐蔽性,整型溢出也是很难被发现的,通常的压力测试往往无法成功的引发整型溢出,对于这种漏洞的发掘,一般需要使用逆行工程对程序代码进行详细的审核。

  评论这张
 
阅读(399)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017