APIHook一直是使大家感興趣的話題。屏幕取詞,內(nèi)碼轉(zhuǎn)化,屏幕翻譯,中文平臺(tái)等等都涉及到了此項(xiàng)技術(shù)。有很多文章涉及到了這項(xiàng)技術(shù),但都閃爍其詞不肯明明白白的公布。我僅在這里公布以下我用Delphi制作APIHook的一些心得。
通常的APIHOOK有這樣幾種方法:
1、自己寫一個(gè)動(dòng)態(tài)鏈接庫,里面定義自己寫的想取代系統(tǒng)的API。把這個(gè)動(dòng)態(tài)鏈接庫映射到2G以上的系統(tǒng)動(dòng)態(tài)鏈接庫所在空間,把系統(tǒng)動(dòng)態(tài)鏈接庫中的該API的指向修改指向自己的函數(shù)。這種方法的好處就是可以取代系統(tǒng)中運(yùn)行全部程序的該API。但他有個(gè)局限,就是只適用于Win9x。(原因是NT中動(dòng)態(tài)鏈接庫不是共享的,每個(gè)進(jìn)程都有自己的一份動(dòng)態(tài)鏈接庫在內(nèi)存中的映射)
2、自己寫一個(gè)動(dòng)態(tài)鏈接庫,里面定義自己寫得象替代系統(tǒng)的API。把這個(gè)動(dòng)態(tài)鏈接庫映射到進(jìn)程的空間里。將該進(jìn)程對(duì)API的調(diào)用指向自己寫的動(dòng)態(tài)鏈接庫。這種方法的好處是可以選擇性的替代哪個(gè)進(jìn)程的API。而且適用于所有的Windows操作系統(tǒng)。
這里我選用的是第二種方法。
第二種方法需要先了解一點(diǎn)PE文件格式的知識(shí)。
首先是一個(gè)實(shí)模式的的DOS文件頭,是為了保持和DOS的兼容。
接著是一個(gè)DOS的代理模塊。你在純DOS先運(yùn)行Win32的可執(zhí)行文件,看看是不是也執(zhí)行了,只是顯示的的是一行信息大意是說該Windows程序不能在DOS實(shí)模式下運(yùn)行。
然后才是真正意義上的Windows可執(zhí)行文件的文件頭。它的具體位置不是每次都固定的。是由文件偏移$3C決定的。我們要用到的就是它。
如果我們在程序中調(diào)用了一個(gè)MessageBoxA函數(shù)那么它的實(shí)現(xiàn)過程是這樣的。他先調(diào)用在本進(jìn)程中的MessageBoxA函數(shù)然后才跳到動(dòng)態(tài)鏈接庫的MessageBoxA的入口點(diǎn)。即:
call messageBoxA(0040106c)
jmp dword ptr [_jmp_MessageBoxA@16(00425294)]
其中00425294的內(nèi)容存儲(chǔ)的就是就是MessageBoxA函數(shù)的入口地址。如果我們做一下手腳,那么......
那就開始吧!
我們需要定義兩個(gè)結(jié)構(gòu)
type
PImage_Import_Entry = ^Image_Import_Entry;
Image_Import_Entry = record
Characteristics: DWORD;
TimeDateStamp: DWORD;
MajorVersion: Word;
MinorVersion: Word;
Name: DWORD;
LookupTable: DWORD;
end;
type
TImportCode = packed record
JumpInstruction: Word; file: //定義跳轉(zhuǎn)指令jmp
AddressOfPointerToFunction: ^Pointer; file: //定義要跳轉(zhuǎn)到的函數(shù)
end;
PImportCode = ^TImportCode;
然后是確定函數(shù)的地址。
function LocateFunctionAddress(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;
參數(shù)Code是函數(shù)在進(jìn)程中的指針,即那條Jmp XXX的指令。$25FF就是跳轉(zhuǎn)指令的機(jī)器碼。
再下一篇我會(huì)講如何替換下那個(gè)XXX的內(nèi)容,讓他跳到你想去的地方。
在這里我將要實(shí)現(xiàn)轉(zhuǎn)跳。有人說修改內(nèi)存內(nèi)容要進(jìn)入Ring 0 才可以??墒荳indows本身提供了一個(gè)寫內(nèi)存的指令WriteProcessMemory。有了這把利器,我們幾乎無所不能。如游戲的修改等在這里我們只談APIHOOK。
function RepointFunction(OldFunc, NewFunc: Pointer): Integer;
var
IsDone: TList;
function RepointAddrInModule(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 := LocateFunctionAddress(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);
RepointAddrInModule(GetModuleHandle(PChar(DLL)), OldFunc, NewFunc);
Func := Pointer(Integer(DOS) + ImportDesc.LookupTable);
while Func^ <> nil do
begin
f := LocateFunctionAddress(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 := RepointAddrInModule(GetModuleHandle(nil), OldFunc, NewFunc);
finally
IsDone.Free;
end;
end;
有了這兩個(gè)函數(shù)我們幾乎可以更改任何API函數(shù)。
我們可以先寫一個(gè)DLL文件。我這里以修改Text相關(guān)函數(shù)為例:
先定義幾個(gè)函數(shù):
type
TTextOutA = function(DC: HDC; X, Y: Integer; Str: PAnsiChar; Count: Integer): BOOL; stdcall;
TTextOutW = function(DC: HDC; X, Y: Integer; Str: PWideChar; Count: Integer): BOOL; stdcall;
TTextOut = function(DC: HDC; X, Y: Integer; Str: PChar; Count: Integer): BOOL; stdcall;
TDrawTextA = function(hDC: HDC; lpString: PAnsiChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
TDrawTextW = function(hDC: HDC; lpString: PWideChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
TDrawText = function(hDC: HDC; lpString: PChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
var
OldTextOutA: TTextOutA;
OldTextOutW: TTextOutW;
OldTextOut: TTextOut;
OldDrawTextA: TDrawTextA;
OldDrawTextW: TDrawTextW;
OldDrawText: TDrawText;
......
function MyTextOutA(DC: HDC; X, Y: Integer; Str: PAnsiChar; Count: Integer): BOOL; stdcall;
begin
OldTextOutA(DC, X, Y, ‘ABC‘, length(‘ABC‘));
end;
function MyTextOutW(DC: HDC; X, Y: Integer; Str: PWideChar; Count: Integer): BOOL; stdcall;
begin
OldTextOutW(DC, X, Y, ‘ABC‘, length(‘ABC‘));
end;
function MyTextOut(DC: HDC; X, Y: Integer; Str: PChar; Count: Integer): BOOL; stdcall;
begin
OldTextOut(DC, X, Y, ‘ABC‘, length(‘ABC‘));
end;
function MyDrawTextA(hDC: HDC; lpString: PAnsiChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
begin
OldDrawTextA(hDC, ‘ABC‘, length(‘ABC‘), lpRect, uFormat);
end;
function MyDrawTextW(hDC: HDC; lpString: PWideChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
begin
OldDrawTextW(hDC, ‘ABC‘, length(‘ABC‘), lpRect, uFormat);
end;
function MyDrawText(hDC: HDC; lpString: PChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;
begin
OldDrawText(hDC, ‘ABC‘, length(‘ABC‘), lpRect, uFormat);
end;
調(diào)用時(shí)我們要把原來的函數(shù)地址保存下來:
if @OldTextOutA = nil then
@OldTextOutA := LocateFunctionAddress(@TextOutA);
if @OldTextOutW = nil then
@OldTextOutW := LocateFunctionAddress(@TextOutW);
if @OldTextOut = nil then
@OldTextOut := LocateFunctionAddress(@TextOut);
if @OldDrawTextA = nil then
@OldDrawTextA := LocateFunctionAddress(@DrawTextA);
if @OldDrawTextW = nil then
@OldDrawTextW := LocateFunctionAddress(@DrawTextW);
if @OldDrawText = nil then
@OldDrawText := LocateFunctionAddress(@DrawText);
然后很順其自然的用自己的函數(shù)替換掉原來的函數(shù)
RepointFunction(@OldTextOutA, @MyTextOutA);
RepointFunction(@OldTextOutW, @MyTextOutW);
RepointFunction(@OldTextOut, @MyTextOut);
RepointFunction(@OldDrawTextA, @MyDrawTextA);
RepointFunction(@OldDrawTextW, @MyDrawTextW);
RepointFunction(@OldDrawText, @MyDrawText);
在結(jié)束時(shí)不要忘記恢復(fù)原來函數(shù)的入口,要不然你會(huì)死得很難看喲!好了我們在寫一個(gè)Demo程序。你會(huì)說怎么文字沒有變成ABC呀?是呀,你要刷新一下才行。最小化然后在最大化??纯醋兞藳]有。
要不然你就寫代碼刷新一下好了。至于去攔截其他進(jìn)程的API那就用SetWindowsHookEx寫一個(gè)其他的鉤子將DLL映射進(jìn)去就行了,我就不再浪費(fèi)口水了。
掌握了該方法你幾乎無所不能。你可以修改其它程序。你可以攔截Createwindow等窗口函數(shù)改變其他程序的窗口形狀、你還可以入侵其它的程序,你還可以......嘿嘿。干了壞事別招出我來就行了。
我還寫了個(gè)例子,請?jiān)贑SDN上下載。
聯(lián)系客服