《windows核心編程系列》十九談談使用遠程線程來注入DLL。

來源:互聯網
上載者:User

          windows內的各個進程有各自的地址空間。它們相互獨立互不干擾保證了系統的安全性。但是windows也為調試器或是其他工具設計了一些函數,這些函數可以讓一個進程對另一個進程進行操作。雖然他們是為調試器設計的,但是任何應用程式都可以調用它們 。接下來我們來談談使用遠程線程來注入DLL。

         從根本上說,DLL注入就是將某一DLL注入到某一進程的地址空間。該進程中的一個線程調用LoadLibrary來載入想要注入的DLL。由於我們不能直接控制其他進程內的線程,因此我們必須在其他進程內建立一個我們自己的線程。我們可以對新建立的線程加以控制,讓他調用LoadLibrary來載入DLL。windows提供了一個函數,可以讓我們在其他進程內建立一個線程:

       在其他進程內建立的線程被稱為:遠程線程,該進程被稱為遠程進程。

    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    );

       很容易吧。該函數除了第一個參數hProcess,標識要建立的線程所屬的進程外,其他參數與CreateThread的參數完全相同。

參數lpstartAddress是線程函數的地址。由於是在遠程進程建立的,所以該函數一定必須在遠程進程的地址空間內。

       現在知道了如何在另一個進程建立一個線程,那麼我們如何讓該線程載入我們的DLL呢?

       先別急著讓線程調用LoadLibrary載入DLL,現在要考慮的是如何讓線程運行起來,即為線程選擇線程函數。因為線程是在其他進程內啟動並執行,所以該線程函數必須符合以下條件:

       1:該函數符合線程函數的原型,

       2:存在於遠程線程地址空間內。

       仔細分析下,遠程線程的任務只有一個。就是調用LoadLibray載入DLL。

       既然如此可不可以讓LoadLibrary直接作為線程函數呢?

         先看第一個條件:函數簽名是否相同。你還別說,除了參數類型有點不一樣外,其他一摸一樣的。由於參數類型可以通過強轉實現,所以第一個條件是滿足的。

         再看第二個條件:該函數是否在遠程進程地址空間內。用屁股想一下我們都知道肯定在。另外他們都有相同的函數呼叫慣例,也就是說他們的參數傳遞是從右至左壓棧的,有子程式平衡堆棧。OK,太棒了。使用LoadLibrary作為線程函數真的是太方便了 。

       難道是微軟故意為我們這樣設計的?無從知曉。但在這裡要謝謝發現這一技巧的牛人。

      查看MSDN可以發現LoadLibrary並不是一個API,它其實是一個宏。

     在WinBase.h可以發現這樣一句話:

      #ifdef UNICODE

      #define LoadLibrary LoadLibraryW

     #else

     #define LoadLibrary LoadLibraryA

     #endif

     明白了嗎?實際上有兩個Load*函數,他們的唯一區別就是參數類型不同。如果DLL檔案名稱是以ANSI形式儲存的,我們就必須調用LoadLibraryA,如果是UNICODE形式儲存的我們就必須調用LoadLibraryW。

      接下來我們要做的事情就簡單了,只需要調用CreateThread函數,傳給標識線程函數的參數LoadLibraryA或是LoadLibraryW。然後將我們要遠程進程載入的DLL的路徑名的地址作為參數傳給它。哈哈,很興奮吧!一切都是那麼的順!

       不要高興的太早。你就沒發現哪有不對的地方嗎?傳給線程函數的參數是DLL路徑名的地址。但是該地址是在我們進城內的。如果遠程進程引用此地址的資料,很可能會導致訪問違規,遠程進程被終止。怎麼樣很嚴重吧。但這也給我們一個破壞其他進程的思路。哈哈。自己發揮吧!

      為瞭解決這個問題,我們應該將該字串放到遠程地址的地址空間去。有沒有相應的函數呢?當然有!

      首先應該在遠程進程的地址空間分配一塊兒記憶體。如何做呢!或許你很熟悉VirtualAlloc,但是他沒有這個功能。他兄弟VirtualAllocEx可以解決這個問題。看原型:

    LPVOID WINAPI VirtualAllocEx(      __in      HANDLE hProcess,      __in_opt  LPVOID lpAddress,      __in      SIZE_T dwSize,      __in      DWORD flAllocationType,      __in      DWORD flProtect    );

     hProcess應該知道是幹嘛的吧。他就是標識你要想在那個進程的地址空間申請記憶體的進程控制代碼。其他參數跟VirtualAlloc完全相同。此處不再介紹。

        當然知道如何申請還有知道如何釋放!看他搭檔:VirtualFreeEx

    BOOL WINAPI VirtualFreeEx(      __in  HANDLE hProcess,      __in  LPVOID lpAddress,      __in  SIZE_T dwSize,      __in  DWORD dwFreeType    );

    與VirtualFree的區別這隻是多一個進程控制代碼。

    現在申請空間的任務完成了,要怎麼樣將本進程的資料複製到另外一個進程呢?可以使用ReadProcessMemory和WriteProcessMemory

    BOOL WINAPI ReadProcessMemory(      __in   HANDLE hProcess,      __in   LPCVOID lpBaseAddress,      __out  LPVOID lpBuffer,      __in   SIZE_T nSize,     __out  SIZE_T *lpNumberOfBytesRead    );


 

    BOOL WINAPI WriteProcessMemory(      __in   HANDLE hProcess,      __in   LPVOID lpBaseAddress,      __in   LPCVOID lpBuffer,      __in   SIZE_T nSize,      __out  SIZE_T *lpNumberOfBytesWritten    );

    由於他們簽名類似,此處放在一塊介紹。

    hProcess是用來標識遠程進程的。

    lpBaseAddress是在遠程進程地址空間的地址,是VirtualAllocEx的傳回值。

    lpBuffer是在本進程的記憶體位址。此處也就是DLL路徑名的地址。

    nSize為要傳輸的字串。

    lpNumberOfByteRead和lpNumberOfByteWrite為實際傳輸的位元組數。

     注意:當調用WriteProcessMemory時有時會導致失敗。此時可以嘗試調用VirtualProtect來修改寫入頁面的屬性,寫入之後再改回來。

    到此為止,看起來沒啥東西了,但是還有一個比較隱晦的問題,如果不對PE檔案格式和DLL載入的方式有所瞭解的話是很難發現的。

       我們知道匯入函數的真真實位址是在DLL載入的時候獲得的。載入程式從匯入表取得每一個匯入函數的函數名(字串),然後在被載入到進程地址空間的DLL中查詢之後,填到匯入表的相應位置(IAT)的。也就是說在運行之前我們並不知道匯入函數的地址(當然模組綁定過得除外)。那麼程式碼中是如何表示對匯入函數的調用呢?有沒有想過這個問題呢。

       你或許覺得應該是:CALL DWORD PTR[004020108]       (   [   ]內僅表示匯入函數地址,無實際意義)。

       由於程式的代碼在經過編譯串連之後就已經確定,而匯入表的地址如00402010是在程式啟動並執行時候獲得的。所以程式在調用匯入函數的時候並不能這樣實現。那到底是如何?的呢?

      [   ]內有一個確定的地址這是毋庸置疑的,但是他的值並不是匯入函數的地址,而是一個子程式的地址。該子程式被稱為轉換函式(thunk)。這些轉換函式用來跳轉到匯入函數。當程式調用匯入函數時,先會調用轉換函式,轉換函式從匯入表的IAT獲得匯入函數的真真實位址時在調用相應地址。

      所以對匯入函數的調用形如如下的形式:

    

                CALL  00401164                ;轉換函式的地址。                    。。。。。。       :00401164                。。。。。                    CALL DWORD PTR [00402010]    ;調用匯入函數。

 

    分析到這兒,我們也可以明白為什麼在聲明一個匯出函數的時候要加上_decllpec(dllimport)首碼。

