Volume 0x0b, Issue 0x3c, Phile #0x0a of 0x10
|=--------------------=[ Basic Integer Overflows ]=----------------------=|
|=-----------------------------------------------------------------------=|
|=-------------------=[ by blexim ]=-------------------=|
中文翻译整理: Sam@sst
airsupply@sst 1: 目录1.1 什么是整数?1.2 什么是整数溢出?1.3 为什么那是危险的?2: 整数溢出2.1 Widthness 溢出2.1.1 Exploiting2.2 运算(Arithmetic)溢出2.2.1 Exploiting3: 符号类型的问题3.1 它们看起来像什么?3.1.1 Exploiting3.2 符号类型的问题导致的整数溢出4: 真实的例子4.1 整数溢出类4.2 符号问题类译者语:前言:近期的出现的一些安全问题都是关于整数溢出问题,比如前段时间的ssh crc32远程溢出漏洞,apache chunked 漏洞,openssh Challenge-Response机制远程溢出漏洞,都是由整数溢出引发的缓冲区溢出问题,整数溢出虽然是间接的导致一些缓冲区溢出问题,但是也算是一种较新的技术,而且在未来的几年中,关于此类的问题会越来越多.最新的phrack60期中刚好讲到了关于整数溢出的一些技术,心血来潮就翻译了这篇文章.由于笔者的英文水平很烂,文章可能会翻译的不是很到位.希望各位帮忙斧正.感谢:感谢我的女朋友帮我一起翻译了这篇文章.以及sst的所有成员. :关于sst:一群很热血的人,因为崇拜blackhat所以走到一起来.Sam@sst--[ 1.0 目录在这篇文章中我将会讲述2种由于不安全编程引发的问题,导致一些恶意的用户修改受影响的进程改变程序执行流程.这2种类型的问题都是由于某一程序变量包含一个不可预料的值,因此这种类型的问题不同于那些程序内存被改写的问题,比如:缓冲区溢出,格式化溢出.所有文章中给出的程序例子都是用C语言编写,所以需要读者熟悉C语言.一些整数在内存中存储方法的知识也是会有所帮助的,但不是全部.----[ 1.1 什么是整数?一个整数, 在计算范围内, 是一个变量可能是一个没有小数部分的实数的.在系统上被编译处理后,整型和指针的尺寸一般是相同的(比如: 在32位的系统中,例如i386, 一个整数是32字节长,在64位的系统中,例如SPARC,一个整数是64字节长).然而一些编译器不使整型和指针为同样尺寸 ,所以为了通俗易懂,所有这里谈到的例子是在32位的系统环境和32位的整数,长度和指针.整数,如同所有的变量只是内存的一个区域, 当我们谈及关于整数,我们通常用10进制来表示它们.换句话说也就是人们经常使用的一种编码方式.计算机是基于数字的,不能直接处理10进制,所以在计算机中整数是以2进制的方式存储的.2进制是另一种编码方式,它们只有2个数字,1和0,与之不同的10进制是用10个数字来表示的.2进制和10进制,16进制是广泛的被使用的在电脑中能够很简单的转换2进制和16进制.因为存储负数通常是必要的,这样就需要一种机制仅仅用位来代表负数,这种方法已经完成了,通过一个变量的最高为来决定正负.如果最高位置1,这个变量就被解释为负数; 如果置0,这个变量就解释为整数.这会导致一些混淆,这可以说明一些符号类型问题的概念,因为不是所有的变量都是有符号之分的,意思就是说并不是所有的类型都需要使用MSB来区分正负.这些变量被定义为无符号,它只能被赋予正数值.如果变量可正可负,可以被称做是无正负的。----[ 1.2 什么是整数溢出?既然一个整数是一个固定的长度 (在本篇文章中使用32位),它能存储的最大值是固定的,当尝试去存储一个大于这个固定的最大值时,将会导致一个整数溢出.在ISO C99的标准中讲到整数溢出将会导致"不能确定的行为",也就是说编译器遵从了这个的规则,那就是完全忽略溢出而退出这个程序.很多编译器似乎忽略了这个溢出,结果是一个意想不到的错误值被存储.----[ 1.3 为什么那是危险的?整数溢出是不能被立即察觉,因此没有办法去用一个应用程序来判断先前计算的结果是否实际上也是正确的.如果是用来计算缓冲区的大小或者计算数组索引排列的距离,这会变的危险.当然很多整数溢出并不是都是可利用的,因为并没有直接改写内存,但是有时,他们可导致其他类型的bugs,缓冲区溢出等.而且,整数溢出很难被发现,因此,就算是审核过的代码也会产生意外。--[ 2.0 整数溢出所以当一个整数溢出已经发生时会发生什么呢? ISO C99 是这样说的:"A computation involving unsigned operands can never overflow,because a result that cannot be represented by the resulting unsignedinteger type is reduced modulo the number that is one greater thanthe largest value that can be represented by the resulting type."译者注:大致的意思是:涉及到无符号操作数计算的时候从不会溢出,因为结果不能被无符号类型表示的时候,就会对比该类型能表示的最大值还大的数求余.这样就能用该结果来表示这种类型了.NB:取模的运算方法是2个数相除取余数的值例子:10 modulo 5 = 011 modulo 5 = 1所以在减轻体重法里面,一个大数被和(最大的int值 + 1)取模,在C语言中,取模操作的符号是%.这里有一个字节是多余的,可能是一个很好的象征性例子证明我们说的"导致不确定的行为".我们有2个无符号的整数,a和b, 2个数都是32位字节长,我们赋值给a 一个32为整数的最大值,b被赋值为1.然后我们让a和b相加然后存储结果到第3个无符号32位的整数r:a = 0xffffffffffb = 0x1r = a + b现在,当相加起来的结果不能用32位的的值来表示,结果,为了和ISO 标准一致,被和0x100000000取模.r = (0xffffffff + 0x1) % 0x100000000r = (0x100000000) % 0x100000000 = 0减轻体重法的取模算法只能计算低于32位的计算结果,所以整数溢出导致结果被截断到一个范围,通常用一个变量来存储这个结果。这个经常被称作一个"环绕"(译者注:类似成语中"否极泰来"的意思,在这篇文章中我们理解为一个正数大到了极点就会变成负数,负数小到了极点就会变成正数),作为这里的结果,就出现了环绕到0.----[ 2.1 Widthness 溢出所以整数溢出是尝试存储一个大数到一个变量中,由于这个变量太小不足以存储该大数导致的结果.用最简单的例子来说明这个问题,存储一个大变量到一个小变量中去:/* ex1.c - loss of precision */#includeint main(void){int l;short s;char c;l = 0xdeadbeef;s = l;c = l;printf("l = 0x%x (%d bits)\n", l, sizeof(l) * 8);printf("s = 0x%x (%d bits)\n", s, sizeof(s) * 8);printf("c = 0x%x (%d bits)\n", c, sizeof(c) * 8);return 0;} /* EOF */让我们看看执行结果nova:signed {48} ./ex1l = 0xdeadbeef (32 bits)s = 0xffffbeef (16 bits)c = 0xffffffef (8 bits)当我们把一个大的变量放入一个小变量的存储区域中,结果是只保留小变量能够存储的位,而其他的位都被截短了.有必要在这里提及整数进位.当一个计算包含大小不同的操作数时,通过计算较小的操作数会被进位到较大的操作数.如果结果将被存储在一个较小的变量里,这个结果将会被重新减小,直到较小的操作数可以容纳.这个例子里:int i;short s;s = i;这里计算结果将被赋给一个不同尺寸的操作数,将发生的是变量s被提升为一个整型(32位),然后整数i的内容被拷贝给新的提升后的s,接着,提升后的变量内容为了能存在s里面被降低回16位.如果超过了s能存储的最大值降位将导致结果被截断..------[ 2.1.1 Exploiting整数溢出并不像普通的漏洞类型, 它们不允许直接的改写内存或者直接改变程序的控制流程.而是更加精巧.程序的所有者面临的事实是没有办法在进程里面检查计算发生后的结果,所以有可能计算结果和正确结果之间有一定的偏差.就因为这样,大多数的整数溢出不能被利用,即使这样,在一些情况下,我们还是有可能强迫一个变量包含错误的值,从而在后