一個完整的hook,如果hook程序是以dll形式生成的,是分兩步:1.完成dll本身的設計和生成,2.完成dll注入程序的設計和生成
本文完成第一步。
第二步在http://blog.csdn.net/arvon2012/article/details/7767437有詳細講解。
最近在64位win7上hook文件複製,拖拽和剪切的hook(這個要通過hook IFileOperating接口實現)。所以學習了API hook。這裏是對自己的學習做個簡單的總結,希望和hook新手們共同探討和進步~~~
最後有全部源碼下載地址(無毒無害)
inline hook API的原理:
最簡單的說,hook api就是找到api所在的位置,然後在這個位置裏做些文章。這樣,當系統調用這個API的時候,它不知不腳的就運行了我們篡改過的代碼,達到我們不可告人的目的~~~網上介紹用detour庫進行APIhook,detour庫是微軟開發的專用的api hook庫,內部hook原理和inline hook是一樣的,但是因爲添加了一些處理調用衝突的代碼,所以會比直接手工inline hook穩定些。
而上面說的“做文章”,具體是幹什麼呢?還是根據例子說的清楚。
下面是一個helloworld級別的hook api,目標:(彈出對話框函數:MessageBoxW)
第一步:找到要hook的api!
我們想hook住這個MessageBoxW,就要知道系統調用它的時候,它在哪裏。有些童鞋懂應該上MSDN查,然後大聲吼出:在windows.h!!
擦!少年~你弱爆了(其實我在描述當時自學的自己,不是說你的,親~)。
要知道windows系統在調用這些函數的時候,他們調用的當然是編譯好的可執行函數(動態鏈接庫--dll文件),當然不是調用源代碼。MSDN這個函數的頭文件下面就寫着:
DLL |
|
---|
下面簡單的調用幾個API就能鎖定函數在這個dll中的位置:
//LoadLibrary:先用自己的程序load目標庫
HMODULE hModule = LoadLibrary(_T("user32.dll"));
//GetProcAddress:在目標中找到自己想hook的函數地址
pOldAPI = (pDefaultAPI)GetProcAddress(hModule, "MessageBoxW");
(關鍵先理解框架級別的東西,現在不追究裏面詳細的類型轉換。)
第二步:在原api這裏做文章
所謂的hook了api其實就是改變了這個api原先的功能,讓調用者調用這個api的時候執行的是我們希望的功能。既然上面我們獲得了目標api的地址,我們是不是希望可以修改這個地址裏執行的代碼。下面是所謂的inline hook的修改方式:函數被調用之後要一步一步往下執行是吧?OK,我們就把這個函數將要執行的第一個指令替換成【跳轉指令】,跳轉到我們設計的代碼的位置。Look:
char szOldAPI[12] = {};
//存放原來API函數在內存中的前12個字節
char szNewAPI[12] = {0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0xC3};
//存放跳轉指令,中間的8個字節是0,因爲現在還沒有存放目標地址
再提醒一下:這裏我們是做12字節的替換,因爲我用的是64位的系統,32位的系統替換前5字節。
上面這兩行代碼以全局變量定義,他們其實是彙編指令,意思是:
mov rax,XX XX XX XX XX XX XX XX ;把要跳轉的地址存到RAX寄存器,XXX就是上面szNewAPI中間的幾字節0
PUSH RAX ;把RAX寄存器壓到棧頂
RET ;把棧頂的數據拿出來,存到指令寄存器(負責告訴系統下一步執行的代碼在哪裏)中
所以,我們在用來做hook工作的函數中,只要先把原函數的前12位保存到szOldAPI中(千萬別搞丟了,用來恢復),然後把存放了目標跳轉地址的跳轉代碼(szNewAPI)塞到這12字節。如下:
ReadProcessMemory ((void*)-1, pOldAPI, szOldAPI, 12, NULL); //讀出原來的前5個字節
WriteProcessMemory((void*)-1, pOldAPI, szNewAPI, 12, NULL); //寫入我們處理後的5個字節
這樣,當系統調用被我們hook住的api時,他剛執行這個函數前面的代碼,就不知不腳的執行了一個跳轉指令,嘿嘿嘿。。。。其實hook的入門原理到這裏就結束了。
下面解決一個遺留問題:定義我們的函數,並且把我們的函數所在地址放到跳轉指令中。請看第三步
第三步:達到不可告人的目的,捏~~哈哈哈~~~~
爲了解釋的方便,這裏先上代碼再解釋:
首先定義我們的函數:
typedef int (WINAPI* pDefaultAPI)(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
對於C語言不太熟悉的親們,看到上面的定義一定會說。。。。。。靠!
C語言的類型定義就是繁雜,這個是歷史遺留問題~~~恩恩。。。
下面和C新手一起探討上面的東西是神馬。
1.上面我們定義了一個類型,從typedef關鍵字可以看出,這個語句的原型是:typedef int XXXX。意思其實就是XXX就是int。
2.大家可以看出來XXX是個函數的摸樣:有函數名(先這麼表達吧),有參數表。想想最初我們學計算機語言的時候,函數是什麼??【這個概念最初是數學裏的函數來的,函數就是一種計算,最後得到的是一個量(返回值)】,所以這個typedef int XXXX形式的定義就是說XXX這個函數的計算結果其實就是int~~~哦~~~~用計算機專用術語說就是:XXX返回值是int。
3.我們要定義的是pDefaultAPI,所以進一步解刨,看看這個複雜的“函數名”是什麼,WINAPI* pDefaultAPI,我們單單看這個,弱爆了~~~這一個片段是:定義了一個指向WINAPI類型的值的指針,指針名字(我們最後要使用的名字)叫做pDefaultAPI。
4.第3個分析的結果和前面的東西串在一起念就是:我們定義了一個指向WINAPI類型的指針(WINAPI類型是一種函數類型,所以有返回值),這個指針指向的WINAPI類型函數的返回值是int型的~~~我去!說人話!嗯。。。壓縮一下就是:定義了一個WINAPI類型、返回值是int類型的“函數指針”,名字叫pDefaultAPI。
好,C語言課上完了,我們繼續hook。
上面的定義,大家仔細看發現:1.返回值和原MessageBoxW返回值一致,都是int 2.參數表也和原函數一致。
原因是:我們是要用這種格式定義出來的指針去保存原函數的地址,所以當然返回值和參數要一直嘍~~~~
這樣,我們就能用這個pDefaultAPI去定義指針變量,並且用這個變量去存放原函數地址。這樣存:
先定義一個全局的pDefaultAPI pOldAPI;然後。。。請看上面“第一步”中對pOldAPI的使用。
終於要定義我們自己的hook函數了:
int WINAPI NewAPI(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
)
{
//爲所欲爲
WriteProcessMemory((void*)-1, pOldAPI, szOldAPI, 12, NULL);//還原原函數
A = MessageBoxW(hWnd,lpText,lpCaption,uType); //調用還原後的原函數
WriteProcessMemory((void*)-1, pOldAPI, szNewAPI, 12, NULL);//原函數執行完,重新hook之!
//爲所欲爲
return A;
}
有上面的概念,大家應該注意到了這裏參數表,返回值都。。。
函數裏面幹了什麼?你可以再可以爲所欲爲的地方添加你爲所欲爲的代碼,但是別忘了執行原函數(中間的三行)。
有兩個問題大家按自己的理解思考下:
1.hook api有沒有必要都保留原函數的功能,可不可以徹底不執行原函數
2.爲什麼不在兩個WriteProcessMemory中間爲所欲爲?對這個地方原函數的執行沒有影響。
第一個問題我沒有可以給大家的有價值的答案。先說說第二個吧。
這裏剛好涉及到inline hook這種hook形式的弊端:不穩定!
我們的系統想必都是多進程,多線程的吧,如果進程A執行到第一個WriteProcessMemory,把原函數開始的地址還原了,然後就在這個瞬間,B進程調用了MessageBoxW,那麼。。。B進程成功逃出了我們的魔掌,不是嗎?所以爲了儘可能的減小inlinehook的這個缺點,我們最好不要在恢復和重新hook這兩個操作之間添加需要執行時間的代碼。(是代碼,就都要執行時間。。。)
重新回到hook的主線上來,我們定義了自己的hook函數,下面要做的就是存下這個函數的地址,然後在替換的時候使用:
DWORD64 dwJmpAddr = 0;
dwJmpAddr = (DWORD64)NewAPI; //存下我們自己的函數的地址
memcpy(szNewAPI + 2, &dwJmpAddr, 8); //把地址寫到szNewAPI中間的8個0字節處
ReadProcessMemory ((void*)-1, pOldAPI, szOldAPI, 12, NULL); //讀出原來的前12個字節
WriteProcessMemory((void*)-1, pOldAPI, szNewAPI, 12, NULL); //寫入我們處理後的12個字節
上面是存放新函數的地址,順便做初次hook
到這裏inline api hook的本身的原理和工作就結束了。
生成:
下面再稍微說下怎麼生成我們想要的hook文件,這裏我是生成dll,因爲dll可以通過dll注入技術,注入其他進程,然後改變其他進程對某某函數的調用。
dll簡單原理:大家要記住dll這個庫給進程提供了可以調用的函數。如果一個進程要使用這個函數,它會先裝載對應的dll,然後dll就會在這個進程中產生一個拷貝。拷貝是什麼意思?就是說這個進程調用的是它進程空間中的dll,它自己的啊,親~~~~。所以如果我們想控制一個進程,肯定要把我們的hook dll注入到這個進程,然後通過hook函數修改這個進程私有的dll中的內容,在這裏我們要修改的就是目標進程的私有user32.dll庫中的MessageBoxW所在位置的數據。
所以說,把我們的hook做成dll,又有了dll注入技術的支持,我們的hook工作就能指哪打哪。用起來很方便。
親~您還等什麼,打開你用的編程工具,新建一個dll工程。然後在dll的主函數DllMain中的case DLL_PROCESS_ATTACH:下添加上你負責初次hook的函數。我的程序中是這樣的:
BOOL APIENTRY DllMain(HANDLE handle, DWORD dwReason, LPVOID reserved)
{
g_hThisModule = (HMODULE)handle;
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
{
HookAPI();//HOOK!
break;
}
case DLL_PROCESS_DETACH:
{
UnHookAPI();
break;
}
}
return TRUE;
}
有什麼不對的地方大家不要吝嗇自己的意見,一定要留言給我指出來,謝謝!
http://download.csdn.net/detail/arvon2012/4440477
這個是核心代碼,大家創建dll工程,或者空的工程,直接把這個代碼拷貝到主文件中就OK,千萬別忘了,如果你要注入的系統是64位的,或者目標是64位的程序,一定編程成x64的dll哦~~~
技術相關更多文章猛擊:哇啦天堂論壇技術區