程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi中API Hook 探秘

Delphi中API Hook 探秘

編輯:Delphi

一、關於API Hook

1.什麼是API Hook不知道大家是否還記得,在DOS 系統中編程,經常會采取截取中斷向量的技術:我們可以設置新的中斷服務程序,當系統其他的程序調用這個中斷時,就讓它先調用我們自己設置的新的中斷服務程序,然後再調用原來的中斷服務程序,這樣就能夠獲得非凡的控制權。許多優秀的軟件和大多數DOS 病毒程序都采用了這個方法。

在Windows 中,我們也可以采取類似技術。當系統調用某個API 函數時,就會先進入我們自己的函數,然後再調用原來的API 函數,這樣,我們的程序就可以取得更多的控制權,我們就可對Windows 系統中的任意一個函數調用進行動態攔截、跟蹤、修改和恢復,就可讓Windows 系統中的任意一個函數按我們的設想工作。這種技術有許多名稱,比如“陷阱技術”、“重入技術”等,不過我認為還是API Hook 最貼切。原因嘛,等一下你看編程就明白了。

這樣重要的技術,大家已經都知道了吧?哈哈,知道的都不說,不知道的呢,你就自己慢慢去摸索吧。偶爾有一兩篇文章見於報端,不是藏頭露尾,就是已經過時了。還有的即使把原理都告訴你了,但就是不說它調用了哪些函數。要源代碼?行,拿錢來。有人說了,這種技術用來編病毒最合適,所以..(因為菜刀可以殺人,所以菜刀已被禁止使用了)。而實際呢,你看看使用了這種技術的國產軟件就知道了:金山詞霸、東方快車、RichWin、東方詞聖..在這裡,我感覺有必要簡單說說金山詞霸的工作原理。

2.金山詞霸的工作原理大家都用過金山詞霸吧?當你把光標指向一個單詞,詞霸就會自動彈出一個窗口並把單詞的意思翻譯出來。這究竟是怎麼做出來的呢?我在這裡簡單說明一下。

( 1 ) 安裝鼠標鉤子。
( 2 )一旦光標在屏幕上移動,系統就會調用鼠標鉤子,詞霸通過鼠標鉤子能夠獲得光標的坐標( x , y ) ,並安裝TextOut()、ExtTextOut()等API 函數鉤子。
(3)詞霸向光標下的窗口發重畫消息,負責繪制該點的應用程序在收到WM_PAINT 消息後,就可能調用TextOut()、ExtTextOut()等函數重繪字體。
(4)調用的函數將被鉤子函數攔截,詞霸就能夠截獲該次調用,從應用程序的數據段中將“文字”指針的內容取出,並作翻譯處理。
(5)退出跟蹤程序,返回到鼠標鉤子,解除對TextOut()、ExtTextOut()等API 函數的跟蹤。
(6)完成了一次“屏幕抓字”。
這裡的關鍵有兩點:安裝鼠標鉤子和API 鉤子。安裝鼠標鉤子非常簡單,而API 鉤子正是取詞的核心代碼。

3.關於Delphi
事實上,隨著互聯網的普及,許多秘密都已不再是秘密,API Hook 也一樣。在網上,你已經可以找到這樣的免費源代碼,但是大部分可能已經過時,而且這些源代碼大都是基於VC++的。如果你想找到用Delphi 編寫的源代碼,那麼,你還是讀一讀我的文章吧。
Delphi 是編程工具史上的一個裡程碑式的作品。如果你在使用它,我向你表示祝賀。如果你沒有使用它,你也沒有什麼損失。網上關於幾種語言誰好誰壞都吵得天翻地覆的了,我不想增加新仇也不想算算舊恨。每種語言都有它的優缺點,每個人都有自己選擇的權利嘛!
不過,用Delphi 編寫API Hook 有幾處“陷阱”。我想,除了介紹API Hook 以外,這也是為什麼我要寫這篇文章的一個原因吧!

