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


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








  • 相关阅读:
    关于代码的一系列调整
    题目清单(更新至2016年12月17日 10:52)
    USACO翻译:USACO 2013 NOV Silver三题
    USACO翻译:USACO 2013 DEC Silver三题
    USACO翻译:USACO 2014 DEC Silver三题
    USACO翻译:USACO 2012 FEB Silver三题
    USACO翻译:USACO 2012 JAN三题(3)
    USACO翻译:USACO 2012 JAN三题(2)
    USACO翻译:USACO 2012 JAN三题(1)
    USACO翻译:USACO 2014 FEB SILVER 三题
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5360668.html
Copyright © 2020-2023  润新知