windows 下实现函数打桩:拦截API方式
windows 下实现函数打桩:拦截API方式
近期由于工作须要,開始研究函数打桩的方法。
由于不想对project做过多的改动,于是放弃了使用Google gmock的想法。
可是也足足困扰另外我一天一宿。
经过奋战,最终有所收获。闲话少说,開始看看有什么方法。
一、基础准备
1. 函数调用的原理:通过函数名(函数的入口地址)对函数进行訪问,如果我们可以改变函数首地址指向的内存的话,使其跳转到还有一个函数去运行的话,那么就行实现函数打桩了。
2. 方法:对函数首地址出写入一条汇编语言 jmp xxx (当中xxx是要跳转的相对地址)。
3. 令原函数为oldFun,新函数为newFun,那么打桩时函数跳转的相对地址 offset = newFun - oldFun - (我们制定的这条指令的大小),此处为绝对跳转指令的长度=5。
jmp xxx一共6字节。
函数:
1. VirtualQuery
WINBASEAPI SIZE_T WINAPI VirtualQuery( __in_opt LPCVOID lpAddress, //所查内存地址 __out_bcount_part(dwLength, return) PMEMORY_BASIC_INFORMATION lpBuffer, //保存内存区域的buffer __in SIZE_T dwLength //信息长度 );该函数用于查询某一段内存区域的内存信息。事实VirtualQueryEx也能够使用。
2. VirtualProtect
WINBASEAPI BOOL WINAPI VirtualProtect( __in LPVOID lpAddress, __in SIZE_T dwSize, __in DWORD flNewProtect, __out PDWORD lpflOldProtect );该函数用于改动指定内存区dwSize个字节的保护模式。
3. VirtualProtectEx
WINBASEAPI BOOL WINAPI VirtualProtectEx( __in HANDLE hProcess, //进程句柄 __in LPVOID lpAddress, //须要改动的内存首地址 __in SIZE_T dwSize, //改动的字节数 __in DWORD flNewProtect, //新的保护属性 __out PDWORD lpflOldProtect //旧的保护属性 );VirtualProtectEx 用于改变指定进程内存段的保护模式。默认情况下函数的内存空间不可写,这就是为什么要用改变保护属性的函数。
4. ReadProcessMemory
WINBASEAPI BOOL WINAPI ReadProcessMemory( __in HANDLE hProcess, __in LPCVOID lpBaseAddress, __out_bcount_part(nSize, *lpNumberOfBytesRead) LPVOID lpBuffer, __in SIZE_T nSize, __out_opt SIZE_T * lpNumberOfBytesRead );读取进程内存,lpProcess是首地址,而lpBuffer用于保存读出的数据,nSize是须要读出的字节数。
5. WriteProcessMemory
WINBASEAPI BOOL WINAPI WriteProcessMemory( __in HANDLE hProcess, __in LPVOID lpBaseAddress, __in_bcount(nSize) LPCVOID lpBuffer, __in SIZE_T nSize, __out_opt SIZE_T * lpNumberOfBytesWritten );该函数用于写进程的内存空间。能够向进程内存注入想要注入的数据,比如函数等。
6. GetCurrentProcess
WINBASEAPI __out HANDLE WINAPI GetCurrentProcess( VOID );该函数返回一个伪进程句柄0xffffffff。不论什么须要进程句柄的内存都能够使用它。
二、对库中API打桩
方案一:
打桩:
#define FLATJMPCODE_LENGTH 5 //x86 平坦内存模式下,绝对跳转指令长度 #define FLATJMPCMD_LENGTH 1 //机械码0xe9长度 #define FLATJMPCMD 0xe9 //相应汇编的jmp指令 // 记录被打桩函数的内容。以便恢复 BYTE g_apiBackup[FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH]; BOOL setStub(LPVOID ApiFun,LPVOID HookFun) { BOOL IsSuccess = FALSE; DWORD TempProtectVar; //暂时保护属性变量 MEMORY_BASIC_INFORMATION MemInfo; //内存分页属性信息 VirtualQuery(ApiFun,&MemInfo,sizeof(MEMORY_BASIC_INFORMATION)); if(VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, PAGE_READWRITE,&MemInfo.Protect)) //改动页面为可写 { memcpy((void*)g_apiBackup,(const void*)ApiFun, sizeof(g_apiBackup)); *(BYTE*)ApiFun = FLATJMPCMD; //拦截API,在函数代码段前面注入jmp xxx *(DWORD*)((BYTE*)ApiFun + FLATJMPCMD_LENGTH) = (DWORD)HookFun - (DWORD)ApiFun - FLATJMPCODE_LENGTH; VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, MemInfo.Protect,&TempProtectVar); //改回原属性 IsSuccess = TRUE; } return IsSuccess; }清桩:
BOOL clearStub(LPVOID ApiFun) { BOOL IsSuccess = FALSE; DWORD TempProtectVar; //暂时保护属性变量 MEMORY_BASIC_INFORMATION MemInfo; //内存分页属性信息 VirtualQuery(ApiFun,&MemInfo,sizeof(MEMORY_BASIC_INFORMATION)); if(VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, PAGE_READWRITE,&MemInfo.Protect)) //改动页面为可写 { memcpy((void*)ApiFun, (const void*)g_apiBackup, sizeof(g_apiBackup)); //恢复代码段 VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, MemInfo.Protect,&TempProtectVar); //改回原属性 IsSuccess = TRUE; } return IsSuccess; }
方案二:
打桩:
bool setStub(LPVOID ApiFun,LPVOID HookFun) { HANDLE file_handler = GetCurrentProcess(); //获取进程伪句柄 DWORD oldProtect,TempProtectVar; char newCode[6]; //用于读取函数原有内存信息 int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH; //须要改动的内存大小 if(!VirtualProtectEx(file_handler,ApiFun,SIZE,PAGE_READWRITE,&oldProtect)) //改动内存为可读写 { return false; } if(!ReadProcessMemory(file_handler,ApiFun,newCode,SIZE,NULL)) //读取内存 { return false; } memcpy((void*)g_apiBackup,(const void*)newCode, sizeof(g_apiBackup)); //保存被打桩函数信息 *(BYTE*)ApiFun = FLATJMPCMD; *(DWORD*)((BYTE*)ApiFun + FLATJMPCMD_LENGTH) = (DWORD)HookFun - (DWORD)ApiFun - FLATJMPCODE_LENGTH; //桩函数注入 VirtualProtectEx(file_handler,ApiFun,SIZE,oldProtect,&TempProtectVar); //恢复保护属性 }清桩:
bool clearStub(LPVOID ApiFun) { BOOL IsSuccess = FALSE; HANDLE file_handler = GetCurrentProcess(); DWORD oldProtect,TempProtectVar; int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH; if(VirtualProtectEx(file_handler,ApiFun,SIZE,PAGE_READWRITE,&oldProtect)) { memcpy((void*)ApiFun, (const void*)g_apiBackup, sizeof(g_apiBackup)); //恢复被打桩函数内存 VirtualProtectEx(file_handler,ApiFun,SIZE,oldProtect,&TempProtectVar); IsSuccess = TRUE; } return IsSuccess; }
方案三:
打桩:
bool setStub(LPVOID ApiFun,LPVOID HookFun) { HANDLE file_handler = GetCurrentProcess(); DWORD oldProtect,TempProtectVar; char newCode[6]; int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH; if(!ReadProcessMemory(file_handler,ApiFun,newCode,SIZE,NULL)) { return false; } memcpy((void*)g_apiBackup,(const void*)newCode, sizeof(g_apiBackup)); *(BYTE*)newCode = FLATJMPCMD; *(DWORD*)((BYTE*)newCode + FLATJMPCMD_LENGTH) = (DWORD)HookFun - (DWORD)ApiFun - FLATJMPCODE_LENGTH; if(!WriteProcessMemory(file_handler,ApiFun,newCode,FLATJMPCODE_LENGTH,NULL)) { return false; } }说来也怪,这个方法没有改变读取权限。竟然也能够,这里写入的方式是用WriteProcessMemory来实现,与直接用指针同理。
清桩同上。可是假设直接用指针来写就会出错,临时不知道原因。
至此我们实现了函数的打桩。可是有个小小的问题,若不过如此,对类函数中成员函数打桩有点小问题。指针无法转换。这是由于类成员函数的指针不不过一个普通的指针,他还包含其它信息。全部这里须要解决问题。网上找到了两个方法:
1. 类的普通函数成员地址转换
LPVOID GetClassFnAddress(...) { LPVOID FnAddress; __asm { lea eax,FnAddress mov edx,[ebp+8] // ebp+8 为第一个形參的地址,ebp+C 为第二个形參的地址,以此类推 mov [eax],edx } return FnAddress; }
2. 类的虚成员函数地址转换
LPVOID GetClassVirtualFnAddress(LPVOID pthis,int Index) //Add 2010.8.6 { LPVOID FnAddress; //pthis 是对象的指针,index是在虚函数表中的偏移量 *(int*)&FnAddress = *(int*)pthis; //lpvtable *(int*)&FnAddress = *(int*)((int*)FnAddress + Index); return FnAddress; }
3. 普通成员函数转换
<pre name="code" class="cpp">template<class T> void * getAddr(T f) { long addr; memcpy(&addr,&f,sizeof(T)); return (int*)addr; }
资料:(非常有參考价值)