Windows 下 API hook 和 Windows hook 应用区别
Hook 字面的意思是钩子,但是实际上更像提供一种过滤,或者说修改消息处理,或者API的机制。
API hook
什么是API的hook呢,其实就是将API调用的地方进行了转移,或者重新实现的一种技术。这种技术不仅仅可以用在windows 上,其他OS上一样可以使用,我曾经在brew mp 上的手机开发上测试过API hook。所以这里主要只讲windows 下的API hook。 API的hook 基本思路都是想办法替换原来的API,使函数的调用关系,直接进行转换, 典型的如下
Function A
Client
1
Function B
2
3
4
可以看到client 端要调用的FunctionA,在A进入之后,就调用了FunctionB , 在Function B 中回到FunctionA 做具体A应该做的事情,从A返回之后,FunctionB 然后在返回给Client,那么在FunctionB 中使用FunctionA的前后,它自己都可以干自己想要干的事情,这样就是一个API的hook, 下面我们主要讨论Windows 上最常用的两种API hook 策略。
A)IAT 替换API hook
所谓的IAT (import address table ) 这个是PE文件中一个重要的部分,这里我们不详细讨论PE文件的构成,因为这个本来就可以写一张内容。我们主要了解的是PE如何在一个已经加载在内存中的PE文件如何到它的IAT。
(1) 为什么要IAT?
一个模块,可能依赖别的API,也可能提供API,供别的模块调用,MS为了在PE文件中,分别用导入表和导出表进行描述两种关系。导入表IAT 主要是描述了这个模块依赖
那些其他模块提供的API。 而导出表EAT 是描述这个模块中都提供那些函数供别的模块调用。如果我们在某个模块的导入表中替换它所依赖的函数,那么这样就可以实现API hook.
(2) 如何IAT?
要IAT 要稍微了解一下PE文件的结构,
一个PE 文件的大概长相就是这样,我们要的IAT,就是要先到PE文件可选头,在这个数据结构中,描述了IAT 描述的位置在那里,然后给距IAT descriptor 就可以到IAT了。
另外要获得导入表描述符,只要使用这个API ImageDirectoryEntryToData 就可以了,
但是这个API是有依赖的。我们使用Dumpbin,或者TC 都可以看到这个exe 确实使用kernel32 dll, 所以,我们下一步要替换我们的自己MyCreateProcessW. 将上面函数稍微优化一下,修改一下,就可以了,
PROC install_api_hook(
HMODULE hHookModule,
const char * szDllName,
PROC pfnHookFunAddr,
createprocessa PROC pfnNewFundAddr
)
{
PROC pOrigFunc = NULL;
unsigned char *pBaseAddr =
reinterpret_cast<unsigned char *>(hHookModule);
PIMAGE_DOS_HEADER pDosHeader =
reinterpret_cast<PIMAGE_DOS_HEADER>(pBaseAddr);
PIMAGE_NT_HEADERS pNtHeader =
reinterpret_cast<PIMAGE_NT_HEADERS>(
pBaseAddr + pDosHeader->e_lfanew );
PIMAGE_OPTIONAL_HEADER pPEOptionHeader =
&pNtHeader->OptionalHeader;
PIMAGE_DATA_DIRECTORY pIATDataDirectory =
&(pPEOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]);
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor =
reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(
pBaseAddr + pIATDataDirectory->VirtualAddress );
for ( ; pImportDescriptor->Name; pImportDescriptor++ )
{
const char* pszModName =
reinterpret_cast<const char*>(
pBaseAddr + pImportDescriptor->Name);
if ( 0 == lstrcmpiA( pszModName, szDllName ) )
{
break;
}
}
if ( 0 == pImportDescriptor->Name )
{
return pOrigFunc;
}
PIMAGE_THUNK_DATA pThunkData =
reinterpret_cast<PIMAGE_THUNK_DATA>(
pBaseAddr + pImportDescriptor->FirstThunk);
while( pThunkData->u1.Function != 0 )
{
PROC *ppFunc = reinterpret_cast<PROC*>(
&pThunkData->u1.Function);
if ( *ppFunc == pfnHookFunAddr )
{
DWORD dwOldProtect = 0;
VirtualProtect( ppFunc, sizeof(PROC), PAGE_READWRITE, &dwOldProtect );
pOrigFunc = *ppFunc;
CopyMemory(ppFunc, &pfnNewFundAddr, sizeof(PROC));
// SIZE_T stMemorySize = 0;
// WriteProcessMemory(
// GetCurrentProcess(),
// ppFunc,
// &uNewFundAddr,
// sizeof(*ppFunc),
// &stMemorySize);
VirtualProtect( ppFunc, sizeof(PROC), dwOldProtect, 0 );
break;
}
}
return pOrigFunc;
}
然后在看这样的使用,那么一定会在CreateProcess 的时候,先弹出一个对话框,这样,就是我们的效果,
在main() 函数中,写下如下的语句
这样就是我们要的结果了。
B)JMP 指令替换
现在你在网上搜索一下,说JMP指令替换的,一般都说保存函数入口处5个字节,其实这是相对地址跳转,当然了,如果使用了绝对跳转,那么就不至5个字节了,如果使用相对短地址跳转,当然少于5个字节,这个部分可以参看80x86 汇编instruction set, operator code.
这里我们也采用相对地址跳转,但是最后会说明如何用短跳转实现API hook, 因为这个很有用。至于是什么跳转,主要是他们跳转地址范围不同。相对地址跳转-2G到2G。
跳转的hook 的原理
第一种, 就是进入FunctionA 就跳转到FunctionB,做完B应该做的事情之后,将FunctionA的入口处的JMP去掉,回复到FunctionA原来的样子。 然后在Function B 中调用FunctionA,
当然这个地方你也可以不调用,根据你的功能决定, 然后再在FunctionA的入口布置JMP指令。 最后就是返回Caller。
我们看看如何实现, 这里补充一下,如何计算JMP指令后的operand. 就是跳转的相对地址。
Label1:
Mov EAX, EAX
Mov Edi, edi
...............................
Label2:
JMP Label1
Label3:
XOR EcX, EcX
那么如果是相对地址跳转JMP label1 汇编机器指令的以后,label1 这个位置的值是多少呢,
因为只有知道这个如何计算,那么我们就知道怎么hardcode JMP 指令。因为计算机当执行
JMP Label1 的时候,地址译码机构已经完成了这条指令的译码,并将其送到了可执行单元,在可执行单元还没有开始执行的时候,地址译码机构不能闲着,已经将地址Label3已经装载到EIP中,当可执行单元进行执行JMP Label1的时候,其实是将EIP + JMP 指令operand 合成出来的,那么就有了下面的等式:
Label1 = EIP + operand;
EIP 等于label3 那么
label1= Lebel3+ operand,
那么
operand = Label1 - Label3.
这个是关键。
通常我们是不知道label3 的地址的,但是我们一定能知道lable2 的地址,这个地方就是我们要放JMP 指令的位置,如果我们放相对跳转的JMP 指令,该指令占用5个byte. 那么label3 就可以通过lable2 + JMP 指令的长度 计算出来。
基本上就是这个样子了,但是这个有一个致命的缺点,就是如果是多线程,那么可能hook 不到,怎么回事呢?我的笑,哈哈.........................
那么第二种JMP hook 技术就出来。
首先和第一种一样都有一个g_StubCode,而这个大小已经变大了,不再是5个Byte, 为什么呢,就是hook 完毕之后,就不但算在恢复原来函数的入口的字节了,等跳转到我们自己函数中,做完要做的事情,然后通过stubCode 在返回原来的函数,这样说明了一个问题,
就是这样hook 只能在原来函数之前做事,之后,就不能做事了,只能直接返回,当然有没有可能还能做事情呢,能,怎么办,有点复杂。 这里我们先讨论,简单这种。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论