IAT HOOK

原文连接:

http://blog.csdn.net/misterliwei/article/details/840983

 

 

一.            IAT   法  

  

IAT  法就是通过修改 IAT  表中的函数地址而达到的 API   截获的方法。 

  

1   .技术与实现   

  

每个调用的 API  函数地址都保存在 IAT  表中。 API  函数调用时,每个输入节( IMPORT SECTION  )所指向的 IAT  结构如下图所示。 

程序中每个调用 API   函数的 CALL  指令所使用的地址都是相应函数登记在 IAT  表的地址。所以为了截获 API   函数,我们将 IAT  表中的地址换成用户自己的 API PROXY  函数地址,这样每个 API  调用都是先调用用户自己的 API PROXY  函数。在这个函数中我们可以完成函数名称的记录、参数的记录、调用原来的过程,并在返回时记录结果。 

 API PROXY  代码是这个 IAT HOOK API   方法的核心了,为了完成它,我使用了两段程序:结构 HOOKAPIPROXY  和函数 PROXYFUN  。其中前者每个函数使用一个这样的结构,它用来保存函数原来的地址和函数名称;而后者只有一个,它用来分析记录函数的名称、参数、结果等等。使用了这两个结构之后,程序的代码结构变成了下图所示:   

下面我们来详细分析这两段程序。 

  

1)       HOOKAPIPROXY    结构程序  

  

typedef struct {
  byte PushCode1;  //0xff
  ULONG OrgAddr;
  byte PushCode2;  //0xff
  ULONG NameAddr;
  byte JmpCode;    //0xe9
  ULONG JmpParam;
}*PHOOKAPIPROXY, HOOKAPIPROXY; 

  

每个 API   函数的 IAT  项都执行一个 HOOKAPIPROXY  结构,这个结构其实是一段代码,它是由三段语句组成。 

 

a) push   原来 IAT  的地址。 

b) push   函数名称的地址。 

c) jmp  到同一个 ProxyFun()  程序段中。这个程序段就是下面的一个程序段了。 

  

2   ) ProxyFun()    代理程序段  

所有的 API   函数都要执行下面的这个统一的代码段。这段核心的代码主要使用 1  )代码中保存在堆栈中的信息,得到函数名称,所有参数,返回结果。函数的代码如下:    

_declspec(naked) void ProxyFun()
{
// 局部变量的定义   

DWORD ByteWrite;   // 写入日志文件中的字节   

PCHAR pFunctionName; // 取函数名称   

char Str[200];   // 字符串   

ULONG Param[PARAMNO]; // 取出的参数   

char StrParam[200], StrTemp[20]; 

ULONG nParam, i; 

ULONG Result; 

  

_asm{
 push ebp
 mov ebp, esp
 sub esp, __LOCAL_SIZE
 push esi
 push edi
 push ebx 

// 先取出函数名称地址。  
 mov eax, [ebp+4]
 mov pFunctionName, eax
 
 //  下面拷贝参数  PARAMNO * 4
 mov ecx, PARAMNO
 mov esi, ebp
 add esi, 12 + PARAMNO * 4   

//4 :原函数 EIP  + 4 :原来函数地址 + 4:FUNCTIONNAME + PARAMNO 参数   

nextmove:
 mov eax, [esi]
 push eax
 sub esi, 4
 dec ecx
 jnz nextmove 

mov nParam, esp
 // 调用原来的函数。  
 call dword ptr [ebp + 8]
 mov Result, eax  //  保存可能的返回值 
 
 mov ecx, esp
 //  先复原  ESP
 mov esp, nParam 
 add esp, PARAMNO * 4
 // 判断到底有几个参数。  
 sub ecx, nParam
 shr ecx, 2
 mov nParam, ecx
 jcxz noparam   //  若没有参数   

// 下面拷贝参数  
 lea esi, Param
 mov edi, ebp
 add edi, 16 

nextparam:
 mov eax, [edi]
 mov [esi], eax
 add esi, 4
 add edi, 4
 loop nextparam
noparam: // 若没有参数 , 便直接出来。  
 } 

  

// 往文件中写数据。  
//  (此处略,下面有详细说明)  ....  


   // 准备返回 
   _asm{   

         mov eax, Result
     mov ecx, nParam
     shl ecx, 2 

 pop ebx
 pop edi
 pop esi
 mov esp, ebp
 pop ebp
 
 add esp, 8  //4 :原来函数地址 + 4:FUNCTIONNAME PARAMNO 参数  
 pop edx
 add esp, ecx
 push edx
 ret
 }
}   

 

  

这是一个naked 函数,关于 naked 函数的书写,我将在以后专门写篇文章来介绍。在这个函数中,需要实现的功能主要是围绕堆栈来进行的。下图是本过程中堆栈的变化图。 

 

由于 proxyfun  函数在整个 IAT APIHOOK  中至关重要,所以下面我们将从函数功能的实现来详细说明它。 

 

a)  函数名称 

这比较简单,因为在 APIHOOKPROXY  结构中将函数放在堆栈中,所以 [EBP + 4]  中保存着函数名称的地址。 

  

b)  函数参数 

