背景
二十多年来,操作系统的发展突飞猛进,windows也走过了各种版本。微软每次都在新版本中加入很多的改进,比如完善某些API,引入新的安全机制(UAC,ASLR,…),调整系统目录结构(Application Data目录 从win98的%windir%/Application Data变成winXp的%USERPROFILE%/Application Data,后又变成Vista的%APPDATA%),这些改动会影响windows的向后兼容性(backward compatibility)。
对于一个开发windows程序的程序员来说,他无法预见未来的windows会作出哪些调整,那么如何保证现在发布的程序也能在未来新版本的windows下运行呢?对于第三方软件商来说,这基本上就是不可能完成的任务。一个在Vista运行得好好的程序很可能在win7下无法运行。因为一个极小的API改动都可能让该程序崩溃。
比如下图,在SDK6.0(Vista)和SDK7.1(win7)下,clusapi.dll 里的CreateCluster() 的参数不一致:新版本的CreateCluster()没有tAcceptPartialFailure这个参数。(如果感兴趣的话,这个链接 http://abi-laboratory.pro/compatibility/提供了更全面的各个版本windows API兼容性的比较。)
假设一个在SDK6.0下编译的程序调用了这个API,代码编译后如下:
Push pvCallbackArg
Push pfnProgressCallback
Push tAcceptPartialFailure
Push pConfig
Call CreateCluster
然而win7下,新版clusapi.dll的CreateCluster()并没有tAcceptPartialFailure这个参数,导致该程序多push进一个参数,这会使该函数在运行时发生不可预见的问题。因此,微软每推出一款新版本的windows,开发者原则上理应检查自己的程序是否在新版本windows上存在问题。如果有问题,则进行兼容性修复,比如重写API调用并且重新编译。但是事实上,大多数软件公司并没有兴趣长期维护旧产品,而且有的软件公司已经倒闭,开发团队都散了。这就造成了很多旧软件无法在新windows下运行。
这种情况下用户会责备谁呢?当然是微软,因为这个程序在win7下运行得好好的,为什么win10下就跑不起来了呢?程序不会有问题,一定是win10的问题!这样一来,微软不得不为第三方软件商擦屁股。传说微软在研发win95的时候特意派人去软件专卖店买了一卡车win3.1下的软件,拿回去逐一测试能否在win95下正常运行。如果发现不能正常运行,如何修复呢?
这就要用到本文的主角:Shim。Shim的本意是垫圈或垫片。理论上说,任何的计算机问题都可以新增一层逻辑来解决。Shim也是这么一个垫在应用程序和windowsAPI之间的逻辑层。
Freebuf上有一篇很好的关于shim的简介( http://www.freebuf.com/news/48878.html)。简而言之,当一个应用程序开始运行的时候,Shim把导入地址表(IAT)里的系统DLL库函数地址用ShimDLL里的函数地址来替换,从而可以透明的拦截其API调用,修改其传递的参数等等。这个过程相当于windows自己做了一个IAThooking。
如图所示,一个应用程序在调用Windows API之前,修改了IAT从而被重定向到Shim。
如果你对Shim的细节感兴趣,请参考这两篇对Shim逆向的研究http://blog.csdn.net/celestialwy/article/details/707148
http://bbs.pediy.com/showthread.php?t=175483
当然,并不是所有的程序都会被Shim。Windows自己维护着一个Shim数据库(sdb文件,位于%windir%/AppPatch/sysmain.sdb)。当一个应用程序创建进程的时候,WindowsLoader会检查sysmain.sdb,并判断时否该程序需要被修复,以及如何被修复。比如下图,当我运行test.exe的时候,用processmonitor可以看见系统读取了sysmain.sdb这个文件。
Shim 数据库包含了大约5000多条记录。每个记录对应一个需要被Shim的应用程序。记录的内容分两部分:
1.定义如何识别该应用程序。比如通过文件名识别,或者通过某些文件属性值(版本号,文件大小,等等)来匹配。例如识别大航海时代4就利用了其文件名(dk4.exe,dk4pk.exe,dk4.ico)并且dk4.exe的”product_name”属性必须包含字符串”大航海时代”.
2.定义如何修复该应用程序。比如修改操作系统的版本号,修改API参数,禁用某些功能等等。
做一个小实验,在win7下随便找一个带窗口的可执行文件,把文件名改为”3D Frog Frenzy.exe”。然后运行这个文件,你会发现AltTab没反应了。这是因为Shim起了作用,如下图所示,Shim只要识别了应用程序文件名为”3DFrog Frenzy.exe”,便启动了对应的修复机制,包括忽略AltTab。因此,只要文件名为”3D Frog Frenzy.exe”,AltTab就失效了。
这里推介一款微软的Shim工具: Application Compatibility Toolkit(ACT) (https://www.microsoft.com/en-us/download/details.aspx?id=7352)这个工具可以查看Shim数据库。打开浏览一下,看到一大堆十几年前的经典软件,比如豪杰超级解霸,东方快车。。。
利用Shim的攻击
除了微软自带的Shim数据库以外,用户还可以给任意程序自定义Shim修复方式。这在提高兼容性的同时也引入了一些安全隐患。特别是ACT工具内嵌了800多种修复方式,ACT工具允许用户很容易的给任意程序添加”特殊的”修复方法(大概30秒就可以搞定)。这些”特殊的”修复方法可能被攻击者用来做一些猥琐的事情。用ACT工具自定义Shim的方法如下:
1.先选择一个应用程序
2.然后可以选择修复方式,大概有300多种可供选择,如果启动ACT的时候用了“/x”参数,修复方式会增加到800多种。
Sean Pierce在blackhat 2015总结过一些可以被恶意利用的Shim修复方法:
1.强制关闭进程:”TerminateExe”可以用来强制关闭某些安全程序(还有那些sysinternal的检测工具),相似的还有”IgnoreLoadLibrary”。
2.禁用防御机制:选择”DisableASLR”, ”DisableSeh”等可以禁用掉某些程序的防御机制,从而进一步溢出攻击。
3.系统驻留:用”InjectDLL”, ”LoadLibraryRedirect”等可以把恶意代码注入到一些常用程序,比如WORD.exe。那么每次启动Word.exe,恶意代码会先运行。这种驻留方法比传统的方法(注册表)更不容易被发现。安全检测工具例如Autorun.exe也发现不了。
4.绕过分析:病毒可以把一部分重要代码(比如脱壳解密)放在DLL里,然后设置自定义Shim用”InjectDLL”调入。在这种情况下,简单的把病毒和DLL拷贝到沙盒里运行进行动态分析往往不会成功。因为“InjectDLL”是自定义的Shim,而沙盒上没有设置同样的Shim。
比如我们如果不想让360正常工作,那么可以给360安全中心设置一个自定义的Shim修复,选择QHSafeMain.exe。
选择”RedirectEXE”并且设置命令行参数, 这里我想设置重定向到IE,并打开腾迅的主页。所以命令行参数我填上 ”c:/program files (x86)/Internet Explorer/iexplore.exe www.qq.com”。
保存,并且安装以后,双击右下角托盘里的360图标,360安全中心没有打开,取而代之的是弹出了腾迅的网站。
当然,这种攻击方法也有2个弱点
1.攻击者需要目标主机的管理员权限来安装自定义Shim。
2.Shim只工作于用户模式,因此只能用于设置用户模式的程序。该攻击方式对内核模块无效。
利用shim 缓存进行取证
每次执行应用程序,系统都要根据Shim数据库来检查该应用程序是否需要被Shim。而Shim数据库里有5000+记录,每次都比对会使效率变得很低。所以实际上windows使用了Shim缓存来提高效率。Shim缓存使用了LRU(Leastfrequently used 最近最少使用)淘汰算法,因此最近执行过的程序会出现在Shim缓存里, 下一次运行该程序的时候就不需要比对Shim数据库。避免了频繁比对从而提高了效率。Win7的Shim缓存可以跟踪应用程序的最近修改时间,文件完整路径和执行标志(后面会介绍什么是执行标志)。该缓存放在内存中,所以我们可以用 Volatility的shimcachemem插件来取出信息。
如下图所示。
安全分析人员在病毒取证的时候往往需要知道最近在该系统上执行过哪些程序。这个时候用volatity查看Shim 缓存就可以给我们足够的信息:Shim缓存里的那些程序就是最近执行过的。不过这里还有一个坑。在某些情况下,预览文件夹的时候explore会把该文件夹里没有运行过的程序也加到Shim缓存里。不过在这种情况下,该缓存记录的执行标志是False,而那些实际运行过的程序执行标志为True。比如上图的例子里,Svchost.exe是运行过的,而audiodg.exe并没有真正运行过。
由于内存信息是易失数据,很多时候安全分析人员可能无法得到完整的内存dump。在这个时候我们可以查看注册表里的Shim缓存影像。Win7在关闭或重启的时候会把Shim缓存序列化到注册表的以下位置。
SYSTEM/CurrentControlSet/Control/SessionManager/AppCompatCache/AppCompatCache
该注册表键的记录长度是固定的,为1024。我们可以用ShimCacheParser.py 来读取该信息。值得注意的是由于windows只有在关闭或重启的时候才会序列化Shim缓存,所以此处是上一次windows运行时的Shim缓存。
这里我模拟一个被感染用户,该用户从邮箱下载并运行了一段恶意javascript脚本。 这个脚本会下载一个PE文件,存于临时目录下,然后运行之。重启后,用ShimCacheParser.py ,结果如下图。
根据LFU算法,最近运行的记录排在前面。可以看到Wscript.exe先运行,Wscript.exe是用来解释执行javascript的。然后看到临时目录下的te0Iogn3cxZHWOM.exe运行了。注意到该程序的最近修改时间(Last modified time)为16年8月11日09点35分48秒,就是我点击恶意javascript的时间,说明这个exe文件是我点击javascript的时候新创建的。结合文件路径信息,取证人员可以很容易的发现问题的源头。
参考文献
[1]. windows 的 shim engine分析 http://blog.csdn.net/celestialwy/article/details/707148
[2]. Windows 8的用户模式Shim Engine小探及利用 http://bbs.pediy.com/showthread.php?t=175483
[3]. Fireeye: Shim Shady, https://www.fireeye.com/blog/threat-research/2015/10/shim_shady_live_inv/shim-shady-part-2.html
[4]. Malicious Application Compatibility Shims, Blackhat 2015
原创文章,作者:kepupublish,如若转载,请注明出处:https://blog.ytso.com/56232.html