概念
dll注入是一种将Windows动态链接库注入到目标进程中的技术,具体的说,就是将dll文件加载到一个进程的虚拟地址空间中。对某个进程进行dll注入,也就意味着dll模块与该进程共用一个进程空间,则这个dll文件就有了操纵这个进程空间的能力,以达到执行dll模块中的代码修改进程数据的能力。dll注入技术在逆向工程,病毒,外挂,调试等技术领域都有广泛的应用,它也是Windows API hook技术的基础。
什么是DLL
dll文件是Windows操作系统下的动态链接库,动态链接库的使用及其广泛,它解决了一些静态链接带来的缺点,如内存空间冗余,文件大小臃肿等问题。通常在没有特意设置的情况下,对源代码进行编译的结果默认是动态链接的,在Windows操作系统中,如kernel32.dll,ntdll.dll,user32.dll几乎是必用的动态库。
dll文件本质上也是PE文件,基本的文件结构和可执行文件是类似的。对开发者来说,dll文件提供了一系列导出函数来使用,可以用显式的方式在程序中加载动态库,也可以在编译环境中提前设置好。而dll注入技术则是将未加载的外部dll模块加载到正在运行的进程当中,当我们无法重新编译某个程序,却又想操纵其进程空间时,就可以用到dll注入技术。
如何实现
由于要对进程级别的对象进行操纵,最自然的方式就是利用Windows API。Windows API是Windows系统为开发者提供的一系列接口,每个接口可以看作是操作系统提供的某项服务。不过Windows API在如今看来有许多明显的缺点,主要是设计方面的问题,所以在使用上可能不像一些拥有良好接口的函数库方便和易用,不过遇到问题多查查手册就好了。dll注入可以使用多种不同的手段达成,但这些手段归恨到底都是让目标进程加载目的dll文件。以下是非常经典的两种注入方式。
全局钩子注入
原理:全局钩子注入实质上是利用了操作系统的加载dll文件的特性来达到注入效果的,即当一个dll模块已经存在于内存当中时,且某个进程对它的函数进行了调用,系统就会将这个dll模块加载到该进程空间当中。Windows提供了一个钩子机制,它可以截获和监视系统中的Windows消息,只要熟悉Windows的都知道,Windows的绝大部分应用程序都是基于消息机制的,而消息钩子每次截获到对应消息时,都可以进行一些处理,这个处理发生在一个回调函数中,也就是说,我们只需要将这个回调函数设置在dll文件中,当钩子截获到其他进程的消息时,操作系统就会调用这个回调函数,于是dll文件也就被注入到了该进程之中。
实现:首先要制作dll文件,主要的功能都包含在这个dll文件之中,包括设置钩子,取消钩子以及钩取后的处理函数。
#include
BOOL SetGlobalHook();
BOOL UnsetGlobalHook();
LRESULT GetMsgProc(int, WPARAM, LPARAM);
#pragma data_seg("my_data")
HHOOK g_hHook = NULL; //全局钩子句柄
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
//由于钩子必须全局的,所以要特别设置一个共享数据段用来存放全局钩子句柄,使得其他进程空间也有权限访问。
HMODULE g_hDllModule = NULL;
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) //回调函数
{
return ::CallNextHookEx(g_hHook, code, wParam, lParam); //传递钩子
}
BOOL SetGlobalHook() //设置钩子
{
g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
if (g_hHook == NULL)
{
return FALSE;
}
return TRUE;
}
BOOL UnsetGlobalHook() //取消钩子
{
if (g_hHook)
{
::UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) //入口点
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
{
g_hDllModule = hInstDll;
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
这里定义的设置钩子和取消钩子的两个函数仅仅是对SetWindowsHookEx和UnhookWindowsHookEx进行了封装,添加了一点条件判断。核心功能在dll文件的代码中已经完成了,剩下的就是启动程序了,这个启动程序应该加载上述dll文件,并调用该dll文件的接口来设置全局钩子,这之后,一旦有进程发送消息到消息队列,dll模块就会注入到该进程,启动代码如下:
#include
#include
int main(int argc, TCHAR* argv[])
{
typedef BOOL(*pfSetGlobalHook)();
typedef BOOL(*pfUnsetGlobalHook)();
HMODULE hDll = NULL;
pfSetGlobalHook SetGlobalHook = NULL;
pfUnsetGlobalHook UnsetGlobalHook = NULL;
BOOL ret;
do {
hDll = ::LoadLibrary("GlobalHook_dll.dll");
if (hDll == NULL)
{
std::cout << "LoadLibrary wrong!" << std::endl;
break;
}
SetGlobalHook = (pfSetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");
if (SetGlobalHook == NULL)
{
std::cout << "get SetGlobalHook wrong!" << std::endl;
break;
}
ret = SetGlobalHook();
if (ret)
std::cout << "set hook ok!" << std::endl;
else
std::cout << "set hook wrong!" << std::endl;
system("pause");
UnsetGlobalHook = (pfUnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");
if (UnsetGlobalHook == NULL)
{
std::cout << "get UnsetGlobalHook wrong!" << std::endl;
break;
}
ret = UnsetGlobalHook();
if (ret)
std::cout << "unset hook ok!" << std::endl;
else
std::cout << "unset hook wrong!" << std::endl;
} while (FALSE);
return 0;
}
远线程注入
相较于全局钩子注入,远线程注入更灵活,也更符合我们的逻辑,可以说大多数dll注入都是使用的这种方法。远线程注入利用了几个关键的Windows API,分别是OpenProcess,VirtualAlloc,WriteProcessMemory,CreateRemoteThread。大概的流程或逻辑如下:
具体代码如下:
BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, const char *pszDllFileName)
{
HANDLE hProcess = NULL;
DWORD dwSize = 0;
LPVOID pDllAddr = NULL;
FARPROC pFuncProcAddr = NULL;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL)
{
ShowError("OpenProcess Wrong!");
return FALSE;
}
dwSize = 1 + strlen(pszDllFileName); //考虑'\0'字符结尾
pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (pDllAddr == NULL)
{
ShowError("memory alloc wrong!");
return FALSE;
}
if (!WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
{
ShowError("write data to process wrong!");
return FALSE;
}
pFuncProcAddr = GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (pFuncProcAddr == NULL)
{
ShowError("get function address wrong!");
return FALSE;
}
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
if (hRemoteThread == NULL)
{
ShowError(("create remote thread wrong!");
return FALSE;
}
::CloseHandle(hProcess);
return TRUE;
}
本质上就是纯粹的API调用,没什么可说的。