原创作者:tombkeeper
内存的读、写、执行属性是系统安全最重要的机制之一。通常,如果要改写内存中的数据,必须先确保这块内存具有可写属性,如果要执行一块内存中的代码,必须先确保这块内存具有可执行属性,否则就会引发异常。然而,windows系统的异常处理流程中存在一些小小的特例,借助这些特例,就可以知其不可写而写,知其不可执行而执行。
0×01 直接改写只读内存 我在cansecwest 2014的演讲《rops are for the 99%》中介绍了一种有趣的ie浏览器漏洞利用技术:通过修改javascript对象中的某些标志,从而关闭安全模式,让ie可以加载类似wscript.shell这样的危险对象,从而执行任意代码而完全无需考虑dep。
不过,修改safemode标志并非是让ie可以加载危险对象的唯一方法。
ie浏览器的某些界面实际上是用html实现的,这些html通常存储在ieframe.dll的资源中,例如:打印预览是res://ieframe.dll/preview.dlg,整理收藏夹是res://ieframe.dll/orgfav.dlg,页面属性则是 res://ieframe.dll/docppg.ppg。
ie浏览器会为这些html创建独立的渲染实例,以及独立的javascript引擎实例。而为这些html创建的javascript引擎实例中,safemode本身就是关闭的。
所以,只需将javascript代码插入到ieframe.dll的资源中,然后触发ie的相应功能,被插入的代码就会被当作ie自身的功能代码在safemode关闭的javascript实例下执行。
不过,pe的资源节是只读的,如果试图用某个能对任意地址进行写入的漏洞直接改写ieframe.dll的资源,会触发写访问违例:
在上面的异常处理链中,mshtml.dll中的异常处理函数最终会调用kernel32!raisefailfastexception()。如果g_ffailfasthandlerdisabled标志是false,就会终止当前进程:
但是,如果g_ffailfasthandlerdisabled标志为true,异常处理链就会执行到 kernel32!unhandledexceptionfilter() ,并最终执行kernel32!checkforreadonlyresourcefilter() :
如果basepallowresourceconversion 也为true,checkforreadonlyresource()函数就会将试图写入的那个内存分页的属性设为可写,然后正常返回。
也就是说,如果先将g_ffailfasthandlerdisabled和basepallowresourceconversion这两个标志改写为true,之后就可以直接修改ieframe.dll的资源,而不必担心其只读属性的问题,操作系统会处理好一切。
另外还有个小问题。如果像上面所说的那样触发了一次checkforreadonlyresource()中的修改内存属性的操作,内存属性的regionsize也会变成一个内存分页的大小,通常是0×1000。而ie在以ieframe.dll中的html资源创建渲染实例前,mshtml!getresource()函数会检查资源所在内存的regionsize属性,如果该属性小于资源的大小,就会返回失败。然而,只需将要改写的资源从头到尾全部改写一遍, regionsize就会相应变大,从而绕过这个检查。
这样,利用windows写访问异常对pe文件资源节开的绿灯,就可以写出非常奇妙的漏洞利用代码。
0×02 直接执行不可执行内存 我在vara 2009的演讲《漏洞挖掘中的时间维度》中介绍了一种较为少见的模块地址释放后重用漏洞。比如一个程序中线程a调用了模块x的函数,模块x又调用了模块y的函数。模块y的函数由于某种原因,耗时比较长才能返回。在它返回前,如能让线程b将模块x释放,那么模块y的函数返回时,返回地址将是无效的。当时发现在opera浏览器中可以利用flash模块触发这种漏洞,一款国产下载工具也有类似问题。
另外还有不少其它类型的漏洞,最终表现也和上述问题一样,可以执行某个固定的指针,但无法控制该指针的值。在无dep环境下,这些漏洞并不难利用,只要喷射代码到会被执行的地址即可。而在dep环境下,这些漏洞通常都被认为是不可能利用的。
但如果在预期会被执行到的地址喷射下面这样的数据:
即使在dep环境下,尽管堆喷射的内存区域确定无疑不可执行,但你会惊奇地发现系统似乎还是执行了这些指令,跳到ecx所设定的地址去了。只要把ecx设为合适的值,就可以跳往任何地址,继而执行rop链。
这是因为windows系统为了兼容某些老版本程序,实现了一套叫atl thunk emulation的机制。系统内核在处理执行访问异常时,会检查异常地址处的代码是否符合atl thunk特征。对符合atl thunk特征的代码,内核会用kiemulateatlthunk()函数去模拟执行它们。
atl thunk emulation机制会检查要跳往的地址是否位于pe文件中,在支持cfg的系统上还会确认要跳往的地址能否通过cfg检查。同时,在vista之后的windows默认 dep policy 下,atl thunk emulation机制仅对没有设置 image_dllcharacteristics_nx_compat的程序生效。如果程序编译时指定了/nxcompat参数,就不再兼容atl thunk emulation了。不过还是有很多程序支持atl thunk emulation,例如很多第三方应用程序,以及32 位的 iexplore.exe。所以,类似hacking team泄露邮件中的cve-2015-2425,如能用某种堆喷成功抢占内存,也可借此技巧实现漏洞利用。
这样,利用系统异常处理流程中的atl thunk emulation能直接执行不可执行内存的特性,就可以让一些通常认为无法利用的漏洞起死回生。
(本文大部分内容完成于2014年10月,涉及的模块地址、符号信息等基于windows technical preview 6.4.9841 x64 with internet explorer 11。)
参考:
[1] rops are for the 99%, cansecwest 2014
[2] bypassing browser memory protections
[3] (cve-2015-2425) “gifts” from hacking team continue, ie zero-day added to mix
[4] 《漏洞挖掘中的时间维度》,vara 2009
*作者:tombkeeper,本文首发于 腾讯玄武实验室博客 ,转载请注明来自freebuf黑客与极客(freebuf.com)