DLL注入和API Hook
第一篇 dll注入
一、什么是dll注入
在Windows操作系统中,运行的每一个进程都生活在自己的程序空间中,每一个进程都认为自己拥有整个机器的控制权,每个进程都认为自己拥有计算机的整个内存空间,这些假象都是操作系统创造的。理论上而言,运行在操作系统上的每一个进程之间都是互不干扰的,即每个进程都会拥有独立的地址空间。比如说进程B修改了地址为0x400000的数据,那么进程C的地址为0x400000处的数据并未随着B的修改而发生改变,并且进程C可能并不拥有地址为0x400000的内存(操作系统可能没有为进程C映射这块内存)。因此,如果某进程有一个缺陷覆盖了随机地址处的内存(这可能导致程序运行出现问题),那么这个缺陷不会影响到其他进程所使用的内存。
正是由于进程的地址空间是独立的,因此我们很难编写能够与其它进程通信或控制其它进程的应用程序。
所谓的dll注入即是让程序A强行加载你给定的a.dll并执行你给定的a.dll里面的代码。注意,你所给定的a.dll原先并不会被程序A加载,但是当你向程序A注入了a.dll后,程序A将会执行a.dll里的代码,这个时候,你的a.dll就进入了程序A的地址空间,你就可以为所欲为了。
二、什么时候需要dll注入
应用程序一般会在以下情况使用dll注入技术来完成某些功能:
1.为被注入的进程添加新的“实用”功能;
2.需要一些手段来辅助调试被注入dll的进程;
3.为被注入的进程安装钩子程序(API Hook);
三、dll注入的方法
一般情况下有如下dll注入方法:
1.修改注册表来注入dll;
2.使用CreateRemoteThread()函数对运行中的进程注入dll;
3.使用SetWindowsHookEx函数对应用程序挂钩(HOOK)迫使程序加载dll;
4.替换应用程序一定会使用的dll;
5.把dll作为调试器来注入;
6.修改被注入进程的exe的导入地址表;
以上这些方法各有各的应用范围也各有各的局限性,接下来将详细介绍如何使用这几种方式完成dll注入。
四、注入方法详解
(一)、修改注册表
如果使用过Windows,那么对注册表应该不会陌生。整个系统的配置都保存在注册表中,我们可以通过修改其中的设置来改变系统的行为。
首先我们打开注册表并定位到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows项,如下图所示,他显示了该注册表项中的条目。
AppInit_DLLs键的值可以是一个dll的文件名或一组dll的文件名(通过逗号或空格来分隔),由于空格是用来分隔文件名的,因此dll文件名不能含有空格。第一个dll的文件名可以包含路径,但其他的dll包含的路径将被忽略。
LoadAppInit_DLLs键的值表示AppInit_DLLs键是否有效,为了让AppInit_DLLs键的值有效,我们需要将LoadAppInit_DLLs的值设置为1。
这两个键值设定后,当应用程序启动并加载User32.dll时,会获得上述注册表键的值,并调用LoadLibrary来调用这些字符串中指定的每一个dll。这时每个被载入的dll可以完成相应的初始化工作。但是需要注意的是,由于被注入的dll是在进程生命期的早期被载入的,因此这些dll在调用函数时应慎重。调用Kernel32.dll中的函数应该没有问题,因为Kernel32.dll是在User32.dll载入前已被加载。但是调用其他的dll中的函数时应当注意,因为进程可能还未载入相应的dll,严重时可能会导致蓝屏。
这种方法很简单,只需要在注册表中修改两个键的值即可,但是他有如下缺点:
1.只有调用了User32.dll的进程才会发生这种dll注入。也就是说某些CUI程序(控制台应用程序)可能无法完成dll注入,比如将dll注入到编译器或链接器中是不可行的;
2.该方法会使得所有的调用了User32.dll的程序都被注入指定的dll,如果你仅仅想对某些程序注入dll,这样很多进程将成为无辜的被注入着,并且其他程序你可能并不了解,盲目的注入会使得其他程序发生崩溃的可能性增大。
3.这种注入会使得在应用程序的整个生命周期内被注入的dll都不会被卸载。注入dll的原则是值在需要的时间才注入我们的dll,并在不需要时及时卸载。
(二)、使用CreateRemoteThread()函数对运行中的进程注入dll
CreateRemoteThread函数的原型如下:
HANDLE WINAPI CreateRemoteThread( _In_ HANDLE hProcess, _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_ LPDWORD lpThreadId );
该函数与CreateThread仅仅只多出第一个参数hProcess,hProcess表示创建的新线程属于哪一个进程。
参数lpStartAddress表示线程函数的起始地址,注意这个地址在目标进程的地址空间中。
现在问题来了,我们如何调用让创建的线程执行LoadLibrary函数来加载我们的dll呢?答案很简单:我们只需要让我们创建的线程的线程函数地址是LoadLibrary函数的起始地址即可。我们都知道,每一个线程创建时应该指定一个参数只有4个字节返回值也只是是个字节的函数即可(从汇编的角度看确实如此),而LoadLibrary函数就满足这些条件。LoadLibrary函数的原型如下:
HMODULE WINAPI LoadLibrary( _In_ LPCTSTR lpFileName );
我们发现LoadLibrary函数完全满足上述条件,LoadLibrary的参数是dll路径的起始地址,这个参数也就是CreateRemoteThread函数的lpParameter参数。但是参数指向的地址应该是目标进程的地址,并且该地址处应保存被加载dll的路径字符串。但是一开始我们并不知道目标进程是否存在这样一个地址并且这个地址恰好保存了我们的dll的完整路径。解决这一问题的最保险的办法是使用VirtualAllocEx函数在目标进程中开辟一块内存存放我们的dll的路径。VirtualAllocEx函数的原型如下:
LPVOID WINAPI VirtualAllocEx( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect );
VirtualAllocEx函数允许我们在目标进程中开辟一块指定大小(字节为单位)的内存,并返回这块内存的起始地址。之后我们就可以用WriteProcessMemory函数将dll文件路径的数据复制到目标进程中。WriteProcessMemory函数的原型如下:
BOOL WINAPI WriteProcessMemory( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_ LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesWritten );
在开始注入前,我们还需要确认一件事,就是目标进程使用的字符编码方式。我们所调用的LoadLibrary函数在底层实际调用有两种可能:
如果目标程序使用的是ANSI编码方式,LoadLibrary实际调用的是LoadLibraryA,其参数字符串应当是ANSI编码;
如果目标程序使用的是Unicode编码方式,LoadLibrary实际调用的是LoadLibraryW,其参数字符串应当是Unicode编码;
这样使得我们的注入过程变得很麻烦,为了减少复杂性,我们索性直接使用LoadLibraryA或LoadLibraryW而不是用LoadLibrary函数即可避免这一麻烦。另一方面,即使我们使用的是LoadLibraryA,LoadLibraryA也会将传入的ANSI编码的字符串参数转换成Unicode编码后再调用LoadLibraryW。综上,我们不妨一致使用LoadLibraryW函数,并且字符串用Unicode编码即可。
最后,我们可能会为获得目标进程中LoadLibraryW函数的起始地址而头疼,但起始这个问题也很简单,因为目标进程中函数LoadLibraryW的起始地址和我们的进程中的LoadLibraryW函数的起始地址是一样的。因此我们只需要用GetProcAddress即可获得LoadLibraryW函数的起始地址。
经过以上漫长的分析,我们对CreateRemoteThread注入方法的原理有了较为清晰的理解,接下来我们就需要总结一下我们必须采取的步骤:
(1).用VirtualAllocEx函数在目标进程的地址空间中分配一块足够大的内存用于保存被注入的dll的路径。
(2).用WriteProcessMemory函数把本进程中保存dll路径的内存中的数据拷贝到第(1)步得到的目标进程的内存中。
(3).用GetProcAddress函数获得LoadLibraryW函数的起始地址。LoadLibraryW函数位于Kernel32.dll中。
(4).用CreateRemoteThread函数让目标进程执行LoadLibraryW来加载被注入的dll。函数结束将返回载入dll后的模块句柄
(5).用VirtualFreeEx释放第(1)步开辟的内存.
在需要卸载dll时我们可以在上述第(5)步的基础上继续执行以下步骤:
(6).用GetProcAddress函数获得FreeLibrary函数的起始地址。FreeLibrary函数位于Kernel32.dll中。
(7).用CreateRemoteThread函数让目标进程执行FreeLibrary来卸载被注入的dll。(其参数是第(4)步返回的模块句柄)
或者,我们需要使用如下步骤来卸载dll:
(1).获得目标进程被注入的dll的模块句柄;
执行上述过程的第(6)、第(7)两步。
接下来给出编写的参考代码,该程序以控制台应用程序方式运行,并在win10上测试通过。
#include "windows.h" #include "stdio.h" #include "tlhelp32.h" #include "io.h" #include "tchar.h" //判断某模块(dll)是否在相应的进程中 //dwPID 进程的PID //szDllPath 查询的dll的完整路径 BOOL CheckDllInProcess(DWORD dwPID, LPCTSTR szDllPath) { BOOL bMore = FALSE; HANDLE hSnapshot = INVALID_HANDLE_VALUE; MODULEENTRY32 me = { sizeof(me), }; if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)))//获得进程的快照 { _tprintf(L"CheckDllInProcess() : CreateToolhelp32Snapshot(%d) failed!!! [%d]\n", dwPID, GetLastError()); return FALSE; } bMore = Module32First(hSnapshot, &me);//遍历进程内得的所有模块 for (; bMore; bMore = Module32Next(hSnapshot, &me)) { if (!_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szExePath, szDllPath))//模块名或含路径的名相符 { CloseHandle(hSnapshot); return TRUE; } } CloseHandle(hSnapshot); return FALSE; } //向指定的进程注入相应的模块 //dwPID 目标进程的PID //szDllPath 被注入的dll的完整路径 BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath) { HANDLE hProcess = NULL;//保存目标进程的句柄 LPVOID pRemoteBuf = NULL;//目标进程开辟的内存的起始地址 DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);//开辟的内存的大小 LPTHREAD_START_ROUTINE pThreadProc = NULL;//loadLibreayW函数的起始地址 HMODULE hMod = NULL;//kernel32.dll模块的句柄 BOOL bRet = FALSE; if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))//打开目标进程,获得句柄 { _tprintf(L"InjectDll() : OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError()); goto INJECTDLL_EXIT; } pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);//在目标进程空间开辟一块内存 if (pRemoteBuf == NULL) { _tprintf(L"InjectDll() : VirtualAllocEx() failed!!! [%d]\n", GetLastError()); goto INJECTDLL_EXIT; } if (!WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL))//向开辟的内存复制dll的路径 { _tprintf(L"InjectDll() : WriteProcessMemory() failed!!! [%d]\n", GetLastError()); goto INJECTDLL_EXIT; } hMod = GetModuleHandle(L"kernel32.dll");//获得本进程kernel32.dll的模块句柄 if (hMod == NULL) { _tprintf(L"InjectDll() : GetModuleHandle(\"kernel32.dll\") failed!!! [%d]\n", GetLastError()); goto INJECTDLL_EXIT; } pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");//获得LoadLibraryW函数的起始地址 if (pThreadProc == NULL) { _tprintf(L"InjectDll() : GetProcAddress(\"LoadLibraryW\") failed!!! [%d]\n", GetLastError()); goto INJECTDLL_EXIT; } if (!CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL))//执行远程线程 { _tprintf(L"InjectDll() : MyCreateRemoteThread() failed!!!\n"); goto INJECTDLL_EXIT; } INJECTDLL_EXIT: bRet = CheckDllInProcess(dwPID, szDllPath);//确认结果 if (pRemoteBuf) VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE); if (hProcess) CloseHandle(hProcess); return bRet; } //让指定的进程卸载相应的模块 //dwPID 目标进程的PID //szDllPath 被注入的dll的完整路径,注意:路径不要用“/”来代替“\\” BOOL EjectDll(DWORD dwPID, LPCTSTR szDllPath) { BOOL bMore = FALSE, bFound = FALSE, bRet = FALSE; HANDLE hSnapshot = INVALID_HANDLE_VALUE; HANDLE hProcess = NULL; MODULEENTRY32 me = { sizeof(me), }; LPTHREAD_START_ROUTINE pThreadProc = NULL; HMODULE hMod = NULL; TCHAR szProcName[MAX_PATH] = { 0, }; if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID))) { _tprintf(L"EjectDll() : CreateToolhelp32Snapshot(%d) failed!!! [%d]\n", dwPID, GetLastError()); goto EJECTDLL_EXIT; } bMore = Module32First(hSnapshot, &me); for (; bMore; bMore = Module32Next(hSnapshot, &me))//查找模块句柄 { if (!_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szExePath, szDllPath)) { bFound = TRUE; break; } } if (!bFound) { _tprintf(L"EjectDll() : There is not %s module in process(%d) memory!!!\n", szDllPath, dwPID); goto EJECTDLL_EXIT; } if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { _tprintf(L"EjectDll() : OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError()); goto EJECTDLL_EXIT; } hMod = GetModuleHandle(L"kernel32.dll"); if (hMod == NULL) { _tprintf(L"EjectDll() : GetModuleHandle(\"kernel32.dll\") failed!!! [%d]\n", GetLastError()); goto EJECTDLL_EXIT; } pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "FreeLibrary"); if (pThreadProc == NULL) { _tprintf(L"EjectDll() : GetProcAddress(\"FreeLibrary\") failed!!! [%d]\n", GetLastError()); goto EJECTDLL_EXIT; } if (!CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL)) { _tprintf(L"EjectDll() : MyCreateRemoteThread() failed!!!\n"); goto EJECTDLL_EXIT; } bRet = TRUE; EJECTDLL_EXIT: if (hProcess) CloseHandle(hProcess); if (hSnapshot != INVALID_HANDLE_VALUE) CloseHandle(hSnapshot); return bRet; } int main() { //InjectDll(6836, L"C:\\a.dll"); EjectDll(6836, L"C:\\a.dll"); return 0; }