关于HOOK拦截函数的几种方法

来源:互联网 发布:python微信公众号开发 编辑:程序博客网 时间:2024/05/17 13:45

【特殊】首先要明白的几个概念:


1、.EXE文件(PE文件)启动:系统将.EXE文件的二进制内容拉伸,让他变成可以运行的映像,但内容是不变的,所以.EXE其实就像.TXT,也可以随意改变其代码.

2、在硬编码中(二进制内容),地址的表示方式:(这就是下面所说的硬编码地址的规则)

                                 1)真正要跳转的地址 = 这条指令的下一行地址 + 地址硬编码


3、以上面的硬编码地址的规则可以推算出我们要在更改.EXE文件运行镜像中的内容的时候,计算出正确的地址:

                                 1)在一个地方插入代码时,计算地址的公式:地址硬编码   =   真正要跳转的地址   -   这条指令的下一行地址


【1】覆盖开头硬编码拦截API:



一、Code模板
CHAR XCode[6] = { 0xE9, 0x00 ,0x00 ,0x00,0x00 };    //编译:jmp 地址    用于记录我们的模拟API函数的[硬编码地址]
CHAR YCode[6] = { 0x00, 0x00 ,0x00 ,0x00,0x00 };    //用于记录被我们覆盖的API函数内数据的前5个字节


二、缺点
1、对CPU的指令统一依赖性强,不同的CPU,JMP指令是不同的,x86、x64、IA-64、其他CPU等,指令都各不相同


三、原理
1、当执行API函数时,PUSH传入参数,代码开始CALL到API所在的DLL中执行代码,在开头执行时,就JMP指令跳转到我们的函数,因为
   我们的函数所有的类型,都与API函数一样,这样就不会导致程序错误(堆栈平衡),又让程序执行了我们的代码


四、注意
1、API函数的数据只有读取、执行的权限,必须使用VirtualProtect函数修改[目标页面]为PAGE_EXECUTE_WRITECOPY(可写、可读、可执行、写时复制)
2、必须严格计算好,下一条指令的地址,先用汇编看一看那块区域的指令,覆盖了多少条指令,严格计算好覆盖后,下一条指令的开始地址,用于计算[硬编码地址]的
3、最好记录[原代码],需要时恢复,虽说有写时复制


五、步骤

1、调用GetModuleHandle函数取函数DLL的模块载入地址

2、GetProcAddress获取API的地址
3、使用VirtualProtect函数修改[API地址处的页面]的保护属性(PAGE_EXECUTE_WRITECOPY)
4、模拟出一个与API:参数类型相同、返回值相同、函数约定相同的函数
5、将[模拟API函数]的地址转换为[硬编码地址],放入Code数组(XCode) //最难,严格计算好硬编码地址
6、保存[API函数]内数据的前5个字节(因为要覆盖5个字节)
7、将[API函数]内数据的前5个字节覆盖成我们的Code数组(XCode)     //当API函数执行时,就会直接jmp跳转到我们的函数内


六、恢复

1、将保存的[API函数]内数据的前5个字节覆盖回去,修改回保护属性即可


【2】Inline Hook监视任意函数堆栈或拦截函数:


一、了解
1、与上面这种方法相同,只不过它可以在函数内部执行到任意位置时,都可以跳转至我们的函数,执行完毕我们的函数,再跳回原来的代码执行下一条指令
2、JMP跳转至[我们的函数]是不会帮我们的平衡堆栈的,但CALL会帮我们平衡堆栈


二、说明
1、想要拦截或监视一个函数,就在它执行函数时,替换其中的硬编码,替换成[跳转至我们注入进去的函数]的地址(与上面的方法相同)
3、如果[硬编码]与[ShellCode]对齐不匹配,用NOP指令填充对齐(替换时必须保证文件的数据长度一致)


三、步骤
1、首先要知道要拦截函数的执行时,某一条指令的地址(看你要替换啥了)
2、准备好[JMP跳转指令]的[ShellCode],地址必须遵守[硬编码地址]规则
3、保存要替换的[原指令],以便再跳回原来的代码继续执行
4、开始替换硬编码,用NOP填充对齐
5、让他跳转至一个[纯C写的__asm函数]中(extern "C" _declspec(naked))
6、看以下的步骤


四、纯C汇编函数的用途
1、首先执行pushad将原来的8个常用寄存器压入堆栈(保存现场,跳回时才能继续执行)
2、然后pushfd将EFL寄存器的值压入堆栈(保存现场,跳回时才能继续执行)
3、CALL再执行另外一个函数,这个函数是我们要执行的函数,并再末尾还原[原先的硬编码](会自动平衡堆栈)
4、函数执行完毕后,回到[汇编函数]
5、使用pushfd将EFL寄存器的值放回EFL寄存器
6、使用pushad将8个常用寄存器的值放回寄存器
7、JMP跳转到[原先的指令]继续执行


【3】修改导入表拦截API函数:


一、原理


1、因为执行API函数时,内部会调用GetProcAddress函数在导入表里找[函数所在的系统dll],然后用函数名查它的[INT表],
   找到[函数名]的所在的IMAGE_THUNK_DATA结构体的位置后,在[IAT表]相同的位置,IMAGE_THUNK_DATA结构体就是API函数的地址
   只要修改这个地址,就会跳转到我们的函数上

二、步骤   //拦截MessageBoxA

1、创建一个DLL,定义一个与[将要拦截的API]相同类型、参数、返回值、函数约定的函数
2、定义一个函数,里面包含dll将要执行的代码,并将这个函数在DLL_PROCESS_ATTACH时调用
3、使用GetModuleHandle函数获取EXE程序的镜像基地址(ImageBase)
4、使用ImageDirectoryEntryToDataEx函数获取[导入表]的PIMAGE_IMPORT_DESCRIPTOR结构体指针
5、用PIMAGE_IMPORT_DESCRIPTOR结构体指针在[导入表]中搜素[USER32.dll]
6、找到[USER32.dll]后,用[USER32.dll]的PIMAGE_IMPORT_DESCRIPTOR结构体指针的OriginalFirstThunk成员 + EXE程序的镜像基地址 = [INT表]的PIMAGE_THUNK_DATA首个数组
7、在[INT表]数组中,遍历查找[MessageBoxA]函数名,遍历时记录循环了多少次才找到的函数名(包括序号导入)
8、用[USER32.dll]的PIMAGE_IMPORT_DESCRIPTOR结构体指针的FirstThunk成员 + EXE程序的镜像基地址 = [IAT表]的PIMAGE_THUNK_DATA首个数组
9、在[IAT表]数组中,偏移循环相同次数后的PIMAGE_THUNK_DATA结构体就是[API函数地址所在处]
10、使用VirtualProtect修改页面访问权限为PAGE_EXECUTE_WRITECOPY(可读、可写、可执行、写时复制)
11、将我们定义好的函数的地址替换进去
12、恢复页面访问权限

三、恢复
1、将原API函数地址记录下来,恢复时替换回去即可


四、技巧
1、要捕获所有exe对指定API的调用,可以Hook全线程,然后修改每一个IAT表,而且DLL也可能调用其他DLL中的
   API,要遍历每一个模块并修改每一个IAT表


2、如果是在修改IAT完毕之后,用LoadLibray加载的dll模块,这时我们需要对LoadLibrayA、LoadLibrayW、
   LoadLibrayExA、LoadLibrayExW进行API拦截


3、如果是使用GetProcAddress直接取系统dll模块的函数地址时(系统dll不用调用LoadLibray系列),也会失效,
   那我们必须拦截GetProcAddress