[原创]Windows Shim Engine 初探即利用
Windows Shim Engine 初探即利用
用过EMET的同学应该知道,EMET会注入一个EMET.dll到被保护进程,不过以前我并没有关心过这个注入的过程。最近注意到Windows中的“程序兼容性”实现,才发现EMET正是利用的这个机制来加载它的dll到指定进程的。稍微了解了一下这个兼容性机制之后,发现这基本上是一个Pattern Matching + 程序修补引擎。
Pattern Matching就是指以什么条件来判断一个程序需要兼容性引擎的介入,介入之后需要做什么。
修补的话基本上两个方面,一个是通过Hook IAT实现API欺骗,以便模拟旧的API的行为,另一个是直接Patch程序的代码。所以其实这个兼容性引擎也被MS用来修补CVE。
稍微具体说一下:基本上整个框架是由apphelp.dll和Windows\AppPatch下面的文件来完成的,sdb是数据库,dll是shim(垫片,用来实现“假”API的逻辑)
这个已经被很多人研究过了,总体来说Windows有几个系统级别的数据库,这些数据库记录了一些已知的、会有兼容性问题的程序或者安装程序列表。每个条目会保存一些条件,比如exe名字是什么、版本是多少、目录下会存在什么文件之类的,只有这些条件都满足的时候,一个兼容性操作才会匹配。至于什么时候去匹配的,这是在父进程CreateProcess的时候,由apphelp.dll去检查的。一旦匹配,apphelp会读取一些信息,然后放到新的进程的内存中,新进程PEB会有指针和flag帮助ntdll找到这些信息。新的进程开始后,ntdll会读取PEB中的相关信息,然后加载修补引擎,这里也就使apphelp,然后进行修补操作。至于信息的查找和转递过程,这个和os的实现有关,不过总之,最终结果就是新进程中的apphelp.dll会加载database中指定的需要的shim。
EMET则是利用了这个机制,虽然EMET.dll不是一个真正的shim(垫片),不过它利用的是这个加载shim的时机去加载自己。所以EMET会自己建立一个sdb,把一些需要保护的exe名字加入列表,基本上匹配条件只是exe的名字。然后兼容性操作(需要加载的shim)设置为EMET.dll。注册这个sdb到windows,就完事了。可见一个自定义的sdb可以被用来编写自己的 修(注) 补(入) 逻辑。不过一个比较麻烦的地方有两个,一个是sdb的建立比较烦,MSDN提到了sdb相关API,不过却是“半公开”状态,一个是shim的接口不公开。
经过前人的研究,大概了解了sdb的构造,它基本就是一个树,每个节点是一个TAG。可以理解为一个文件系统的目录,目录里面可以有文件,也可以有子目录。LIST类别的TAG相当于目录,其他类别的TAG相当于文件,根目录就是TAG_ROOT。一些项目比如sdb2xml, sdb-explorer可以帮助理解sdb本身的结构。它们利用的就是apphelp中的sdb API。至于怎么构造,为了方便起见,我做了一个简易的sdb打包器,从xml中读取信息,然后打包成sdb,实测可用。
shim的话,就是一个dll,但是要导出两个接口函数:
PVOID WINAPI GetHookAPIs(LPCSTR Cmdline, LPCWSTR ShimName, PDWORD TotalApis); BOOL WINAPI NotifyShims(DWORD Reason, PLDR_DATA_TABLE_ENTRY LdrEntry);
note:接口有可能是这样的,不确定原始声明,但这样写在汇编角度是不会出问题的(不会去破坏函数的调用协议)
导出的名字一定要保证是GetHookAPIs和NotifyShims,所以需要def重命名一下。因为刚开始研究,所以我基本上两个api不做什么事情,GetHookAPIs返回NULL,NotifyShims返回TRUE就可以了。不过稍微研究了一下,发现GetHookAPIs实际上是返回一个需要hook的列表的指针,然后把TotalApis设置为列表中的项数。至于Cmdline和ShimName,调试发现Cmdline一般是空字符串,ShimName则是shim dll的文件名。NotifyShims是loader用来通知shim关于dll的加载情况,Reason包括有进程初始化,dll动态加载,dll动态卸载,进程结束。基本来说shim需要了解进程中dll状态的变化以便采取不同措施。具体数值可以通过调试确定。LdrEntry也就是指引发通知的dll对应的LDR_ENTRY,留给shim足够的方便去做各种变换。总体说如果你不想去利用这个机制,而只想注入一个dll,那么确实可以啥都不干,返回NULL和TRUE就行了。
测试的时候,打包好的sdb需要通过sdbinst来注册到系统,然后把自己的shim放到windows\AppPatch下面。64位的话,需要放到windows\AppPatch\AppPatch64下面。几个有用的调试开关可以参考http://blogs.msdn.com/b/cjacks/archive/2008/05/20/enabling-diagnostic-output-from-shims.aspx,另外我发现SHIMENG_DEBUG_LEVEL环境变量也是一个控制调试输出的开关,数值调高一些就可以了,然后shim engine会在进程启动,dll动态加载的时候不断输出调试信息。基本上你会看到shim engine会现在进程初始化的时候加载你的shim然后去尝试hook一些api,不过如果你GetHookAPIs直接返回NULL的话,shim engine是不会真正做什么的。
到此,基本上就是目前的研究情况,样例xml和sdb打包的源码在下面给出,打包器需要vs2013+boost编译。不过需要注意的是这个打包器还是比较天真的一种实现,所以可能会有些bug,最好是在调试器下面跑,因为里面的c++异常处理基本上都是直接throw,而没有什么catch,我一般为了省事,直接用vs的调试功能去观察执行的情况,毕竟这个东西也就是为了打包一个sdb。运行的时候直接sdb_test sample.xml sample.sdb就行了,打包器会读取sample.xml,然后生成sample.sdb
在样例xml中SHIM_TAGID是按照路径的形式给出的,因为这个会被打包器替换为最终的TAGID,样例中"../../0/0"的意思就是表示目标“目录”相对于当前“目录”的路径。至于其他的一些条件,比如指定exe的版本之类的,个人还没有测试,不过应该可以用。
打包器和样例:https://github.com/ganboing/sdb_packer
样例shim,可以进行dll注入:https://github.com/ganboing/shim_test
另外一些资料可以
http://bbs.pediy.com/showthread.php?t=175483
http://blogs.msdn.com/b/cjacks/archive/2010/10/20/shims-are-they-really-such-nasty-bits.aspx
http://blogs.technet.com/b/askperf/archive/2011/06/17/demystifying-shims-or-using-the-app-compat-toolkit-to-make-your-old-stuff-work-with-your-new-stuff.aspx
https://www.blackhat.com/docs/asia-14/materials/Erickson/WP-Asia-14-Erickson-Persist-It-Using-And-Abusing-Microsofts-Fix-It-Patches.pdf
http://www.alex-ionescu.com/?p=39
http://recxltd.blogspot.hk/2012/04/windows-appcompat-research-notes-part-1.html
http://wedday.blogspot.co.uk/2008/08/shimeng.html
http://wedday.blogspot.co.uk/2008/08/shimeng.html
