WINDOWS和LINUX内存保护机制
⼀、windows下的内存保护机制
0x00、⼆进制漏洞
⼆进制漏洞是可执⾏⽂件(PE、ELF⽂件等)因编码时考虑不周,造成的软件执⾏了⾮预期的功能。⼆进制漏洞早期主要以栈溢出为主。
我们都知道在中调⽤⼀个函数,在编译后执⾏的是CALL指令,CALL指令会执⾏两个操作:
(1)、将CALL指令之后下⼀条指令⼊栈。
(2)、跳转到函数地址。
函数在开始执⾏时主要⼯作为保存该函数会修改的寄存器的值和申请局部变量
空间,⽽在函数执⾏结束时主要的⼯作为:
(1)、将函数返回值⼊eax
(2)、恢复本函数调⽤前的寄存器值
(3)、释放局部变量空间
(4)、调⽤ret指令跳转到函数调⽤结束后的下⼀条指令(返回地址)
栈溢出指的是局部变量在使⽤过程中,由于代码编写考虑不当,造成了其⼤⼩超出了其本⾝的空间,覆盖掉了前栈帧EBP和返回地址等。由于返回地址不对,函数调⽤结束后跳转到了不可预期的地址,造成了程序崩溃。
早期的栈溢出漏洞利⽤就是将函数的返回地址覆盖成⼀个可预期的地址,从⽽控制程序执⾏流程触发shellcode。漏洞发⽣时,能控制的数据(包含shellcode)在局部变量中,局部变量⼜存在于栈上⾯,因此要想执⾏shellcode必须将程序执⾏流程跳转到栈上。
shellcode存好了,返回地址也可控,如果将返回地址改写为shellcode地址就OK了,可偏偏栈的地址在不同环境中是不固定的。
这时候有聪明的程序员发现了⼀条妙计,栈地址不固定,但是程序地址是固定的。通过在程序代码中搜索jmp esp指令地址,将返回地址改成jmp esp的地址,就可以实现控制程序执⾏流程跳转到栈上执⾏shellcode。
附上⼀张函数调⽤过程中的栈空间分布图:
0x01、启⽤GS选项
启⽤GS选项是在编辑器中可以启⽤的⼀项选择。
启⽤GS选项之后,会在函数执⾏⼀开始先往栈上保存⼀个数据,等函数返回时候检查这个数据,若不⼀致则为被覆盖,这样就跳转进⼊相应的处理过程,不再返回,因此shellcode也就⽆法被执⾏,这个值被称为“Security cookie”。
感兴趣的同学可以编写⼀个DEMO启⽤GS后编译,使⽤IDA便可以看软件到调⽤Check_Security_Cookie()检查栈是否被覆盖。
可是他们忽略了异常处理SEH链也在栈上因此可以覆盖SEH链为jmp esp的地址,之后触发异常跳转到esp执⾏shellcode。
有关SEH链的的技术可以参考。
0x02、SafeSEH
SafeSEH是在程序编译的时候,就将所有的异常处理函数进⾏注册。凡是执⾏过程中触发异常后,都要经过⼀个检验函数,检查SEH链指向的地址是否在注册的列表中。
可是再检验函数的逻辑中阻⽌执⾏的情况只有在SEH链指向模块(exe、dll)地址的情况下,如果SEH链指向的地址不在这些模块中,那就可以执⾏了。因此在程序中⾮模块的数据空间到jmp esp,⽐⽅说nls后缀的资源⽂件等。或者是在⽀持脚本的软件中(浏览器等),通过脚本申请堆空间写⼊shellcode。
0x03、DEP
数据执⾏保护(DEP)指的是堆和栈只有读写权限没有执⾏权限。
对抗DEP的⽅式是将shellcode写⼊堆栈中,从程序⾃⾝的代码去凑到执⾏VirtualProtect()将shellcode所在内存属性添加上可执⾏权限,将函数返回值或者SEH链覆盖成代码⽚段的起始地址。这种利⽤程序⾃⾝碎⽚绕过DEP的⽅式被称作ROP,关于ROP的技术细节可以参考。
ROP技术是通过拼凑代码碎⽚执⾏API,在最开始没有相应辅助⼯具的时候,构建ROP链是耗时耗⼒的。随着研究⼈员的增多,相应的辅助⼯具也被开发出来,ROP链的构建已经相对容易了。
0x04、ASLR
ROP技术的前提是代码⽚段的地址固定,这样才能知道往函数返回值或者SEH链中填写哪个地址。因此地址空间布局随机化(ASLR)应运
⽽上,ALSR即是让exe、dll的地址全都随机。
对抗ASLR的⽅式是暴⼒把程序空间占满,全铺上shellcode,只要跳转地址没落在已有模块中,落在我们的空间中即可以执⾏了shellcode,但是这样做⽆法绕过DEP,这种将程序空间全部占满铺上shellcode的技术被称为堆喷射技术,堆喷射技术只能对抗ASLR,缺⽆法对抗ASLR+DEP的双重防护。
ASLR+DEP的双重防护使得⼤多数软件的漏洞只能造成崩溃,⽆法稳定利⽤。将程序空间占满的技术,称之为堆喷射(Heap Spraying),这种技术只能应⽤在可以执⾏JS等脚本的软件上,如浏览器等。
堆喷射通过⼤⾯积的申请内存空间并构造适当的数据,⼀旦EIP指向这⽚空间,就可以执⾏shellcode。堆喷射已经是不得已⽽为之,有时候会造成系统卡⼀段时间,容易被发现;另⼀点,如果EIP恰好指向shellcode中间部分就会造成漏洞利⽤失败,因此不能保证100%成功。
关于堆喷射技术可以参考。
因为ASLR+DEP的双重防护使得PC上的软件若存在漏洞也⽆法稳定利⽤,因为safeSEH技术的存在⼤多数的软件安全研究者都转向了浏览器+JS,可是因为JS的效率问题,JS也渐渐的使⽤变少,此时FLASH的AS脚本渐渐进⼊了⼈们的视野,⽽且FLASH不仅仅在windows上,包括和Andriod均可以使⽤。因此很多的软件安全研究⼈员转向了浏览器+AS。
由于⼆进制漏洞破坏了程序执⾏流程,因此如果执⾏了shellcode之后不做其他处理,软件会崩溃掉。通常的做法是shellcode调⽤ExitProcess退出进程,这样会造成软件打开了闪退掉。⽽Flash作为浏览器插件的存在,居然发展出很多不卡不闪不挂的漏洞,Flash漏洞利⽤研究如星⽕燎原般炽热。因此⼤多数的浏览器渐渐的不再⽀持FLASH插件,动画技术也渐渐的发展起来。
0x05、CFG
虽然FlASH的漏洞使得其他⼚商有点⽆奈,但是微软并没有停⽌防守的脚步。微软在Win 8.1 Update 3以及Win 10中启⽤了⼀种抵御内存泄露攻击的新机制,即Control Flow Guard(CFG)——控制流防护。这项技术是为了弥补此前不完美的保护机制,例如地址空间布局随机化(ASLR)导致了堆喷射的发展,⽽数据执⾏保护(DEP)造成了漏洞利⽤代码中返回导向编程(ROP)技术的发展。
有关CFG控制流保护的分析可以参考。
绕过CFG的研究也在不断的进⾏⽐如以及XCon2016的议题《JIT喷射技术不死—利⽤WARP Shader JIT喷射绕过控制流保护(CFG)》。⼆、LINUX下的内存防护机制
0x00、checksec
checksec是⼀个shell编写的脚本软件源码参见。
checksec ⽤来检查可执⾏⽂件属性,例如PIE, RELRO, PaX, Canaries, ASLR, Fortify Source等等属性。
⼀般来说,如果是学习⼆进制漏洞利⽤的朋友,建议⼤家使⽤gdb⾥peda插件⾥⾃带的checksec功能,如下:
0x01、CANNARY
CANNARY(栈溢出保护)是⼀种缓冲区溢出攻击缓解⼿段当。
启⽤栈保护后,函数开始执⾏的时候会先往栈⾥插⼊cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停⽌程序运⾏。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败⽽阻⽌shellcode的执⾏。在中我们将cookie信息称为canary。
gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all编译参数以⽀持栈保护功能,4.9新增了-fstack-protector-strong编译参数让保护的范围更⼴。
cookie阻止好还是不阻止好这种⽅法很像windows下的启⽤GS选项
0x02、FORTIFY
这个⽹上的信息很少因此举个例⼦来描述:
1void fun(char *s) {
2char buf[0x100];
3 strcpy(buf, s);
4/* Don't allow gcc to optimise away the buf */
5 asm volatile("" :: "m" (buf));
6 }
⽤包含参数-U_FORTIFY_SOURCE编译
108048450 <fun>:
2 push %ebp ;
3 mov %esp,%ebp
4
5 sub $0x118,%esp ; 将0x118存储到栈上
6 mov 0x8(%ebp),%eax ; 将⽬标参数载⼊eax
7 mov %eax,0x4(%esp) ; 保存⽬标参数
8 lea -0x108(%ebp),%eax ; 数组buf
9 mov %eax,(%esp) ; 保存
10 call 8048320 <strcpy@plt>
11
12 leave ;
13 ret
⽤包含参数-D_FORTIFY_SOURCE=2编译
108048470 <fun>:
2 push %ebp ;
3 mov %esp,%ebp
4
5 sub $0x118,%esp ;
6 movl $0x100,0x8(%esp) ; 把0x100当作⽬标参数保存
7 mov 0x8(%ebp),%eax ;
8 mov %eax,0x4(%esp) ;
9 lea -0x108(%ebp),%eax ;
10 mov %eax,(%esp) ;
11 call 8048370 <__strcpy_chk@plt>
12
13 leave ;
14 ret
我们可以看到gcc⽣成了⼀些附加代码,通过对数组⼤⼩的判断替换strcpy, memcpy, memset等函数名,达到防⽌缓冲区溢出的作⽤。
0x03、NX
NX即No-eXecute(不可执⾏)的意思,NX的基本原理是将数据所在内存页标识为不可执⾏,当程序溢出成功转⼊shellcode时,程序会尝试在数据页⾯上执⾏指令,此时CPU就会抛出异常,⽽不是去执⾏恶意指令。和windows下的DEP原理相同。不再赘述。
关于NX的绕过⽅式在linux叫做Ret2libc,与WINDOWS下的ROP⼤相径庭。
既然注⼊Shellcode⽆法执⾏,进程和动态库的代码段怎么也要执⾏吧,具有可执⾏属性,那攻击者能否利⽤进程空间现有的代码段进⾏攻击,答案是肯定的。
linux下shellcode的功能是通过execute执⾏/bin/sh,那么系统函数库(Linux称为glibc)有个system函数,它就是通过/bin/sh命令去执⾏⼀个⽤户执⾏命令或者脚本,我们完全可以利⽤system来实现Shellcode的功能。EIP⼀旦改写成system函数地址后,那执⾏system函数时,它需要获取参数。⽽根据Linux X86 32位函数调⽤约定,参数是压到栈上的。噢,栈空间完全由我们控制了,所以控制system的函数不是⼀件难事情。
这种攻击⽅法称之为ret2libc,即return-to-libc,返回到系统库函数执⾏的攻击⽅法。
有关ret2libc的攻击⽅式可以参考。
0x04、PIE
⼀般情况下NX和地址空间分布随机化会同时⼯作。在linux下内存空间随机化被称作PIE。
内存地址随机化机制,有以下三种情况
0 - 表⽰关闭进程地址空间随机化。 1 - 表⽰将mmap的基址,stack和vdso页⾯随机化。 2 - 表⽰在1的基础上增加栈(heap)的随机化。
可以防范基于Ret2libc⽅式的针对DEP的攻击。ASLR和DEP配合使⽤,能有效阻⽌攻击者在堆栈上运⾏恶意代码。
Built as PIE:位置独⽴的可执⾏区域(position-independent executables)。这样使得在利⽤缓冲溢出和移动中存在的其他内存崩溃缺陷时采⽤⾯向返回的编程(return-oriented programming)⽅法变得难得多。
0x05、RELRO
在Linux系统安全领域数据可以写的存储区就会是攻击的⽬标,尤其是存储函数指针的区域. 所以在安全防护的⾓度来说尽量减少可写的存储区域对安全会有极⼤的好处.
GCC, GNU linker以及Glibc-dynamic linker⼀起配合实现了⼀种叫做relro的技术: read only relocation.⼤概实现就是由linker指定binary的⼀块经过dynamic linker处理过 relocation之后的区域为只读.
RELRO设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从⽽减少对GOT(Global Offset Table)攻击。
有关RELRO的技术细节。
有关GOT攻击的技术原理参考。
三、⼩结
这是⼀场没有硝烟的战争,这也是⼀场没有尽头的战争。曾经office默认执⾏宏造成了宏病毒的泛滥,之后微软将Office⽂档的宏改为⼿动启⽤,宏病毒销声匿迹,⼆进制⽂档漏洞开始兴起。随着ASLR+DEP防护的加强,Office还额外增加了⼀些防护⼿段,⼆进制⽂档漏洞难以为继,曾经的宏病毒摇⾝⼀变⼜杀了回来。当然,宏依然是不能够⾃动执⾏,但是可以通过⽂档内容,诱使⼈点击执⾏。
攻也好,防也好,技术的背后是⼈。当技术⼿段的发展受到制约,利⽤⼈⾃⾝弱点的社会⼯程学就会兴起。
所以安全的最终还是关于主要⼈员的安全意识。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论