進程控制

 【前言】

  寫這篇文檔的時候由於我足夠菜,碰到了不少問題,多謝bkbll,a1rsupply和SobeIt的指點,還有TCH的辛勤勞動,纔有這篇文檔的誕生,本文中可能存在一些錯誤,這些錯誤都是由於我的失誤造成的,如果您有什麼意見和看法,歡迎來http://www.itaq.org指出,或者E-mail:[email protected]
  【概述】
  在服務器上實現對進程創建的控制有很大的意義,通過監控進程的創建,我們可以讓被允許運行的進程正確創建,而未被允許的程序則會創建失敗,這樣就可以防止未知***,病毒和蠕蟲對服務器的威脅。要實現上述目的,必須hook windows創建進程相關的API,根據《inside the windows NT》和《Native API Reference》中記載,加上softIce的實際跟蹤,windows創建進程的API調用流程如下:
  
  【代碼 】
  CreateProcessA-> CreateProcessW-> CreateProcessInternalW->…->最終調用ZwCreateProcess
  本文檔中我們選用CreateProcessW來實現我們的目的,當然你也可以使用其它幾個API。本文檔的演示代碼稍做改動可應用於任意Ring3函數。
  對於hook一個API而言,可使用的辦法有很多,本文選用改寫函數入口點的辦法來實現掛接CreateProcessW,更多的詳細資料請參閱SobeIt寫的《windows下hook API的幾種辦法》。
  
  【copy-on-write】
  最初試驗時,我使用softice的 a CreateProcessW改寫函數入口點的代碼,F5切換回windows之後發現一切如願以償,但是當我編寫程序修改CreateProcessW入口點代碼時,發現所做的改動僅對本進程有效,而對於系統的其他進程沒有產生任何影響。用softice跟蹤後發現本進程中CreateProcessW的虛擬地址被映射到了一個新的,與其它進程不同的物理地址上,如果你讀過Webcrazy的《copy-on-write機制》一文,就不難看出這是copy-on-write機制產生的影響。對於系統的dll,每個dll都被映射在不同進程的相同的虛擬地址上,而這些虛擬地址又指向相同的物理地址,通過這種機制,系統實現最低的資源消耗.當某個進程試圖改寫物理內存中的數據時,爲了不對其它進程產生影響,系統自動新分配一塊物理內存,把原物理內存中的數據複製過去,改寫,然後把改寫內存的那個進程的虛擬地址重新映射到新的物理內存上去,而其它進程則還是映射在原來的物理內存上,這就是“寫時複製技術”(copy-on-write),那麼系統是如何判斷何時應該使用copy-on-write呢?這是以虛擬地址的PTE來決定的,當PTE中copy-on-write標誌被置位時,任何對該虛擬地址的寫操作都將導致一個copy-on-writ
  
  【三種可行的辦法】
  爲了實現全局hook,我們不能被copy-on-write機制所限制住,目前我想到了三種辦法來達到我們的目的。
  1. 通過驅動來修改頁表項(PTE)的屬性,使CreateProcessW對應的虛擬地址失去copy-on-write的屬性,這樣在本身進程中對CreateProcessW入口點代碼的修改會對系統中所有進程生效,從而實現全局hook。
  
  2. 通過windows本身提供的一個對象\\phymem來對物理內存進行直接讀寫,先定位本身進程的Eprocess(KTEB)(PS:如何在Ring3下定位任意進程的Eprocess請參考我之前寫的《獲取進程的Eprocess》一文),獲得Eprocess之後,可以得到進程的頁目錄,然後利用\\phymem讀取存放頁目錄的物理內存的內容,再模擬操作系統進行虛擬地址->物理內存地址的轉換,最終得到CreateProcessW所對應的物理地址,利用\\phymem我們避開copy-on-write機制,直接改寫CreateProcessW。
  
  3. 通過最常規的手段來達到目的,先枚舉系統中所有進程,然後通過VirtualQueryEx,VirtualAllocEx,VirtualProtectEx等函數修改每個進程的頁面屬性,分配新的空間等。最後將我們的代碼用WriteProcessMemory寫到各進程的空間中,利用改寫CreateProcessW入口爲Jmp *******來跳到我們的代碼中,改變函數的執行流程。
  
  以上三個辦法中,方法1只是一個構想,還沒成爲現實,有空的話我回去試試看的,當然頁歡迎各位高手去實現,然後mail一份代碼給我:P方法2我寫了一份完整的代碼來實現它,但是在本文檔中不進行討論,否則文檔會變的很長,我將在另一份文檔中專門說明這種辦法的具體實現。方法3使本文討論的重點,下面就方法3進行詳細說明。
  
  查詢CreateProcessW的基址及屬性
  這裏我們使用VirtualQueryEx這個函數,其原型如下:
  SIZE_T 
  VirtualQueryEx
  (
  HANDLE hProcess,
  LPCVOID lpAddress,
  PMEMORY_BASIC_INFORMATION lpBuffer,
  SIZE_T dwLength
  );
  參數說明:
  HANDLE hProcess 想要查詢內存信息的進程句柄
  LPCVOID lpAddress 指向想要查詢內存區域的指針
  PMEMORY_BASIC_INFORMATION lpBuffer 指向MEMORY_BASIC_INFORMATION結構的指針
  SIZE_T dwLength lpBuffer的大小
  
  調用這個函數之後,相關的信息存放在lpBuffer指向的結構中
  
  修改CreateProcessW的頁屬性
  對於一個頁來說,有如下幾種屬性:
  PAGE_EXECUTE
  PAGE_EXECUTE_READ
  PAGE_EXECUTE_READWRITE
  PAGE_EXECUTE_WRITECOPY
  PAGE_NOACCESS
  PAGE_READONLY
  PAGE_READWRITE
  PAGE_WRITECOPY
  
  我們通過VirtualProtectEx來修改頁的屬性:
  BOOL 
  VirtualProtectEx
  (
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD flNewProtect,
  PDWORD lpflOldProtect
  );
  
  參數說明:
  HANDLE hProce
  ss 進程句柄
  LPVOID lpAddress 指向想要修改的內存區域的指針
  SIZE_T dwSize 修改的內存區域的大小
  DWORD flNewProtect 新的頁屬性
  PDWORD lpflOldProtect 指向保存老的頁屬性的內存的指針
  
  從後面的代碼中我們可以看到,爲了改寫函數入口點代碼,我們必須賦予它PAGE_EXECUTE_READWRITE屬性。
  
  在進程中分配可用空間
  光修改函數入口點代碼是不夠的。我們必須自己編寫一段code來接管CreateProcessW的工作,由於進程空間是相互隔離的,爲了達到全局hook的目標,我們必須向每個進程索要一塊空間來存放我們的代碼,這就要用到VirtualAllocEx這個函數了,VirtualAllocEx原型如下:
  LPVOID 
  VirtualAllocEx
  (
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD flAllocationType,
  DWORD flProtect
  );
  
  參數說明:
  HANDLE hProcess 進程句柄
  LPVOID lpAddress 指向分配內存區域的指針
  SIZE_T dwSize 分配的區域的大小
  DWORD flAllocationType 內存類型
  DWORD flProtect 新內存的屬性
  
  <將代碼寫入遠程進程空間>
  我們使用WriteProcessMemory這個函數來向遠程進程寫入我們的代碼和數據,其原型如下:
  BOOL 
  WriteProcessMemory(
  HANDLE hProcess,
  LPVOID lpBaseAddress,
  LPCVOID lpBuffer,
  SIZE_T nSize,
  SIZE_T* lpNumberOfBytesWritten
  );
  
  參數說明:
  HANDLE hProcess 進程句柄
  LPVOID lpBaseAddress 指向寫入地址的指針
  LPCVOID lpBuffer 指向寫入數據的指針
  SIZE_T nSize lpBuffer的大小
  SIZE_T* lpNumberOfBytesWritten 實際寫入的字節數
  
  
  編譯器的魔術
  我在使用WriteProcessMemory把我自己寫的一個函數JmpToAddress的內容寫入遠程進程空間時,發現無論我的JmpToAddress的內容是什麼,寫入空間的都是E9****這幾個字節,這令我非常困惑,從機器碼來看,這是一條相對跳轉指令。那麼它又是從何而來呢,爲了搞清楚這個問題,我用VC調試了一下,在watch窗口中輸入JmpToAddress,顯示出JmpToAddress的虛擬地址0x00410XXX,然後打開memory窗口,查看這段內存中存放的內容,發現確實是JmpToAddress的代碼,這就奇怪了,那神祕的E9****是從何而來呢,於是我請教了a1rsupply,他告訴我VC的調試版本會生成跳轉表,這下真相大白,原來是編譯器玩的魔術。
  
  爲了向遠程進程正確寫入代碼,我們必須自己計算真正的函數地址,下面我寫了一段代碼來計算真正的函數地址:
  __asm
  {
  pushad
  lea eax,JmpToAddress
  mov ecx,JmpToAddress
  shr ecx,8
  add eax,ecx
  add eax,5
  mov JmpAfterCalc,eax
  popad
  }
  
  解決麻煩的定位問題
  在編寫代碼的過程中,我遇到的另一個較大的問題就是如何定位地址。我編寫的JmpToAddress()函數如下:
  void __declspec(naked) JmpToAddress(void)
  {
  __a
  
  sm jmp [HookedAddr]
  }
  在本地進程中這句代碼沒什麼問題,但是當它被寫入遠端進程後便會產生種種問題,我們來看看它的彙編代碼,如下
  jmp [00401Cxxx]
  我們注意到本進程中這個虛擬地址裏存放的是HookedAddr的地址,但是在遠程進程中,這個地址指向的是別的什麼東西,jmp過去會產生不可預料的結果,爲了實現正確的行爲,我們先用WriteProcessMemory向遠程進程寫入HookedAddr的內容,然後用一個相對地址引用它
  void __declspec(naked) JmpAddress(void)
  {
  __asm call flag
  flag:
  __asm pop eax
  __asm add eax,0x0e
  __asm mov ebx,[eax]
  __asm jmp ebx
  }
  pop eax後,eax裏面存放的就是本條指令的虛擬的地址,加上一個固定值後,[eax]就是我們通過WritePr 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章