Heap Spray:高危漏洞的垫脚石

Tags: Heap Spray | 信息安全 | 高危漏洞

网络木马已经成为当今网络世界里最大的危害,而它们的散播源,则来自各个知名或不知名的网站,经由网页浏览器破窗而入,悄悄地在受害者的系统里扎根潜伏,最终给受害者带来一定的损失,这是一种几乎无法完全防御的瘟疫。

http://www.chip.cn/index.php?option=com_content&view=article&id=1543:chipheap-spray&catid=7:test-technology&Itemid=15

装上金山网盾以后,小茹突然发现网络世界是多么的危险。仅在短短十分钟的时间里,金山网盾就已经弹出了三十几次网页木马报警窗口。最令她奇怪的是,她刚才只不过是查了一些有关会计的数据资料而已,而且那些网站看起来也都很正常。
因此,小茹也没有过于在意,将报警窗口关闭后就继续做自己的事情了。
几天过后,小茹已经对时不时弹出来的木马拦截警告框见怪不怪了。但是一个频繁出现的词语却还是在她心里留下了深刻印象。在木马拦截警告里经常会出现“发现iexplore.exe试图触发Heap Spray漏洞,已被成功阻止!”的字样。
什么是“Heap Spray”?小茹查了很多网站,却只找到一些已经被公开了技术细节的过时网页木马源代码,一番搜索下来,这个词组反而越发神秘了……我们可以通过词典查到“Heap”是“堆”的意思。那么,“堆”是什么呢?“Heap Spray”又到底是什么呢?
“堆”(Heap)经常与“栈”(Stack)同时出现,很多技术人员也都习惯于将这两者连起来读作“堆栈”,实际上它们却是两种不同的概念。在信息安全和编程方面,除非特别指明是代表“数据结构”,否则大家提到的“堆”和“栈”,实际上都是指操作系统对内存管理实现的两种不同分配形式。

栈:后进先出

无论是数据结构领域还是内存分配方面,对于“栈”(Stack)的结构描述和用法都是很类似的。它是一种只能在某一端插入和删除数据的特殊线性表。你可以把栈想象成筒装“品客”薯片,这个长筒一端封口一端开口,当这个长筒里没有数据时,我们称之为“空栈”。长筒在流水线上被一片片的薯片填满的过程称为“进栈”(PUSH),在操作系统里,表示将一定的数据填进栈结构里。从这样的结构里我们不难看出,当你要从栈里取数据时,你是只能从最后一片薯片开始取的,而不能一开始就指定了要取位于筒底的第一片薯片,你也不能任意打乱它们的排列顺序,而只能老老实实的从最后一片开始取到最早放进去的那一片,这就是“后进先出”,而取数据的过程,被称为“退栈”(POP)。
栈在系统里通常由操作系统自己分配,用于储存局部变量、函数所需的参数等。在Windows系统里,最常见的栈操作就是各种系统API函数的调用。

堆:按需索取

堆在数据结构和内存分配方面的工作方式并不相同,在数据结构中堆是一种特殊的二叉树,而我们平时提及的堆是一种内存分配的类型,它是一种类似于链表的结构。
操作系统自身定义了许多链表,其中一个链表专门用于记录当前尚未使用的内存地址,当一个程序申请一块空闲内存的时候,它就是在请求一个堆。系统在空闲内存地址链表中寻找一个足够大小的内存空间并将它标注出来,之后再进行分配。
堆被用于存储动态数据变量,你也许会想,前面提到的栈结构不也是用于存储数据吗?它们的确都是用于数据存储,但是,栈是由系统分配的、预定义了大小的内存区块,系统是在严格的控制下对栈进行管理的。栈的大小由操作系统决定,在Windows系统下这个大小为1MB或2MB,这样的设置对于存放更新频繁的临时变量十分合适,但是它不能对程序在运行中另外需要分配的未知数据进行预设,这时候就要用到堆了。
因此,堆一般被用于存放事先不知道数量和大小的数据变量,以及占用内存空间超过栈大小的大尺寸对象等。操作系统默认在每个程序执行时都预先给它分配一个堆用于存放进程初始数据,程序在运行时还可以根据需要另行申请别的堆。堆在使用结束后系统并不会自动清空,只能由开发人员自行调用指令删除使用过的堆。如果开发人员没有对被用过的堆进行处理,它就会一直驻留在内存空间浪费资源。当内存里能用的空间都被消耗殆尽时,程序乃至系统自身都会发生异常甚至系统崩溃。但是,如果程序被关闭,系统将会自动释放所有与这个进程有关的内存。

