HanDs
管理员

[Delphi文章] 制作API HOOK 





学习中请遵循国家相关法律法规,黑客不作恶。没有网络安全就没有国家安全

本站需要登陆后才能查看

转自 橙子的博客 http://hi.baidu.com/rarnu/blog/item/e0bc4ffb7fb89a63024f566b.html
由于近期的项目需要,特此研究下API HOOK。研究前,我在网上找了许多资料,也大致了解了一下原理。其实说白了也不是很困难。核心思想就是用你自己的处理过程,取代掉Windows的API处理。这非常类似于Java等的AOP。下面就是一些实现。

首先是API Hook的核心代码了,这个代码即是用于替换函数指针。
定义入口记录:

type
   PIMAGE_IMPORT_ENTRY = ^TIMAGE_IMPORT_ENTRY;
   TIMAGE_IMPORT_ENTRY = packed record
      Characteristics: DWORD;
       TimeDateStamp: DWORD;
       MajorVersion: Word;
      MinorVersion: Word;
      Name: DWORD;
      LookupTable: DWORD;
end;

这个结构的源头来自于MSDN,没有MSDN的话,也很难自己想到。当然了,如果对程序PE很有研究的话,也应该能凭经验写出这个记录来。

接下来,需要另一个记录,用于保存跳转指令和要跳转到的API函数。

type
   PTIMPORT_CODE = ^TIMPORT_CODE;
   TIMPORT_CODE = packed record
      JmpPtr: Word;
     PtrAdd: ^Pointer;
end;

其中JmpPtr保存的是导入表地址,在Delphi中可以用$25FF表示。这个值的来源,可以自己反汇编调试得到。在Call API的时候,跟踪即可,如下所示:

@@ PUSH 0
@@ MOV EAX,123
@@ CALL EAX
@@ POPFD
@@ POPAD
@@ FF25 5D108000 JMP DWORD PTR DS:[EBP]+4A
@@ NOP

注意红色的那句,将最前面那个导入表的地址,高低位互换即可。

接下来实现两个方法,一个是用于得到真实API的地址,另一个用于将自定义的函数替换掉API

function GetAPIAddress(ApiPtr: Pointer): Pointer;
begin
   Result := ApiPtr;
   if ApiPtr= nil then
      exit;
   try
      if (PTIMPORT_CODE(ApiPtr).JmpPtr = $25FF) then
        Result := PTIMPORT_CODE(ApiPtr).PtrAdd^;
   except
      Result := nil;
   end;
end;

function SwapPtr(OldPtr, NewPtr: Pointer): Integer;
var
   lstDosHead: TList;
   function hkSwapPtr(h: Cardinal; OldPtr, NewPtr: Pointer): Integer;
   var
      DosHeader: PImageDosHeader;
      NTHeader: PImageNTHeaders;
      ImpEty: PIMAGE_IMPORT_ENTRY;
      VAddr: DWORD;
      Func: ^Pointer;
      DLLName: string;
      fOld: Pointer;
      wBytes: DWORD;
   begin
      Result := 0;
      DosHeader := Pointer(h);
      if lstDosHead.IndexOf(DosHeader) >= 0 then
   exit;
      lstDosHead.Add(DosHeader);
      OldPtr := GetAPIAddress(OldPtr);
      if IsBadReadPtr(DosHeader, SizeOf(TImageDosHeader)) then
   exit;
      if DosHeader.e_magic <> IMAGE_DOS_SIGNATURE then
       exit;
      NTHeader := Pointer(Integer(DosHeader) + DosHeader._lfanew);
      VAddr := NTHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
      if VAddr = 0 then
         exit;
      ImpEty := Pointer(integer(DosHeader) + VAddr);
      while ImpEty^.Name <> 0 do
      begin
         DLLName := PChar(Integer(DosHeader) + ImpEty^.Name);
       hkSwapPtr(GetModuleHandle(PChar(DLLName)), OldPtr, NewPtr);
         Func := Pointer(Integer(DosHeader) + ImpEty^.LookupTable);
         while Func^ <> nil do
   begin
        fOld := GetAPIAddress(Func^);
          if fOld = OldPtr then
          begin
         WriteProcessMemory(GetCurrentProcess, Func, @NewPtr, 4, wBytes);
         if wBytes > 0 then
             Inc(Result);
   end;
   Inc(Func);
   end;
   Inc(ImpEty);
    end;