这是复杂的过程。在这里,我使用了一个小技巧——这个技巧对于 WINAPI  函数调用方式 (stdcall)  是有效的。为了计算参数个数,我们先将 PARAMNO(  定义的整数 )  个 DWORD  堆栈值并保存好 ESP  。然后调用原来的函数 (call [ebp + 8])  。由于 WINAPI  调用方式的函数在返回前释放堆栈中的参数,所以在函数返回后 ESP  值与原来的是不一样的(当每个参数时是一样的),而它们之间的差值就是参数的个数的信息。 

  

c)  函数的返回值 

调用完函数后,函数的返回值放在 EAX  中。当然 VOID  无返回值的函数也将记录 EAX  值。 

  

d  )返回至函数调用点 

函数的调用点 EIP  值放在堆栈的 [EBP + 12]  中。 

  

2   .信息保存   

  

上面的代码中,我们没有涉及到代码的保存部分。这是个独立的部分,我们将信息保存在 LOG  文件中。    

// 打开文件   

hLog = CreateFile("c://ApiHook.txt", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 

hEvent = CreateEvent(NULL, FALSE, TRUE, "BitBltEvent"); 

.... 

  

// 记录信息   

StrParam[0] = '/0'; 

for(i = 0; i < nParam; i ++) 

sprintf(StrTemp, " 0x%08x ", Param[i]); 

strcat(StrParam, StrTemp); 

  

WaitForSingleObject(hEvent, INFINITE); 

SetFilePointer(hLog, 0, NULL, FILE_END); 

if ((ULONG)pFunctionName >= 0x80000000) 

sprintf(Str, "[Ord: 0x%x]  参数个数 : %d [%s] 返回值 : 0x%x /x0d/x0a", (ULONG)pFunctionName - 0x80000000, nParam,     StrParam, Result);  

else 

sprintf(Str, "[%s]  参数个数 : %d [%s] 返回值 : 0x%x /x0d/x0a", pFunctionName, nParam, StrParam, Result);  

WriteFile(hLog,   Str, strlen(Str), &ByteWrite, NULL); 

SetEvent(hEvent); 

  

... 

// 关闭文件   

CloseHandle(hEvent); 

CloseHandle(hLog); 

  

 

因为可能有多个线性调用 API  函数并被截获,所有需要用一个事件内核对象 (hEvent)  来同步对文件的写操作。 

  

需要注意上面的记录信息这段代码是不能放在 proxyfun  中的。因为你可能已经截获了 WaitForSingleObject  、 SetFilePointer  、 WriteFile  或者 SetEvent  ,这样会引起递归调用而引起死锁和栈溢出。为了解决这个问题,我们在 HOOK API  前先将这些函数的地址保存在全局变量中。 

WriteFileAddr = (ULONG)WriteFile; 

SetFilePointerAddr = (ULONG)SetFilePointer; 

SetEventAddr = (ULONG)SetEvent; 

WaitForSingleObjectAddr = (ULONG)WaitForSingleObject; 

 

于是 proxyfun  的信息记录过程将变成如下: 

  

#define PSETFILEPOINTER DWORD (WINAPI *)(HANDLE, LONG, PLONG, DWORD) 

#define PWRITEFILE BOOL (WINAPI *)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED) 

#define PWAITFORSINGLEOBJECT DWORD (WINAPI *)(HANDLE, DWORD) 

#define PSETEVENT BOOL (WINAPI *)(HANDLE) 

  

... 

// 往文件中写数据。  

StrParam[0] = '/0'; 

for(i = 0; i < nParam; i ++) 

sprintf(StrTemp, " 0x%08x ", Param[i]); 

strcat(StrParam, StrTemp); 

  

((PWAITFORSINGLEOBJECT)WaitForSingleObjectAddr)(hEvent, INFINITE); 

((PSETFILEPOINTER)SetFilePointerAddr)(hLog, 0, NULL, FILE_END); 

if ((ULONG)pFunctionName >= 0x80000000) 

sprintf(Str, "[Ord: 0x%x]  参数个数 : %d [%s] 返回值 : 0x%x /x0d/x0a", (ULONG)pFunctionName - 0x80000000, nParam, StrParam, Result); 

else 

sprintf(Str, "[%s]  参数个数 : %d [%s] 返回值 : 0x%x /x0d/x0a", pFunctionName, nParam, StrParam, Result); 

((PWRITEFILE)WriteFileAddr)(hLog,Str,strlen(Str),&ByteWrite,NULL); 

((PSETEVENT)SetEventAddr)(hEvent);  

  

这样便完成了信息的记录工作了。 

  

3   .优缺点   

使用此方法进行 API  截获时,你会发现有一些 API  函数总是截获不了,比如消息循环中的 GetMessage  等消息,这是因为编译器做了大量的优化工作,它可能早将 IAT  中函数地址保存在某个寄存器或者变量(就像上文介绍的信息记录的方法)了,所以你即使改变了 IAT  表中的地址值,也不会截获到这些 API  调用。这便是 IAT  法的最大的缺点——不能完全截获。 

这个方法的优点是实现简单,兼容性好。

 

源代码下载地址:

http://download.csdn.net/detail/misterliwei/3316726

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值