close
在WINDOWS中,每個進程都有自己獨立的地址空間,這樣一個應用程序就無法進入另一個進程的地址空間而不會破壞另一個進程的運行,這樣使得系統更加的穩定。但這樣一來,相反的,如果我們要對我們感興趣的進程進行操作也就變得複雜起來。比如,我們要為另一個進程創建的窗口建立子類或是要想從其中一個感興趣的進程中取得一些有趣的信息(比如你想得到WIN2000用戶登錄的密碼)。而DLL注入技術就是正好可以解決這些問題。DLL注入就是將DLL插入到其它你指定的進程的地址空間中,使得我們可以對感興趣的進程進行操作。 在我們的DLL注入到指定的進程空間時,為了可以使我們更清楚地看到它已經成功對注入到了指定的進程空間,所以我們有需要使用一個簡單的工具來查看指定的進程空間中所載入的所有模塊,以便確定我們的DLL是否已經成功注入。如果你系統中裝有WINDOWS優化大師,那麼你可以利用它提供的進程工具來查看,沒有也沒關係,我用BCB寫了一個小工具,雖然不怎麼方便,但也可以清楚地看到指定進程空間中的所有載入模塊。 該工具的主要代碼如下: //--------------------------------------------------------------------------- void __fastcall TfrmMain::btLookClick(TObject *Sender) { DWORD dwProcessId; BOOL bRet; MODULEENTRY32 hMod = {sizeof(hMod)}; HANDLE hthSnapshot = NULL; BOOL bMoreMods = FALSE; ListView->Clear(); if (Edit->Text == "") return; else dwProcessId = StrToInt(Edit->Text); // 為進程建立一個快照 hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId); if (hthSnapshot == NULL) { MessageBox(Handle,("CreateToolhelp32Snapshot failed with error " + IntToStr(GetLastError())).c_str(),"Error!", MB_ICONINFORMATION + MB_OK); return; } // 獲取模塊列表中的模塊 bMoreMods = Module32First(hthSnapshot, &hMod); if (bMoreMods == FALSE) { MessageBox(Handle,("Module32First failed with error " + IntToStr(GetLastError())).c_str(),"Error!", MB_ICONINFORMATION + MB_OK); return; } for (; bMoreMods; bMoreMods = Module32Next(hthSnapshot, &hMod)) { TListItem *Item; Item = ListView->Items->Add(); Item->Caption = String(hMod.szExePath); Item->ImageIndex = 0; } // 關閉句柄 CloseHandle(hthSnapshot); } 接下來就開始我們的正題吧。 DLL注入主要有三種方法,即應用HOOK技術、創建遠程線程和特洛伊DLL三種。 一、應用HOOK技術進行DLL注入 我原來寫過有關HOOK的介紹,如果你看過了或者是以前寫過HOOK程序,那麼你已經會這種DLL注入了。它其它就是為系統或某個線程安裝一個鉤子。這裡要說的是,如果是全局鉤子,那麼你的DLL將會在進程調用時載入到任意一個調用的進程的地址空間中,這樣是相當浪費資源的。因此我在下載的演示中就只對某一個指定的線程安裝線程鉤子。 1、用BCB建立一個DLL工程(如果你用的是VC或其它,請自己對照),輸入以下代碼: //=========================================================================== // 文件: UnitLib.cpp // 說明: 演示利用鉤子技術進行DLL注入. // 將本DLL中的代碼注入到指定的進程空間. // 作者: 陶冶(無邪) //=========================================================================== // 函數聲明 extern "C" __declspec(dllexport) __stdcall bool SetHook(DWORD dwThreadId); extern "C" __declspec(dllexport) __stdcall LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam); static HHOOK hHook = NULL; // 鉤子句柄 static HINSTANCE hInst; // 當前DLL句柄 int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { hInst = hinst; return 1; } //--------------------------------------------------------------------------- // 安裝鉤子函數 bool __declspec(dllexport) __stdcall SetHook(DWORD dwThreadId) { if (dwThreadId != 0) { MessageBox(NULL, ("DLL已經注入!\nThreadId = " + IntToStr(dwThreadId)).c_str(),"DLL", MB_ICONINFORMATION + MB_OK); // 安裝指定線程的鉤子 hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MyProc, hInst,dwThreadId); if (hHook != NULL) return true; }else { MessageBox(NULL, "DLL即將從記事本進程空間中撤出!","DLL", MB_ICONINFORMATION + MB_OK); return (UnhookWindowsHookEx(hHook)); } return true; } // 鉤子函數 LRESULT CALLBACK __declspec(dllexport) __stdcall MyProc(int nCode, WPARAM wParam, LPARAM lParam) { // 因為只是演示DLL注入,所以這裡什麼也不做,交給系統處理 return (CallNextHookEx(hHook, nCode, wParam, lParam)); } //--------------------------------------------------------------------------- 該DLL中有兩個函數,一個為安裝鉤子函數(SetHook),另一個為鉤子函數(MyProc)。其中安裝鉤子函數提供了一個參數,由該參數指定安裝到哪個線程,如果該參數為0,則卸載鉤子。 編譯該工程,即生成我們要用來注入到指定進程中的DLL文件了。 2、建立測試工程。用BCB建立一個應用程序工程,在窗體中添加兩個按鈕,一個用來安裝線程鉤子,一個用來卸載。代碼如下: //--------------------------------------------------------------------------- // SetHook函數原型聲明 typedef BOOL (WINAPI *LPSETHOOK)(unsigned long dwThreadId); //--------------------------------------------------------------------------- __fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- // 安裝鉤子 void __fastcall TfrmMain::Button1Click(TObject *Sender) { String szPath; LPSETHOOK lproc; HANDLE hDll; BOOL bRet; PROCESS_INFORMATION info; STARTUPINFO start; memset(&start, 0, sizeof(start)); // 取得要載入的DLL文件名 szPath = Application->ExeName; szPath = szPath.SubString(0, szPath.Length() - String(StrRScan(szPath.c_str(),'\\')).Length()); szPath = szPath + "[url=file://\\DllLib.dll]\\DllLib.dll[/url]"; // 載入DLL hDll = LoadLibrary(szPath.c_str()); if (hDll != NULL) { lproc = (LPSETHOOK)GetProcAddress(hDll,"SetHook"); if (lproc != NULL) { // 因為沒有適當的工具可以取得線程ID,也為了簡單起見,所以這裡新創建了一個記事本進程,以便取得它的線程ID,對其安裝鉤子,把我們的DLL注入到記事本進程中。 bRet = CreateProcess(NULL, "c:\\winnt\\system32\\notepad.exe", NULL, NULL, TRUE, 0, NULL, NULL, &start, &info); if (bRet != 0) { if((*lproc)(info.dwThreadId) == false) ShowMessage("Sethook failed with error " + IntToStr(GetLastError())); } else { ShowMessage("CreateProcess failed with error " + IntToStr(GetLastError())); } } } } //--------------------------------------------------------------------------- // 卸載鉤子 void __fastcall TfrmMain::Button2Click(TObject *Sender) { String szPath; LPSETHOOK lproc; HANDLE hDll; szPath = Application->ExeName; szPath = szPath.SubString(0, szPath.Length() - String(StrRScan(szPath.c_str(),'\\')).Length()); szPath = szPath + "[url=file://\\DllLib.dll]\\DllLib.dll[/url]"; hDll = LoadLibrary(szPath.c_str()); if (hDll != NULL) { lproc = (LPSETHOOK)GetProcAddress(hDll,"SetHook"); if (lproc != NULL) (*lproc)(0); } } //--------------------------------------------------------------------------- 接下來生成可執行文件,點擊第一個安裝鉤子按鈕,然後你就可以用我們最開始寫的查看模塊的工具來查看了,你將會在模塊中看到你剛才DLL的路徑及文件名,這表明我們已經成功地將自己的DLL注入到了記事本進程空間。點擊卸載按鈕後,再查看記事本進程中的模塊,將不會看到我們DLL文件的完整文件名,這表明已經成功撤消了對記事本進程的注入。 二、利用遠程線程來進行DLL注入 這種方法同前一種方法相比,要顯得複雜一些,並且這種方法只能在WIN2000中使用(XP,和最新的2003不知道)。具體步驟如下: 1)、取得遠程進程的進程ID; 2)、在遠程進程空間中分配一段內存用來存放要注入的DLL完整路徑; 3)、將要注入的DLL的路徑寫到剛才分配的遠程進程空間; 4)、從Kernel32.dll中取得LoadLibray的地址; 5)、調用CreateRemoteThread函數以從Kernel32.dll中取得的LoadLibrary函數的地址為線程函數的地址,以我們要注入的DLL文件名為參數,創建遠程線程; 在第二三步中,為什麼要把我們要注入的DLL的文件名寫到遠程進程的地址空間進行操作,《WINDOWS核心編程》中是這樣描述的: 「(要注入的DLL文件名)字符串是在調用進程的地址空間中。該字符串的地址已經被賦予新創建的遠程線程,該線程將它傳遞給L o a d L i b r a r y A。但是,當L o a d L i b r a r y A取消對內存地址的引用時, D L L路徑名字符串將不再存在,遠程進程的線程就可能引發訪問違規」; 至於第四步中為什麼不直接對LoadLibrary進行調用,《WINDOWS核心編程》中是這樣描述的: 「如果在對C r e a t e R e m o t e T h r e a d的調用中使用一個對L o a d L i b r a r y A的直接引用,這將在你的模塊的輸入節中轉換成L o a d L i b r a r y A的形實替換程序的地址。將形實替換程序的地址作為遠程線程的起始地址來傳遞,會導致遠程線程開始執行一些令人莫名其妙的東西。其結果很可能造成訪問違規。」 好了,下面開始我們的例子。 |
全站熱搜
留言列表