1. 程式人生 > >用跨程序子類化技術實現對其它程序訊息的攔載

用跨程序子類化技術實現對其它程序訊息的攔載

  大家都知道每個視窗都有預設的視窗函式來進行對視窗訊息的處理.
  而子類化技術就是替換視窗的視窗函式為自己定義的函式的技術.例如下面的程式碼:
var
  Form1: TForm1;
  OldWndProc: Pointer;
implementation

{$R *.dfm}
function NewWndProc(hHwnd, Msg, wParam, lParam: LongWORD): Longint; stdcall;
begin
  if Msg=WM_CLOSE then
    exit;
  Result := CallWindowProc(OldWndProc, hHwnd, Msg, wParam, lParam);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  {儲存舊的視窗函式地址}
  OldWndProc := Pointer(GetWindowLong(Self.Handle, GWL_WNDPROC));
  {設定新的視窗函式為自定義函式}
  SetWindowLong(Self.Handle, GWL_WNDPROC, Longint(@NewWndProc));
end;
  這樣在視窗建立時就對視窗實現了子類化,這時按下視窗的關閉按鈕就會發現關不了視窗,因為新的視窗處理函式把WM_CLOSE訊息過濾掉了,要取消子類化,只需要簡單的把以前的視窗函式恢復過來就可以了.SetWindowLong(Self.Handle, GWL_WNDPROC, Longint(OldWndProc));

  現在看來似乎很簡單,只要對其它程序中的目標視窗進行子類化就可以實現對其訊息的攔載監視了.但是在WIN32下,每一個程序都有自己獨立的記憶體空間,新的視窗函式必須和目標視窗在同一個程序內,直接使用SetWindowLong(其它程序中視窗的控制代碼, GWL_WNDPROC, 新視窗函式)就會失敗,所以就要想辦法把我們的視窗函式程式碼放到目標程序內,這兒有二個辦法,一是使用CreateRemoteThread在目標程序內建立執行緒,但這函式只在NT及以上作業系統實現,而且還要涉及到API地址重定位等問題,很麻煩(請參考http://www.csdn.net/develop/Read_Article.asp?Id=21079

).另一個方法就是使用HOOK技術(SetWindowsHookEx,如果不知道,請先參考HOOK技術方面的文章),大家都知道,對其它程序進行HOOK時,此程序會自動載入HOOK過程所在的DLL,如果我們把視窗函式也放在DLL中,那視窗函式就相當於載入到了目標程序的地址空間中了,這方法簡單易行.在這裡我們就採用HOOK技術來實現跨程序子類化.

  最後一個問題是如何在DLL中實現全域性變數,因為DLL中的變數在每個程序載入這個DLL時都申請新的空間來存放變數,所以DLL中的變數在各個程序內不一樣,可以利用記憶體檔案對映,WM_COPYDATA等方法來實現全域性變數.這兒採用記憶體檔案對映.

  現在需要的知識都已瞭解了,就讓我們來看具體的程式碼吧(這兒是把所有函式放在一個DLL中):
library Hook;

uses
  SysUtils,windows, Messages;

const
  WM_UNSUBCLASS = WM_USER + 1001;  {解除安裝子類化訊息}
  WM_NEWMESSAGE = WM_USER + 1002;  {通知監視視窗攔到了新訊息}
  HOOK_EVENT_NAME = 'MyHook';

type
  PMyDLLVar = ^TMyDLLVar;
  TMyDLLVar = record
    SubClass: Boolean;                 {是否已經子類化}
    HookWindow, SpyWindow: LongWORD;   {要安裝HOOK的視窗及用於接收訊息的視窗}
    hHook: LongWORD;                   {HOOK控制代碼}
    OldWndProc: pointer;               {舊的視窗過程}
    MsgHwnd: LongWORD;
    Msg: TMessage;
  end;

var
  DLLData: PMyDLLVar;