4.哪些人可以讀這篇文章
當然,讀這篇文章並沒有什麼限制。但是你最好已經懂得鼠標鉤子的制作過程,手邊有MSDN 那就再好不過了。我認為,只要你是Windows 的程序員,就一定要有MSDN。原因?有一套就明白啦。如果你懂得PE文件結構,那就更好了。在這篇文章裡,我給出了所有的源代碼(還不到2 0 0行)。如果你想修改程序,最好用SoftIce。

5.關於我的程序
本文中的程序在Windows Me 的操作環境下,使用Delphi5.0 編程調試通過。無論是商用還是個人使用,你都可以隨意使用和修改本文中的程序,並且不需要在程序中加注我的個人信息。

二、用Delphi 編寫API Hook

1.改寫API 函數
為了使我們改寫的代碼正確運行,我們的函數必須和要改寫的API 函數具有同樣形式的形參。在我的程序中,我攔截了MessageBoxA 和MessageBoxW 兩個函數。所以我這樣定義自己的函數:function MyBoxA(hwn:hwnd;lptext:pchar;lpcapion:pchar;utype:cardinal):integer;stdcall;function MyBoxW(hwn:hwnd;lptext:pchar;lpcapion:pchar;utype:cardinal):integer;stdcall;注意到我使用了stdcall 關鍵字,這是為了我的函數的形參的進出棧順序與我們要攔截的函數一致。我們知道,為了系統的安全,Win32 並不允許直接改寫內存中的代碼段。所以,有人想了好多種方法繞過系統的保護。實際上,Win32 為了我們能安全地改寫內存中的代碼,提供了一個函數:WriteProcessMemory。有許多人曾經告訴我,WriteProcessMemory 也不能用來改寫,不過我一直使用得很好。也許用它產生了一些BUG,只是我並不知道罷了,所以還請這方面的專家指正。在PE文件中,當你呼叫另一模塊中的函數(例如USER32.DLL中的GetMessage),編譯器編譯出來的CALL指令並不會把控制權直接傳給DLL 中的函數,而是傳給一個JMP DWordPTR [XXXXXXXX]指令,[XXXXXXXX]內含該函數的真正地址(函數進入點)。為了得到A P I 函數的地址,我們可以這樣:Address:=@MessageBoxA。如上文所說的,我們得到的僅僅是一個跳轉指令,後面緊接著的才是MessageBoxA真正的開始代碼的地址(具體可以查閱PE 文件的資料)。在下面的程序中我自定義了一個結構(叫記錄?我習慣了,改不過口來),注意,這裡使用了packed 關鍵字:
TImportCode = packed record
JumpInstruction : Word; // 應該是$25FF,JUMP 指令
AddressOfPointerToFunction: PPointer;// 真正的開始地址
end;
PImportCode = ^TImportCode;
其中,PPointer = ^Pointer ;
用以下函數返回函數的真正地址:
function TrueFunctionAddress(func: Pointer): Pointer;
var
Code: PImportCode;
begin
Result:= func;
if func = nil then exit;
try
Code := func;
if (Code.JumpInstruction = $25FF) then begin
Result := Code.AddressOfPointerToFunction^;
end;
except
Result := nil;
end;
end;
這樣,只要用我們的函數的地址替代它就可以了。替換函數:
Procedure PermuteFunction(OldFunc:PPOinter; NewFunc:Pointer);
var
written: DWord;
begin
WriteProcessMemory(GetCurrentProcess, OldFunc, @NewFunc, 4, written);
end;
你新建一個Unit APIHook,把上面的函數和結構寫進去並保存下來。