栈溢出和堆溢出

一个被申请了的指定大小的内存空间,无论是堆还是栈,都被统称为“缓冲区”(Buf fer)。因为系统要求这个空间必须大于实际数据需要的大小,也就是一个贴着“最大可装100片”的筒装薯片里往往不可能真的装满100片薯片。但是如果有人刻意往标注了“最大可装100片”的筒子里塞了200片薯片呢?毫无疑问,多出来的100片将会在流水线上散落得到处都是,甚至还会掉到其他的薯片筒里去,这个现象就是“溢出”。

在内存空间里发生的溢出往往会让情况变得很复杂,因为内存里各个指令和数据的地址是相当紧凑的,并没有多少空闲内存刚好可以接纳这些超额的数据,那么数据会往哪里去呢?它可能会恰好覆盖到另一个API的栈数据,从而成为别人的参数,也可能覆盖到自己的下一个指令,导致CPU执行一个无法执行的指令,最终造成内存异常。在早期的Win98系统里,这个问题将会直接导致蓝屏出现。而在内存管理机制相当完善和严谨的NT架构系统里,这种内存异常只要不是发生在内核领域,就能顺利触发系统的异常处理机制,令系统强行中止发生异常的程序,于是我们能看到由溢出引发的程序崩溃。而这个运行机制就引出了我们今天的主角——Heap Spray技术。

Heap Spray:溢出漏洞的垫脚石

虽然前面将溢出解释得十分简单,但是实际环境里要实现真正有用的溢出并不容易。不过我们可以用下面这个简单的实验来再现溢出。在Windows XP中选择“开始  | 运行”,输入“cmd”后回车打开命令行输入窗口,在里边输入“dir / /?/AAAAA……”(超过四行的A),由于命令行输入窗口里用于复制字符串的API忽略了长度检查,就导致了栈溢出,溢出的字符串数据在内存中编码为“00 41”(A的Unicode编码),因为“41”是没有对应任何汇编指令的,所以溢出发生后很多进程的内存空间受到波及,最直接的一个影响就是将CPU下一步执行的指令覆盖成了不可被解析执行的“41 00”。这会触发了程序异常处理机制SEH(Structured Exception Handling,结构化异常处理),而由于SHE运行需要的内存空间也已经被同样的“41  00”覆盖了,最终预置的异常处理机制都无法执行了出现崩溃退出的情况。
如果将溢出用的“A”换作“q”,这个过程又会不一样了,因为“q”的Unicode编码是“00  71”,在发生溢出后,CPU执行的指令将会是“7 1”,这对应着汇编指令“JNO”(不溢出跳转)。这个溢出的内容被执行后,紧随在“71”之后的“00”会被视为跳转地址,于是这个指令最终的意思是“NOP”(空指令,不跳转到任何地方)。此时,因为剩下来执行到的指令全都是“空”,CPU不会遇到无法执行指令而退出的情况,因此用户很难发现异常。但是如果这个溢出是由黑客运行的,执行的就可能是一个攻击指令了。
溢出的难点在于寻找它的溢出根源、溢出类型以及相关地址,并不是随便在某个程序界面里胡乱输入一些字符就能实现有效溢出的。而“程序异常处理机制”(SEH)的存在更是为溢出增加了一定难度,有时候虽然攻击者构造的恶意执行指令(Shellcode)已经成功触发了溢出,但是随之被触发的SHE会将程序跳转到SEH指定的异常处理过程中去,这会使攻击者构造的指令只能引发一个错误提示或程序崩溃的结果。
因此,攻击者就必须想办法在溢出发生时把SHE跳转用另外一个地址给覆盖掉,这样一来就控制了程序在遭遇异常后执行的代码流向,一旦CPU被成功带到攻击者放置了攻击代码的位置,攻击者的意图就达到了。
为了实现有效的溢出攻击,攻击者使用特定大小的Shellcode内容来重复申请堆,并将Shellcode数据填充进去,这段数据块一定要足够大才能确保溢出后能实现它真正的功能。由于目前Shellcode在网页木马中使用的机会较多,它们必须依赖于JavaScript执行,而如果用户当前打开的网页较多的话JavaScript申请到的内存就不一定还能出现在预期的范围内,所以攻击者通过重复填充一块又一块内存空间的手段来提高命中率。通常申请堆的步骤在触发溢出之前就已经做好,只要成功触发了溢出通常就能使SHE失效并实现恶意代码执行。这个手段的统称,就是“Heap Spray”。
如果用户对网络安全有兴趣并学习了一些溢出教程,会发现这些教程大部分会告诉用户要设法将恶意指令写到“0x0C0C0C0C”这个内存位置上,这是由JavaScript自身特性决定的。JavaScript解释器在请求堆的时候是从“0x00000000”的位置开始申请内存块的,如果攻击者只请求了一个堆,就无法让溢出后的程序跳转到指定指令所在的内存位置。因此攻击者需要构造至少几百个堆,每个堆里的内容都是一样的,最终会有一个堆刚好分配到“0x0C0C0C0C”内存位置上,这个过程就是“Heap Spraying”。

