有人的地方就有江湖,有江湖的地方就有争斗,有争斗就有攻防。人类争斗最初是利用拳脚,冷兵器时代是刀枪,热兵器时代是枪炮,而在计算机平台上,人们的武器换成了——代码。
注:本文注重漏洞攻防的思路对抗过程,因此并未完全按照时间先后顺序描述防护措施。
二进制漏洞是可执行文件(PE、ELF文件等)因编码时考虑不周,造成的软件执行了非预期的功能。二进制漏洞早期主要以栈溢出为主,那时候操作系统和软件厂商还没有相应的安全意识,漏洞利用在当时来说可谓是如入无人之境。
要理解栈溢出,首先要掌握C语言中函数的调用过程:
C语言中调用一个函数,在编译完成后执行的是汇编语句Call指令。Call指令会执行两个操作:
(1)将Call指令之后的下一条指令入栈; (2)跳转到函数地址。
函数开始执行时,主要工作为保存本函数会修改的寄存器值和申请局部变量空间:
函数执行结束时,主要工作为:
(1)将函数返回值入eax (2)恢复本函数调用前的寄存器值 (3)释放局部变量空间 (4)调用ret指令跳转到函数调用结束后的下一条指令(返回地址)
备注:这里我们顺带就能理解为什么说局部变量的生存周期随着函数调用结束而结束。
函数调用过程中栈的空间分布如下:
栈溢出指的是局部变量在使用过程中,由于代码编写考虑不当,造成了其大小超出了其本身的空间,覆盖掉了前栈帧EBP和返回地址等。由于返回地址不对,函数调用结束后跳转到了不可预期的地址,造成了程序崩溃。
早期的栈溢出漏洞利用就是将函数的返回地址覆盖成一个可预期的地址,从而控制程序执行流程触发shellcode。
漏洞发生时,能控制的数据(包含shellcode)在局部变量中,局部变量又存在于栈上面,因此要想执行shellcode必须将程序执行流程跳转到栈上。
shellcode存好了,返回地址也可控,如果将返回地址改写为shellcode地址就OK了,可偏偏栈的地址在不同环境中是不固定的。
这时候有聪明的程序员发现了一条妙计,栈地址不固定,但是程序地址是固定的啊。通过在程序代码中搜索jmp esp指令地址,将返回地址改成jmp esp的地址,就可以实现控制程序执行流程跳转到栈上执行shellcode!
至此,各种栈溢出漏洞利用粉墨登场。浏览网页、看个视频、听个音乐、看个文档甚至聊个QQ都可能被别有用心的人控制电脑。
等到MS08-067远程漏洞被曝出后,只要电脑联网,你什么都不干都会被人控制,这简直是丧心病狂。微软你出来,我保证不打死你!
守方:你不是要覆盖返回地址吗?我在函数执行一开始先往栈上保存个数据,等函数返回之前先检查这个数据,要是不一致那一定是被覆盖了,我就不返回了!
这一技术可以在编译器选项的设置为启用GS选项,而保存的这个数据称之为“Security Cookie”。IDA可以清楚的看到启用GS选项之后的反汇编代码,函数开始执行时:
在函数返回前,调用Check_Security_Cookie检查栈是否被覆盖
校验函数代码
一旦校验不通过,函数跳到相应的处理过程,不再返回因此shellcode也就无法获得执行机会。
攻方:你以为我只有覆盖返回地址这一招吗?你忘记了还有异常处理SEH链也在栈上吗?我可以通过覆盖SEH链为jmp esp的地址,之后触发异常跳转到jmp esp执行shellcode。
本回合总结:
有关异常处理SEH链的技术可以参考http://blog.csdn.net/hustd10/article/details/51167971这边文章,这里不再叙述。
有一点得说明一下,利用覆盖SEH链的方法同样是覆盖了栈空间,因此必须满足:在函数检查Security_Cookie之前就触发异常,具体原因读者可以自行思考。为了能够触发异常,这里有两个思路可以参考:
(1)尽可能多的覆盖栈空间。输入的数据如果足够大,保证覆盖掉SEH链后继续覆盖超出栈空间,OK,触发异常。 (2)通过控制数据,在函数触发漏洞之后到返回之前的代码中触发异常。
由于GS选项是软件编译之时加入的,使得之前的版本无法添加此安全特性,尤其是在很多用户不喜欢更新软件的现实环境中。就算新版,也有可以通过覆盖SEH链来实现漏洞利用,所以这一次防守短期内基本没有达到预期目的。
守方:兵来将挡,水来土屯。既然可以覆盖SEH链实现执行shellcode,那我就改进下溢出处理的机制。
在程序编译的时候,就将所有的异常处理函数进行注册。凡是执行过程中触发异常后,都要经过一个检验函数,检查SEH链指向的地址是否在注册的列表中。如果不在,那就不去执行,嗯,这个机制就叫SafeSEH吧。
攻方大神:别慌,我们先来分析下他的检验函数的逻辑。一袋烟功夫之后,嗯,这逻辑有点怪。阻止执行的情况只有在SEH链指向模块(exe、dll)地址的情况下,如果SEH链指向的地址不在这些模块中,那就可以执行了?
攻方A:他这是弄啥咧,只要不在注册的列表中,就不执行,多简单的逻辑。我觉得这可能是个阴谋!
攻方B:爱咋咋地,搞起!
攻方C:这么说在程序中非模块的数据空间找到jmp esp就行了呗,比方说nls后缀的资源文件等。
攻方D:说是这么说,第一回合我们就损失了小部分战场,要是有软件在数据空间找不到jmp esp呢?
攻方E:管不了那么多了,再说了不是还有浏览器这类支持JS脚本的软件吗?那申请点堆空间自己写个jmp esp不就得了。
攻方大神:傻啊,都能写jmp esp了,直接把shellcode写进去不得了,脑残。
攻方:干杯!
本回合总结:
(1)对于不支持JS等脚本的软件,在数据空间中寻找jmp esp地址,覆盖SEH链指向此地址。 (2)对于支持JS等脚本的软件,直接通过脚本申请堆空间写入shellcode,覆盖SEH链指向堆上的shellcode地址。
守方:攻方太狡猾了,简直是欺人太甚。看来小打小闹是不行了,是你们逼我的。大招伺候:数据执行保护(DEP)。
攻方A(一脸懵逼):数据执行保护是个啥?
攻方大神(深深的吸了一口烟):就是堆和栈只有读写权限,没有执行权限了。
攻方B:啥玩意啊,虚头巴脑的,直接叫“堆栈不可执行”不就得了。
攻方大神:照你这么说,以后新闻联播中的“与会双方深入交换了意见”就得说成“与会双方大吵一架”。
攻方B:……
攻方大神:这个有点麻烦啊,容我三思!
……
守方:我得意的笑。
很长一段时间后……
攻方A:既然堆栈都不能执行了,我们就从程序自身的代码去拼凑shellcode。
攻方B:你干脆让所有程序员给你留一个后门得了。
攻方大神:还别说,这句话提醒了我,我们不去凑shellcode,这不现实。shellcode还在堆栈中,如果可以从程序自身的代码去凑到执行VirtualProtect将shellcode所在内存属性添加上可执行权限就可以了。
若干天后……
攻方全体:这个技术好,大部分软件可以通过拼凑代码片段执行VirtualProtect,将函数返回值或者SEH链覆盖成代码片段的起始地址,又可以愉快的玩耍了。
守方:
本回合总结:
这种利用程序自身的代码碎片绕过DEP的技术称之为ROP。有关ROP的资料可以参考http://rickgray.me/2014/08/26/bypass-dep-with-rop-study.html。
ROP技术是通过拼凑代码碎片执行API,在最开始没有相应辅助工具的时候,构建ROP链是耗时耗力的。随着研究人员的增多,相应的辅助工具也被开发出来,ROP链的构建已经相对容易了。
守方:本以为堆栈不可执行就世界太平了,没想到这帮小子居然还可以这么玩。老虎不发威,你当我是病猫啊。
依我看,ROP技术的前提是代码片段的地址固定,这样才能知道往函数返回值或者SEH链中填写哪个地址。这次再放一个大招,地址空间布局随机化(ASLR),我就不信了。不是要构建ROP么,我让exe、dll的地址全都随机,有本事来战啊。
攻方A:给跪了,膜拜。
攻方B:洗洗睡了。
攻方C:收拾东西,摆地摊手机贴膜去。
……
攻方大神:别慌,我们不是还有一些不能利用的漏洞吗?如果这些漏洞能够泄露出模块地址,两个漏洞结合起来,不是还有一线生机吗?
攻方A:听上去有点道理。
若干天后
攻方A:这个方法很不通用啊,而且泄露地址的漏洞得不能让程序崩溃才能有机会触发后面的可执行漏洞。
攻方B:洗洗睡了。
攻方C:收拾东西,摆地摊手机贴膜去。
……
攻方大神:你们够了!再不行,我们暴力把程序空间占满,全铺上shellcode,只要跳转地址没落在已有模块中,落在我们的空间中不就可以执行了shellcode了么?
攻方B:这样不好吧,把内存全占满了,系统都卡的不行了。另外这样也绕不过DEP吧?
攻方大神:你有更好的办法吗?
……
本回合总结:
ASLR+DEP的双重防护使得大多数软件的漏洞只能造成崩溃,无法稳定利用。将程序空间占满的技术,称之为堆喷射(Heap Spraying),这种技术只能应用在可以执行JS等脚本的软件上,如浏览器等。
堆喷射通过大面积的申请内存空间并构造适当的数据,一旦EIP指向这片空间,就可以执行shellcode。堆喷射已经是不得已而为之,有时候会造成系统卡一段时间,容易被发现;另一点,如果EIP恰好指向shellcode中间部分就会造成漏洞利用失败,因此不能保证100%成功。
堆喷射只是绕过了ASLR,无法绕过DEP,对于ASLR+DEP的双重防护依然是无效的。
堆喷射技术学习可以参考http://blog.chinaunix.net/uid-24917554-id-3492618.html这篇文章。
守方:世界上没有什么问题是一个大招不能解决的,如果有,那就两个。
攻方A:哎,路子越来越窄,日子难过啊。
攻方B:这JS也不好用,不但卡,还经常挂,成功率受影响啊,要是有别的脚本语言就好了。
攻方大神(眼前一亮):别的脚本语言没有,Flash不是可以自己编写AS代码吗?而且Flash在Winodws、Linux和Android上都有应用!
攻方B:不知道Flash好用不好用,试试看吧。
一段时间后……
攻方全体:Flash好人啊,上帝关上门,感谢Flash给我们开了一扇窗。
本回合总结:
ASLR+DEP的防护,使得漏洞利用稳定成功率,越来越多的研究者从浏览器+JS转向了浏览器+Flash。刚开始的Flash漏洞利用和JS一样也是不稳定,随着研究的深入,Flash漏洞利用越来越成熟。
由于二进制漏洞破坏了程序执行流程,因此如果执行了shellcode之后不做其他处理,软件会崩溃掉。通常的做法是shellcode调用ExitProcess退出进程,这样会造成软件打开了闪退掉。而Flash作为浏览器插件的存在,居然发展出很多不卡不闪不挂的漏洞,Flash漏洞利用研究如星火燎原般炽热。
攻方:整形溢出、类型混淆、双重释放、UAF,看我万箭齐发!
守方:无力吐槽了,不怕神一样的对手,就怕猪一样的队友,Adobe,你看着办!
Adobe:我补,我再补,我继续补,我接着补,我……
攻方:整形溢出、类型混淆、双重释放、UAF,万箭齐发!
Adobe:来人,护驾!
吃瓜群众:Adobe你行不行啊?
苹果:大家放弃Flash吧!
FireFox:+1
Amazon:+2
Chrome:+3
FaceBook:你们。。。我的游戏,我的收入!
微软:+10086
FaceBook:算了,我还是转HTML5吧。
攻方:别这样。。。我刚精通AS代码。
安全厂商:推荐用户卸载Flash!
吃瓜群众:卸载?网页上全是框框,视频看不了,页游玩不了….
Adobe:不是国军无能,是共军太狡猾!
全体:滚!
虽然Flash的漏洞使得其他厂商有点无奈,但是微软并没有停止防守的脚步。微软在Win 8.1 Update 3以及Win 10中启用了一种抵御内存泄露攻击的新机制,即Control Flow Guard(CFG)——控制流防护。
这项技术是为了弥补此前不完美的保护机制,例如地址空间布局随机化(ASLR)导致了堆喷射的发展,而数据执行保护(DEP)造成了漏洞利用代码中返回导向编程(ROP)技术的发展。
有关CFG控制流保护的分析可以参考文章http://www.freebuf.com/articles/security-management/58373.html。
有盾就有矛,有守就有攻。
绕过CFG的研究也在不断的进行中,其中的一些技术可以参考绿盟科技的《分析及防护:Win10 执行流保护绕过问题》,地址http://blog.nsfocus.net/win10-cfg-bypass/,以及XCon2016的议题《JIT喷射技术不死—利用WARP Shader JIT喷射绕过控制流保护(CFG)》。
这是一场没有硝烟的战争,这也是一场没有尽头的战争。曾经office默认执行宏造成了宏病毒的泛滥,之后微软将Office文档的宏改为手动启用,宏病毒销声匿迹,二进制文档漏洞开始兴起。
随着ASLR+DEP防护的加强,Office还额外增加了一些防护手段,二进制文档漏洞难以为继,曾经的宏病毒摇身一变又杀了回来。当然,宏依然是不能够自动执行,但是可以通过文档内容,诱使人点击执行。
攻也好,防也好,技术的背后是人。当技术手段的发展受到制约,利用人自身弱点的社会工程学就会兴起。你躲得了初一,躲得过十五吗?