CTF实战27 缓冲区溢出原理

CTF实战27 缓冲区溢出原理


小姐姐优先~


然后来拜一下祖师爷吧~


重要声明

该培训中提及的技术只适用于合法CTF比赛和有合法授权的渗透测试,请勿用于其他非法用途,如用作其他非法用途与本文作者无关



那开始这些课程之前,我们还是要介绍一下什么是缓冲区溢出


缓冲区溢出


缓冲区溢出(buffer overflow),是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权


缓冲区溢出原指当某个数据超过了处理程序限制的范围时,程序出现的异常操作。造成此现象的原因有:


肯定第一位是存在缺陷的程序设计


尤其是C语言,不像其他一些高级语言会自动进行数组或者指针的边界检查,增加溢出风险


C语言中的C标准库还具有一些非常危险的操作函数,使用不当也为溢出创造条件


因黑客在Unix的内核发现通过缓冲区溢出可以获得系统的最高等级权限,而成为攻击手段之一


也有人发现相同的问题也会出现在Windows操作系统上,以致其成为黑客最为常用的攻击手段,蠕虫病毒利用操作系统高危漏洞进行的破坏与大规模传播均是利用此技术


比较知名的蠕虫病毒冲击波蠕虫,就基于Windows操作系统的缓冲区溢出漏洞


例如一个用途是对SONY的掌上游戏机PSP-3000的破解,通过特殊的溢出图片,PSP可以运行非官方的程序与游戏


同样在诺基亚智能手机操作系统Symbian OS中发现漏洞用户可以突破限制运行需要DRM权限或文件系统权限等系统权限的应用程序


缓冲区溢出攻击从理论上来讲可以用于攻击任何有相关缺陷的程序,包括对杀毒软件、防火墙等安全产品的攻击以及对银行程序的攻击。在嵌入式设备系统上也可能被利用,例如PSP、智能手机等


在部分情况下,当一般程序(除了驱动和操作系统内核)发生此类问题时,C++运行时库通常会终止程序的执行


缓冲区溢出是一种非常普遍,非常危险的漏洞,在各种操作系统,应用软件中广泛存在


利用缓冲区溢出攻击,可以导致程序运行失败,系统宕机、重新启动等后果

更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作


栈溢出攻击


那既然提到了栈(stack)溢出攻击,什么是栈呢?


栈是计算机科学中一种特殊的串列形式的抽象数据类型,其特殊之处在于只能允许在链表或数组的一端


就像是我们的一叠书,书只能从最上面那本开始取,因为在最下面的书很重,假设我们的程序打算从栈中取一个书,在它取之前呢,我们用我们想让程序执行的书放在原本真正的书上面,程序不知道我们已经做了覆盖,取了最上面的那本书之后,就开始执行那本我们想要他执行的书


这样我们就完成了栈溢出攻击了


这里有一个程序在计算机内存中的分布图


CTF实战27 缓冲区溢出原理



这里有个简单的代码,我相信信安毕业的毕业生都见过这个代码了吧啊哈哈哈

我记得是课本上的例子


int stack_overflow(char *str)

{

    char buf[10];

    strcpy(buf, str);

    return 0;

}


int main()

{

    stack_overflow(“0123456789abcdefg”);

    return 0;

}


我们的这个长度大于10的字符串,复制到了buf中之后,不但填满了buf,还把buf后面的数据也覆盖了


堆溢出攻击



在系统中的堆结构,使用的是链表方式组织系统堆,并且使用的是双向链表

CTF实战27 缓冲区溢出原理


这里从51CTO里面找了一个图


一个常用的堆溢出技术叫DWORD SHOOT攻击


DWORD SHOOT


原理就是当发生溢出时,将后端堆块的控制头覆盖


然后在堆块释放的时候发送DWORD SHOOT攻击,最后DWORD SHOOT 就会修改任意地址的一个DWORD长度内容


当释放(free)一个堆块时,会进行链表的<卸载>操作链表操作


操作呢也很简单


还是用上面那个双向链表来解释


其中的三个结构体(除了头结点)分布对应了node0, node1, node2


CTF实战27 缓冲区溢出原理


当我们准备释放node1的堆块的时候


我们执行


node1->pre->next = node1->next


感觉在给各位上数据结构的课啊哈哈哈


node1->pre

是指向了node0



node1->next


指向的是node2


所以上面那个代码我们可以换成这样的伪代码


node0->next = node2


于是我们的node0的next指针直接就跳过了node1指向了node2


然后同样的一个操作


node->flink->blink = node->blink


这个操作把node3的pre指针跳过了node2直接指向了node1

这就是堆块的释放


如果我们在堆块释放的时候,将node0本来要指向node2的尾指针覆盖掉,并且指向了我们的shellcode


就是DWORD SHOOT攻击(感兴趣的同学可以看看0day安全这本书)


数字溢出


在无符号整数和有符号整数的对比容易出现这样的问题


