網(wǎng)絡安全編程:遠程線程編程
Windows操作系統(tǒng)下,為了避免各個進程相互影響,每個進程地址空間都是被隔離的。所謂 “遠程線程”,并不是跨計算機的,而是跨進程的。簡單來說,就是進程A要在進程B中創(chuàng)建一個線程,這就叫遠程線程。
遠程線程被木馬、外掛等程序廣泛使用,反病毒軟件中也離不開遠程線程的技術。技術應用的兩面性取決于自己的個人行為意識,良性的技術學習對自己的人生發(fā)展是非常有好處的,就算談不上好處,至少不會給自己帶來不必要的麻煩。
關于遠程線程的知識,本文介紹3個例子,分別是DLL的注入、卸載遠程DLL和不依賴DLL進行代碼注入。
1. DLL遠程注入
木馬或病毒編寫的好壞取決于其隱藏的程度,而不在于其功能的多少。無論是木馬還是病毒,都是可執(zhí)行程序。如果它們是EXE文件的話,那么在運行時必定會產(chǎn)生一個進程,就很容易被發(fā)現(xiàn)。為了不被發(fā)現(xiàn),在編寫木馬或病毒時可以選擇將其編寫為DLL文件。DLL文件的運行不會單獨創(chuàng)建一個進程,它的運行被加載到進程的地址空間中,因此其隱蔽性相對較好。DLL文件如果不被進程加載又如何在進程的地址空間中運行呢?方式是強制讓某進程加載DLL文件到其地址空間中去,這個強制的手段就是現(xiàn)在要介紹的遠程線程。
創(chuàng)建遠程線程的函數(shù)CreateRemoteThread()的定義如下:
- HANDLE CreateRemoteThread(
- HANDLE hProcess,
- LPSECURITY_ATTRIBUTES lpThreadAttributes,
- DWORD dwStackSize,
- LPTHREAD_START_ROUTINE lpStartAddress,
- LPVOID lpParameter,
- DWORD dwCreationFlags,
- LPDWORD lpThreadId
- );
該函數(shù)的功能是創(chuàng)建一個遠程的線程。我們把CreateThread()函數(shù)和CreateRemoteThread()函數(shù)進行比較。對于CreateThread()函數(shù)來說,CreateRem oteThread()函數(shù)比其多了一個hProcess參數(shù),該參數(shù)是指定要創(chuàng)建線程的進程句柄。其實CreateThread()函數(shù)的內(nèi)容實現(xiàn)就是依賴于CreateRemoteThread()函數(shù)來完成的。CreateThread()函數(shù)的代碼實現(xiàn)如下:
- /*
- * @implemented
- */
- HANDLE
- WINAPI
- CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
- DWORD dwStackSize,
- LPTHREAD_START_ROUTINE lpStartAddress,
- LPVOID lpParameter,
- DWORD dwCreationFlags,
- LPDWORD lpThreadId)
- {
- /* 創(chuàng)建遠程線程
- return CreateRemoteThread(NtCurrentProcess(),
- lpThreadAttributes,
- dwStackSize,
- lpStartAddress,
- lpParameter,
- dwCreationFlags,
- lpThreadId);
- }
在上面的代碼中,NtGetCurrentProcess()函數(shù)的功能是獲得當前進程的句柄。
CreateRemoteThread()函數(shù)是給其他進程創(chuàng)建線程使用的,其第一個參數(shù)是指定某進程的句柄,獲取進程的句柄使用API函數(shù)OpenProcess(),該函數(shù)需要提供PID作為參數(shù)。
除了hProcess參數(shù)以外,剩余的關鍵參數(shù)就只有l(wèi)pStartAddress和lpParameter兩個了。lpStartAddress指定線程函數(shù)的地址,lpParameter指定傳遞給線程函數(shù)的參數(shù)。前面提到,每個進程的地址空間是隔離的,那么新創(chuàng)建的線程函數(shù)的地址也應該在目標進程中,而不應該在調(diào)用CreateRemoteThread()函數(shù)的進程中。同樣,傳遞給線程函數(shù)的參數(shù)也應該在目標進程中。
如何讓線程函數(shù)的地址在目標進程中呢?如何讓線程函數(shù)的參數(shù)也可以傳遞到目標進程中呢?在討論這個問題以前,先來考慮線程函數(shù)要完成的功能。這里主要完成的功能是注入一個DLL文件到目標進程中,那么線程函數(shù)的功能就是加載DLL文件。加載DLL文件使用的是LoadLibrary()函數(shù)。LoadLibrary()函數(shù)的定義:
- HMODULE LoadLibrary(
- LPCTSTR lpFileName
- );
- 看一下線程函數(shù)的定義格式,具體如下:
- DWORD WINAPI ThreadProc(
- LPVOID lpParameter
- );
比較兩個函數(shù)可以發(fā)現(xiàn),除了函數(shù)的返回值類型和參數(shù)類型以外,其函數(shù)格式是相同的。這里只考慮其相同的部分。因為其函數(shù)的格式相同,首先調(diào)用約定相同,都是WINAPI(也就是__stdcall方式);其次函數(shù)個數(shù)相同,都只有一個。那么,可以直接把LoadLibrary()函數(shù)作為線程函數(shù)創(chuàng)建到指定的進程中。LoadLibrary()的參數(shù)是欲加載的DLL文件的完整路徑,只要在CreateRemoteThread()函數(shù)中賦值一個指向DLL文件完整路徑的指針給LoadLibrary()函數(shù)即可。這樣使用CreateRemoteThread()函數(shù)就可以創(chuàng)建一個遠程線程了。不過,還有兩個問題沒有解決,首先是如何將LoadLibrary()函數(shù)的地址放到目標進程空間中讓CreateRemoteThread()調(diào)用,其次是傳遞給LoadLibrary()函數(shù)的參數(shù)也需要在目標進程空間中,并且要通過CreateRemoteThread()函數(shù)指定給LoadLibrary()函數(shù)。
首先解決第1個問題,即如何將LoadLibrary()函數(shù)的地址放到目標進程空間中。LoadLibrary()函數(shù)是系統(tǒng)中的Kernel32.dll的導出函數(shù),Kernel32.dll這個DLL文件在任何進程中的加載位置都是相同的,也就是說,LoadLibrary()函數(shù)的地址在任何進程中的地址都是相同的。因此,只要在進程中獲得LoadLibrary()函數(shù)的地址,那么該地址在目標進程中也可以使用。CreateRemoteThread()函數(shù)的線程地址參數(shù)直接傳遞LoadLibrary()函數(shù)的地址即可。
其次解決第2個問題,即如何將欲加載的DLL文件完整路徑寫入目標進程中。這需要借助WriteProcessMemory()函數(shù),其定義如下:
- BOOL WriteProcessMemory(
- HANDLE hProcess, // handle to process
- LPVOID lpBaseAddress, // base of memory area
- LPVOID lpBuffer, // data buffer
- DWORD nSize, // number of bytes to write
- LPDWORD lpNumberOfBytesWritten // number of bytes written
- );
該函數(shù)的功能是把lpBuffer中的內(nèi)容寫到進程句柄是hProcess進程的lpBaseAddress地址處,寫入長度為nSize。
參數(shù)說明如下。
hProcess:該參數(shù)是指定進程的進程句柄。
lpBaseAddress:該參數(shù)是指定寫入目標進程內(nèi)存的起始地址。
lpBuffer:該參數(shù)是要寫入目標進程內(nèi)存的緩沖區(qū)起始地址。
nSize:該參數(shù)是指定寫入目標內(nèi)存中的緩沖區(qū)的長度。
lpNumberOfBytesWritten:該參數(shù)用于接收實際寫入內(nèi)容的長度。
該函數(shù)的功能非常強大,比如在破解方面,用該函數(shù)可以實現(xiàn)一個“內(nèi)存補丁”;在開發(fā)方面,該函數(shù)可以用于修改目標進程中指定的值(比如游戲修改器可以修改游戲中的錢、紅、藍等)。
使用該函數(shù)可以把DLL文件的完整路徑寫入到目標進程的內(nèi)存地址中,這樣就可以在目標進程中用LoadLibrary()函數(shù)加載指定的DLL文件了。解決了上面的兩個問題,還有第3個問題需要解決。WriteProcessMemory()函數(shù)的第2個參數(shù)是指定寫入目標進程內(nèi)存的緩沖區(qū)起始地址。這個地址在目標進程中,那么這個地址在目標進程的哪個位置呢?目標進程中的內(nèi)存塊允許把DLL文件的路徑寫進去嗎?
第3個要解決的問題是如何確定應該將DLL文件的完整路徑寫入目標進程的哪個地址。對于目標進程來說,事先是不會準備一塊地址讓用戶進行寫入的,用戶能做的是自己在目標進程中申請一塊內(nèi)存,然后把DLL文件的路徑進行寫入,寫入在目標進程新申請到的內(nèi)存空間中。在目標進程中申請內(nèi)存的函數(shù)是VirtualAllocEx(),其定義如下:
- LPVOID VirtualAllocEx(
- HANDLE hProcess,
- LPVOID lpAddress,
- SIZE_T dwSize,
- DWORD flAllocationType,
- DWORD flProtect
- );
VirtualAllocEx()函數(shù)的參數(shù)說明如下。
hProcess:該參數(shù)是指定進程的進程句柄。
lpAddress:該參數(shù)是指在目標進程中申請內(nèi)存的起始地址。
dwSize:該參數(shù)是指在目標進程中申請內(nèi)存的長度。
flAllocationType:該參數(shù)指定申請內(nèi)存的狀態(tài)類型。
flProtect:該參數(shù)指定申請內(nèi)存的屬性。
該函數(shù)的返回值是在目標進程申請到的內(nèi)存塊的起始地址。
到此,關于編寫一個DLL注入的所有知識都已經(jīng)具備了?,F(xiàn)在開始編寫一個DLL注入的工具,其界面如圖1所示。
圖1 DLL注入/卸載器
該工具有2個作用,分別是注入DLL和卸載被注入的DLL。關于卸載被注入的DLL的功能,將在后面進行介紹。在界面上要求輸入兩部分內(nèi)容,第1部分是欲注入的DLL文件的完整路徑(一定要是完整路徑),第2部分是進程的名稱。
首先看一下關于界面的操作,代碼如下:
- void CInjectDllDlg::OnBtnInject()
- {
- // 添加處理程序代碼
- char szDllName[MAX_PATH] = { 0 };
- char szProcessName[MAXBYTE] = { 0 };
- DWORD dwPid = 0;
- GetDlgItemText(IDC_EDIT_DLLFILE, szDllName, MAX_PATH);
- GetDlgItemText(IDC_EDIT_PROCESSNAME, szProcessName, MAXBYTE);
- // 由進程名獲得 PID
- dwPid = GetProcId(szProcessName);
- // 注入 szDllName 到 dwPid
- InjectDll(dwPid, szDllName);
- }
代碼中調(diào)用了另外兩個函數(shù),第1個是由進程名獲得PID的函數(shù),第2個是用于DLL注入的函數(shù)。GetProcId()函數(shù)的代碼如下:
- DWORD CInjectDllDlg::GetProcId(char *szProcessName)
- {
- BOOL bRet;
- PROCESSENTRY32 pe32;
- HANDLE hSnap;
- hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
- pe32.dwSize = sizeof(pe32);
- bRet = Process32First(hSnap, &pe32);
- while ( bRet )
- {
- // strupr()函數(shù)是將字符串轉化為大寫
- if ( lstrcmp(strupr(pe32.szExeFile),strupr(szProcessName)) == 0 )
- {
- return pe32.th32ProcessID;
- }
- bRet = Process32Next(hSnap, &pe32);
- }
- return 0;
- } +
InjectDll()函數(shù)的代碼如下:
- VOID CInjectDllDlg::InjectDll(DWORD dwPid, char *szDllName)
- {
- if ( dwPid == 0 || lstrlen(szDllName) == 0 )
- {
- return ;
- }
- char *pFunName = "LoadLibraryA";
- // 打開目標進程
- HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);
- if ( hProcess == NULL )
- {
- return ;
- }
- // 計算欲注入 DLL 文件完整路徑的長度
- int nDllLen = lstrlen(szDllName) + sizeof(char);
- // 在目標進程申請一塊長度為 nDllLen 大小的內(nèi)存空間
- PVOID pDllAddr = VirtualAllocEx(hProcess,NULL, nDllLen,MEM_COMMIT,PAGE_READWRITE);
- if ( pDllAddr == NULL )
- {
- CloseHandle(hProcess);
- return ;
- }
- DWORD dwWriteNum = 0;
- // 將欲注入 DLL 文件的完整路徑寫入在目標進程中申請的空間內(nèi)
- WriteProcessMemory(hProcess, pDllAddr, szDllName,nDllLen, &dwWriteNum);
- // 獲得 LoadLibraryA()函數(shù)的地址
- FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"),pFunName);
- // 創(chuàng)建遠程線程
- HANDLE hThread = CreateRemoteThread(hProcess,NULL, 0,(LPTHREAD_START_ROUTINE)pFunAddr,pDllAddr, 0, NULL);
- WaitForSingleObject(hThread, INFINITE);
- CloseHandle(hThread);
- CloseHandle(hProcess);
- }
InjectDll()函數(shù)有 2 個參數(shù),分別是目標進程的 ID 值和要被注入的 DLL 文件的完整路徑。在代碼中獲得的不是 LoadLibrary()函數(shù)的地址,而是 LoadLibraryA()函數(shù)的地址。在系統(tǒng)中其實沒有 LoadLibrary()函數(shù),有的只是 LoadLibraryA()和 LoadLibraryW()兩個函數(shù)。這兩個函數(shù)分別針對 ANSI 字符串和 UNICODE 字符串。而 LoadLibrary()函數(shù)只是一個宏。在編寫程序的時候,直接使用該宏是可以的。如果要獲取 LoadLibrary()函數(shù)的地址,就要明確指定是獲取 LoadLibraryA()還是 LoadLibraryW()。
LoadLibrary()宏定義如下:
- #ifdef UNICODE
- #define LoadLibrary LoadLibraryW
- #else
- #define LoadLibrary LoadLibraryA
- #endif // !UNICODE
只要涉及字符串的函數(shù),都會有相應的ANSI版本和UNICODE版本;其余不涉及字符串的函數(shù),沒有ANSI版本和UNICODE版本的區(qū)別。
為了測試DLL加載是否成功,在代碼的DllMain()函數(shù)中加入如下代碼:
- case DLL_PROCESS_ATTACH:
- {
- MsgBox("!DLL_PROCESS_ATTACH!");
- break;
- }
現(xiàn)在測試一下注入的效果,如圖2和圖3所示。
圖2 DLL文件被注入成功的提示
圖3 查看進程中的DLL列表確認被裝載成功
在圖2中,彈出的對話框是DLL程序在DLL_PROCESS_ATTACH時出現(xiàn)的。其所在的進程為notepad.exe。從圖2中可以看出,彈出提示框的標題處是notepad.exe進程的路徑。圖3是用工具查看進程中所加載的DLL文件列表,可以看出,通過注入工具注入的DLL文件已經(jīng)被加載到notepad.exe的進程空間中。
如果要對系統(tǒng)進程進行注入的話,由于進程權限的關系是無法注入成功的。在打開目標進程時用到了OpenProcess()函數(shù),由于權限不夠,會導致無法打開進程并獲得進程句柄。通過調(diào)整當前進程的權限,可以打開系統(tǒng)進程并獲得進程句柄。如果在Win8或更高版本上運行注入程序的話,需要選中注入工具單擊右鍵,選擇“以管理員身份運行”才可以完成注入。
2. 卸載被注入的DLL文件
DLL注入如果應用在木馬方面,危害很大,這里完成一個卸載被注入DLL的程序。卸載被注入DLL程序的思路和注入的思路是一樣的,而且代碼的改動也非常小。區(qū)別在于現(xiàn)在的功能是卸載,而不是注入。
DLL卸載使用的API函數(shù)是FreeLiabrary(),其定義如下:
- BOOL FreeLibrary(
- HMODULE hModule // handle to DLL module
- );
該函數(shù)的參數(shù)是要卸載的模塊的句柄。
FreeLibrary()函數(shù)使用的模塊句柄可以通過Module32First()和Module32Next()兩個函數(shù)獲取。在使用Module32First()和Module32Next()兩個函數(shù)的時候,需要用到MODULEENTRY32結構體,該結構體中保存了模塊的句柄。MODULEENTRY32結構體的定義如下:
- typedef struct tagMODULEENTRY32 {
- DWORD dwSize;
- DWORD th32ModuleID;
- DWORD th32ProcessID;
- DWORD GlblcntUsage;
- DWORD ProccntUsage;
- BYTE * modBaseAddr;
- DWORD modBaseSize;
- HMODULE hModule;
- TCHAR szModule[MAX_MODULE_NAME32 + 1];
- TCHAR szExePath[MAX_PATH];
- } MODULEENTRY32;
- typedef MODULEENTRY32 *PMODULEENTRY32;
該結構體中的hModule為模塊的句柄,szModule為模塊的名稱,szExePath是完整的模塊的名稱(所謂完整,包括路徑和模塊名稱)。
卸載遠程進程中DLL模塊的代碼如下:
- VOID CInjectDllDlg::UnInjectDll(DWORD dwPid, char *szDllName)
- {
- if ( dwPid == 0 || lstrlen(szDllName) == 0 )
- {
- return ;
- }
- HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,dwPid);
- MODULEENTRY32 me32;
- me32.dwSize = sizeof(me32);
- // 查找匹配的進程名稱
- BOOL bRet = Module32First(hSnap, &me32);
- while ( bRet )
- {
- if ( lstrcmp(strupr(me32.szExePath),
- strupr(szDllName)) == 0 )
- {
- break;
- }
- bRet = Module32Next(hSnap, &me32);
- }
- CloseHandle(hSnap);
- char *pFunName = "FreeLibrary";
- HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);
- if ( hProcess == NULL )
- {
- return ;
- }
- FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"),pFunName);
- HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
- (LPTHREAD_START_ROUTINE)pFunAddr,me32.hModule, 0, NULL);
- WaitForSingleObject(hThread, INFINITE);
- CloseHandle(hThread);
- CloseHandle(hProcess);
- }
卸載遠程進程中DLL的實現(xiàn)代碼比DLL注入的代碼要簡單,這里就不做過多的介紹了。
3. 無DLL的代碼注入
DLL文件的注入與卸載都完成了,整個注入與卸載的過程其實就是讓遠程線程執(zhí)行一次LoadLibrary()函數(shù)或FreeLibrary()函數(shù)。遠程線程裝載一個DLL文件,通過DllMain()調(diào)用DLL中的具體功能代碼,這樣注入DLL后就可以讓DLL做很多事情了。是否可以不依賴DLL文件直接向目標進程寫入要執(zhí)行的代碼,以完成特定的功能呢?答案是可以。
要在目標進程中完成一定的功能,就需要使用相關的API函數(shù),不同的API函數(shù)實現(xiàn)在不同的DLL中。Kernel32.dll文件在每個進程中的地址是相同的,但是并不代表其他DLL文件在每個進程中的地址都是一樣的。這樣,在目標進程中調(diào)用API函數(shù)時,必須使用LoadLibrary()函數(shù)和GetProcAddress()函數(shù)動態(tài)調(diào)用用到的每個API函數(shù)。把想要使用的API函數(shù)及API函數(shù)所在的DLL文件都封裝到一個結構體中,直接寫入目標進程的空間中。同時也直接把要在遠程執(zhí)行的代碼也寫入目標進程的內(nèi)存空間中,最后調(diào)用CreateRemoteThread()函數(shù)即可將其運行。
通過實現(xiàn)一個簡單的例子讓遠程線程彈出一個提示對話框,但是不借助于DLL。本程序所使用的API函數(shù)在前面都已經(jīng)介紹過了。根據(jù)前面的步驟先來定義一個結構體,其定義如下:
- #define STRLEN 20
- typedef struct _DATA
- {
- DWORD dwLoadLibrary;
- DWORD dwGetProcAddress;
- DWORD dwGetModuleHandle;
- DWORD dwGetModuleFileName;
- char User32Dll[STRLEN];
- char MessageBox[STRLEN];
- char Str[STRLEN];
- }DATA, *PDATA;
該結構體中保存了LoadLibraryA()、GetProcAddress()、GetModuleHandle()和GetModu leFileName()四個API函數(shù)的地址。這四個API函數(shù)都屬于Kernel32.dll的導出函數(shù),因此可以在注入前進行獲取。User32Dll中保存“User32.dll”字符串,因為MessageBoxA()函數(shù)是由User32.dll的導出函數(shù)。Str中保存的是通過MessageBoxA()函數(shù)彈出的字符串。
注入代碼類似于前面介紹的注入代碼,不過需要在注入代碼中定義一個結構體變量,并進行相應的初始化,代碼如下:
- VOID CNoDllInjectDlg::InjectCode(DWORD dwPid)
- {
- // 打開進程并獲取進程句柄
- HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);
- if ( hProcess == NULL )
- {
- return ;
- }
- DATA Data = { 0 };
- // 獲取 kernel32.dll 中相關的導出函數(shù)
- Data.dwLoadLibrary = (DWORD)GetProcAddress(
- GetModuleHandle("kernel32.dll"),"LoadLibraryA");
- Data.dwGetProcAddress = (DWORD)GetProcAddress(
- GetModuleHandle("kernel32.dll"),"GetProcAddress");
- Data.dwGetModuleHandle = (DWORD)GetProcAddress(
- GetModuleHandle("kernel32.dll"),"GetModuleHandleA");
- Data.dwGetModuleFileName = (DWORD)GetProcAddress(
- GetModuleHandle("kernel32.dll"),"GetModuleFileNameA");
- // 需要的其他 DLL 和導出函數(shù)
- lstrcpy(Data.User32Dll, "user32.dll");
- lstrcpy(Data.MessageBox, "MessageBoxA");
- // MessageBoxA()彈出的字符串
- lstrcpy(Data.Str, "Inject Code !!!");
- // 在目標進程申請空間
- LPVOID lpData = VirtualAllocEx(hProcess, NULL, sizeof(Data),
- MEM_COMMIT | MEM_RELEASE,PAGE_READWRITE);
- DWORD dwWriteNum = 0;
- WriteProcessMemory(hProcess, lpData, &Data,
- sizeof(Data), &dwWriteNum);
- // 在目標進程空間申請的用于保存代碼的長度
- DWORD dwFunSize = 0x4000;
- LPVOID lpCode = VirtualAllocEx(hProcess, NULL, dwFunSize,
- MEM_COMMIT,PAGE_EXECUTE_READWRITE);
- WriteProcessMemory(hProcess, lpCode, &RemoteThreadProc,
- dwFunSize, &dwWriteNum);
- HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0
- (LPTHREAD_START_ROUTINE)lpCode,lpData, 0, NULL);
- WaitForSingleObject(hThread, INFINITE);
- CloseHandle(hThread);
- CloseHandle(hProcess);
- }
上面的注入代碼除了對結構體變量初始化外,還將線程函數(shù)代碼寫入目標進程空間的內(nèi)存中。線程函數(shù)的代碼如下:
- DWORD WINAPI RemoteThreadProc(LPVOID lpParam)
- {
- PDATA pData = (PDATA)lpParam;
- // 定義 API 函數(shù)原型
- HMODULE (__stdcall *MyLoadLibrary)(LPCTSTR);
- FARPROC (__stdcall *MyGetProcAddress)(HMODULE, LPCSTR);
- HMODULE (__stdcall *MyGetModuleHandle)(LPCTSTR);
- int (__stdcall *MyMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT);
- DWORD (__stdcall *MyGetModuleFileName)(HMODULE, LPTSTR, DWORD);
- // 對各函數(shù)地址進行賦值
- MyLoadLibrary = (HMODULE (__stdcall *)(LPCTSTR))
- pData->dwLoadLibrary;
- MyGetProcAddress = (FARPROC (__stdcall *)(HMODULE, LPCSTR))
- pData->dwGetProcAddress;
- MyGetModuleHandle = (HMODULE (__stdcall *)(LPCSTR))
- pData->dwGetModuleHandle;
- MyGetModuleFileName = (DWORD (__stdcall *)(HMODULE, LPTSTR, DWORD))
- pData->dwGetModuleFileName;
- // 加載 User32.dll
- HMODULE hModule = MyLoadLibrary(pData->User32Dll);
- // 獲得 MessageBoxA 函數(shù)的地址
- MyMessageBox = (int (__stdcall *)(HWND, LPCTSTR, LPCTSTR, UINT))
- MyGetProcAddress(hModule, pData->MessageBox);
- char szModuleFileName[MAX_PATH] = { 0 };
- MyGetModuleFileName(NULL, szModuleFileName, MAX_PATH);
- MyMessageBox(NULL, pData->Str, szModuleFileName, MB_OK);
- return 0;
- }
上面就是無DLL注入的全部代碼,編譯連接并運行它。啟動一個記事本程序來進行測試,可惜報錯了。問題出在哪里呢?VC6的默認編譯是Debug版本,這樣會加入很多調(diào)試信息。而某些調(diào)試信息并不存在于代碼中,而是在其他DLL模塊中。這樣,當執(zhí)行到調(diào)試相關的代碼時會訪問不存在的DLL模塊中的代碼,就導致了報錯。
將以上代碼使用Release方式進行編譯連接,然后可以無誤地執(zhí)行,如圖4所示。
圖4 Release方式下編譯注入成功
編譯的Debug版也可以進行無DLL的注入,只是實現(xiàn)起來略有不同。