为何要把DLL搜索路径(DLL ORDER)和DLL劫持(DLL Hajack)拿到一块儿讲呢?呵呵,其实没啥深意,仅仅是两者有因果关系而已。能够讲正是由于Windows系统下面DLL的搜索路径存在的漏洞才有了后来的一段时间的DLL劫持大肆流行。ios
最近(其实不是最近,哈,是之前分析过,断断续续的……)简单分析了一个DLL劫持下载者的行为,感受有必要写点东西说明一下。其实DLL劫持是比较好预防的,从编程规范上咱们能够进行规避(后面会专门讲到),从实时防御的角度来说咱们也能够想出一些办法进行拦截。新的DLL劫持者基本都是经过当前路径来入侵,一些老的DLL劫持者通常都是经过exe的安装目录来入侵的,为何会这样,后面还会讲到。编程
要搞清DLL劫持的原理,首先要搞清DLL搜索路径,到哪去搞清?固然是问微软啦!MSDN上面有一篇专门讲DLL搜索顺序的文章(Dynamic-Link Library Search Order http://msdn.microsoft.com/en-us/library/ms682586%28VS.85%29.aspx),虽然是英文的可是并不复杂讲得很清楚,你们若是对这块兴趣大的话能够仔细研读下,我就不翻译了。安全
Dynamic-Link Library Search Order里面主要讲到一个安全DLL搜索模式的问题,你们能够经过下面的表格来看一下不一样系统对安全DLL搜索模式的支持状况(下表中用SDS表明安全DLL搜索模式):网络
注:在vista和win7下没有作过实验,有兴趣的能够本身作作实验。app
注:上面说到经过注册表开启是指将HKLM\System\CurrentControlSet\Control\Session Manager键值下的属性SafeDllSearchMode的值设置为1(若是没有SafeDllSearchMode就本身手动建立)。函数
在安全DLL搜索模式开启的状况下,搜索顺序是:测试
一、应用程序EXE所在的路径。编码
二、系统目录。spa
三、16位系统目录插件
四、Windows目录
五、当前目录
六、PATH环境变量指定的目录
若是安全DLL搜索模式不支持或者被禁用,那么搜索顺序是:
一、应用程序EXE所在的路径。
二、当前目录
三、系统目录。
四、16位系统目录
五、Windows目录
六、PATH环境变量指定的目录
说了这么多?咱们怎么校验本身的系统的DLL的搜索顺序呢?实际上是很简单的,咱们首先构造两个简单的程序,一个DLL程序一个EXE程序,代码很简单,以下:
DLL程序:
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
char szDllPath[MAX_PATH] = {0};
GetModuleFileNameA(hModule, szDllPath, MAX_PATH);
cout << "DLL PATH: " << szDllPath << endl;
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
default:
break;
}
return TRUE;
}
EXE程序:
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE hDll = LoadLibrary(TEXT("DLLHijackSO.dll"));
if (!hDll)
{
cout << "DLLHijackSO.dll Load Faild!" << endl;
}
return 0;
}
够简单了吧,思路也很明确,在DLL的DLLMAIN里面会打印出DLL所在的路径,在EXE程序里面经过DLL的名字(不是全路径)去加载这个DLL,若是加载失败会打印出一条加载失败信息。而后根据上面提到的6个地方,分别放一个DLL程序编译出来的DLL(我起的名字是DLLHijackSO.dll),EXE编译出来的DLLHijackApp.exe是放在H:\Prj_N\DLLHijackSO\Release里面的,而后把cmd启动,cmd启动以后,它的当前路径通常都是设置的用户目录,譬如个人机器上面就是C:\Documents and Settings\magictong,经过CD命令对当前文件夹的切换,当前路径也随之改变。实验的基本过程,由于整个系统放置了6个DLLHijackSO.dll,每运行DLLHijackApp.exe一次,若是成功加载DLL,那么就把加载的那个DLL删除,持续进行,直到加载失败。好,实验开始……
个人系统的六个位置(其中最后一个PATH变量指定的路径,你选取一个就能够了):
H:\Prj_N\DLLHijackSO\Release
C:\WINDOWS\system32
C:\WINDOWS\system
C:\WINDOWS
C:\Documents and Settings\magictong
C:\Python25
实验结果如图:
根据结果,我想已经很明确了,个人系统是启用了安全DLL搜索模式的,由于个人系统是XPSP3。另外就是关于当前路径的问题,其实当前路径是能够由进程的父进程来设置的,你们能够去看CreateProcess里面的参数,有一项是设置当前路径的,也就是为何CMD启动你的进程的时候,当前路径会在“你想不到的地方”,explorer启动进程则是把当前路径设置为应用程序所在的路径(当前路径能够经过GetCurrentDirectory这个API来获取)。
我想若是DLL搜索路径搞清楚了,DLL劫持的原理就很简单了。我的以为一两句话就能够说清楚:在进行DLL加载时,若是不是绝对路劲,系统会按照DLL的搜索路径依次进行目标DLL的搜索直到找到目标DLL或者加载失败,若是你在真实的DLL被找到以前的路径放入你的劫持(同名)DLL,那么应用程序将先加载到你的DLL,这样就是DLL劫持的过程。
原理虽然简单,你的劫持DLL的选取和编写则要有些技巧,不是全部的DLL均可以被劫持的,有些DLL是受系统保护的,譬如user32.dll等,这些是不能劫持的。在一些老的DLL劫持病毒里面通常是选取usp10.dll,lpk.dll等,缘由很简单,通常的应用程序都会加载它们,并且没有被系统保护(好很差用,谁用谁知道,我反正没用过)。
首先简单总结下DLL劫持和DLL注入的区别:
下面讲一下新老两种DLL劫持的攻击原理和防护方案:
以前提到过一种老的DLL劫持的利用,劫持usp10.dll,lpk.dll等等。这些DLL的实际目录在system32下,病毒利用DLL的搜索排名第一的是应用程序自身所在的目录,释放同名的劫持DLL到应用程序目录下,这样应用程序启动时就会先加载了劫持DLL,达到不可告人目的。应用程序加载了劫持DLL以后又有两种后续的攻击方案,一种是转发调用到正常的DLL,使应用程序毫无觉察,同时秘密在后台下载更多的下载器木马等。另外一种就是直接破坏形成程序没法运行,这种主要用于干掉杀软等安全软件。
通用免疫方案:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs]在此注册表项下定义一个“已知DLL名称”,那么凡是此项下的DLL文件就会被禁止从EXE自身目录下调用,而只能从系统目录,也就是system32目录下调用。根据这个方案是否是能够写一个简单的DLL劫持免疫器了呢?固然对于一个安全软件来讲,这个地方也是要保护的地方之一。下图是我机器上面该键值下面的一些DLL名称:
以前看过讲微点的一篇文章,讲的是对这种老的DLL劫持实行的一种拦截方案:若是发现一个系统下的敏感DLL被加载,则经过堆栈回溯找到该敏感DLL的加载者的地址,若是是在一个同名的DLL里面则认为是被DLL劫持了,报告发现病毒。从对抗的角度来说,这种方案很容被过掉,只要修改栈帧指针可能就发现不了了。
新的DLL劫持的攻击原理和防护方案:
不少应用程序都是支持插件和扩展的,尤为是一些播放器软件,支持解码插件,第一次安装的时候可能只装了常见的一些音视频解码插件,在遇到一些特殊的音视频格式时就须要实时去网络上拖取一个解码插件下来进行解码操做,固然软件会首先尝试加载这个解码插件(一般是一个DLL),这个时候一些设计有缺陷的产品(譬如不是经过绝对路径加载插件)在加载DLL时就会搜索上面提到过的各个路径。通常这种状况下,恶意攻击者会在网络上提供一些用户感兴趣名字的视频图片神马的,用户下载压缩包解压后,其实压缩文件中包含着两个以上的文件,用户很难发现,解压后,劫持DLL和视频或者图片文件是放在同一个目录的,固然劫持DLL文件的属性是系统隐藏,而后用户高高兴兴的去双击那个视频或者图片文件,杯具发生了……这其实是利用的DLL搜索时会搜索当前目录这个特色来进行DLL劫持的,为何当前目录是视频图片文件存放的目录呢?
这个能够作个实验,与文件关联有关,在注册表里面注册一个._magic后缀的文件类型,打开这种文件的应用程序是c盘下的DLLHijackApp.exe(文件关联这块有兴趣的本身能够查资料,由于与本文关系不大就不细讲了,见下图),DLLHijackApp.exe的做用就是弹出一个MessageBox打出当前目录,若是直接双击运行DLLHijackApp.exe,当前目录就是c盘,若是在桌面上新建一个x._magic文件再双击运行,打印出的当前目录则是桌面目录(也就是x._magic文件所在的目录,见下面的图)。
直接双击运行C盘下的DLLHijackApp.exe,当前目录:
双击打开桌面上的x._magic的文件,当前目录:
如今你们应该明白为何当前目录是文件所在的目录了吧。病毒正是利用这一点,把劫持DLL和音视频,图片文件捆绑在一块儿下载,达到入侵的目的。
防护方案:
暂无通用的防护方案,由于劫持的都是一些第三方的DLL,暂时只能经过下载保护之类的途径进行保护(这类攻击最终仍是会转去下载更多的盗号木马或者下载器之类的,而后进行一些盗号、破坏等等的事情)。
讲了这么多,来看一个DLL劫持的实例,是简单写的一个说明原理的小例子:
劫持DLL要保证应用程序运行正常,不被用户发现,除了和原来的DLL有相同的名字以外还须要导出和原DLL同样的函数。咱们如今已经有了一个名字是DLLHijackSO.dll的DLL,他导出一个Add函数,这个函数原型是int Add(int a, int b),很简单吧。假设这个DLL是一个系统DLL,是放在system32目录下。
原DLL的代码以下(Add函数经过def文件导出):
// DLLHijackSO.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include <iostream>
using namespace std;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
char szDllPath[MAX_PATH] = {0};
GetModuleFileNameA(hModule, szDllPath, MAX_PATH);
cout << "ORC DLL - DLL PATH: " << szDllPath << endl;
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
default:
break;
}
return TRUE;
}
int Add(int a, int b)
{
return a + b;
}
咱们的应用程序的代码以下(名字是DLLHijackApp.exe,加载这个DLL,而后调用它的Add函数):
// DLLHijackApp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
SetDllDirectoryW(TEXT(""));
HMODULE hDll = LoadLibrary(TEXT("DLLHijackSO.dll"));
if (!hDll)
{
cout << "DLLHijackSO.dll Load Faild!" << endl;
}
else
{
typedef int (*PFUNADD)(int , int );
cout << "App - Add(int a, int b)" << endl;
HMODULE hMod = LoadLibrary(TEXT("DLLHijackSO.dll"));
if (hMod)
{
PFUNADD pfnAdd = (PFUNADD)GetProcAddress(hMod, "Add");
cout << "App 1022 + 1022 = " << pfnAdd(1022, 1022) << endl;
}
}
if (hDll)
{
FreeLibrary(hDll);
hDll = NULL;
}
char szCurrentDir[MAX_PATH] = {0};
GetCurrentDirectoryA(MAX_PATH, szCurrentDir);
MessageBoxA(NULL, szCurrentDir, "当前目录", MB_OK);
cout << "CUR PATH: " << szCurrentDir << endl;
return 0;
}
咱们先测试一下,把DLLHijackSO.dll放入system32下,而后应用程序DLLHijackApp.exe放在任意位置,运行结果以下:嗯,是没有问题的。
如今咱们写劫持DLL,其实也很简单,它编译出来的DLL名字也是DLLHijackSO.dll,也经过def文件导出了Add函数:
// DLLHijackHijack.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include <iostream>
#include <tchar.h>
using namespace std;
namespace DLLHijackName
{
HMODULE m_hModule = NULL; //原始模块句柄
// 加载原始模块
inline BOOL WINAPI Load()
{
TCHAR tzPath[MAX_PATH] = {0};
GetSystemDirectory(tzPath, MAX_PATH);
_tcsncat_s(tzPath, MAX_PATH, TEXT("\\DLLHijackSO.dll"), _TRUNCATE);
m_hModule = LoadLibrary(tzPath);
if (!m_hModule)
{
cout << "没法加载DLLHijackSO.dll,程序没法正常运行。" << endl;
}
return (m_hModule != NULL);
}
// 释放原始模块
inline VOID WINAPI Free()
{
if (m_hModule)
FreeLibrary(m_hModule);
}
// 获取原始函数地址
FARPROC WINAPI GetOrgAddress(PCSTR pszProcName)
{
FARPROC fpAddress;
if (m_hModule == NULL)
{
if (Load() == FALSE)
ExitProcess(-1);
}
fpAddress = GetProcAddress(m_hModule, pszProcName);
if (!fpAddress)
{
cout << "没法找到函数,程序没法正常运行。" << endl;
ExitProcess(-2);
}
return fpAddress;
}
}
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
char szDllPath[MAX_PATH] = {0};
GetModuleFileNameA(hModule, szDllPath, MAX_PATH);
cout << "DLL Hijack - DLL PATH: " << szDllPath << endl;
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
DLLHijackName::Free();
break;
default:
break;
}
return TRUE;
}
typedef int (*PFUNADD)(int , int );
int Add(int a, int b)
{
cout << "DLL Hijack - Add(int a, int b)" << endl;
PFUNADD pfnAdd = (PFUNADD)DLLHijackName::GetOrgAddress("Add");
if (pfnAdd)
{
return pfnAdd(a, b);
}
return 0;
}
好,如今咱们把这个劫持的DLLHijackSO.dll放在DLLHijackApp.exe同目录下,运行:
劫持成功!
固然,咱们的重点仍是要放在避免咱们编写的软件被DLL劫持,通常有如下一些针对DLL劫持的安全编码的规范(其实你们也应该能够从上述的DLL劫持的原理本身总结出来)::
1)调用LoadLibrary,LoadLibraryEx,CreateProcess,ShellExecute等等会进行模块加载操做的函数时,指明模块的完整(全)路径,禁止使用相对路径(这样基本就能够防死上面所讲的第二种DLL劫持了)。
2)在应用程序的开头调用SetDllDirectory(TEXT(""));从而将当前目录从DLL的搜索列表中删除,也就是搜索时不搜索当前目录。
3)打上最新的系统补丁,确保安全DLL搜索模式是开启状态。
4)对于安全软件来说要确保用户的机器上面的KnownDLLs下是完整的。
5)DLL的重定向等须要注意的问题。---------------------