攻击:访问网页即中招

下面我们以今年七月份爆发的最新0day漏洞“MPEG-2 ActiveX Remote Buf fer Over f low  Exploi t”为例,介绍一下Head Spray是怎么让恶意代码被成功执行的。
这个漏洞的根源在于MPEG2视频组件的msvidctl.dll模块里有个方法调用的系统函数ReadFile被传递了错误的参数类型。在大家都是“乖宝宝”的情况下,这个隐患不会被暴露出来,然而,又有人不按规矩出牌了。
首先,攻击者编写一段Shellcode(恶意指令),这段代码要小于溢出时能构造的有效溢出代码的空间。然后使用总共占据0x30000个字节的空指令NOP加上末尾的Shellcode代码,最终构造出0x040404大小的数据块。通过反复申请300个堆将这数据块内容填充进去,这样就能保证至少最后一块内存位置是0x0C0C0C0C。
如果有人观察过多种漏洞溢出利用代码的示范样本就会发现,它们几乎都是以大量的空指令打头阵的,为什么不直接干脆用Shel lcode自身来反复填充内存位置呢?这是因为JavaScript解释器可能会由于用户浏览了多个网页而不能从最低地址开始申请堆,所以它申请到的内存位置可能会与攻击者设想的起始处有偏差。这样会导致Shellcode到0x0C0C0C0C位置的时候只有部分指令被溢出,也就是JavaScript申请到的堆并没有按照攻击者设想的那样刚好把代码开始的第一个字节填充到0x0C0C0C0C这个位置上,这样一来当溢出发生时CPU执行到这里的代码就会由于缺少了一部分指令而导致溢出失败。而如果攻击者在Shellcode代码的前面以大量空指令填充呢?由于代码前部是空指令,它们被截断成多少份也不会影响到后面实际代码的执行。即使一段恶意指令被覆盖到0x0C0C0C0C位置时前面少了很多空指令,它仍然能被执行。
当然,有时候即使用了大量空指令做铺垫,也会出现溢出失败的情况。
那是因为攻击者的代码构造不合理或Shellcode只有一部分进入了0x0C0C0C0C位置。因此要想让溢出成功几率更大些,计算一个合适大小的Shellcode数据块是相当重要的。
回到前边提到的MPEG-2漏洞,当攻击者以适当长度的空指令和Shel lcode代码反复填充了多个内存地址后,0x0C0C0C0C位置也被这段代码所占据,然后溢出代码调用MPEG-2组件加载一个精心构造了错误内容的GIF文件,这个文件第14个字节开始的内容导致了ReadFile函数发生溢出。溢出数据里的0xFFFFFFFF和0x0C0C0C0C刚好将SEH地址覆盖,于是SHE的跳转地址变为0x0C0C0C0C。这时候又由于前边发生了溢出而启动了异常处理机制,程序代码会执行SHE。在正常情况下,这一步后应该会导致程序崩溃或执行自身预置的异常处理过程,例如弹出错误消息提示等。但是现在,跳转地址被改写为0x0C0C0C0C,于是CPU就跳转到0x0C0C0C0C位置去执行攻击者预先编写的Shellcode了。
虽然堆是由程序员自己控制的,但是这并不意味着我们可以随心所欲地指定程序在某个内存位置去新建一个堆,但是我们能控制每个堆的大小以及数量,而且堆的初始位置是可以被预测的。因此利用大量重复数据生成堆的Heap Spray技术成了控制溢出代码位置的最流行手段。虽然实际上我们仍然无法真正控制代码被填充到指定内存位置,但是通过大量的循环,总能有一段代码被成功填充到指定位置上,这样就能被视为控制成功了。
“Heap Spray”还具备跨平台跨浏览器的能力,特别是在基于JavaScript的攻击方面,无论用户是使用漏洞很多的IE浏览器还是宣称安全性较强的Firefox,攻击者总能找出一个适合它们的攻击手法,再结合Heap Spray溢出攻击总有机会实现。
因此,“Heap Spray”并不是指某一种漏洞或木马手段,它自身是一种合理存在的技术手段罢了,只不过被大量用于溢出Shellcode,成为恶意木马得以顺利执行的垫脚石。