2.第一個程序
新建一個Application TRY1,主Form 的單元名稱不妨叫TRYUnit1。把上面的Unit APIHook 加進來。再新建一個UnitMESS,添加如下代碼:
unit mess;
interface
uses
Windows, Messages, SysUtils, Classes, APIHook;
procedure API_Hookup;
procedure Un_API_Hook;
var
FuncMessageboxA, FuncMessageboxW: PImportCode;
implementation
type
TMessageA = function(hwn: hwnd; lptext: pchar; lpcapion: pchar; utype: cardinal):
integer; stdcall;
TMessageW = function(hwn: hwnd; lptext: pwidechar; lpcapion: pwidechar;
utype: cardinal): integer; stdcall;
var
OldMessageBoxA: TMessageA;
OldMessageBoxW: TMessageW;
function MyBoxA(hwn:hwnd;lptext:pchar;lpcapion:pchar;utype:cardinal): integer; stdcall;
begin
result := OldMessageBoxA(hwn, 'Succes Hook A !', lpcapion, utype);
end;
function MyBoxw(hwn:hwnd;lptext:pwidechar;lpcapion:pwidechar;utype:cardinal):
integer; stdcall;
begin
result := OldMessageBoxW(hwn, '成功掛上W!', lpcapion, utype);
end;
procedure API_Hookup;
begin
if @OldMessageBoxA = nil then
@OldMessageBoxA := TrueFunctionAddress(@messageboxA);
if @OldMessageBoxW = nil then
@OldMessageBoxW := TrueFunctionAddress(@messageboxW);
PermuteFunction(FuncMessageboxA.AddressOfPointerToFunction, @MyBoxA);
PermuteFunction(FuncMessageboxW.AddressOfPointerToFunction, @MyBoxW);
end;
procedure Un_API_hook;
begin
If @OldMessageBoxA <> nil then begin
PermuteFunction(FuncMessageboxA.AddressOfPointerToFunction,
@OldMessageboxA);
PermuteFunction(FuncMessageboxW.AddressOfPointerToFunction,
@OldMessageboxW);
end;
end;
initialization
FuncMessageboxA := @MessageboxA;
FuncMessageboxW := @MessageboxW;
end.
在主窗體上添加三個按鈕,添加Onclick 代碼,如下:
procedure TForm1.Button1Click( Sender : TObject);
begin
API_HookUp;
end;
procedure TForm1.Button3Click( Sender : TObject);
begin
Un_API_Hook;
end;
procedure TForm1.Button2Click( Sender : TObject);
begin
MessageBoxA(Form1.Handle,'NO HOOK UP A','MessageBoxA',MB_OK);
MessageBoxW(Form1.Handle,'NO HOOK UP W','MessageBoxW',MB_OK);
end;
記得要保存,在後面我們還要使用它們。編譯一下,
運行..啊哈,成功了!且慢,別高興得太早。如果現在新建
一個Application TestTry,在Form 上添加一個按鈕,Onclick事
件如下:
procedure TForm1.Button1Click(Sender: TObject);
begin
MessageBoxA(Form1.Handle,'NO HOOK UP A','MessageBoxA',MB_OK);
MessageBoxW(Form1.Handle,'NO HOOK UP W','MessageBoxW',MB_OK);
MessageBox (Form1.Handle,'NO HOOK UP BOX','MessageBox',MB_OK);
end;
先運行T R Y 1 ,再運行T e s t T r y 。結果呢?原來,APIHook 僅僅在TRY1 中掛上了,並沒有在所有的系統進程中掛上。
你也許聽說過,必須把我們的函數和A P I _ H o o k u p 、Un_API_Hook 放在一個動態鏈接庫裡(這是對的)。那麼,你就試試吧。如果你這樣做了,也並不能在所有的系統進程中掛上。原因很簡單,我們僅僅改變了進程指向API 函數的指針,系統中其他的進程還是各管各的。

3.Hook Hook Hook Hook Hook Hook..
我們想想做鼠標鉤子時的做法:用SetWindowsHookEx掛上鼠標鉤子,當其他的進程發出鼠標消息時,我們的程序就會攔截到並作出響應。我們還可以用UnhookWindowsHookEx 解除鼠標鉤子。我們也必須為我們的函數掛上鉤子。不過,鼠標有各種消息響應其他進程,我們的進程有什麼消息呢?如果沒有消息又怎樣響應其他進程呢?即使我們自定義了消息,其他的進程又怎樣“懂得”我們的消息呢?真像走入了絕境。
不用怕, 至少我們有兩種方法。一是完全模仿SetWindowsHookEx,編制自己的MySetWindowsHookEx,我看過大多數的API Hook 程序裡都用了這個方法。其實,我們不必捨近而求遠, 我們完全可以繼續使用SetWindowsHookEx,因為系統還為我們提供了一個函數:GetMsgProc。在Delphi 中輸入GetMsgProc,然後光標停在上面,按F1 鍵。怎麼樣,Delphi 幫助裡講得夠清楚的吧?好了,我們的消息也有了。
想一想吧:我們在動態鏈接庫中掛上WH_GETMESSAGE消息鉤子,當其他的進程發出WH_GETMESSAGE 消息時,就會加載我們的動態鏈接庫,如果在我們的DLL 加載時自動運行API_Hook,不就可以讓其他的進程掛上我們的API Hook嗎?

