API hook原理和實例快速入門(inline hook),以dll線程注入方式使用(win7-64bit)

一個完整的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

User32.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哦~~~

技術相關更多文章猛擊:哇啦天堂論壇技術區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章