22.4 使用远程线程来注入DLL
22.4.1 概述
(1)远程线程注入是指一个进程在另一个进程中创建线程,然后载入我们编写的DLL,并执行该DLL代码的技术。其基本思路是通过CreateRemoteThread创建一个远程线程,并将LoadLibrary函数作为该线程函数来启动线程,同时将Dll文件名作为线程函数的参数传入。大致执过程如下:CreateRemoteThread()→LoadLibrary()→DllMain()。
(2)核心函数:CreateRemoteThread
参数 |
说明 |
HANDLE hProcess |
要创建远程线程的进程句柄。除了这参数外,CreateRemoteThread与CreateThread函数参数含义完全相同! |
PSECURITY_ATTRIBUTES psa |
用于定义新线程的安全属性,这里设为NULL采用默认值即可 |
DWORD dwStackSize |
初始化线程堆栈大小,NULL为默认大小 |
PTHREAD_START_ROUTINE pfnStartAddr |
线程函数的地址,这里传入LoadLibrary函数的地址,但它须在远程进程的地址空间中,因为它是让远程线程调用的。如何获得远程进程中这函数地址,可参考后面内容。 |
PVOID pvParam |
线程函数参数,这里一般是DLL文件名,但这个名称须保存在远程进程的地址空间中,这也是个比较棘手的问题。 |
DWORD fdwCreate |
函数表示创建线程后线程的运行状态 |
PDWORD pdwThreadId |
返回线程ID,不关心可以设为NULL不返回 |
备注:使用这个函数关键要解决三个参数问题:①获得远程线程的进程句柄,而且要确保相应权限(如Debug权限);②获取远程进程中线程函数的开始地址,而非本地地址;③向远程线程成功传入DLL路径字符串。 |
(3)其他函数
①在远程进程中分配/释放内存:VirtualAllocEx/VirtualFreeEx
②对远程进程地址空间进行读写:ReadProcessMemory/WriteProcessMemory
22.4.2 获取LoadLibrary函数的远程地址
(1)LoadLibrary要作为远程线程函数来使用,必须满足两个条件:
①该函数符合线程函数的原型。(查看MSDN,他们有相同的调用约定,都有一个参数和一个返回值。至于类型不同,可以通过强转类型得到,所以该条件满足)
②该函数存在于远程线程地址空间内。这一点也可以保证的,因为LoadLibrary函数位于Kernel32.dll中,对于Windows系统而言,本地进程和远程进程中的Kernel32.dll被映射到地址空间的同一内存地址,因而只要通过GetProcAddress获取本地进程中LoadLibrary的地址,在远程进程中也同样是这个地址,可以直接传给CreateRemoteThread。
(2)LoadLibrary是被定义为一个宏,而不是函数。有两个版本LoadLibraryA和LoadLibraryW。
(3)为什么CreateRemoteThread的pfnStartAddr参数不能直接写成LoadLibrary W(或A),而要使用GetProcAddress获得的LoadLibary函数的地址。
①LoadLibrary W(或A)是Kernel32.dll中的一个导出函数,但我们的Dll中直接引用该函数时,会在Dll的导入表记录下来。我们都知道导入函数的真实地址是在DLL加载的时候才能确定的,加载程序会从导入表中取得导入函数名,在被加载到进程地址空间后,会计算出该函数地真实地址,然后填入导入表(IAT)相应的位置。这种函数在编译期无法知道切确的地址,所以被编译成CALL DWORD PTR[XXXXXXXX]之类的代码,中括号中的数值虽然是一个确定的数值,但并不是导入函数的真实地址(形如CALL XXXXXXXX),而是一个子程序的地址,该程序被称为转换函数(Thunk)。【顺便说一下,这也是为什么在声明一个导入函数时要加上__declspec(dllimport)前缀的原因,因为编译器无法区分应用程序是对一般函数调用还是对导入函数调用。当加上这个前缀时,编译器会认为此函数来自导入函数,就会产生CALL DWORD PTR[XXXXXXXX]的指令,而不是CALL XXXXXXXX。】
②当程序调用导入函数时,编译器会处理成先调用转换函数,然后转换函数从IAT表中获得导入函数的真实地址,再调用相应的地址。所以如果将CreateRemoteThread的pfnStartAddr参数写成LoadLibraryW(或A),这里地址将被编译成转换函数的地址,而不是LoadLibrary的真实地址。
22.4.3 将DLL的路径字符串存放到远程的地址空间中
(1)如果直接向CreateRemoteThread()传入DLL路径,如”C:\\MyDLL.dll”那么实际向远程线程传递的是一个本地的指针值,这个值在远程进程的地址空间中是没有意义的。
(2)可以使用VirtualAllocEx()函数在远程进程中先分配一段空间,然后再使用WriteProcessMemory将DLL路径字符串复制到远程进程的地址空间中去,最后将该远程内存的指针传给CreateRemoteThead相应的参数。
24.4.4 总结使用远程线程注入DLL的步骤
①用VirtualAllocEx函数在远程进程的地址空间中分配一块内存。
②用WriteProcessMemory函数反映Dll的路径名复制到第1步分配的内存中
③用GetProcAddress函数来得到LoadLibrary W(或A)函数在Kernel32.dll的真实地址。
④用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并在参数中传入第1步分配的内存地址。这时,DLL己经被注入到远程进程的地址空间中,DLL的DllMain函数会收到DLL_PROCESS_ATTACH通知并县城可以执行我们想要执行的代码。当DllMain返回时,远程线程会从线程函数(LoadLibraryW/A)调用返回到线程启动函数RtlUserThreadStart(该函数的实现可参考第6章),最后调用ExitThread使远程线程终止。
⑤此时远程进程中那块在第1步分配的内存还在,DLL也还在远程进程的地址空间中。这里只需调用VirtualFreeEx就可以释放远程进程的内存。
⑥但DLL的释放,要先通过GetProcAddress获得FreeLibrary的地址,然后再通过CreateRemoteThread在远程进程中创建一个线程,让该线程调用FreeLibrary,pvParam参数传入远程DLL中句柄。
【InjectLibrary示例程序】
注入进程本身 注入“记事本”程序
//ImgWalk动态库,这个DLL用来检测被注入的进程中当前载入的各个模块名称
/************************************************************************ Module: ImgWalk.cpp Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre ************************************************************************/ #include "../../CommonFiles/CmnHdr.h" #include <tchar.h> ////////////////////////////////////////////////////////////////////////// BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID fImpload){ if (fdwReason == DLL_PROCESS_ATTACH){ char szBuf[MAX_PATH * 100] = { 0 }; PBYTE pb = NULL; MEMORY_BASIC_INFORMATION mbi; while (VirtualQuery(pb, &mbi, sizeof(mbi)) == sizeof(mbi)){ int nLen; char szModName[MAX_PATH]; if (mbi.State == MEM_FREE) mbi.AllocationBase = mbi.BaseAddress; if ((mbi.AllocationBase == hInstDll) || //该区域包含该DLL,则隐藏掉该DLL,不显示在后面对话框中 (mbi.AllocationBase !=mbi.BaseAddress || //块不是区域的开始地址 (mbi.AllocationBase == NULL))){ //区域地址为NULL nLen = 0; } else{ nLen = GetModuleFileNameA((HINSTANCE)mbi.AllocationBase, szModName, _countof(szModName)); } if (nLen > 0){ wsprintfA(strchr(szBuf, 0),//找到第一个0 "\n%p-%s", mbi.AllocationBase, szModName); } pb += mbi.RegionSize; } //注意:正常情况下,不应该在DllMain里显示一个对话框,因为 //装载器被锁。但为了程序简单化,这里违返了这个规则 chMB(&szBuf[1]); //从1开始,因为 "\n%p-%s",表示szBuf[0]为换行符 } return (TRUE); }
//InjLib,创建远程线程用于将上述的DLL注入到指定的进程中去
1 /************************************************************************ 2 Module: InjLib.cpp 3 Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre 4 ************************************************************************/ 5 6 #include "../../CommonFiles/CmnHdr.h" 7 8 #include <tchar.h> 9 #include <malloc.h> // For alloca 10 #include <strsafe.h> 11 #include <TlHelp32.h> 12 #include "resource.h" 13 14 ////////////////////////////////////////////////////////////////////////// 15 #ifdef UNICODE 16 #define InjectLib InjectLibW 17 #define EjectLib EjectLibW 18 #else 19 #define InjectLib InjectLibA 20 #define EjectLib EjectLibA 21 #endif 22 23 ////////////////////////////////////////////////////////////////////////// 24 //创建远程线程并注入DLL 25 //参数:dwProcessID——进程ID 26 // pszLibFile ——要注入的DLL路径(含名称) 27 BOOL WINAPI InjectLibW(DWORD dwProcessId, PCWSTR pszLibFile){ 28 BOOL bOk = FALSE; //假设注入失败 29 HANDLE hProcess = NULL, hThread = NULL; 30 PWSTR pszLibFileRemote = NULL; 31 32 __try{ 33 //获取目标进程句柄 34 hProcess = OpenProcess( 35 PROCESS_QUERY_INFORMATION | 36 PROCESS_CREATE_THREAD | //For CreateRemoteThread 37 PROCESS_VM_OPERATION | //For VirtualAllocEx/VirtualFreeEx 38 PROCESS_VM_WRITE, //For WriteProcessMemory 39 FALSE,dwProcessId); 40 41 if (hProcess == NULL) __leave; 42 43 //计算存储Dll路径名所需的字节数 44 int cch = 1 + lstrlen(pszLibFile);//字符个数,因strlen不含\0,所以加1为\0预留 45 int cb = cch*sizeof(wchar_t); 46 47 //为远程进程分配一个内存以存储DLL路径名称 48 pszLibFileRemote = (PWSTR) 49 VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE); 50 if (pszLibFileRemote == NULL) __leave; 51 52 //复制DLL路径名称到远程进程的内存中 53 if (!WriteProcessMemory(hProcess, pszLibFileRemote, 54 (PVOID)pszLibFile, cb, NULL)) __leave; 55 56 //获取LoadLibraryW在Kernel32.dll中的地址 57 PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) 58 GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); 59 60 if (pfnThreadRtn == NULL) __leave; 61 62 //创建远程线程调用LoadLibraryW(DllPathName); 63 hThread = CreateRemoteThread(hProcess, 64 NULL, 0, 65 pfnThreadRtn, //LoadLibraryW(远程进程地址空间中) 66 pszLibFileRemote, //Dll路径名(远程进程地址空间中) 67 0, NULL); 68 if (hThread == NULL) __leave; 69 70 //等待远程线程结束 71 WaitForSingleObject(hThread, INFINITE); 72 73 bOk = TRUE; //注入成功 74 } 75 __finally{ 76 //释放用于保存Dll路径名称的内存 77 if (pszLibFileRemote != NULL) 78 VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE); 79 80 if (hThread != NULL) 81 CloseHandle(hThread); 82 83 if (hProcess != NULL) 84 CloseHandle(hProcess); 85 } 86 return (bOk); 87 } 88 89 ////////////////////////////////////////////////////////////////////////// 90 BOOL WINAPI InjectLibA(DWORD dwProcessId, PCSTR pszLibFile){ 91 //分配一个栈内存(无需手用释放),用于存储Unicode版本的路径名 92 SIZE_T cchSize = lstrlenA(pszLibFile) + 1;//字符个数,含\0 93 PWSTR pszLibFileW = (PWSTR) 94 _alloca(cchSize*sizeof(wchar_t)); 95 96 //将ANSI路径名转化为等价的Unicode版本 97 StringCchPrintfW(pszLibFileW, cchSize, L"%S", pszLibFile); 98 99 return (InjectLibW(dwProcessId, pszLibFileW)); 100 } 101 102 ////////////////////////////////////////////////////////////////////////// 103 //将DLL从进程地址空间中撤销 104 //先根据DLL文件名,在进程加载的模块中查找是否该DLL己被加载 105 //如果被加载,记下这个DLL的句柄。然后创建远程线程去调用FreeLibrary卸载 106 BOOL WINAPI EjectLibW(DWORD dwProcessId, PCWSTR pszLibFile){ 107 BOOL bOk = FALSE; //假定撤销失败 108 HANDLE hthSnapshot = NULL; 109 HANDLE hProcess = NULL, hThread = NULL; 110 __try{ 111 //抓取进程快照 112 hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,//指定进程中加载的所有模块 113 dwProcessId); 114 115 //获得Unicode版本的目标DLL库句柄 116 MODULEENTRY32W me = { sizeof(me) }; 117 BOOL bFound = FALSE; 118 BOOL bMoreMods = Module32FirstW(hthSnapshot, &me); //Unicode版本 119 for (; bMoreMods;bMoreMods = Module32NextW(hthSnapshot,&me)){ 120 bFound = (_wcsicmp(me.szModule, pszLibFile) == 0) || 121 (_wcsicmp(me.szExePath, pszLibFile) == 0); 122 if (bFound) 123 break; 124 } 125 if (!bFound) __leave; 126 127 //获得目标进程的句柄 128 hProcess = OpenProcess( 129 PROCESS_QUERY_INFORMATION | 130 PROCESS_CREATE_THREAD | //For CreateRemoteThread 131 PROCESS_VM_OPERATION, //For VirtualAllocEx/VirtualFreeEx 132 FALSE, dwProcessId); 133 if (hProcess == NULL) __leave; 134 135 //获取FreeLibrary在Kernel32.dll中的地址 136 PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) 137 GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary"); 138 if (pfnThreadRtn == NULL) __leave; 139 140 //创建远程线程 141 hThread = CreateRemoteThread(hProcess, 142 NULL, 0, 143 pfnThreadRtn, //FreeLibrary(远程进程地址空间中) 144 me.modBaseAddr, 145 0, NULL); 146 if (hThread == NULL) __leave; 147 148 //等待远程线程结束 149 WaitForSingleObject(hThread, INFINITE); 150 151 bOk = TRUE; //撤销成功 152 153 } 154 __finally{ 155 156 if (hthSnapshot != NULL) 157 CloseHandle(hthSnapshot); 158 159 if (hThread != NULL) 160 CloseHandle(hThread); 161 162 if (hProcess != NULL) 163 CloseHandle(hProcess); 164 } 165 166 return (bOk); 167 } 168 169 ////////////////////////////////////////////////////////////////////////// 170 BOOL WINAPI EjectLibA(DWORD dwProcessId, PCSTR pszLibFile){ 171 //分配一个栈内存(无需手用释放),用于存储Unicode版本的路径名 172 SIZE_T cchSize = lstrlenA(pszLibFile) + 1;//字符个数,含\0 173 PWSTR pszLibFileW = (PWSTR) 174 _alloca(cchSize*sizeof(wchar_t)); 175 176 //将ANSI路径名转化为等价的Unicode版本 177 StringCchPrintfW(pszLibFileW, cchSize, L"%S", pszLibFile); 178 179 return (EjectLibW(dwProcessId, pszLibFileW)); 180 } 181 182 ////////////////////////////////////////////////////////////////////////// 183 BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam){ 184 chSETDLGICONS(hwnd, IDI_INJLIB); 185 return (TRUE); 186 } 187 ////////////////////////////////////////////////////////////////////////// 188 void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity){ 189 switch (id) 190 { 191 case IDCANCEL: 192 EndDialog(hwnd, id); 193 break; 194 195 case IDC_INJECT: 196 DWORD dwProcessId = GetDlgItemInt(hwnd, IDC_PROCESSID, NULL, FALSE); 197 if (dwProcessId == 0){ 198 //如果dwProcessId为0,表示注入到本地进程 199 dwProcessId = GetCurrentProcessId(); 200 } 201 202 TCHAR szLibFile[MAX_PATH]; 203 GetModuleFileName(NULL, szLibFile, _countof(szLibFile)); //获得当前进程的完整路径 204 PTSTR pFilename = _tcsrchr(szLibFile, TEXT('\\')) + 1; 205 _tcscpy_s(pFilename, _countof(szLibFile) - (pFilename - szLibFile), 206 TEXT("22_ImgWalk.DLL")); 207 208 if (InjectLib(dwProcessId,szLibFile)){ 209 chVERIFY(EjectLib(dwProcessId, szLibFile)); 210 chMB("DLL注入/撤消成功!"); 211 } else{ 212 chMB("DLL注入/撤消失败!"); 213 } 214 215 216 break; 217 } 218 } 219 220 ////////////////////////////////////////////////////////////////////////// 221 INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ 222 switch (uMsg) 223 { 224 chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog); 225 chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand); 226 } 227 return (FALSE); 228 } 229 230 ////////////////////////////////////////////////////////////////////////// 231 int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int){ 232 DialogBox(hInstExe, MAKEINTRESOURCE(IDD_INJLIB), NULL, Dlg_Proc); 233 return (0); 234 }
//resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 22_InjLib.rc 使用 // #define IDD_INJLIB 1 #define IDC_INJECT 100 #define IDC_PROCESSID 101 #define IDI_INJLIB 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
//.rc文件
// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_INJLIB ICON "InjLib.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_INJLIB DIALOGEX 15, 24, 158, 24 STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "远程线程注入DLL" FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN LTEXT "进程ID(10进制):",-1,4,6,69,8 EDITTEXT IDC_PROCESSID,78,4,36,12,ES_AUTOHSCROLL DEFPUSHBUTTON "注入",IDC_INJECT,120,4,36,12,WS_GROUP END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO BEGIN IDD_INJLIB, DIALOG BEGIN END END #endif // APSTUDIO_INVOKED #endif // 中文(简体,中国) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
22.5 使用木马DLL来注入DLL
(1)如果某个进程必须载入一个DLL(如xyz.dll)。则可以创建自己的DLL并给他起同样的名称,然后进行替换。并将旧的xyz.dll改为别的名称(如abc.dll)。
(2)在我们的xyz.dll的内部,导出原来xyz.dll导出的所有符号。(用函数转发器导为abc.dll相应的符号)。
(3)在我们的xyz.dll内部,可以增加一些我们自己的代码,以便以我们操作被注入的进程。