4.第二個程序
說干就干。新建一個動態鏈接庫library TryDLL,把原來的Unit APIHook 和 Unit MESS 加進來。library TryDLL 的代碼如下:
uses
Windows,
SysUtils,
Classes,
APIHook in 'APIHook.pas',
mess in 'mess.pas';
{$R *.RES}
function GetMsgProc(code: integer; removal: integer; msg: Pointer): Integer; stdcall;
begin
Result := 0;
end;
Var HookHandle: THandle;
procedure StartHook; stdcall;
begin
HookHandle := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgProc, HInstance,
0);
end;
procedure StopHook; stdcall;
begin
UnhookWindowsHookEx( HookHandle );
end;
exports StartHook, StopHook;
begin
API_Hookup; //加載時掛上
end.
為了卸載時能解除鉤子,在Unit MESS 單元最後加上一句:
finalization
Un_API_hook;
當然,別忘了對MESS 做相應修改。編譯好後別忘了存盤。新建Application TRY2 程序,主Form 的單元名稱不妨叫TRYUnit2,在Form1 上添加三個Button,並聲明:
procedure StartHook; stdcall; external 'TRYDLL.DLL';
procedure StopHook; stdcall; external 'TRYDLL.DLL';
三個Button 的OnClick 代碼如下:
procedure TForm1.Button1Click(Sender: TObject);
begin
StartHook;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
MessageBoxA(Form1.Handle,'NO HOOK UP A','MessageBoxA',MB_OK);
MessageBoxW(Form1.Handle,'NO HOOK UP W','MessageBoxW',MB_OK);
MessageBox (Form1.Handle,'NO HOOK UP BOX','MessageBox',MB_OK);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
StopHook;
end;
編譯好後運行吧。這次倒好,無論你怎樣測試,都得不到鉤上的信息。整理一下我們的工作吧:TRY2 運行 --> TryDLL 加載 --> 運行API_Hookup,為TryDLL 掛上MyBox。即使我們按下了Button1,其他的進程加載了TryDLL,也僅僅是運行API_Hookup,為TryDLL 掛上MyBox 而已,而其他的進程(包括TRY2 本身)並沒有掛上MyBox。不信,你可以在TryDLL 中加上一個啟動MyBox 的函數,測試一下。例如,你可以在T r y D L L 的S t o p H o o k 函數中的UnhookWindowsHookEx 語句前,加上一句:MessageBoxW(0,'MessageBoxW','這是測試DLL是否加載了MyBox',MB_OK);你可以看到彈出窗口中的信息是“成功掛上W!”而不是“MessageBoxW”。並且由於我們的TryDLL 加載時就啟動了API_Hookup,卸載時才運行Un_API_Hook,所以不論你是否按下Button1 和Button3,並且不論你按下了幾次,每次你按下Button3 都會得到“成功掛上W!”的信息。看來,真正的麻煩才剛剛開始。實際上,我們剛才的工作都是有用的。我們剩下的工作就是改進Unit APIHook 中的TrueFunctionAddress 函數而已。想不到吧?為了能讓其他的進程掛上MyBox,我們必須了解一下PE文件的格式。

