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;
}


资料:(非常有參考价值)






本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5360668.html,如需转载请自行联系原作者
相关文章
|
2月前
|
缓存 JavaScript 算法
活用 Composition API 核心函数,打造卓越应用(下)
活用 Composition API 核心函数,打造卓越应用(下)
|
2月前
|
存储 JavaScript API
活用 Composition API 核心函数,打造卓越应用(上)
活用 Composition API 核心函数,打造卓越应用(上)
|
2月前
|
人工智能 关系型数据库 Serverless
Serverless 应用引擎常见问题之API生成的函数镜像改为自定义的镜像如何解决
Serverless 应用引擎(Serverless Application Engine, SAE)是一种完全托管的应用平台,它允许开发者无需管理服务器即可构建和部署应用。以下是Serverless 应用引擎使用过程中的一些常见问题及其答案的汇总:
39 3
|
25天前
|
JavaScript API
Vue3 API函数及功能
Vue3 API函数及功能
7 0
|
4月前
|
API Python Windows
python3应用windows api对后台程序窗口及桌面截图并保存的方法
python3应用windows api对后台程序窗口及桌面截图并保存的方法
114 1
|
2月前
|
存储 安全 Linux
【Linux 创建临时文件 API】编程中的瞬息之光:临时文件的艺术与智慧 tmpnam,tmpfile,mkstemp,mkdtemp等函数解析...
【Linux 创建临时文件 API】编程中的瞬息之光:临时文件的艺术与智慧 tmpnam,tmpfile,mkstemp,mkdtemp等函数解析...
26 0
|
2月前
|
数据库连接 API 数据库
SQLite3 数据库 C语言API 打开函数sqlite3_open 详解
SQLite3 数据库 C语言API 打开函数sqlite3_open 详解
66 0
|
2月前
|
编译器 API C++
【C++ 动态库设计】动态库中的模板函数:解决如果将模板函数封装成API库
【C++ 动态库设计】动态库中的模板函数:解决如果将模板函数封装成API库
56 0
|
2月前
|
Java 数据库连接 API
Java 学习路线:基础知识、数据类型、条件语句、函数、循环、异常处理、数据结构、面向对象编程、包、文件和 API
Java 是一种广泛使用的、面向对象的编程语言,始于1995年,以其跨平台性、安全性和可靠性著称,应用于从移动设备到数据中心的各种场景。基础概念包括变量(如局部、实例和静态变量)、数据类型(原始和非原始)、条件语句(if、else、switch等)、函数、循环、异常处理、数据结构(如数组、链表)和面向对象编程(类、接口、继承等)。深入学习还包括包、内存管理、集合框架、序列化、网络套接字、泛型、流、JVM、垃圾回收和线程。构建工具如Gradle、Maven和Ant简化了开发流程,Web框架如Spring和Spring Boot支持Web应用开发。ORM工具如JPA、Hibernate处理对象与数
95 3
|
5月前
|
存储 API 数据安全/隐私保护
FreeRTOS入门教程(信号量的概念及API函数使用)
FreeRTOS入门教程(信号量的概念及API函数使用)
125 0