博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
windows 下实现函数打桩:拦截API方式
阅读量:2392 次
发布时间:2019-05-10

本文共 5833 字,大约阅读时间需要 19 分钟。

最近因为工作需要,开始研究函数打桩的方法。由于不想对工程做过多的修改,于是放弃了使用Google gmock的想法。但是也足足困扰另外我一天一宿。经过奋战,终于有所收获。闲话少说,开始看看有什么方法。

一、基础准备

1. 函数调用的原理:通过函数名(函数的入口地址)对函数进行访问,假设我们能够改变函数首地址指向的内存的话,使其跳转到另一个函数去执行的话,那么就可以实现函数打桩了。
2. 方法:对函数首地址出写入一条汇编语言 jmp xxx (其中xxx是要跳转的相对地址)。
3. 令原函数为oldFun,新函数为newFun,那么打桩时函数跳转的相对地址 offset = newFun - oldFun - (我们制定的这条指令的大小),此处为绝对跳转指令的长度=5。  jmp xxx一共6字节。
函数:

1. VirtualQuery

[cpp]
  1. WINBASEAPI  
  2. SIZE_T  
  3. WINAPI  
  4. VirtualQuery(  
  5.     __in_opt LPCVOID lpAddress,   //所查内存地址  
  6.     __out_bcount_part(dwLength, return) PMEMORY_BASIC_INFORMATION lpBuffer,   //保存内存区域的buffer  
  7.     __in     SIZE_T dwLength                                                  //信息长度                                                  
  8.     );  
该函数用于查询
某一段内存区域的内存信息,事实VirtualQueryEx也可以使用。

2. VirtualProtect

[cpp]
  1. WINBASEAPI  
  2. BOOL  
  3. WINAPI  
  4. VirtualProtect(  
  5.     __in  LPVOID lpAddress,  
  6.     __in  SIZE_T dwSize,  
  7.     __in  DWORD flNewProtect,  
  8.     __out PDWORD lpflOldProtect  
  9.     );  
该函数用于修改指定内存区dwSize个字节的保护模式。

3. VirtualProtectEx

[cpp]
  1. WINBASEAPI  
  2. BOOL  
  3. WINAPI  
  4. VirtualProtectEx(  
  5.     __in  HANDLE hProcess,   //进程句柄  
  6.     __in  LPVOID lpAddress,  //需要修改的内存首地址  
  7.     __in  SIZE_T dwSize,     //修改的字节数  
  8.     __in  DWORD flNewProtect,  //新的保护属性  
  9.     __out PDWORD lpflOldProtect  //旧的保护属性  
  10.     );  
VirtualProtectEx 用于改变指定进程内存段的保护模式,默认情况下函数的内存空间不可写,这就是为什么要用改变保护属性的函数。

4. ReadProcessMemory

[cpp]
  1. WINBASEAPI  
  2. BOOL  
  3. WINAPI  
  4. ReadProcessMemory(  
  5.     __in      HANDLE hProcess,  
  6.     __in      LPCVOID lpBaseAddress,  
  7.     __out_bcount_part(nSize, *lpNumberOfBytesRead) LPVOID lpBuffer,  
  8.     __in      SIZE_T nSize,  
  9.     __out_opt SIZE_T * lpNumberOfBytesRead  
  10.     );  
读取进程内存,lpProcess是首地址,而lpBuffer用于保存读出的数据,nSize是需要读出的字节数。

5. WriteProcessMemory

[cpp]
  1. WINBASEAPI  
  2. BOOL  
  3. WINAPI  
  4. WriteProcessMemory(  
  5.     __in      HANDLE hProcess,  
  6.     __in      LPVOID lpBaseAddress,  
  7.     __in_bcount(nSize) LPCVOID lpBuffer,  
  8.     __in      SIZE_T nSize,  
  9.     __out_opt SIZE_T * lpNumberOfBytesWritten  
  10.     );  
该函数用于写进程的内存空间,可以向进程内存注入想要注入的数据,例如函数等。

6. GetCurrentProcess

[cpp]
  1. WINBASEAPI  
  2. __out  
  3. HANDLE  
  4. WINAPI  
  5. GetCurrentProcess(  
  6.     VOID  
  7.     );  
该函数返回一个伪进程句柄0xffffffff,任何需要进程句柄的内存都可以使用它。

二、对库中API打桩

方案一:

打桩:
[cpp]
  1. #define FLATJMPCODE_LENGTH 5            //x86 平坦内存模式下,绝对跳转指令长度  
  2. #define FLATJMPCMD_LENGTH  1            //机械码0xe9长度  
  3. #define FLATJMPCMD         0xe9         //对应汇编的jmp指令  
  4.   
  5. // 记录被打桩函数的内容,以便恢复  
  6. BYTE g_apiBackup[FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH];  
  7.   
  8. BOOL setStub(LPVOID ApiFun,LPVOID HookFun)  
  9. {  
  10.     BOOL    IsSuccess = FALSE;  
  11.     DWORD   TempProtectVar;              //临时保护属性变量  
  12.     MEMORY_BASIC_INFORMATION MemInfo;    //内存分页属性信息  
  13.       
  14.     VirtualQuery(ApiFun,&MemInfo,sizeof(MEMORY_BASIC_INFORMATION));  
  15.       
  16.     if(VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize,  
  17.         PAGE_READWRITE,&MemInfo.Protect))                            //修改页面为可写  
  18.     {  
  19.         memcpy((void*)g_apiBackup,(const void*)ApiFun, sizeof(g_apiBackup));  
  20.   
  21.         *(BYTE*)ApiFun = FLATJMPCMD;                                 //拦截API,在函数代码段前面注入jmp xxx  
  22.         *(DWORD*)((BYTE*)ApiFun + FLATJMPCMD_LENGTH) = (DWORD)HookFun -  
  23.             (DWORD)ApiFun - FLATJMPCODE_LENGTH;  
  24.           
  25.         VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize,  
  26.             MemInfo.Protect,&TempProtectVar);                        //改回原属性  
  27.           
  28.         IsSuccess = TRUE;  
  29.     }  
  30.       
  31.     return IsSuccess;  
  32. }  
清桩:
[cpp]
  1. BOOL clearStub(LPVOID ApiFun)  
  2. {  
  3.     BOOL    IsSuccess = FALSE;  
  4.     DWORD   TempProtectVar;              //临时保护属性变量  
  5.     MEMORY_BASIC_INFORMATION MemInfo;    //内存分页属性信息  
  6.       
  7.     VirtualQuery(ApiFun,&MemInfo,sizeof(MEMORY_BASIC_INFORMATION));  
  8.       
  9.     if(VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize,  
  10.         PAGE_READWRITE,&MemInfo.Protect))                            //修改页面为可写  
  11.     {  
  12.         memcpy((void*)ApiFun, (const void*)g_apiBackup, sizeof(g_apiBackup));  //恢复代码段  
  13.           
  14.         VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize,  
  15.             MemInfo.Protect,&TempProtectVar);                        //改回原属性  
  16.           
  17.         IsSuccess = TRUE;  
  18.     }  
  19.       
  20.     return IsSuccess;  
  21. }  

方案二:

打桩:
[cpp]
  1. bool setStub(LPVOID ApiFun,LPVOID HookFun)  
  2. {  
  3.     HANDLE file_handler = GetCurrentProcess();           //获取进程伪句柄  
  4.     DWORD oldProtect,TempProtectVar;  
  5.     char newCode[6];                                     //用于读取函数原有内存信息  
  6.     int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH;     //需要修改的内存大小  
  7.     if(!VirtualProtectEx(file_handler,ApiFun,SIZE,PAGE_READWRITE,&oldProtect))  //修改内存为可读写  
  8.     {  
  9.         return false;  
  10.     }  
  11.     if(!ReadProcessMemory(file_handler,ApiFun,newCode,SIZE,NULL))              //读取内存  
  12.     {  
  13.         return false;  
  14.     }  
  15.     memcpy((void*)g_apiBackup,(const void*)newCode, sizeof(g_apiBackup));      //保存被打桩函数信息  
  16.     *(BYTE*)ApiFun = FLATJMPCMD;                                      
  17.         *(DWORD*)((BYTE*)ApiFun + FLATJMPCMD_LENGTH) = (DWORD)HookFun - (DWORD)ApiFun - FLATJMPCODE_LENGTH;   //桩函数注入   
  18.     VirtualProtectEx(file_handler,ApiFun,SIZE,oldProtect,&TempProtectVar);  //恢复保护属性  
  19. }  