3.PE文件格式
分配表、頁模式、虛擬內存、內存映射..夠寫一本書的了吧?我這兒只想簡單地說兩句。PE文件格式是Windows 9X以上版本和Windows NT操作系統中廣泛采用的32 位可執行文件格式。與16 位的NE 格式不同的是,如果在內存中建立Module(Module這一術語通常用來表示已裝入內存的可執行文件或DLL的代碼、數據及資源,除了程序中直接用到的代碼和數據外,一個Module也指用於判定代碼及數據在內存中位置的支撐數據結構),則這個Module 中的代碼、數據、輸入表、輸出表以及其他有用的數據結構等使用的內存都放在一個連續的內存塊中。編程人員只要知道裝載程序文件映像到內存後的地址,即可通過映像後面的各種指針找到Module中的所有內容。具體來說,PE格式中的許多項是以RVA(相對虛擬地址)方式指定的,RVA就是某個項相對於文件映像地址的偏移。例如,裝載程序將一個PE文件裝入到虛擬地址空間,從0x10000開始的內存中,如果PE文件中某個表在映像中的起始地址是0x10800,那麼該表的RVA就是0x800。將RVA轉化為可用的指針,只要將RVA的值加上Module的基地址即可。基地址是指裝入到內存中的EXE或DLL程序的開始地址,它是Windows編程中的一個重要概念。為了方便起見,Win32將Module的基地址作為Module的實例句柄(Instance Handle)。在Win32 中,你可以直接調用GetModuleHandle取得指向DLL的指針,通過該指針訪問該DLL Module的內容。
PE文件格式可執行文件共有五部分組成:MS-DOS首部、PE 首部、信息塊表、信息塊、輔助信息。MS-DOS首部是一個極小的DOS 程序,一般是為了顯示像“Thisprogram cannot be run in MS-DOS mode”這類的信息。在Delphi 中,其定義在PImageDOSHeader 結構中。該首部還給出了PE首部結構的起始地址,_lfanew字段就是真正PE 首部的相對偏移。PE 首部在Delphi 中定義為PImageNTHeaders結構,該結構是由一個雙字的標志項和兩個子結構構成:
Signature : DWord;
FileHeader : TImageFileHeader;
OptionalHeader : TImageOptionalHeader;
標志項是為了說明該可執行文件是“PE\O\O”、“NE”還是“LE”。TImageFileHeader包含了編譯器產生的COFF OBJ信息。TImageOptionalHeader包含有堆棧初始大小、塊表大小、數據目錄(Data Directory)及其他一些重要信息。你並不需要知道TImageOptionalHeader的所有字段。最重要的兩個字段是I m a g e B a s e 和S u b s y s t e m 。其中還有一個重要的字段——IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]。數組一開始的元素內含可執行文件重要部位的RVA及大小。數組的第一個元素代表exported function table(如果有的話)的地址和大小,第二個元素代表imported functiontable的地址和大小,依此類推。在我們的程序裡,用它得到RVA:
RVA := NT^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
這樣,我們也得到了exported function table 和imported function table。這裡有必要談一談importedfunction table。
EXE/DLL 在被加載內存之前,存放在PE 文件的imported table(或稱為.idata)中的信息是給加載器用來決定函數地址並修補它們,以便完成image 用的。而在被加載之後, .idata 內含的是指針,指向EXE/DLL的輸入函數。.idata section(imported table)是以一個IMAGE_IMPORT_DESCRIPTOR數組開始。在PE文件中聯結(implicitly link)的DLLs 都會在此有一個對應的IMAGE_IMPORT_DESCRIPTOR 結構。最後一個IMAGE_IMPORT_DESCRIPTOR結構的內容全部為NULL,以此作為結束符號。IMAGE_IMPORT_DESCRIPTOR的格式描述如下:
DWORD Characteristics/OriginalFirstThunk:這是一個偏移值(一個RVA),對應於一個輸入函數。DWord TimeDateStamp:這是文件的產生時刻。通常此欄為0 。然而微軟的B I N D 程序可以將此IMAGE_IMPORT_DESCRIPTOR所對應的DLL的產生時刻寫到這裡來。
DWORD ForwarderChain:這個字段關系到所謂的forwarding(轉交),意味著一個DLL 函數在參考(呼叫、利用)另一個DLL。這個字段內含一個索引,指向FirstThunk數組。被這個索引所指定的函數就是一個轉交函數。DWORD Name:這是一個RVA,指向一個以NULL為結束字符的ASCII字符串,內含imported DLL的名稱。PIMAGE_THUNK_DATA FirstThunk:這是一個R V A , 指向一個D W O R D s(IMAGE_THUNK_DATA)數組。大部分情況下,該D W O R D 被解釋為一個指向IMAGE_IMPORT_BY_NAME 結構的指針。然而,以函數序號(而非函數名稱)輸入,也是有可能的。IMAGE_IMPORT_DESCRIPTOR結構中,最重要的部分是i m p o r t e d D L L 的名稱以及兩個IMAGE_THUNK_DATA DWORDs 數組。每一個IMAGE_THUNK_DATA DWORDs對應一個輸入函數。在EXE 文件中,兩個數組(分別由Characteristics 和FirstThunk 欄位指向)平行存在,並且都以NULL 為結束符號。第一個數組(由Characteristics 指向)從不被修改,有時候它又被稱為hint-name table。第二個數組(由FirstThunk 指向)則被加載器改寫。載入器一一檢閱每一個IMAGE_THUNK_DATA, 並找出它所記錄的函數的地址,然後把位址寫入IMAGE_THUNK_DATA這個DWord 之中。
我們在改寫API Hook 那一節談過,對DLL 函數的呼叫會導致一個JMP DWord PTR[XXXXXXXX]指令。[XXXXXXXX]事實上參考到FirstThunk數組中的一個元素。由於這個IMAGE_THUNK_DATA 數組內容已被加載器改寫為輸入函數的地址,所以它又被稱為ImportedAddress Table(IAT)。
我希望大家能仔細地研究一下MSDN裡關於這方面的文章。你天天編程都和PE文件打交道,不了解可不行喲。我感覺說得太多了。我之所以說了這麼多,特別是對IMAGE_IMPORT_DESCRIPTOR結構,因為Delphi好像並沒有定義IMAGE_IMPORT_DESCRIPTOR結構,也許是我沒有找到。在我的程序裡,自定義了這個結構,為了找到這個結構都快把我累瘋了。你也找找吧,如果找到了可要告訴我哦。
type
PIMAGE_IMPORT_DESCRIPTOR = ^
IMAGE_IMPORT_DESCRIPTOR;
IMAGE_IMPORT_DESCRIPTOR = packed record
OriginalFirstThunk : DWord;
TimeDateStamp : DWord;
ForwarderChain : DWord;
Name : DWord;
FirstThunk : DWord;
end;
注意,這裡也使用了packed 關鍵字。packed record相當於C 語言中的structure(知道為什麼我把packedrecord 稱作結構了吧)。
還記得我們前面提到的GetModuleHandle嗎?Delphi的幫助裡是這麼說的:“Parameters lpModuleName Pointsto a null-terminated string that names a Win32 module(either a .DLL or .EXE file)..If this parameter isNULL, GetModuleHandle returns a handle of the fileused to create the calling process.”如果我們把GetModuleHandle的參數設為NULL,哈哈,一切都有了(你有我有全都有)!
為了保證正確地攔截,我們必須窮舉P E 文件中的IMAGE_IMPORT_DESCRIPTOR 數組,看是否有我們的M o d u l e (例如USER32.DLL)。如是,則窮舉了IMAGE_THUNK_DATA,看是否引入了我們需攔截的函數。在我的程序裡, 我窮舉了P E 文件中的IMAGE_IMPORT_DESCRIPTOR 數組,並窮舉IMAGE_THUNK_DATA,和我們攔截的函數的真正地址比較,如是,則替換它。這樣做的好處是我們不必知道我們攔截的函數是從USER32.DLL、GDI32.DLL還是從KERNEL32.DLL中引入的。
唉,說多了讓人心煩,也許你早就全知道啦,實在不行,聰明的你讀讀代碼也就全明白了。還是看看關鍵的截獲代碼吧,這是PermuteFunction 的終極版本,只需用它代替原版本,程序就全部完成了。關鍵的代碼只有10行哦。

