前言
本文(其實是隨筆)和實例代碼描述的內容主要包括:API攔截,消息鉤子,枚舉子窗體,注冊系統熱鍵。其中消息鉤子、API攔截、枚舉子窗體實現代碼在dll中,注冊系統熱鍵代碼實現在測試exe中,另外exe代碼段還涉及自定義消息和系統欄圖標。希望對你有用。
一年過去了,仍然是老樣子——為生活而奔波、為money而忙碌。好在身體還蠻健康,吃得好,睡得香:-)但願新的一年裡腰包會鼓一些。今天整理硬盤,發現了這些代碼,回想當初開始寫的時候好像還在 VCKBASE 論壇裡提出過一些問題,也得到了論壇很多朋友的熱心幫助,在此感謝。本來想好好的寫一下心得,特別是能讓VC初學者學到一些東西。但自己語文功底太差,就只能想到哪寫到哪了。希望你邊品茶,邊上網,一手聊天,一手回帖,左眼看PP,右眼能堅持看完下面的這些方塊字。
寫這個程序的來由:最初由於客戶的需求不是很明顯,只是說把他的一個數據庫程序的數據作一個臨時性的欺騙,以應付檢查(誰檢查就不知道了,哈,讓我寫作弊程序呢,考我吶),唯一的要求是不更改真實的數據。我其實比較頭疼數據庫,就想不研究他的數據庫結構了,心想采用一個比較懶的辦法實現就行了。9月份的時候我也挺閒的,在網上狂搜了一通之後,基本就決定用屏幕取詞的方式來做了。就是截獲顯示到屏幕上的數據,只要是數字類型的,就乘以一個基數,然後把算好的數據回寫到屏幕上。其實這個屏幕取詞的技術網上早就公開了,我也剛好想順便練練手。寫得差不多的時候,吐血的時候也就到了,客戶要求除了臨時欺騙之外,還要把顯示的數據當場用原來程序上的打印按鈕打印出來。我傻眼了,所以也就廢棄了這種方法,吐了一通血、憋了一通氣、老老實實的研究了原程序的數據庫結構,還好是 SQL Server 2K,以前弄過,終於趕在檢查之前交了活。
一不小心就說了這麼多廢話。好了,轉到正題。下面分別道來:
攔截API(或者說截獲API 反正一個意思)
先說說截獲API了,在HOOKAPI.h和cpp文件中,這個是繁體版本。網上流行很多個版本,我覺得還是這個比較簡潔一點(簡單萬歲嘛)。原作者不知道是台灣還是香港的,注釋用的BIG5碼。截獲API一般就幾個步驟:
獲得函數地址(GetProcAddress),取得被攔截的函數地址和要替代這個被攔截的函數的地址(也就是攔截後要處理的函數);
形成JMP指令(__asm),准備避開被攔截的函數跳轉到處理函數直接處理;
設置內存可寫(VirtualProtect),跳轉後的函數執行;
記得恢復內存。
網上關於API攔截的教程很多,反正我也說不清楚就不多說了。其實我的理解很簡單,就是想辦法打開老板辦公室的們、潛入、然後替換獎金單、然後關門、走人……就這麼簡單。注意點有三個:一個就是原函數的地址,在哪個dll中,別查錯了;一個就是替換函數的參數,必須遵照游戲規則,該什麼類型就什麼類型;另外一個就是內存的讀寫順序,不能漏也不能反。具體可以參考HookAPI的代碼段,這裡就不帖了。
鉤子(Hook)
關於消息鉤子,其實就兩句話:SetWindowsHookEx ,UnhookWindowsHookEx ,裝載鉤子和卸載鉤子,不過也就這兩句話,學寫鉤子的時候,折騰了好一陣子。給初學者一個建議:理解瘟到死的消息機制,暫時放下MFC,嘗試寫至少一個SDK程序,動手寫之前,好好看看MSDN,很多問題都可以在這裡得到答案。SetWindowsHookEx有四個參數,主要就是注意1和4這兩個參數,第一個參數建議少用WH_CALLWNDPROC 除非必須這麼做。一般情況下,有消息,鍵盤和鼠標鉤子就夠用了,當然特殊情況除外。
枚舉子窗體
至於枚舉子窗體,一般采用兩種方法:
1、EnumChildWindows回調函數 //例子 調用:EnumChildWindows(g_hWndTag, EnumChildWindowsProc,0); 函數:
//-------------------------------------------------------------------------
//枚舉子窗體回調函數
BOOL CALLBACK EnumChildWindowsProc( HWND hWnd, LPARAM lParam )
{
char buff[256]={''\0''};
if(::GetWindowLong(hWnd,GWL_STYLE)& WS_VISIBLE)
{
::GetWindowText(hWnd,buff,256);
//注銷這些按鈕或窗體
if( NULL != strstr(buff,"打印") ||
NULL != strstr(buff,"Excel") ||
NULL != strstr(buff,"導出") )
{
//這種只是屏蔽,界面上仍可看見
//EnableWindow(hWnd,FALSE);
//徹底注銷,一勞永逸:-)
DestroyWindow(hWnd);
}
}
return TRUE;
}
2、GetWindow的方法
//例子:百試不爽的while大法,小心while一去不復還
這裡多說一句,一些換膚程序所采用的窗體子類化,大多就是采用的這兩種方法之一。不過我始終覺得那是一項龐大費時且容易使人崩潰的工作,我曾經嘗試寫了一個小小的類,僅僅只是對按鈕用GDI重畫了一次,就已經接近忍耐極限,最終決定放棄了。
HWND hWndChild=NULL;
hWndChild = ::GetWindow(g_hWnd,GW_CHILD);
while( NULL != hWndChild )
{
//styleXP.CreateClassXP(hWndChild);
hWndChild = ::GetWindow(hWndChild,GW_HWNDNEXT);
}
注冊系統熱鍵
要用到RegisterHotKey函數,三步曲:
1、要注冊哪些鍵,先申明一下 //注冊系統熱鍵
2、程序初始化的時候,調用注冊熱鍵函數,當然你直接寫在初始化函數中我也不能反駁,這是你的自由:P
#define ID_A 1501
#define ID_B 1502
#define ID_C 1503
#define ID_D 1504
#define ID_E 1505//RegSysHotkey();
//-------------------------------------------------------------------------
//注冊系統熱鍵
void CTestDlg::RegSysHotkey()
{
HWND hWnd = this->m_hWnd;
RegisterHotKey(hWnd, ID_A, MOD_ALT, 65); //Alt + A
RegisterHotKey(hWnd, ID_B, MOD_ALT, 66); //Alt + B
RegisterHotKey(hWnd, ID_C, MOD_ALT, 67);
RegisterHotKey(hWnd, ID_D, MOD_ALT, 68);
RegisterHotKey(hWnd, ID_E, MOD_ALT, 69);
}
3、重載PreTranslateMessage,在你程序運行的時候來截獲熱鍵的輸入。 BOOL CTestDlg::PreTranslateMessage(MSG* pMsg)
自定義消息和系統欄圖標
{
switch (pMsg->message)
{
//處理系統熱鍵WM_HOTKEY消息
case WM_HOTKEY:
switch(pMsg->wParam)
{
case ID_A:
OnAppShow();
break;
case ID_B: //徹底隱藏
HideMe();
break;
case ID_C:…….具體請參照exe代碼片斷
就不詳敘了,請參照代碼。
好了,就說這麼多了,新年新氣象,希望大家都有一個好心情。給出的代碼沒有什麼版權,想用就用吧,代碼也沒怎麼整理,只是重新編譯了一下,在demo路徑下,好像用的是靜態鏈接,希望在你的機器上能運行。運行不起來可別怪我。不明白的地方請看目錄下的readme文件和代碼的注釋,或者去VCKBASE論壇提出吧,有很多大俠會幫忙解決的。
本文配套源碼