API函数头部HOOK法
来源:互联网 发布:东莞大数据协会 编辑:程序博客网 时间:2024/05/22 15:37
二.API函数头部HOOK法
本方法通过改变API函数的头部字节以达到HOOK的目的。
1. 技术与实现
每个程序中需要使用到的API函数的地址都放在IAT上,通过这个地址,每个CALL能正确的到达函数的头部。其流程如下图所示:
我们的截获工作就是在这个头部的位置——我们用一个JMP指令替换掉在这儿的一条或几条指令,让它指向各自的HOOKAPIPROXY代码,而在HOOKAPIPROXY中存放一些信息到堆栈后再JMP到proxyfun()函数——一个综合处理函数名称、参数和结果的代码段,最后在proxyfun()中返回到函数调用点。其流程变成下图:
API头部HOOK法的重点还是两个结构/代码段和替换指令,下面进行详细分析:
1) 替换原来的指令
我门需要使用一个JMP指令替换掉头部的一条或几条指令。一个JMP指令共占5个字节,那么到底要替换掉原来的几个指令呢?很显然,INTEL是没有提供专门的指令来判断一条指令占用几个字节,不过有个牛人Z0MBiE,他提供了一个函数,这个函数以指令地址为参数,返回这条指令占用多少字节。
知道了需要占用替换几个字节,我们将原来的指令拷贝至HOOKAPIPROXY结构中。然后加上JMP指令(5字节),空隙部分用0x90填充。
2) HOOKAPIPROXY结构
在本方法中,这个结构被定义成:
typedef struct {
byte PushCode; //0xff
ULONG NameAddr;
byte CallCode; //0XEA
ULONG CallAddr;
byte OldBytes[20];
byte JmpCode; //0xe9
ULONG JmpAddr;
int nCopyBytes;
}*PHOOKAPIPROXY, HOOKAPIPROXY;
它是由四部分组成,
a) push 函数名,保存函数名地址到堆栈中
b) call proxyfun()函数。这里用到了病毒程序的一个技巧,通过call指令将下一个指令的地址(即OldBytes[20])保存到堆栈中。
c) 替换前原API函数的前面头部的部分字节,字节数就是nCopyBytes。
d) Jmp 到原API函数的头部下一条指令。
3) proxyfun()代码
下面是proxyfun()的代码:
_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 + 8]
mov pFunctionName, eax
//下面拷贝参数PARAMNO * 4
mov ecx, PARAMNO
mov esi, ebp
add esi, 12 + PARAMNO * 4//4:Buffer地址 + 4:Name地址 + 4:CALLER地址。 PARAMNO参数
nextmove:
mov eax, [esi]
push eax
sub esi, 4
dec ecx
jnz nextmove
mov nParam, esp
//调用原来的函数。
call dword ptr [ebp + 4]
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
}
}
这段代码和IAT法的代码有较多的相似之处,也是围绕着堆栈来开展工作的,它的堆栈图如下:
从这张图,我们可以非常直观的看出有两点不同:
a) 保存函数名称的堆栈位置变化了。
b) 原来存放函数原来地址变成了 “原来函数的头部”这段内存的地址了,所以直接调用这个地址(CALL [EBP + 4])就可以调用原来的函数功能了。
2. 信息保存
由于和IAT法的截获方法不同,所以在Proxyfun()中信息保存的方法也是不同的。在本方法中,我们为每个需要用到的函数定义了一个有25个字节长的数组:前20个字节保存着从原API函数拷贝的头部;后5字节是个JMP指令,直接转移到API函数的后面字节。
通过这样的处理,虽然初始化时和IAT法不同,但是在Proxyfun()中却是一样的调用原函数的J
3. 优缺点
这个函数的优点很明显,它可以截获所有函数的调用。对于在IAT法中出现的保存IAT地址从而不能截获的情况能轻易解决。
但是这个问题有个很大可以说是致命的缺点:就是如何控制好函数的替换。Z0MBiE提供的代码虽然可以得出指令的长度,但是假如被替换掉的指令本身就包含指令跳转(如JMP/CALL/RET等)该如何重新计算它们的相对偏移?假如API函数的后面代码中有跳转到头部的指令(如JMP到头部的JMP指令中间的位置,因为这个位置本来可能就是一个指令的开始),该如何控制?这些问题,尤其是后一个问题,可以说是直接宣布了这个方法的死亡。
为了挽救这个方法,还有一个替代方法,就是《delphi下深入WINDOWS核心编程》中使用的——仅仅用JMP指令的5个字节覆盖API函数头部,在proxyfun()中在调用原函数前,先用原来的5个字节覆盖,等调用完,再用JMP指令替换。其实这是换汤不换药,而且带来更大的隐患——线程同步调用同一个API函数会发生错误,同时会有API“漏网”,最重要的是不能解决上文提出的关于API函数内存的JMP头部冲突。所以在《windows核心编程》中,jeffrey Richter直接否定了这个方法。
总之,对于你熟悉的API函数,没有上述的“JMP等跳转”情况的,使用本方法是简单又高效的,但是若想将本方法作为一个通用的API HOOK方法,估计是不太适合的。
- API函数头部HOOK法
- Hook API相关技术以及例子,Hook API的原理其实是通过核心函数强制修改原API的头部指针
- HOOK 改变API函数行为
- HOOK API 函数跳转详解
- 针对函数的多线程inline API HOOK
- 针对函数的多线程inline API HOOK
- API HOOK
- Hook API
- hook api
- hook api
- API HOOK
- Hook API
- API HOOK
- HOOK API
- API HOOK
- API Hook
- API Hook
- API Hook
- 手机RFID尝试移动通信业 价格安全难两全
- BPEL演示
- 做技术,切不可沉湎于技术!
- C#操作IIS
- 好用的Google Analytics分析服務
- API函数头部HOOK法
- $X
- js写的Hashtable类
- 自己寫程序操作VSS
- 学英语 之 黄健翔激情解说
- NIS网络信息系统简介
- .net资源站点汇总
- WinCE tips
- 群件的功能需求