4.關鍵的代碼
(* ------------------------------------------- *)
(* PermuteFunction功能 :用 NewFunc替代 OldFunc *)
(* Windows Me + Delphi 5.0 *)
(* ------------------------------------------ *)
unit APIHook;
interface
uses
Windows, Classes ;
type
PImage_Import_Entry = ^Image_Import_Entry;
Image_Import_Entry = record
Characteristics : DWord;
TimeDateStamp : DWord;
MajorVersion : Word;
MinorVersion : Word;
Name : DWord;
LookupTable : DWord;
end;
Function TrueFunctionAddress(Code: Pointer): Pointer;
Function PermuteFunction(OldFunc, NewFunc: Pointer):
Integer;
implementation
type
TImportCode = packed record
JumpInstruction: Word;
AddressOfPointerToFunction: ^Pointer;
end;
PImportCode = ^TImportCode;
function TrueFunctionAddress(Code: Pointer): Pointer;
var func: PImportCode;
begin
Result := Code;
if Code = nil then exit;
try
func := code;
if (func.JumpInstruction=$25FF) then begin
Result := func.AddressOfPointerToFunction^;
end;
except
Result := nil;
end;
end;
Function PermuteFunction(OldFunc, NewFunc: Pointer):
Integer;
var IsDone: TList;
Function PermuteAddrInModule(hModule: THandle; OldFunc,
NewFunc: Pointer): Integer;
var
Dos : PImageDOSHeader;
NT : PImageNTHeaders;
ImportDesc : PImage_Import_Entry;
RVA : DWord;
Func : ^Pointer;
DLL : String;
f : Pointer;
written : DWord;
begin
Result := 0;
DOS := Pointer(hModule);
if IsDone.IndexOf(DOS) >= 0 then exit;
IsDone.Add(DOS);
OldFunc := TrueFunctionAddress(OldFunc);
if IsBadReadPtr(Dos,SizeOf(TImageDOSHeader)) then exit;
if Dos.e_magic <> IMAGE_DOS_SIGNATURE then exit;
NT := Pointer(Integer(Dos) + DOS._lfanew);
RVA := NT^.OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if RVA = 0 then exit;
ImportDesc := pointer(integer(DOS)+RVA);
While(ImportDesc^.Name<>0) do
begin
DLL := PChar(Integer(DOS) + ImportDesc^.Name);
PermuteAddrInModule(GetModuleHandle(PChar(DLL)),
OldFunc,NewFunc);
Func := Pointer(Integer(DOS) + ImportDesc.LookupTable);
While Func^ <> nil do
begin
f := TrueFunctionAddress(Func^);
if f = OldFunc then
begin
WriteProcessMemory(GetCurrentProcess,Func,
@NewFunc,4,written);
If Written > 0 then Inc(Result);
end;
Inc(Func);
end;
Inc(ImportDesc);
end;
end;
begin
IsDone := TList.Create;
try
Result := PermuteAddrInModule(GetModuleHandle(nil),
OldFunc,NewFunc);
finally
IsDone.Free;
end;
end;
end.
我們測試一下。運行TRY2..怎麼,又失敗了?天啊!(我蒙上眼睛)不要這麼悲觀嘛!我們不要在Delphi的編譯環境下運行它們。不必關閉Delphi,我們來到資源管理器,找到剛才編譯好的TRY2.exe,再運行它。怎麼樣,成功了吧?我想,這就是大多數人沒有用Delphi編寫成功API Hook 的原因吧。不過,你知道其中的原因嗎?就算是留給你的作業題了。再測試一下。我們發現TRY2運行時本進程就會掛上鉤子,而TESTTRY需要運行TRY2中的BUTTON1後才掛上鉤子,這是對的。因為TRY2運行時就會加載TRYDLL.D L L ,而其他的進程需要S e t W i n d o w s H o o k E x(WH_GETMESSAGE, @GetMsgProc, HInstance, 0)運行後才會加載TRYDLL.DLL。
一切OK!怎麼樣?用它編了什麼好程序也讓我分享分享哦。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved