以下將給出一個簡單的例子,作為HOOK API的入門。這裡是HOOK 自己程序的MessageBox,即將自己程序對MessageBox API的調用重定向到自己實現的API中,在自己定義的API中實現內容的替換。
需要注意的是,本例子的HOOK僅僅對自己實現的MFC窗口程序,當開始HOOK 後,自己的程序調用MessageBox將被重定向,但其他程序滴啊用MessageBox時是正常的。
在Windows中,每個進程都有自己的進程控制塊,有自己的安全運行空間,各函數在初始化時被加載到進程的地址空間中,各進程的地址空間是不相交的。本實例中,HOOK API僅僅在自己程序的地址空間中實現了地址的替換,因此不影響其他進程的工作,若想HOOK其他程序,那麼就要想辦法將自己實現的API注入到目標進程的地址空間中,並替換原API的地址,才能實現我們想要的功能,這將在後續的學習中進一步介紹。
本事例僅對自己的程序進行HOOK,實用性不是很大,但是對於入門,理解HOOK API的過程還是很有幫助的。
在自己實現的窗體程序(Windows-A)中實現一個與MessageBox API定義一模一樣的API(MessBox-New),這個API除了完成原API(MessBox-Old)的工作之外,還將顯示內容進行修改。Windows A 加載時,對將自己所使用的API地址都加載到自己的地址空間中,這裡包括我們自己寫的MessBox-New,因此我們可以很方便的使用MessBox-New的調用地址來替換MessBox-Old的入口地址,進而實現對MessBox-Old的重定向、即替換。地址被替換之後,只要本程序調用MessageBox這個API,就會被重定向到我們實現的MessBox-New中。此過程中,若想要恢復正常,只需要將MessBox-Old的入口地址恢復即可。
本小節將介紹程序的實現過程。
定義自己的API,因為我們這裡要HOOK 自己程序的MessageBox,因此就要定義一個原型與MessageBox API一模一樣的API。查MSDN,可得MessageBox有兩種調用形式,分別是MessageBoxA和MessageBoxW,前者處理窄字符串,即每個字符占一個字節;後者處理寬字符串,即一個字符占兩個字節。我們這裡HOOK MessageBoxW,其原型為:
int WINAPI MessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType
);
由此,可以定義我們自己的API如下:
這裡很容易漏掉函數前面的 WINAPI,若是少了將無法正常實現HOOK,一定要注意我們實現的函數的原型要與原API一致。
//
// 自己定義的,用於替換相應API的,假的API
//
int WINAPI MyMessageBoxW(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCation,UINT uType)
{
TRACE(lpText);
/*
調用原函數之前,先停止HOOK,也就是恢復原系統API函數的入口,否則無法調用到原API函數,
而是繼續調用自己的API,會造成死循環,進而造成堆棧溢出,崩潰。
*/
HookOff();
/*
調用原來的MessageBoxW打印我們的信息。
*/
int ret = MessageBoxW(hwnd,_T("哈哈,被HOOK咯!!"),lpCation,uType);
/*
調用完原系統API後,記得恢復HOOK,也就是啟動HOOK,將原API函數入口換成我們自己定義的函數入口,
否則下一次調用MessageBoxW的時候就無法轉到我們自己定義的API函數中,也就無法實現HOOK。
*/
HookOff();
return ret;
}
定義原API的類型,下面的TypeMessageBoxW其它普通的數據類型的使用方法是一樣的。定義一個TypeMessageBoxW類型的變量,用於存儲原API的指針,還定義一個遠指針類型,pfOldMsgBoxW,因為系統API是在動態鏈接庫(DLL)中實現的,因此程序實際上是通過遠地址指針來DLL中相應的API調用。而本實例中設計的MessageBox是在 User32.dll 中的。關於遠地址指針這裡不做多介紹,需要了解的可以查閱相關資料。
typedef int (WINAPI *TypeMessageBoxW)(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);
TypeMessageBoxW OdlMsgBoxW = NULL; // 指向函數原型指針
FARPROC pfOldMsgBoxW; // 指向函數遠指針
由MSDN可知,原API在User32.dll中實現,因此在此之前要加載User32.dll,並獲取到原API的函數入口。實現代碼如下:
HMODULE hmod = LoadLibrary(_T("User32.dll"));
if ( NULL == hmod)
{
AfxMessageBox(_T("加載User32.dll失敗"));
return;
}
OdlMsgBoxW = (TypeMessageBoxW)::GetProcAddress(hmod,"MessageBoxW");
pfOldMsgBoxW = (FARPROC)OdlMsgBoxW;
if ( pfOldMsgBoxW == NULL)
{
AfxMessageBox(_T("獲取原API入口地址出錯"));
return;
}
為了最後恢復原API的地址,必須要在HOOK之前將原API的入口地址保存起來。而這裡為什麼是5個字節呢?因為我們使用jmp xxxx 指令實現原API的重定向,該指令的長度為5個字節,jmp占一個字節,而xxxx表示新API的入口地址,占4個字節,我們使用jmp xxxx這條指令來替換掉原API入口的5個字節,這樣一來當本程序調用MessageBoxW時,就會跳轉到我們實現的API。綜上所述,我們這裡需要保存原API的前5個字節,實現代碼如下:
//
// 將原API的入口5個字節代碼保存到OdeCode[]中
//
_asm
{
lea edi,OldCode // 取數組OldCode[]地址,存放到edi中
mov esi,pfOldMsgBoxW // 獲取原API入口地址,存入esi中
cld // 設置方向
movsd // 移動dword ,4 Byte
movsb // 移動 1 Byte
}
保存好原API的入口之後,我們這裡需要設置jmp xxxx指令,xxxx為新API的入口地址,以便之後實現地址的替換。
而xxxx如何計算呢,可遵循前人總結的一條計算公式:
int xxxx = MyFunAddr – SystemFunAddr - CodeLength;
jmp xxxx;
MyFunAddr : 我們編寫的新的API的地址;
SystemFunAddr : 原API的地址;
CodeLength : 入口指令長度,本實例是 jmp xxxx 的長度,為5個字節。
//
// 新的API入口保存到NewCode[]中,即jmp xxxx,xxxx為新API地址,該指令總長度為5個字節
//
NewCode[0] = 0xe9; // 0xe9相當於jmp指令
_asm
{
lea eax,MyMessageBoxW
mov ebx,pfOldMsgBoxW
sub eax,ebx
sub eax,CODE_LENGTH
mov dword ptr[NewCode+1],eax
}
在保存了原API和新API的入口地址之後,接下來就是要實現地址的替換,即使用新API入口替換原API入口,從而實現HOOK MessageBoxW。這裡涉及到兩個重要的API,VirtualProtectEx 和 WriteProcessMemory,關於API的詳細說明可以查詢MSDN。
/*
啟動HOOK,將原API的入口地址換成我們自己定義函數的入口地址
*/
VOID HookOn()
{
//
// 確保本程序進程句柄hProcess不為NULL
//
ASSERT(hProcess!=NULL);
DWORD dwTemp;
DWORD dwOldProtect;
SIZE_T writedByte;
//
// 修改API入口的前5個字節,jmp xxxx
//
VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect);
WriteProcessMemory(hProcess,pfOldMsgBoxW,NewCode,CODE_LENGTH,&writedByte);
if (writedByte == 0)
{
AfxMessageBox(_T("替換原API地址失敗"));
}
VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp);
}
在HOOK後,對MessageBox的調用會被重定向到我們實現的API中,若需要調用原API,則必須回復原API的入口地址,否則會出現死循環。實現代碼如下:
/*
停止HOOK,將入口換成原來的API入口地址
*/
VOID HookOff()
{
ASSERT(hProcess != NULL);
DWORD dwTemp;
DWORD dwOldProtect;
SIZE_T wirtedByte;
//
// 回復原API地址
//
VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect);
WriteProcessMemory(hProcess,pfOldMsgBoxW,OldCode,CODE_LENGTH,&wirtedByte);
if (wirtedByte == 0)
{
AfxMessageBox(_T("回復原API地址失敗"));
}
VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp);
}
//
// 啟動 HookMessageBoxW
//
void CHookMessageboxWindowDlg::OnBnClickedButtonStart()
{
// TODO: 在此添加控件通知處理程序代碼
AdjustPrivileges(); // 提升權限,因為調用 OpenProcess() 需要合適的權限
DWORD dwPid = ::GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);
if (hProcess == NULL)
{
CString logInfo;
logInfo.Format(_T("獲取進程句柄失敗!!,進程 id = 0x%x ,錯誤代碼 = 0x%x"),dwPid,GetLastError());
AfxMessageBox(logInfo);
return;
}
GetApiEntrancy(); // 獲取新舊API入口,並開始HOOK
m_status.SetWindowText(_T("Hook已啟動"));
}
//
// 終止 HookMessageBoxW
//
void CHookMessageboxWindowDlg::OnBnClickedButtonStop()
{
// TODO: 在此添加控件通知處理程序代碼
HookOff();
m_status.SetWindowText(_T("Hook已停止"));
}
//
// 調用 HookMessageBoxW
//
void CHookMessageboxWindowDlg::OnBnClickedButtonCall()
{
// TODO: 在此添加控件通知處理程序代碼
::MessageBoxW(m_hWnd,_T("這是正常的MessageBoxW"),_T("Hello"),0);
}
bool AdjustPrivileges() {
HANDLE hToken;
TOKEN_PRIVILEGES tp;
TOKEN_PRIVILEGES oldtp;
DWORD dwSize=sizeof(TOKEN_PRIVILEGES);
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
if (GetLastError()==ERROR_CALL_NOT_IMPLEMENTED) return true;
else return false;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
CloseHandle(hToken);
return false;
}
ZeroMemory(&tp, sizeof(tp));
tp.PrivilegeCount=1;
tp.Privileges[0].Luid=luid;
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
/* Adjust Token Privileges */
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &oldtp, &dwSize)) {
CloseHandle(hToken);
return false;
}
// close handles
CloseHandle(hToken);
return true;
}
以下給出主要實現的所有源代碼,以便從整體上把握整個實現過程。
// HookMessageboxWindowDlg.cpp : 實現文件
//
#include "stdafx.h"
#include "HookMessageboxWindow.h"
#include "HookMessageboxWindowDlg.h"
#include "afxdialogex.h"
// 定義API類型
#define CODE_LENGTH 5 // 入口指令長度
typedef int (WINAPI *TypeMessageBoxW)(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);
TypeMessageBoxW OdlMsgBoxW = NULL; // 指向函數原型指針
FARPROC pfOldMsgBoxW; // 指向函數遠指針
BYTE OldCode[CODE_LENGTH]; // 原系統API入口
BYTE NewCode[CODE_LENGTH]; // 自己實現的API的入口,(jmp xxxx),xxxx為新API入口地址
HANDLE hProcess = NULL; // 本程序進程句柄
HINSTANCE hInst = NULL; // API所在的dll文件句柄
VOID HookOn(); // 開始HOOK
VOID HookOff(); // 停止HOOK
VOID GetApiEntrancy(); // 獲取API入口地址
bool AdjustPrivileges();// 提高權限
//
// 自己定義的,用於替換相應API的,假的API
//
int WINAPI MyMessageBoxW(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCation,UINT uType)
{
TRACE(lpText);
/*
調用原函數之前,先停止HOOK,也就是恢復原系統API函數的入口,
否則無法調用到原API函數,而是繼續調用自己的API,會造成死
循環,進而造成堆棧溢出,崩潰。
*/
HookOff();
/*
調用原來的MessageBoxW打印我們的信息。
*/
int ret = MessageBoxW(hwnd,_T("哈哈,被HOOK咯!!"),lpCation,uType);
/*
調用完原系統API後,記得恢復HOOK,也就是啟動HOOK,將原API函數入口換成我們自己定義的函數入口,
否則下一次調用MessageBoxW的時候就無法轉到我們自己定義的API函數中,也就無法實現HOOK。
*/
HookOff();
return ret;
}
/*
啟動HOOK,將原API的入口地址換成我們自己定義函數的入口地址
*/
VOID HookOn()
{
//
// 確保本程序進程句柄hProcess不為NULL
//
ASSERT(hProcess!=NULL);
DWORD dwTemp;
DWORD dwOldProtect;
SIZE_T writedByte;
//
// 修改API入口的前5個字節,jmp xxxx
//
VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect);
WriteProcessMemory(hProcess,pfOldMsgBoxW,NewCode,CODE_LENGTH,&writedByte);
if (writedByte == 0)
{
AfxMessageBox(_T("替換原API地址失敗"));
}
VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp);
}
/*
定制HOOK,將入口換成原來的API入口地址
*/
VOID HookOff()
{
ASSERT(hProcess != NULL);
DWORD dwTemp;
DWORD dwOldProtect;
SIZE_T wirtedByte;
// 回復原API地址
VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect);
WriteProcessMemory(hProcess,pfOldMsgBoxW,OldCode,CODE_LENGTH,&wirtedByte);
if (wirtedByte == 0)
{
AfxMessageBox(_T("回復原API地址失敗"));
}
VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp);
}
/*
保存原API和新API的地址
*/
VOID GetApiEntrancy()
{
//
// 保存原來API地址
//
HMODULE hmod = LoadLibrary(_T("User32.dll"));
if ( NULL == hmod)
{
AfxMessageBox(_T("加載User32.dll失敗"));
return;
}
OdlMsgBoxW = (TypeMessageBoxW)::GetProcAddress(hmod,"MessageBoxW");
pfOldMsgBoxW = (FARPROC)OdlMsgBoxW;
if ( pfOldMsgBoxW == NULL)
{
AfxMessageBox(_T("獲取原API入口地址出錯"));
return;
}
//
// 將原API的入口5個字節代碼保存到OdeCode[]中
//
_asm
{
lea edi,OldCode // 取數組OldCode[]地址,存放到edi中
mov esi,pfOldMsgBoxW // 獲取原API入口地址,存入esi中
cld // 設置方向
movsd // 移動dword ,4 Byte
movsb // 移動 1 Byte
}
//
// 新的API入口保存到NewCode[]中,即jmp xxxx,xxxx為新API地址,該指令總長度為5個字節
//
NewCode[0] = 0xe9; // 0xe9相當於jmp指令
_asm
{
lea eax,MyMessageBoxW
mov ebx,pfOldMsgBoxW
sub eax,ebx
sub eax,CODE_LENGTH
mov dword ptr[NewCode+1],eax
}
//
// 填充完畢,開始HOOK,即使用NewCode[]替換原API入口
//
HookOn();
}
bool AdjustPrivileges() {
HANDLE hToken;
TOKEN_PRIVILEGES tp;
TOKEN_PRIVILEGES oldtp;
DWORD dwSize=sizeof(TOKEN_PRIVILEGES);
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
if (GetLastError()==ERROR_CALL_NOT_IMPLEMENTED) return true;
else return false;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
CloseHandle(hToken);
return false;
}
ZeroMemory(&tp, sizeof(tp));
tp.PrivilegeCount=1;
tp.Privileges[0].Luid=luid;
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
/* Adjust Token Privileges */
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &oldtp, &dwSize)) {
CloseHandle(hToken);
return false;
}
// close handles
CloseHandle(hToken);
return true;
}
//
// 啟動 HookMessageBoxW
//
void CHookMessageboxWindowDlg::OnBnClickedButtonStart()
{
// TODO: 在此添加控件通知處理程序代碼
AdjustPrivileges(); // 提升權限,因為調用 OpenProcess() 需要合適的權限
DWORD dwPid = ::GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);
if (hProcess == NULL)
{
CString logInfo;
logInfo.Format(_T("獲取進程句柄失敗!!,進程 id = 0x%x ,錯誤代碼 = 0x%x"),dwPid,GetLastError());
AfxMessageBox(logInfo);
return;
}
GetApiEntrancy(); // 獲取新舊API入口,並開始HOOK
m_status.SetWindowText(_T("Hook已啟動"));
}
//
// 終止 HookMessageBoxW
//
void CHookMessageboxWindowDlg::OnBnClickedButtonStop()
{
// TODO: 在此添加控件通知處理程序代碼
HookOff();
m_status.SetWindowText(_T("Hook已停止"));
}
//
// 調用 HookMessageBoxW
//
void CHookMessageboxWindowDlg::OnBnClickedButtonCall()
{
// TODO: 在此添加控件通知處理程序代碼
::MessageBoxW(m_hWnd,_T("這是正常的MessageBoxW"),_T("Hello"),0);
}