清桩:
[cpp]
  1. bool clearStub(LPVOID ApiFun)  
  2. {  
  3.     BOOL    IsSuccess = FALSE;  
  4.     HANDLE file_handler = GetCurrentProcess();  
  5.     DWORD oldProtect,TempProtectVar;  
  6.     int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH;  
  7.     if(VirtualProtectEx(file_handler,ApiFun,SIZE,PAGE_READWRITE,&oldProtect))  
  8.     {  
  9.         memcpy((void*)ApiFun, (const void*)g_apiBackup, sizeof(g_apiBackup));             //恢复被打桩函数内存  
  10.         VirtualProtectEx(file_handler,ApiFun,SIZE,oldProtect,&TempProtectVar);  
  11.         IsSuccess = TRUE;   
  12.     }  
  13.       
  14.     return IsSuccess;  
  15. }  

方案三:

打桩:
[cpp]
  1. bool setStub(LPVOID ApiFun,LPVOID HookFun)  
  2. {  
  3.     HANDLE file_handler = GetCurrentProcess();  
  4.     DWORD oldProtect,TempProtectVar;  
  5.     char newCode[6];  
  6.     int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH;  
  7.     if(!ReadProcessMemory(file_handler,ApiFun,newCode,SIZE,NULL))  
  8.     {  
  9.         return false;  
  10.     }  
  11.     memcpy((void*)g_apiBackup,(const void*)newCode, sizeof(g_apiBackup));  
  12.     *(BYTE*)newCode = FLATJMPCMD;                                      
  13.     *(DWORD*)((BYTE*)newCode + FLATJMPCMD_LENGTH) = (DWORD)HookFun - (DWORD)ApiFun - FLATJMPCODE_LENGTH;    
  14.     if(!WriteProcessMemory(file_handler,ApiFun,newCode,FLATJMPCODE_LENGTH,NULL))  
  15.     {  
  16.         return false;  
  17.     }  
  18. }  
 说来也怪,这个方案没有改变读取权限,居然也可以,这里写入的方式是用WriteProcessMemory来实现,与直接用指针同理。清桩同上。但是如果直接用指针来写就会出错,暂时不知道原因。
至此我们实现了函数的打桩,但是有个小小的问题,若仅仅是如此,对类函数中成员函数打桩有点小问题,指针无法转换,这是因为类成员函数的指针不仅仅是一个普通的指针,他还包括其他信息。所有这里需要解决这个问题,网上找到了两个方法:

1. 类的普通函数成员地址转换

[cpp]
  1. LPVOID GetClassFnAddress(...)  
  2. {  
  3.     LPVOID FnAddress;  
  4.     __asm  
  5.     {  
  6.         lea eax,FnAddress  
  7.         mov edx,[ebp+8]    // ebp+8 为第一个形参的地址,ebp+C 为第二个形参的地址,以此类推  
  8.         mov [eax],edx  
  9.     }  
  10.     return FnAddress;  
  11. }  

2. 类的虚成员函数地址转换

[cpp]
  1. LPVOID GetClassVirtualFnAddress(LPVOID pthis,int Index) //Add 2010.8.6  
  2. {  
  3.     LPVOID FnAddress;                                       //pthis 是对象的指针,index是在虚函数表中的偏移量  
  4.     *(int*)&FnAddress = *(int*)pthis;                       //lpvtable      
  5.     *(int*)&FnAddress = *(int*)((int*)FnAddress + Index);  
  6.     return FnAddress;  
  7. }  
至此函数打桩的介绍告一段落。

3. 普通成员函数转换

[cpp]
  1. <pre name="code" class="cpp">template<class T>  
  2. void * getAddr(T f)  
  3. {  
  4.       long addr;  
  5.       memcpy(&addr,&f,sizeof(T));  
  6.       return  (int*)addr;  
  7. }  
资料:(很有参考价值)

转载地址:http://cehab.baihongyu.com/

你可能感兴趣的文章
Java-8-Stream接口
查看>>
Junit4入门
查看>>
Java与算法(11)
查看>>
Java与算法(12)
查看>>
Java与算法(13)
查看>>
用Marven构建第一个Spring Boot应用
查看>>
Spring-MVC无xml文件全注解实现简单登录实例
查看>>
Spring三种配置注入方式
查看>>
Spring三种实现自动代理
查看>>
Spring JDBC入门
查看>>
基于Node.js,Express,Socket.io创建简单聊天室
查看>>
Spring+Spring MVC+Spring JDBC+MySql实现简单登录注册
查看>>
Java操作MongoDB
查看>>
Spring,Spring MVC,MongoDB实现登录注册
查看>>
Node.js+Express+MySql实现用户登录注册
查看>>
Node.js+Express+MongoDB实现简单登录注册功能
查看>>
Python时间模块
查看>>
Python的深拷贝与浅拷贝
查看>>
Python文件处理
查看>>
Python的condition和阻塞队列Queue
查看>>