整数越界


int number_overflow(char *buf, int len)

{

    char tbuf[100];

    if(len > sizeof(tbuf)){

        return -1; 

    }

    return memcpy(tbuf, buf, len);

}


这里我们如果我们输入的len是个负数的话


这里的问题在于memcpy 使用无符号整数作为len参数


但是在之前的数据长度检测中使用了有符号整数


(int定义的是有符号整型,而sizeof返回的是size_t类型,一般在stddef.h中定义size_t的为unsigned int为无符号整型)


所以呢我们可以提供一个负数的len(因为定义len的是int,是有符号的)

就可以绕过的检测了


假如我们提供的len为-1,那么-1肯定是会小于sizeof(tbuf)的


于是这里的数据长度检查的if语句就不会执行


于是我们就绕过到了下面的memcpy函数


但是这个值同样被使用在memcpy函数的里面


len会被转换成一个非常大的正整数


这里估计有些同学看不懂,那么我们稍微解释一下吧


在计算机中,负数以其正值的补码形式表达,记好这里


比如我们取一个int类型的值为-1


正值就为


0000 0001


然后我们取反码是这样的


1111 1110

然后反码加一,就是补码

于是-1在计算机中的值就是


1111 1111


如果我们申明了这个数是有符号数,那么计算机就知道如何来计算这个数值

因为第一位是1,所以计算机知道这个数是负数,最后就会化为了-1


但是如果计算机不知道这个数是负数,那么按照正数来计算的话,最后就会得到这个数是255,就会是一个很大的整数


于是乎,这样用负数代入之后,tbuf缓冲区后面的数据被重写


那么还是有同学会问,假如我们传入的buf[]里面的数据长度是小于tbuf的,那么复制完了不久会停下来咩,这样根本就不会有溢出啊?


我们还是稍微解释一下


我们知道数据在程序运行的时候是存在内存中的


堆是从低地址到高地址存数据,栈是从高地址往低地址存数据,但是他们都是在内存中,具体查看上面那个程序在计算机中的分布图


堆(heap)和栈(stack)的存储数据的方向是相反的,于是有时间我们要从内存中读取栈的数据要倒着读


              heap —>

00416000 00 00 9C A4 40 00 00 20 A8 A4 40 00 00 1C A0 4F

00416010 41 00 00 20 8C A9 40 00 00 20 B8 AA 40 00 00 00

00416020 A4 BA 40 00 00 00 38 BD 40 00 00 05 28 C7 40 00

00416030 00 04 B8 C4 40 00 00 0A F4 E5 40 00 00 0A 20 EE

00416040 40 00 00 0A 28 E9 40 00 00 0A 14 F3 40 00 00 01

00416050 20 FA 40 00 00 03 10 FF 40 00 00 02 8C 04 41 00

                                          <— stack


套用上面那个内存的分布,假如我们这里有一个内存是这样分布的


00416000 00 00 9C A4 40 00 00 20 A8 A4 40 00 00 1C A0 4F

00416010 41 00 00 20 8C A9 40 00 00 20 B8 AA 40 00 00 00

00416020 A4 BA 40 00 00 00 38 BD 40 00 00 05 28 C7 40 00

00416030 00 04 B8 C4 40 00 00 0A F4 E5 40 00 00 0A 20 EE

00416040 40 00 00 0A 28 E9 40 00 00 0A 14 F3 40 00 00 01

00416050 20 FA 40 00 00 03 10 FF 40 00 00 02 8C 04 41 00


假如我们的数据是从内存地址00416002那里开始,也就是前两个00后面的9c那里


        00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00416000 00 00 9C A4 40 00 00 20 A8 A4 40 00 00 1C A0 4F


那么正常来说,这个数据到00041604的地址就到达数据的末尾了,因为出现了截断符00 00(也就是C语言里面的,这个道理和%00截断上传有异曲同工之处)


为什么是00 00是截断符,而不是00,因为有些00是为了内存地址的对齐而被计算机故意填进去的,这个说来又会要长篇大论了,这个感兴趣的同学可以自己去看一些资料


如果我们使用的是strcpy函数的,那么strcpy函数发现,我们已经到了的地方,那么就会停止复制了


但是memcpy函数不同,memcpy函数一定会复制完函数第三个参数n那么多的数据


void *memcpy(void *dest, const void *src, size_t n);


于是,我们传入了一个很大的数为n,你就会一直复制,从内存地址中一直往后复制下去,直到复制够了n


于是我们tbuf后面的大片地址,就会被从内存中复制的无用数据所覆盖(当然,黑客不会任由函数复制无用的数据)


这就是数字溢出


CTF实战27 缓冲区溢出原理


本文完


下期内容:Windows缓冲区溢出


该文在初音的小站也有同步,点击阅读原文跳转~


CTF实战27 缓冲区溢出原理

发表评论

电子邮件地址不会被公开。 必填项已用*标注