防范:如何阻止Heap Spray


在这个网页木马满天飞的时代里,“裸奔”(不在电脑里安装任何信息安全工具)已经成了疯狂的代名词,你永远无法保证今天还正常浏览的网站是否明天就会被黑客入侵并放上了一个网页木马。而也许这个木马恰巧可以攻击你电脑上尚未修补的漏洞。因此在电脑中安装杀毒软件是非常必要的。但是这样还不够,杀毒软件通常只能在病毒木马已经被下载或执行时进行补救,它并不能预防或阻止溢出的发生,而这个触发的溢出很可能存在针对杀毒软件攻击代码,这些代码可以关掉杀毒软件,此时系统也就毫无安全性可言了。因此仅仅安装杀毒软件并不能百分之百地防范针对漏洞的攻击,用户还必须选择一款网络安全防护工具。
目前许多厂商都推出了能够检测浏览器和网络行为的监控工具,如金山网盾、趋势云安全、超级巡警等。
那么监控工具是如何发现Heap Spray嫌疑代码的呢?首先,它将自身监控模块注入浏览器进程,或者直接将自身作为浏览器的一个BHO对象让浏览器主动加载。随后,当浏览器的脚本解释器开始重复申请堆的时候,监控模块记录堆的大小、内容和数量,当判断这些重复的堆请求到达了一个阀值或覆盖了指定的地址时,监控模块会阻止这个脚本执行过程并弹出警告,由于脚本执行被中断,后面的溢出也就无法实现了。于是一般情况下浏览器不会发生异常,就像用户刚才根本没有被恶意网页纠缠过。
然而,监控工具也仍然可能会存在问题,例如一种新的针对监控工具的溢出代码,就有可能会令它们的模块失效,最终用户还是被木马渗透。虽然当前还没有发生这种攻击,但是当攻击者们将监控工具研究透以后,他们总能找到绕过的方法。因此,有经验的用户最好能在使用监控工具的基础上继续安装一款主动防御类工具,如SSM等。

你可能感兴趣的:(Heap Spray:高危漏洞的垫脚石)