程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> 用VC編程阻止全局鉤子的加載

用VC編程阻止全局鉤子的加載

編輯:vc教程

先說一下全局鉤子是怎麼進入到我們的程序裡來的。假如有個程序A安裝了WH_GETMESSAGE的全局鉤子,鉤子函數在B.dll中,那麼當其它程序在調用GetMessage函數從自己的消息隊列中取消息的時候,系統發現程序A安裝了WH_GETMESSAGE的全局鉤子,就會檢查調用GetMessage的進程是否加載了B.dll,如果沒有,就調用LoadLibrary進行加載,然後調用B.dll中的鉤子過程。這樣,鉤子dll就會在所有調用GetMessage的進程中加載。

我們要做的工作,就是在系統調用LoadLibrary的時候讓它失敗。這樣做有兩個問題:

LoadLibrary函數這一次失敗了,下一次系統還是會去嘗試加載它。看起來可能會影響效率,但是即使你不讓它失敗,每次有消息的時候,系統也是會去調用那個鉤子過程的,哪種方法更影響效率呢?這就不知道了,呵呵;

怎麼知道什麼時候讓LoadLibrary失敗呢?不能都讓它失敗吧,這樣會死的很慘的:(.經過研究發現,正常的加載dll函數調用都是從kernel32.dll中來的,而只有加載鉤子過程是在user32.dll中進行的(winxp系統下,以後的不知道是否也是這樣)。我們可以判斷一下LoadLibrary函數的返回地址,如果是在user32.dll的地址空間,就認為是鉤子dll的加載,直接返回0就可以了。

然後就來談談我們的API攔截。因為user32.dll中是用的LoadLibraryExW來加載鉤子dll的,所以我們只需要攔截這麼一個函數就可以了。分成三個步驟:

提供一個替代LoadLibraryExW的函數,假設名字叫newLoadLibraryExW,注意,函數原型要和LoadLibraryExW一模一樣,本進程內所有對LoadLibraryExW的調用都會轉到這兒來;

HMODULE WINAPI newLoadLibraryExW(LPCWSTR lpLibFileName,HANDLE hFile,DWord dwFlags)
{
 //獲取函數的返回地址參考文章最後的注1
 DWord dwCaller;
 __asm push dWord ptr [ebp+4]
 __asm pop  dWord ptr [dwCaller] 
 
 //判斷是否是從User32.dll調用的
 //m_dwUser32Low和m_dwUser32Hi保存user32.dll的加載地址的上下限
 if(dwCaller > m_dwUser32Low && dwCaller < m_dwUser32Hi)
 {
   //TRACE something hint infomation
   return 0;
 }
 return rawLoadLibraryExW(lpLibFileName,hFile,dwFlags);
}  
 
提供一塊空間,假設這塊空間的起始地址是fakeLoadLibraryExW,把LoadLibraryExW函數前N個字節保存下來,然後再用一個jmp指令跳回(LoadLibraryExW+N)地址處繼續執行,在這裡,N取7,具體原因在下邊講;

修改LoadLibraryExW函數的前5個字節,用一個jmp 指令跳到我們的newLoadLibraryExW函數起始處。雖然這裡只用了5個字節,但是我們先看一下LoadLibraryExW函數的前兩條指令:

//你機器上的版本具體的數字可能和我的不一樣
push 34h?//6A 34
push 7C80E288h?//68 88 E2 80 7C

一共有7個字節,我們不能只修改前5個字節,然後從fakeLoadLibraryExW函數跳到第6個字節處開始執行,而要跳到第三條指令即第8個字節開始處,這就是上一步N為什麼取7的原因。畫個圖示意一下,修改前:

 

 修改後:

  以下是封裝的一個類,使用時定義一個該類的全局變量,調用一下PatchLoadLibrary函數即可。

//***********************************************************************************//
//  FileName : GBlockHookDll.h                    
//  Author :耿海增            
//  Date : 2006.10.07                  
//***********************************************************************************//

#pragma once

#include 
#pragma comment(lib,"psapi.lib")

class GBlockHookDll
{
public:
 GBlockHookDll()
 {
  MODULEINFO user32ModInfo = {0};
 
  //獲取user32.dll的加載基址和映象大小  
  GetModuleInformation(GetCurrentProcess(),GetModuleHandle("user32.dll"),&user32ModInfo,sizeof(user32ModInfo));
  m_dwUser32Low = (DWord)user32ModInfo.lpBaSEOfDll;
  m_dwUser32Hi = (DWord)user32ModInfo.lpBaSEOfDll+user32ModInfo.SizeOfImage;
 }
 void PatchLoadLibrary()
 {
  //LoadLibraryExW
  //7C801AF1 6A 34                push        34h
  //7C801AF3 68 88 E2 80 7C       push        7C80E288h
  LPVOID* pfnRaw = (LPVOID*)&rawLoadLibraryExW;
  LPVOID fnNew = (LPVOID)newLoadLibraryExW;
  BYTE* fnRaw = (BYTE*)*pfnRaw;

  //1 save the first 7 bytes
  const int nFirstBytes = 7;
  BYTE* fnFake = (BYTE*)fakeLoadLibraryExW;
  memcpy(fnFake,*pfnRaw,nFirstBytes);
  fnFake[nFirstBytes] = 0xE9;  //jmp to rawAddr+nFirstBytes
  *(UINT32*)(fnFake + nFirstBytes+1) = (UINT32)fnRaw+nFirstBytes - (UINT32)(fnFake + nFirstBytes + 5);
  //2 modify the raw to jmp to fnNew
  DWord dwOldProtect = 0;
  VirtualProtect(fnRaw,nFirstBytes,PAGE_READWRITE,&dwOldProtect); //修改該代碼段的屬性為可寫
  *fnRaw = 0xE9;
  *(UINT32*)(fnRaw+1) = (UINT32)fnNew - (UINT32)(fnRaw + 5);
  VirtualProtect(fnRaw,nFirstBytes,dwOldProtect,0);
  //3 change the rawPointer
  *pfnRaw = fnFake;
 }

private: 
 static HMODULE WINAPI newLoadLibraryExW(LPCWSTR lpLibFileName,HANDLE hFile,DWord dwFlags)
 {
  //get the return address
  DWord dwCaller;
  __asm push dWord ptr [ebp+4]
  __asm pop  dWord ptr [dwCaller]
  if(dwCaller > m_dwUser32Low && dwCaller < m_dwUser32Hi)
  {
 #ifdef _DEBUG
   UINT uLenWide = lstrlenW(lpLibFileName);
   char* pNewChar = new char[uLenWide + 1];
   memset(pNewChar,0,uLenWide+1);
   WideCharToMultiByte(CP_ACP,0,lpLibFileName,-1,pNewChar,uLenWide,NULL,NULL);
   TRACE2(".......................LoadLibrary:return addr 0x%x,%s ",dwCaller,pNewChar);
   TRACE("Blocked....................... ");
   delete []pNewChar;
 #endif
   return 0;
  }
  return rawLoadLibraryExW(lpLibFileName,hFile,dwFlags);
 }
private:
 static DWord m_dwUser32Low;    //user32.dll 的加載基址
 static DWord m_dwUser32Hi;    //user32.dll 的加載基址+ImageSize
 static BYTE  fakeLoadLibraryExW[12]; //save first bytes of the raw function,and jmp back to that function
 //保存LoadLibraryExW的指針,然後修改為fakeLoadLibraryExW
 static HMODULE (WINAPI *rawLoadLibraryExW)( LPCWSTR lpLibFileName, HANDLE hFile, DWord dwFlags );
};
DWord GBlockHookDll::m_dwUser32Low = 0;
DWord GBlockHookDll::m_dwUser32Hi  = 0;
BYTE GBlockHookDll::fakeLoadLibraryExW[12] = {0};
HMODULE (WINAPI *GBlockHookDll::rawLoadLibraryExW)(LPCWSTR lpLibFileName,HANDLE hFile,DWord dwFlags) = LoadLibraryExW;

  注1:怎麼知道函數的返回地址呢?我們都知道,函數調用的時候,先要把參數入棧,然後把返回地址入棧,這樣,在我們的函數裡,esp指向的應該就是函數的返回地址了。但是為了返回函數時恢復原來的棧和在函數中方便引用傳遞的參數,編譯器一般都會產生兩條指令:

   push ebp
   mov ebp,esp

  先把ebp入棧,把原來的esp保存在ebp寄存器中,這樣,我們的返回地址就是[ebp+4],第一個參數是[ebp+8],第二個是[ebp+0xC]
注2:如果想寫一個通用一點兒的API Hook,就不能簡單的patch前5個或者前7字節了,需要根據不同的指令分析需要patch多少字節。可以參考微軟的 Detours 的實現。

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved