IAT HOOK简介
API HOOK估计没必要多介绍了,简单的来讲就是通过某种方式来改变API函数的工作流程.一般来讲有两种方法:IAT HOOK和INLINE HOOK.前一种应用较为广泛,一方面因为简单,还一方面因为稳定.他的原理就是改写进程空间中要HOOK的API所在模块的函数引入表,使之指向替换原 API函数的函数地址(某些木马就是利用IAT HOOK的方式,挂钩NtQuerySystemInformation的方式来实现进程隐藏).这里感觉还是有必要再说一点INLINE HOOK,这个复杂点,直接进入被HOOK的API函数内部去修改他,采用指令call或者jmp等,迫使API改变流程,跳到自己的替换函数中.通常都 是在函数头部前10个字节内修改.
如何搞定IAT HOOK
注意这里说的是搞定,而不是修复IAT HOOK,搞定所指的就是只要不让那个IAT HOOK起作用就行了.
方案1:通过LoadLibrary和GetProcAddress来动态获取API地址.
忘了是在哪看到过这个方法,实际上这个方法可以说根本无效,Jeffrey Richter在核心编程里面给出的例子就提到过这个问题,为了在动态获取API调用的情况下也能让HOOK生效,首先就应该把 LoadLibraryA,LoadLibraryW......等等那几个可以实现动态获取的函数全部都HOOK住.这种方案被直接否决了.
方案2:直接硬编码,从ntdll.dll里面调用NativeAPI.
虽然听起来有点恐怖,但这种方式确实比方案1要有效,只是ntdll.dll有500多函数,全部应编码有点天方夜谭,如果单纯了为了对付某几个特定的HOOK还是可以的.
方案3:直接从PE文件入手,自己读取导出表获取API地址
这也是我认为对付IAT HOOK最有效的方式,无论是检测IAT HOOK还是绕过IAT HOOK或者是修复IAT HOOK都必须走这一步.
实现方式
既然从PE文件入手,就不能不熟悉PE文件格式了,这里着重介绍一下导出表,因为这个表里面dll所导出的API函数地址,先来看一看导出表的数据结构:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image,函数地址相对于镜像基址的偏移
DWORD AddressOfNames; // RVA from base of image,函数名表相对于镜像基址的偏移
DWORD AddressOfNameOrdinals; // RVA from base of image,函数名和序号的对应表相对于镜像基址的偏移
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
重要的就是那三个加了中文注释的地方,这里存着没有被HOOK践踏过的真真切切的API信息,但是问题来了,这里不能直接用,因为是内存镜像偏移, 先要转化为文件偏移才可以,如何转换呢?罗sir的<WIN32汇编语言程序设计>里面写的很清楚了,首先看虚拟偏移RAV落在内存镜像中的 哪一个节中,然后减去内存镜像中这个节的起始值得到一个RAV',然后在节表中看这个节在文件中所在的偏移地址PointToRawData,然后用 PointToRawData+RAV'就是在文件中的偏移了.
算法看起来好像有点繁,但实际上不要忘了我们是为了对抗IAT HOOK的,这样一来可以简化不少,看一看那几个dll,ntdll.dll,kernel32.dll等等等等包含常用API的dll,我们要的东西都 在.text节里面,上述转换过程可以直接变为: dwRav-m_pOptionHeader->BaseOfCode+m_pOptionHeader->SizeOfHeaders.
转换问题解决了,就来看看如何获取我们想要的地址,废话少说,上代码:
//m_pBuffer是将dll用ReadFile读入内存后的缓冲区地址
FARPROC CLoadDll::SearchProcAddress(LPCTSTR strFunctionName)
{
DWORD dwIndex = 0;
DWORD dwRawNameAddr; //函数名表地址
DWORD dwRawAddrIndex; //函数名与编号转换表
DWORD dwRawFuncAddr; //函数表地址
DWORD dwFuncOffset; //函数的文件偏移地址
dwRawNameAddr = RavToOffset(m_pExportDesc->AddressOfNames); //获取PE文件中函数名表的偏移
dwRawAddrIndex = RavToOffset(m_pExportDesc->AddressOfNameOrdinals); //获取PE文件中函数名-序号对应表的偏移
dwRawFuncAddr = RavToOffset(m_pExportDesc->AddressOfFunctions); //获取PE文件中函数地址表偏移
for(dwIndex = 0; dwIndex < m_pExportDesc->NumberOfFunctions; dwIndex++){
if(strcmp(strFunctionName, (TCHAR *)(m_pBuffer + RavToOffset(*(DWORD *)(m_pBuffer+dwRawNameAddr+dwIndex*4)))) == 0){
dwFuncOffset = *(DWORD *)(m_pBuffer + dwRawFuncAddr + (*(WORD *)(m_pBuffer + dwRawAddrIndex + 2*dwIndex))*4);
dwFuncOffset += m_dwImageBase;
return (FARPROC)dwFuncOffset;
}
}
MessageBox(NULL, "导出表中无此函数!", "提示", MB_OK | MB_ICONINFORMATION);
return NULL;
}
这样以来我们就可以随心所欲的绕过R3态IAT HOOK了,直接去ntdll.dll导出native api来用,怎一个爽字了得.
最后来看看效果截图,用自己的CDllLoad类做了一个进程管理器,直接去ntdll.dll中获取NtQuerySystemInformation来看看进程,另外还开了一个IAT HOOK来隐藏进程,再拿优化大师的进程管理器对比一下效果:
结束语
原理已经知道了,实际我们还可以做更多的事情,比如枚举dll中的导出函数获取地址,可以检查系统中安装的IAT HOOK,或者再走远一点,直接用获取的未污染的API地址,把HOOK修复吧.