第22章 DLL注入和API拦截(2)

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示例程序】

第22章 DLL注入和API拦截(2)_第1张图片        第22章 DLL注入和API拦截(2)_第2张图片

注入进程本身                                                                                    注入“记事本”程序                 

 //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内部,可以增加一些我们自己的代码,以便以我们操作被注入的进程。

你可能感兴趣的:(第22章 DLL注入和API拦截(2))