end;
begin
   lstDosHead:= TList.Create;
   try
       Result := hkSwapPtr(GetModuleHandle(nil), OldPtr, newptr);
   except
   end;
   lstDosHead.Free;
end;

这两步做好后,接下来的事表就很简单了,我尝试修改FindWindow这个API的处理。
type
   TFindWindow = function (lpClassName, lpWindowName: PChar): HWND; stdcall;
   TFindWindowA = function (lpClassName, lpWindowName: PAnsiChar): HWND; stdcall;
   TFindWindowW =function (lpClassName, lpWindowName: PWideChar): HWND; stdcall;
var
   OldFindWindow: TFindWindow;
   OldFindWindowA: TFindWindowA;
   OldFindWindowW: TFindWindowW;

function hkFindWindow(lpClassName, lpWindowName: PChar): HWND; stdcall;
begin
   if lpWindowName = 'Form1' then
      Result := 0
   else
      Result := OldFindWindow(lpClassName, lpWindowName);
end;

function hkFindWindowA(lpClassName, lpWindowName: PAnsiChar): HWND; stdcall;
begin
   if lpWindowName = 'Form1' then
      Result := 0
   else
      Result := OldFindWindowA(lpClassName, lpWindowName);
end;

function hkFindWindowW(lpClassName, lpWindowName: PWideChar): HWND; stdcall;
begin
   if lpWindowName = 'Form1' then
      Result := 0
   else
      Result := OldFindWindowW(lpClassName, lpWindowName);
end;

很明确了,接管的结果应该是,当调用FindWindow这个API时,如果lpWindowName这个参数是Form1,那么直接返回0。也就是说,不想让别的程序能找到Form1。如果找的不是Form1,那么就把控制权再交还给原先的FindWindow,让它继续执行。

下面的代码是处理用自己的函数替换掉API函数。替换过后,API就被接管了。

procedure DoHook;
begin
   if @OldFindWindow = nil then
      @OldFindWindow := GetAPIAddress(@FindWindow);
   if @OldFindWindowA = nil then
      @OldFindWindowA := GetAPIAddress(@FindWindowA);
   if @OldFindWindowW = nil then
      @OldFindWindowW := GetAPIAddress(@FindWindowW);
   SwapPtr(@OldFindWindow, @hkFindWindow);
   SwapPtr(@OldFindWindowA, @hkFindWindowA);
   SwapPtr(@OldFindWindowW, @hkFindWindowW);
end;

procedure DoUnHook;
begin
   if @OldFindWindow <> nil then SwapPtr(@hkFindWindow, @OldFindWindow);
   if @OldFindWindowA <> nil then SwapPtr(@hkFindWindowA, @OldFindWindowA);
   if @OldFindWindowW <> nil then SwapPtr(@hkFindWindowW, @OldFindWindowW);
end;

前一个函数是用于替换API,当程序结束时,为了不影响系统的正常运作,应当把替换过的API再换回正常的。

替换API的工作完成后,我们需要将它挂到系统上,这样才能够在全局范围内替换掉API。如果不进行系统级的挂勾,那么对于API的替换,只作用于当前程序本身。

挂勾到系统也很简单,如下:

var
   HookHandle: Cardinal;

function GetMsgProc(code: integer; removal: integer; msg: Pointer): Integer; stdcall;
begin
   Result := 0;
end;

procedure DoHookSystem;
begin
   HookHandle := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgProc, HInstance, 0);
end;

procedure DoUnHookSystem;
begin
   UnhookWindowsHookEx(HookHandle);
end;

这里借用了GetMsgProc这个回调函数,并直接返回0,使挂勾能持续运作。
与DoHook时一样,为了使程序退出后系统不受影响,也需要一个DoUnHookSystem方法。

到此为止,一个简单的API Hook就完成了。对于Windows API的编程有些时候的确是很麻烦,必要时还得借助汇编或是Spy++之类的工具来帮助完成任务。当然了,通过此例,也可以看到,API Hook其实并不是什么非常高深的技术,凭借着对PE的了解,加上手头有个MSDN,许多问题就能迎刃而解了。  


学习中请遵守法律法规,本网站内容均来自于互联网,本网站不负担法律责任
制作
#1楼
发帖时间:2016-7-9   |   查看数:0   |   回复数:0
游客组
快速回复