用DLL控制Windows中程式的方法(轉)

RegisterForBlog發表於2007-08-11
用DLL控制Windows中程式的方法(轉)[@more@]

  在Microsoft Windows中,每個程式都有它自己的私有地址空間。當使用指標來引用記憶體時,指標的值將引用你自己程式的地址空間中的一個記憶體地址。你的程式不能建立一銎湟用屬於另一個程式的記憶體指標。因此,如果你的程式存在一個錯誤,改寫了一個隨機地址系記憶體,那麼這個錯誤不會影響另一個程式使用的記憶體。Windows 98 在Windows 98下執行的各個程式共享2 GB的地址空間,該地址空間從0x80000000至0xFFFFFFFF。只有記憶體映像檔案和統元件才能對映到這個區域。

  

  獨立的地址空間對於程式設計人員和使用者來說都是非常有利的。對於程式設計人員來說,系統更容易捕獲隨意的記憶體讀取和寫入操作。對於使用者來說,作業系統將變得更加健壯,因個應用程式無法破壞另一個程式或作業系統的執行。當然,作業系統的這個健壯特性是要凍代價的,因為要編寫能夠與其他程式進行通訊,或者能夠對其他程式進行操作的應用程式難得多。

  

  有些情況下,必須打破程式的界限,訪問另一個程式的地址空間,這些情況包括:

  

  當你想要為另一個程式建立的視窗建立子類時。

  當你需要除錯幫助時(例如,當你需要確定另一個程式正在使用哪個DLL時)。

  當你想要掛接其他程式時。

  

  這裡將介紹兩種方法,可以用來將DLL插入到另一個程式的地址空間中。一旦你的DLL進入另一個程式的地址空間,就可以對另一個程式為所欲為。這一定會使你非常害怕,因此,究竟應該怎樣做,要三思而後行。

  

  1 插入DLL:一個例子

  假設你想為由另一個程式建立的視窗建立一個子類。你可能記得,建立子類就能夠改變視窗的行為特性。若要建立子類,只需要呼叫SetWindowLongPtr函式,改變視窗的內嬋中的視窗過程地址,指向一個新的(你自己的) WndProc。Platform SDK文件說,應用程蠆能為另一個程式建立的視窗建立子類。這並不完全正確。為另一個程式的視窗建立子類的關鍵問題與程式地址空間的邊界有關。

  

  當呼叫下面所示的SetWindowsLongPtr函式,建立一個視窗的子類時,你告訴系統,傳送到或者顯示在hwnd設定的視窗中的所有訊息都應該送往MySubclassProc,而不是送往口的正常視窗過程:

  

  程式A中程式碼:

  EXE file:

  LRESUlT WndProc(HWND hend,UNIT uMsg,...){.....}

  USER32.DLL file

  LONG DispatchMessage(CONST MSG*msg)

  {

  LONG lRet;

  WNDPROC lpfnWndProc=

  (WNDPROC)GetWindowLongPtr(msg,hwnd,GWLP_WNDPROC

  );

  lRet=lpfnWndProc(msg.hwnd,msg.message,msg.wParam,mag.

  lParam);

  return lRet;

  }

  程式B中:

  EXE file

  void Somefunc(void)

  {

  HWND hwnd=Findwindow("class A",NULL);

  SetWindowLongPtr(hwnd,GWLP_WNDPROC,MySubclassProc);

  }

  USER32.DLL file ......

  

  

  換句話說,當系統需要將訊息傳送到指定視窗的WndProc時,要檢視它的地址,然後直接呼叫WndProc。在本例中,系統發現MySubclassProc函式的地址與視窗相關聯,因此就直接呼叫MySubclassProc函式。

  

  為另一個程式建立的視窗建立子類時遇到的問題是,建立子類的過程位於另一個地址空間中。下面舉個例子,說明視窗過程是如何接受訊息的。程式A正在執行,並且已經建立了一個視窗。檔案User32.dll被對映到程式A的地址空間中。

  

  對User32.dll檔案的對映是為了接收和傳送在程式A中執行的任何執行緒建立的任何視窗中傳送和顯示的訊息。當User32.dll的映像發現一個訊息時,它首先要確定視窗的WndProc的地址,然後呼叫該地址,傳遞視窗的控制程式碼、訊息和wParam和lParam值。當WndProc處理該訊息後,User32.dll便迴圈執行,並等待另一個視窗訊息被處理。

  

  程式B中的執行緒試圖為程式A中的執行緒建立的視窗建立子類現在假設你的程式是程式B,你想為程式A中的執行緒建立的視窗建立子類。你在程式B中的程式碼必須首先確定你想要建立子類的視窗的控制程式碼。這個操作使用的方法很多。上面的例子只是呼叫FindWindow函式來獲得需要的視窗。接著,程式B中的執行緒呼叫SetWindowLongPtr函式,試圖改變視窗的WndProc的地址。請注意我說的“試圖”二字。這個函式呼叫⒉進行什麼操作,它只是返回NULL。SetWindowLongPtr函式中的程式碼要檢視是否有一個程式正在試圖改變另一個程式建立的視窗的WndProc地址,然後將忽略這個函式的呼叫。

  

  如果SetWindowLongPtr函式能夠改變視窗的WndProc,那將出現什麼情況呢?系統將把MySubclassProc的地址與特定的視窗關聯起來。然後,當有一條訊息被髮送到這個視窗中時,程式A中的User32程式碼將檢索該訊息,獲得MySubclassProc的地址,並試圖呼叫這個地址。但是,這時可能遇到一個大問題。MySubclassProc將位於程式B的地址空間中,而程式A是個活動程式。顯然,如果User32想要呼叫該地址,它就要呼叫程式A的地址空間中的一個地址,這就可能造成記憶體訪問的違規。

  為了避免這個問題的產生,應該讓系統知道M y S u b c l a s s P r o c是在程式B的地址空間中,然後,在呼叫子類的過程之前,讓系統執行一次上下文轉換。M i c r o s o f t沒有實現這個輔助函式功能,原因是:應用程式很少需要為其他程式的執行緒建立的視窗建立子類。大多數應用程式只是為它們自己建立的視窗建立子類,Wi n d o w s的記憶體結構並不阻止這種建立操作。

  

  切換活動程式需要佔用許多C P U時間。

  

  程式B中的執行緒必須執行M y S u b c l a s s P r o c中的程式碼。系統究竟應該使用哪個執行緒呢?是現有的執行緒,還是新執行緒呢?

  

  U s e r 3 2 . d l l怎樣才能說明與視窗相關的地址是用於另一個程式中的過程,還是用於同一個程式中的過程呢?

  

  由於對這個問題的解決並沒有什麼萬全之策,因此M i c r o s o f t決定不讓S e t Wi n d o w s L o n g P t r改變另一個程式建立的視窗過程。不過仍然可以為另一個程式建立的視窗建立子類―只需要用另一種方法來進行這項操作。這並不是建立子類的問題,而是程式的地址空間邊界的問題。如果能將你的子類過程的程式碼放入程式A的地址空間,就可以方便地呼叫S e t Wi n d o w L o n g P t r函式,將程式A的地址傳遞給M y S u b c l a s s P r o c函式。我將這個方法稱為將D L L“插入”程式的地址空間。有若干種方法可以用來進行這項操作。下面將逐個介紹它們

  

  2.透過掛鉤插入DLL

  可以使用掛鉤將D L L插入程式的地址空間。為了使掛鉤能夠像它們在1 6位Wi n d o w s中那樣工作,M i c r o s o f t不得不設計了一種方法,使得D L L能夠插入另一個程式的地址空間中。

  

  下面讓我們來看一個例子。程式A(類似Microsoft Spy++的一個實用程式)安裝了一個掛鉤W N _ G E T M E S S A G E,以便檢視系統中的各個視窗處理的訊息。該掛鉤是透過呼叫下面的S e t Wi n d o w s H o o k E x函式來安裝的:

  

  第一個引數W H _ G E T M E S S A G E用於指明要安裝的掛鉤的型別。第二個引數G e t M s g P r o c用於指明視窗準備處理一個訊息時系統應該呼叫的函式的地址(在你的刂房佔渲校5三個引數h i n s t D l l用於指明包含G e t M s g P r o c函式的D L L。在Wi n d o w s中,D L L的h i n s t D l l的值用於標識DLL被對映到的程式的地址空間中的虛擬記憶體地址。最後一個引數0用於指明要掛接的執行緒。

  

  對於一個執行緒來說,它可以呼叫S e t Wi n d o w s H o o k E x函式,傳遞系統中的另一個執行緒的I D。透過為這個引數傳遞0,就告訴系統說,我們想要掛接系統中的所有G U I執行緒。

  

  現在讓我們來看一看將會發生什麼情況:

  

  1) 程式B中的一個執行緒準備將一條訊息傳送到一個視窗。

  

  2) 系統檢視該執行緒上是否已經安裝了W H _ G E T M E S S A G E掛鉤。

  

  3) 系統檢視包含G e t M s g P r o c函式的D L L是否被對映到程式B的地址空間中。

  

  4) 如果該D L L尚未被對映,系統將強制該D L L對映到程式B的地址空間,並且將程式B中的D L L映像的自動跟蹤計數遞增1。

  

  5) 當D L L的h i n s t D l l用於程式B時,系統檢視該函式,並檢查該D L L的h i n s t D l l是否與它用於程式A時所處的位置相同。

  

  如果兩個h i n s t D l l是在相同的位置上,那麼G e t M s g P r o c函式的記憶體地址在兩個程式的地址空間中的位置也是相同的。在這種情況下,系統只需要呼叫程式A的地址空間中的G e t M s g P r o c函式即可。

  

  如果h i n s t D l l的位置不同,那麼系統必須確定程式B的地址空間中G e t M s g P r o c函式的虛擬記憶體地址。這個地址可以使用下面的公式來確定:

  

  將GetMsgProc A的地址減去hinstDll A的地址,就可以得到G e t M s g P r o c函式的地址位移(以位元組為計量單位)。將這個位移與hinstDll B的地址相加,就得出G e t M s g P r o c函式在用於程式B的地址空間中該D L L的映像時它的位置。

  

  6) 系統將程式B中的D L L映像的自動跟蹤計數遞增1。

  

  7) 系統呼叫程式B的地址空間中的G e t M s g P r o c函式。

  

  8) 當G e t M s g P r o c函式返回時,系統將程式B中的D L L映像的自動跟蹤計數遞減1。

  

  注意,當系統插入或者對映包含掛鉤過濾器函式的D L L時,整個D L L均被對映,而只是掛鉤過濾器函式被對映。這意味著D L L中包含的任何一個函式或所有函式現在都存在,並且可以從程式B的環境下執行的執行緒中呼叫。

  

  若要為另一個程式中的執行緒建立的視窗建立子類,首先可以在建立該視窗的掛鉤上設定一個W H _ G E T M E S S


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10763080/viewspace-948401/,如需轉載,請註明出處,否則將追究法律責任。

相關文章