API Hook基本原理和實現
阿新 • • 發佈:2017-07-04
use 概率 缺省 後綴 origin gif object cati mov API Hook基本原理和實現
2009-03-14 20:09
windows系統下的編程,消息message的傳遞是貫穿其始終的。這個消息我們可以簡單理解為一個有特定意義的整數,正如我們看過的老故事片中的“ 長江長江,我是黃河”一個含義。windows中定義的消息給初學者的印象似乎是“不計其數”的,常見的一部分消息在winuser.h頭文件中定義。 hook與消息有著非常密切的聯系,它的中文含義是“鉤子”,這樣理解起來我們不難得出“hook是消息處理中的一個環節,用於監控消息在系統中的傳遞,並在這些消息到達最終的消息處理過程前,處 理某些特定的消息”。這也是hook分為不同種類的原因。 api hook是什麽? 在windows系統下編程,應該會接觸到api函數的使用,常用的api函數大概有2000個左右。今天隨著控件,stl等高效編程技術的出現,api 的使用概率在普通的用戶程序上就變得越來越小了。當諸如控件這些現成的手段不能實現的功能時,我們還需要借助api。最初有些人對某些api函數的功能不 太滿意,就產生了如何修改這些api,使之更好的服務於程序的想法,這樣api hook就自然而然的出現了。我們可以通過api hook,改變一個系統api的原有功能。基本的方法就是通過hook“接觸”到需要修改的api函數入口點,改變它的地址指向新的自定義的函數。api hook並不屬於msdn上介紹的13類hook中的任何一種。所以說,api hook並不是什麽特別不同的hook,它也需要通過基本的hook提高自己的權限,跨越不同進程間訪問的限制,達到修改api函數地址的目的。對於自身進程空間下使用到的api函數地址的修改,是不需要用到api hook技術就可以實現的。 api hook和pe格式的關系 api hook技術的難點,並不在於hook技術,初學者借助於資料“照葫蘆畫瓢”能夠很容易就掌握hook的基本使用技術。但是如何修改api函數的入口地 址?這就需要學習pe可執行文件(.exe,.dll等)如何被系統映射到進程空間中,這就需要學習pe格式的基本知識。windows已經提供了很多數 據結構struct幫助我們訪問pe格式,借助它們,我們就不要自己計算格式的具體字節位置這些繁瑣的細節。但是從api hook的實現來看,pe格式的訪問部分仍然是整個編程實現中最復雜的一部分,對於經常crack的朋友不在此列。 假設我們已經了解了pe格式,那麽我們在哪裏修改api的函數入口點比較合適呢?這個就是輸入符號表imported symbols table(間接)指向的輸入符號地址。 pe格式的基本組成 +-------------------+ | DOS-stub | --DOS-頭 +-------------------+ | file-header | --文件頭 +-------------------+ | optional header | --可選頭 |- - - - - - - - - -| | | | data directories | --(可選頭尾的)數據目錄 | | +-------------------+ | | | section headers | --節頭 | | +-------------------+ | | | section 1 | --節1 | | +-------------------+ | | | section 2 | --節2 | | +-------------------+ | | | ... | | | +-------------------+ | | | section n | --節n | | +-------------------+ 在上圖中,我們需要從“可選頭”尾的“數據目錄”數組中的第二個元素——輸入符號表的位置,它是一個IMAGE_DATA_DIRECTORY結構,從它中的VirtualAddress地址,“順藤摸瓜”找到api函數的入口地點。 下圖的簡單說明如下: OriginalFirstThunk 指向IMAGE_THUNK_DATA結構數組,為方便只畫了數組的一個元素,AddressOfData 指向IMAGE_IMPORT_BY_NAME結構。 IMAGE_IMPORT_DESCRIPTOR數組:每個引入的dll文件都對應數組中的一個元素,以全0的元素(20個bytes的0)表示數組的結束 IMAGE_THUNK_DATA32數組:同一組的以全0的元素(4個bytes的0)表示數組的結束,每個元素對應一個IMAGE_IMPORT_BY_NAME結構 IMAGE_IMPORT_BY_NAME:如..@[email protected]$qqrv. 表示 Unmangled Borland C++ Function: qualified function __fastcall Consts::initialization() 為了減少這個圖的大小,不得已將匯編和c++的結構都用上了。這個圖是輸入符號表初始化的情形,此時兩個IMAGE_THUNK_DATA結構數組的對應元素都指向同一個IMAGE_IMPORT_BY_NAME結構。 程序加載到進程空間後,兩個IMAGE_THUNK_DATA結構數組指向有所不同了。看下圖: 始化的,“兩個結構都指向同一個IMAGE_IMPORT_BY_NAME”,此時還沒有api函數地址 當PE文件準備執行時,前圖已轉換成上圖。一個結構指向不變,另一個出現api函數地址 如果PE文件從kernel32.dll中引入10個函數,那麽IMAGE_IMPORT_DESCRIPTOR 結構的 Name1域包含指向字符串"kernel32.dll"的RVA,同時每個IMAGE_THUNK_DATA 數組有10個元素。(RVA是指相對地址,每一個可執行文件在加載到內存空間前,都以一個基址作為起點,其他地址以基址為準,均以相對地址表示。這樣系統 加載程序到不同的內存空間時,都可以方便的算出地址) 上述這些結構可以在winnt.h頭文件裏查到。 具體編程實現 我將手上的vc示例代碼進行了適當修正,修改了一些資源泄漏的小問題,移植到c++builder6 & update4上,經過測試已經可以完成基本的api hook功能。有幾個知識點說明一下: 1、 dll中共享內存變量的實現 正常編譯下的dll,它的變量使用到的內存是獨立的。比如你同時運行兩個調用了某個dll的用戶程序,試圖對某一個在dll中定義的全局變量修改賦值的時候,兩個程序裏的變量值仍然是不同的。 共享的方法為:在.cpp文件(.h文件裏如此設置會提示編譯錯誤)的頭部寫上如上兩行: #pragma option -zRSHSEG // 改變缺省數據段名 #pragma option -zTSHCLASS // 改變缺省數據類名 HINSTANCE hdll = NULL; // 用來保存該動態連接庫的句柄 HHOOK hApiHook = NULL; // 鉤子句柄 HHOOK hWndProc = NULL; // 窗口過程鉤子用來攔截SendMessage int threadId = 0; 另外建立一個與dll同名,不同後綴的def文件,如HookDll.def文件,寫上: LIBRARY HookDll.dll EXPORTS ;... SEGMENTS SHSEG CLASS ‘SHCLASS‘ SHARED ;end 這樣設置後在.cpp文件中定義的變量,如果進行了初始化,將進入“SHCLASS”共享內存段(如果不初始化,將不改變其默認段屬性)。 上述的共享對於本示例代碼並不是必須的,只是稍微演示了一下。 2、 api hook修改api函數入口點地址的時機 很顯然,我們必須通過hook進入目標進程的地址空間後,再在位於該地址空間裏的hook消息處理過程裏修改輸入符號表“指向”的api函數入口點地址, 退出hook前也必須在這個消息處理過程裏恢復原來的地址。只要我們牢記修改的過程發生在目標進程的地址空間中,就不會發生訪問違例的錯誤了。 示例代碼使用了WH_GETMESSAGE、WH_CALLWNDPROC兩中hook來演示如何hook api,但WH_GETMESSAGE實際上並沒有完成具體的功能。 為了讓初學者盡快的掌握重點,我將代碼進行了簡化,是一個不健壯、不靈活的演示示例。 3、 函數的內外部表現形式 例如api函數MessageBox,這個形式是我們通常用到的,但到了dll裏,它的名字很可能出現了兩個形式,一個是MessageBoxA,另一個 是MessageBoxW,這是因為系統需要適應Ansi和Unicode編碼的兩種形式,我們不在函數尾端添加“A”或“W”,是不能hook到需要的 函數的。 4、 輔助pe格式查看工具 PE Explorer是一個非常好的查看pe資源的工具,通過它可以驗證自己手工計算的pe地址,可以更快的掌握pe格式。 調試器ollydbg也是非常好的輔助工具,例如查看輸入符號表中的api函數。 5、 程序文件列表 dll基本文件:Hook.h,Hook.cpp,HookDll.def client驗證方基本文件:HookTest.h,HookTest.cpp,ApiHookTest.cpp 6、 實現的功能 對記事本的MessageBoxW函數進行了hook,先執行自定義的 int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR M1, LPCWSTR M2, UINT M3) { return oldMessageBoxW(hWnd, M1, L"my api hook", M3); } 從這裏可以看到,由於目標進程空間中的執行線程並不知道你已經改變了api函數的實際入口地址,它在調用時仍舊將參數一成不變的壓入堆棧(這個說法是匯編代碼時看到的等價情形),事實上你已經提前接收到了函數調用的所有參數。這裏就是篇首帖子的回復了。 hook之前 hook以後 示例代碼 1、client驗證方的代碼非常簡單。建立一個Application工程,在窗體上放一個memo(提示信息),兩個button(一個SetHook,另一個RemoveHook)。 void __fastcall TForm1::Button1Click(TObject *Sender) { DWORD dwProcessId, dwThreadID; HWND hWnd = FindWindow("Notepad", NULL); if (!hWnd) { Memo1->Lines->Add("Nodepad is not found"); } else { dwThreadID = GetWindowThreadProcessId(hWnd, &dwProcessId); Memo1->Lines->Add(dwThreadID); SetHook(dwThreadID); } } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { RemoveHook(); } //--------------------------------------------------------------------------- 2、api hook dll稍微復雜些,建立一個dll工程之後,修改之。代碼中有一些函數並未用上,ReplaceApiAddress是核心函數,完整代碼參見附件。 參考文獻 1、《iczelion匯編程序設計教程》pe專題部分 2、《WINDOWS核心編程》第22章 3、《PE文件格式 1.9版》漢譯版,原著B. Luevelsmeyer 4、《跨進程API Hook》,出自http://blog.csdn.net/detrox/archive/2004/01/29/17511.aspx,作者detrox 5、《DLL木馬註入程序》,出自http://www.mydown.com/code/245/245731.html 6、另有兩vc6下的源代碼包,APIHOOK與pw,因時間久遠,出處不明。在此對原作者的辛勤工作表示真摯的謝意。 |
http://blog.csdn.net/hack_wg/article/details/4568419
API Hook基本原理和實現