动态链接库的静态链接导致程序的DLL劫持漏洞
借助QQ程序xGraphic32.dll描述
不想啰嗦这么多了,直接开题。
一、 库
首先明确一下库的概念,库里存放的都是二进制编码。纵观编程技术的发展路线,可以看到一条清晰的发展脉络:代码>静态库>动态库。
假如我们要编写一个程序叫做Calc.exe,而现在有现成的库,库里面存放的是已经编译好的函数Add(),Sub()以及其他相关的符号等等,并且静态库(calcfun.lib)和动态库(calcfun.dll)各有一个版本。
那么我们就只需要编码Calc.exe的主程序,在其中使用库中的函数(可以导出函数类常量等待这些统称为符号),而不需要在编码实现这些函数。
A. 使用静态库
静态库只有一个lib文件calcfun.lib(忽略导出函数声明calcfun.h文件),这个lib文件里面包含的就是二进制编码。在链接期间,链接器将会抽出库中我们所引用的符号的二进制编码,并且把这些二进制编码合并到生成的Calc.exe中,最终我们得到的程序就只有并且只需要一个Calc.exe就可以运行(这里忽略了操作系统系统所需的库)。
B. 使用动态库
标准的动态库一般有两个文件calcfun.dll,calcfun.lib(忽略导出函数声明calcfun.h文件),不要混淆这个lib文件与上述的静态库lib文件,这个lib文件准确的名称是到导入库,包含的是dll中导出符号的地址表,只是提供给链接器定位dll中符号之用,并不是二进制代码,真正的二进制代码存在于calcfun.dll中。为什么会有两个文件?因为对于动态链接库的使用方法可以有两种:静态链接(隐式链接),动态链接(显示链接)。
1) 静态链接(隐式链接)
也只有使用这种方式的时候才会用到动态库的lib文件,即导入库calcfun.lib,这种方式的效果是在生成的calc.exe的PE头部的导入表中添加了一IMAGE_IMPORT_DESCRIPTOR,并且根据程序中所引用的符号写入了相应的IMAGE_THUNK_DATA,最终生成的calc.exe若要能正确运行必须依赖于calcfun.dll。这种链接方式的实现是设置链接器参数,比如vc的链接器可以直接把倒入库当作参数传递给链接器link.exe,也可以使用预编译处理:#pragma comment(lib, "calcfun.lib")
2) 动态链接(显示链接)
使用这种链接方式只需要一个dll文件,lib文件就是多余的了,因为dll本身也包含自身导出符号的地址表,所以只需要把这个dll载入到我们的程序的地址空间,然后搜索到我们需要使用的函数或者其他符号的地址就可以了。这种链接方式不会添加信息到生成的calc.exe的PE头部。生成的calc.exe若要能正确云清必须依赖于calcfun.dll。这种链接方式的实现是通过LoadLibrary()载入dll模块,通过GetProcessAddress()来搜索到目标符号的地址,通过FreeLibrary()卸载dll模块。
3) 载入时机
动态库除了上述使用方式不同之外,还有就是真正载入的时机。编译是我们来做的,而运行时的各种工作都是由操作系统来做的。静态链接生成的exe中的PE头部的导入表里包含了所需的dll信息,所以程序Loader会读取导入表,并加载导入表中的所有的dll(延迟加载除外,本文不讨论)。而动态链接库则是在程序调用LoadLibrary()函数时才会载入指定的dll。
静态库没有动态库常见,所以现在普遍存在于windwos操作系统中的都是动态链接库,从上面的介绍可知道,DLL最终被载入程序的进程空间都是操作系统来完成,那么操作系统如何知道所需的DLL的位置呢?很明显,操作系统需要一套搜索规则来加载动态链接库。
二、 动态链接库(DLL)的搜索顺序
程序可以通过指定全路径或者使用清单等机制来控制从何处加载DLL,如果没有使用这些方法,系统将会进行DLL搜索。
A. 影响搜索的要素
1. 如果一个同名的DLL已经被加载入内存中,系统在解析即将被载入的DLL前只会检查重定向和清单,无论这个DLL在哪一个目录,也就是说系统不会去搜索DLL。
2. 如果一个程序所需的DLL在本机的Known DLLs列表中存在,系统将直接使用这个已知的DLL而不会去搜索DLL。当前系统的Know DLLs列表在注册表中的路径:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\KnownDLLs
3. 如果一个DLL(例如a.dll)依赖于其他的DLL(例如b.dll,c.dll等等),系统将会只按照模块名来搜索依赖的DLL(b.dll,c.dll...),即使第一个DLL(a.dll)是通过全路径加载的,这条规则也适用。
B. 标准的搜索顺序
系统有一套标准的搜索DLL路径的规则,这套规则又分为两种搜索模式,安全搜索模式,非安全搜索模式。安全搜索模式是默认开启的,如果要禁用安全搜索模式,可以在注册表中创建HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager\SafeDllSearchMode键值,并且设置值为0。
1. 安全搜索模式顺序
>1.exe程序的所在目录
>2.系统目录(GetSystemDirectory()获得)
>3.16位系统目录
>4.Windows目录
>5.进程当前目录
>6.系统PATH环境变量中的目录
2. 非安全搜索模式
>1.exe程序的所在目录
>2.进程当前目录
>3.系统目录(GetSystemDirectory()获得)
>4.16位系统目录
>5.Windows目录.
>6.系统PATH环境变量中的目录
所有通过静态链接的动态链接库都遵循以上A.B搜索顺序的规则。
C. 通过LoadLibrary(LPCTSTR lpFileName)或者LoadLibraryEx(LPCTSTRlpFileName, …)函数实现的动态链接,则有一下规则。
1. lpFileName包含文件路径+文件名
则先搜索指定路径的文件,文件存在则函数返回成功,文件不存在函数返回失败。
2. lpFileName不包含文件路径+文件名
函数将转而使用系统的标准搜索路径
关于动态链接库的搜索顺序的更多详细资料请参阅http://msdn.microsoft.com/en-us/library/ms682586%28VS.85%29.aspx
三、 QQ程序存在的一个DLL劫持利用漏洞
这个漏洞应该是QQ2009版本之后到目前为止一直存在的漏洞。漏洞的形成原因是QQ的界面引擎GF.dll引入的。
先来说一下MSIMG32.DLL这个文件,这个文件是windows GDI函数簇的一个库其中只有四个函数
存在于系统目录C:\windows\system32\msimg32.dll。
我们用Dependency Walker查看一下QQ.exe启动的时候,存在的DLL依赖关系:
QQ.exe ->GF.dll -> xGraphic32.dll -> Msimg32.dl。
在这里可能有人会说,从那个带箭头的图标来看Msimg32显示是已经被加载过了。有这个疑问是正常的,我们可以看到另一条依赖关系链:QQ.exe -> User32.dll->Msimg32.dl。
乍一看没有问题,不存在劫持的可能:有这样一个规则,Windows在加载时读取exe的导入表,然后加载依赖模块,但是无论导入表的顺序如何,只要导入表中存在的dll并且在Knows Dll列表中存在,那么系统就会优先加载这些模块,User32.dll是肯定存在与Knows Dll列表中的,那么User32.dll肯定先于GF.dll的加载,这样一来,系统就会直接加载系统目录下的User32.dll,User32.dll又会从自己所在目录加载Msimg32.dll(即系统的原始msimg32.dll),那么后面再加载GF->xGraphic32.dll->Msimg32.dll就会直接返回之前加载过的木块句柄,这样看就不存在Msimg32.dll被劫持的可能了。
不过,读者应该看到User32.dll中的msimg32.dll前面的图标显示是延迟加载的!所以只要QQ.exe在加载xGraphic32.dll之前不调用msimg32.dll中导出的任何函数,这个模块就不会被加载,所以最终第一次加载msimg32.dll应该是在xGraphic32.dll中。那么QQ程序中到底是如何呢?我们可以用Process Monitor来验证一下!
设置Process Monitor的过滤选项。然后启动QQ.EXE
在QQ启动完成显示出登录窗口后产看“文件操作”捕获结果,搜索msimg32
发现,第一次加载确实实在xGraphic32.dll中!
真相:QQ.EXE中第一次加载msimg32.dll是在一个非系统模块中,并且msimg32.dll没有被微软列为KnownDlls。所以如果第三方软件开发者实现了一个模块,并且在自己的模块中静态链接了msimg32.dll的话,那么就留下了一个可以利用系统搜索DLL顺序来进行DLL劫持的漏洞。很不幸,GF的依赖模块xGraphic32.dll就是这样一个模块。
结合上述标准搜索顺序,当加载xGraphic32.dll模块的时候依赖msimg32.dll,然后系统先搜索QQ.exe所在目录,如果不存在则搜索则继续按照规则搜索,最终能搜索到系统目录下的原始的msimg32.dll。但是,如果有人通过DLL导出符号转发器实现了一个伪造的msimg32.dll放置于QQ.exe的目录中会有什么后果呢?无论有没有开启安全搜索,系统总是会第一个搜索QQ.exe所在目录,从而把伪造的DLL加载到QQ.exe的进程空间,而msimg32.dll的DllMain中可以做任何想做的事,破坏QQ程序已经危害不小,如果做成病毒,木马,后门的话,问题就不是一般的严重了。
解决方法:
1. 可以看到xGraphic32.dll中只是用了msimg32.dll导出的一个函数AlphaBlend,所以可以尝试将用到这个函数的地方自己实现,不过这个基于效率来说,这个方案不是很可行。
2. 重写xGraphic32.dll,改用显式的动态链接,指定全路径,这个好像是目前来说最好的解决方案了。
3. 让微软把Msimg32.dll添加到Known Dlls列表中,这个是最最完美的解决方案,但是需要去和微软沟通,希望下次看到系统的SP补丁包的时候会实现这个想法。
4 补充一个方法,在QQ.exe中判断当前目录是否存在msimg32.dll文件,如果存在就报错,不存在正常启动。虽然这个方法比较笨,但是确实很有效的方法,但是如果QQ.exe被逆向的话,找到判断代码段并且修改的话,仍然能继续劫持,不过这样做的话就要承担法律风险责任了。
四、 补充话题:DLL劫持
可以回顾一下曾经比较实用的WS2_32.dll一度成为DLL劫持的不二对象,之前微软没有把它列为Known Dlls,虽然说这个DLL中函数比较多,但是如果想实行劫持的话,只需要将目标exe所引入的函数转发就可以了。不过并不是所有的程序都需要调用这个动态链接库的,因为他是Windows socket函数簇的模块。但是msimg就不一样了,因为User32.DLL也引用了该函数,所以如果没有把所有函数都转发的的话,可能引起错误,好在Msimg32.dll只有四个函数,毫不费力就可以完全转发,但是这恰恰是一个严重的漏洞产生原因。