{---------------------------------------}
{函式名:NewWndProc
{函式功能:新的視窗過程
{函式引數:hHwnd:視窗控制代碼 Msg:訊息ID
{         wParam, lParam:訊息引數
{函式返回值:下一個視窗過程的返回值
{---------------------------------------}
function NewWndProc(hHwnd, Msg, wParam, lParam: LongWORD): Longint; stdcall;
begin
  if Msg = WM_UNSUBCLASS then   {如果收到解除安裝子類化訊息就恢復以前的WndProc}
  begin
    SetWindowLong(DLLData^.HookWindow, GWL_WNDPROC, longint(DLLData^.OldWndProc));
    exit;
  end;
  {這兒是把收到的訊息放在對映的記憶體中,我們自己的程式可以通過讀這個記憶體來得到監視到的訊息.}
  DLLData^.Msg.Msg := Msg;           
  DLLData^.Msg.WParam := wParam;
  DLLData^.Msg.LParam := lParam;
  DLLData^.MsgHwnd := hHwnd;
  {給監視視窗傳送攔載新訊息的訊息}
  SendMessage(DLLData^.SpyWindow, WM_NEWMESSAGE, 0, 0);
  {這兒可以新增自己對目標程序訊息處理的程式碼,因為己經是在目標程序的地址空間內,現在可以為所
  欲為 ^_^)
  Result := CallWindowProc(DLLData^.OldWndProc, hHwnd, Msg, wParam, lParam);
end;

{------------------------------------}
{過程名:HookProc
{過程功能:HOOK過程
{過程引數:nCode, wParam, lParam訊息的相
{         關引數
{------------------------------------}
procedure HookProc(nCode, wParam, lParam: LongWORD);stdcall;
var
  hEvent: THandle;
begin
  if not DllData^.SubClass then  {如果此視窗未子類化}
  begin                          {儲存視窗過程地址並子類化}
    if hEvent <> 0 then
    begin
      WaitForSingleObject(hEvent, INFINITE);
      CloseHandle(hEvent);
    end;
    DLLData^.OldWndProc := pointer(GetWindowLong(DLLData^.HookWindow, GWL_WNDPROC));
    SetWindowLong(DLLData^.HookWindow, GWL_WNDPROC, integer(@NewWndProc));
    DLLData^.SubClass := True;
    hEvent := OpenEvent(Synchronize, False, HOOK_EVENT_NAME);
  end;
  {呼叫下一個Hook}
  CallNextHookEx(DLLData^.hHook, nCode, wParam, lParam);
end;


{------------------------------------}
{函式名:InstallHook
{函式功能:在指定視窗上安裝HOOK
{函式引數:HWindow:要安裝HOOK的視窗
{         SWindow:用於接收訊息的視窗
{返回值:成功返回TRUE,失敗返回FALSE
{------------------------------------}
function InstallHook(HWindow, SWindow: LongWORD):Boolean;stdcall;
var
  ThreadID: LongWORD;
  hEvent: THandle;
begin
  Result := False;
  DLLData^.hHook := 0;
  DLLData^.HookWindow := HWindow;
  DLLData^.SpyWindow := SWindow;
  {得到指定視窗的執行緒ID}
  ThreadID := GetWindowThreadProcessId(HWindow, nil);
  {給指定視窗掛上鉤子}
  hEvent := CreateEvent(nil, True, False, HOOK_EVENT_NAME);
  DLLData^.hHook := SetWindowsHookEx(WH_GETMESSAGE, @HookProc, Hinstance, ThreadID);
  SetEvent(hEvent);
  CloseHandle(hEvent);
  if DLLData^.hHook > 0 then Result := True;  {是否成功HOOK}
end;

{------------------------------------}
{過程名:UnHook
{過程功能:解除安裝HOOK
{過程引數:無
{------------------------------------}
procedure UnHook;stdcall;
begin
  {傳送解除安裝子類化訊息給指定視窗}
  SendMessage(DLLData^.HookWindow, WM_UNSUBCLASS, 0, 0);
  DLLData^.SubClass := False;
  {解除安裝Hook}
  UnhookWindowsHookEx(DLLData^.hHook);
end;

{------------------------------------}
{過程名:DLL入口函式
{過程功能:進行DLL初始化,釋放等
{過程引數:DLL狀態
{------------------------------------}
procedure MyDLLHandler(Reason: Integer);
var
  FHandle: LongWORD;
begin
  case Reason of
    DLL_PROCESS_ATTACH:
    begin            {建立檔案對映,以實現DLL中的全域性變數}
      FHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, $ff, 'MYDLLDATA');
      if FHandle = 0 then
      if GetLastError = ERROR_ALREADY_EXISTS then
      begin
        FHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, 'MYDLLDATA');
        if FHandle = 0 then Exit;
      end else Exit;
      DLLData := MapViewOfFile(FHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
      if DLLData = nil then
        CloseHandle(FHandle);
    end;
    DLL_PROCESS_DETACH:
      if Assigned(DLLData) then
      begin
        UnmapViewOfFile(DLLData);
        DLLData := nil;
      end;
    DLL_THREAD_ATTACH:;
    DLL_THREAD_DETACH:;
  end;
end;

{$R *.res}
exports
  InstallHook, UnHook, HookProc;

begin
  DLLProc := @MyDLLHandler;
  MyDLLhandler(DLL_PROCESS_ATTACH);
end.

  編譯這個DLL,然後在我們的程式中載入這個DLL,並呼叫InstallHook(目標視窗控制代碼, 自己視窗控制代碼)就可  以實現對目標視窗訊息的監視了(在接收到WM_NEWMESSAGE訊息時讀對映的記憶體),呼叫UnHook則可以解除安裝掉子類化和HOOK.具休的程式碼還請讀者自行編寫.