windows 下实现函数打桩:拦截API方式

本文介绍了在Windows环境下实现函数打桩的三种方法,并详细解释了如何通过改变函数首地址的内存来实现对函数的拦截。同时提供了具体的C/C++代码示例,包括打桩和清桩的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

windows 下实现函数打桩:拦截API方式

windows 下实现函数打桩:拦截API方式

最近因为工作需要,开始研究函数打桩的方法。由于不想对工程做过多的修改,于是放弃了使用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;
    *(int*)&FnAddress = *(int*)pthis;                       //lpvtable
    *(int*)&FnAddress = *(int*)((int*)FnAddress + Index);
    return FnAddress;
}

至此函数打桩的介绍告一段落。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值