原因是:編譯器無法區分應用程式是對一般函數的調用還是對匯入函數的調用。當我們在一個函數前加上此首碼就是告訴編譯器此函數來自匯入函數,編譯器就會產生如上的指令。而不是CALL XXXXXXXX的形式。

所以在寫一個輸出函數的時候一定要在函式宣告前加上修飾符:_decllpec(dllimport)。

 

         言歸正傳.之所以說這麼多,就是因為我們傳給CreateRemoteThread的線程函數LoadLibrary*,會被解析成我們進程內的轉換函式的地址。如果把這個轉換函式的地址作為線程函數的起始地址很可能導致訪問違規。解決方案是:強制代碼略過轉換函式而直接調用LoadLibrary*.

       這可以通過GetProAddress來實現。

    FARPROC WINAPI GetProcAddress(      __in  HMODULE hModule,      __in  LPCSTR lpProcName     );

     hModule是模組控制代碼。標誌某一模組。

    lpProcName是該模組內某一函數的函數名。

    它返回該函數在模組所屬進程地址空間的地址。

    如GetProcAddress(GetModuleHandle("Kernel.dll","LoadLibraryW"));

     此語句取得LoadLibrary在Kernel.dll所在進程空間的真真實位址。注意此時僅僅是取得在本進程Kernel.dll的地址和LoadLibraryW的地址。難道在遠程進程內也是一樣嗎?

      《windows核心編程》第五版 589頁第三段中說,”從作者的經驗來看,Kernel.dll映射到每個進程的地址都是相同的。“基於此,我們可以認為,我們調用此語句是取得了Kernel.dll和LoadLibraryW在遠程地址空間的地址。

       下面來介紹一個例子。通過遠程線程向explorer.exe進程注入DLL。

        explorer.exe:資源管理員進程。隨系統啟動而啟動,且一直運行。因此它經常被用來被當做遠程線程的寄主。

       步驟:

        1:獲得explorer進程的控制代碼。

          這可以通過調用CreatehlpSnapshot,獲得此時系統的一個快照。然後遍曆該快照。找到進程名稱為explorer.exe的進程。並得到起進程物件控點。

        看代碼:

      PROCESSENTRY32 pe32;pe32.dwSize=sizeof(pe32);HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPALL,0);int ret=Process32First(hSnapshot,&pe32);CString a;UpdateData();if(-1==m_processToFind.Find(".exe",0))  m_processToFind+=".exe";while(ret){if(pe32.szExeFile==m_processToFind){        a.Format("進程:%s找到,它的進程ID為:%d",m_processToFind,pe32.th32ProcessID);MessageBox(a);break ;}ret=Process32Next(hSnapshot,&pe32);}

 

HANDLE WINAPI CreateToolhelp32Snapshot(  __in  DWORD dwFlags,  __in  DWORD th32ProcessID);

       該函數用於擷取指定進程的快照,以及該進程使用的堆,線程等。

       dwFlags用來表示此快照中包含的項目。具體參考MSDN。

        此處傳入TH32CS_SNAPALL,表示此快照包括系統中所有的進程和線程,以及在th32ProcessID中指定的進程的各模組和線程的資訊。

        th32ProessID指定要包括到此快照的進程ID,當傳入0時表示當前進程。

執行成功返回快照控制代碼。否則返回INVALID_HANDLE_VALUE。可以調用GetLastError查看更多錯誤資訊。

BOOL WINAPI Process32First(  __in     HANDLE hSnapshot,  __inout  LPPROCESSENTRY32 lppe);
BOOL WINAPI Process32Next(  __in   HANDLE hSnapshot,  __out  LPPROCESSENTRY32 lppe);

         以上兩個函數,用於在CreateHlpSnapshot中遍曆各項。用法請參考上例,其他資訊請參考MSDN。
          2:獲得explorer的進程ID之後,還要調用OpenProcess來獲得該進程的控制代碼。函數執行成功返回進程控制代碼。

      HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,pe32.th32ProcessID); 

         3:在explorer的地址空間中申請儲存要注入的DLL的路徑名的空間。

      PVOID addr=VirtualAllocEx(hProcess,NULL,50,MEM_COMMIT,  PAGE_READWRITE);  if(addr==NULL)  {CString a;int ret=GetLastError();a.Format("在遠程進程申請空間失敗!錯誤碼為:%d",ret);MessageBox(a);  }   else   { MessageBox("遠程進程地址空間中申請空間成功!");   }             

     4:將路徑名寫入在explorer進程申請的空間。

char path[50]="F:\\injectDll.dll";int retval=WriteProcessMemory(hProcess,addr,(LPVOID)path,sizeof(path),NULL);if(retval){MessageBox("寫入成功!");}elseMessageBox("寫入失敗!");

      5:建立遠程線程。

//獲得LoadLibraryA在遠程進程中的地址。(與本進程的地址相同。)

PTHREAD_START_ROUTINE pfnThread=(PTHREAD_START_ROUTINE)                            GetProcAddress(GetModuleHandle("kernel32.DLL"),"LoadLibraryA");

HANDLE hRemoteThread=CreateRemoteThread(        hProcess,//in   HANDLE hProcess,        NULL,      0,//__in   SIZE_T dwStackSize,        pfnThread,  addr,       0,       NULL); 
if(hRemoteThread==INVALID_HANDLE_VALUE) {  MessageBox("遠程線程穿件失敗!"); } else {  MessageBox("遠程線程建立成功!"); }

6:建立要注入到遠程進程的DLL。此處不再介紹。可以參考《windows核心編程系列》談談DLL基礎。

 如果在注入的DLL建立一個線程,就可以執行我們想讓它做的工作。

比如監控某程式的運行,一旦程式運行,就將另一個DLL載入到此進程。此DLL會掛在全域鉤子,獲得使用者鍵盤的動作。這也是鍵盤盜取qq的原理。可以自己發揮啊。

 

     到此為止,關於遠程線程就介紹完畢。

     參考自《windows核心編程》第五版 第二十二章 ,《加密與解密》第二版 段鋼著,第十章

     以上僅僅在參考各書籍的基礎之上加以總結。如有錯誤,請不吝賜教。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.