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

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














windows 下实现函数打桩:拦截API方式的更多相关文章

  1. Windows下对函数打桩,及Linux类似技术

    一个简单的桩实现类: #define JMPCODE_LENGTH 5 //x86 平坦内存模式下,绝对跳转指令长度 #define JMPCMD_LENGTH 1 //机械码0xe9长度 #defi ...

  2. MySql入门(2-1)windows下安装mysql的两种方式

    一.下载mysql 1.下载解压MySQL 登录oracle主页,需要用户名和口令: lshengqi@netease.com/1wsx**** 下载路径:: https://dev.mysql.co ...

  3. MongoDb在windows下的安装与以auth方式启用服务

    一.下载安装 1.去官网上下载适合自己电脑的MongoDB版本  下载MongoDB 2.安装MongoDB 安装还是比较简单,按照步骤一步一步往下走就可以了. 3.启动MongodDB 安装完成之后 ...

  4. windows下mysql安装(zip包方式)

    1.安装地址 https://dev.mysql.com/downloads/mysql/ 2. 解压MySQL压缩包 发现并没有my-default.ini 配置文件主要的作用是设置编码字符集.安装 ...

  5. Windows下createfile函数用GENERIC_READ访问模式打不开磁盘

    这两天做毕设,快气死了!想读写磁盘扇区,我就百度了,都是这样写的: HANDLE hDevice = CreateFile(TEXT("\\\\.\\PhysicalDrive1" ...

  6. 【CUDA】Windows 下常用函数头文件

    CUDA 函数 头文件 __global__ __device__ #include <cuda_runtime.h> threadIdx #include <device_laun ...

  7. vim、gvim在windows下中文乱码的终极解决方式

    測试成功,完美解决. 仅仅需改动VIM文件夹以下的这个文件_vimrc. 加油吧,骚年.非常强大的! set encoding=utf-8 set fileencodings=utf-8,chines ...

  8. windows下的c语言和linux 下的c语言以及C标准库和系统API

    1.引出我们的问题? 标准c库都是一样的!大家想必都在windows下做过文件编程,在linux下也是一样的函数名,参数都一样.当时就有了疑问,因为我们非常清楚 其本质是不可能一样的,源于这是俩个操作 ...

  9. 二、Windows 下 ShellCode 编写初步

    第二章.Windows 下 ShellCode 编写初步 (一)shellcode 定义:最先的 Shell 指的是人机交互界面,ShellCode 是一组能完成我们想要的功能的机器代码,通常以十六进 ...

随机推荐

  1. Darwin Streaming Server Relay Setting

    安装完Darwin Streaming Server,就可以使用VLC通过RTSP协议播放流媒体文件了.但是我现在有一个需求,需要将一台DSS(假设为A机)上的媒体文件发送到另一台DSS(假设为B机) ...

  2. 【大数取模】HDOJ-1134、CODEUP-1086

    1086: 大数取模   题目描述 现给你两个正整数A和B,请你计算A mod B.为了使问题简单,保证B小于100000. 输入 输入包含多组测试数据.每行输入包含两个正整数A和B.A的长度不超过1 ...

  3. 【Mysql】安装 mysql-5.7.5 指南

    因为同学需要安装mysql,安装过程,一路百度,在这里记录一下步奏.以后还会用到. 1.mysql-5.7.5-m15-winx64.zip下载 官方网站下载地址:http://cdn.mysql.c ...

  4. FileReader乱码

    出现原因:FileReader读取文件的过程中,FileReader继承了InputStreamReader,但并没有实现父类中带字符集参数的构造函数,所以FileReader只能按系统默认的字符集来 ...

  5. android学习笔记六

    Android中Activity的Intent大全 Api Level 3: (SDK 1.5) android.intent.action.ALL_APPS android.intent.actio ...

  6. Hibernate优化

    前言 在一般情况下,Hibernate需要将执行转换为SQL语句从而性能低于JDBC.但是在经过比较好的性能优化之后,性能还是让人相当满意的,特别是应用二级缓存之后,甚至可以获得比较不使用缓存的JDB ...

  7. Domain Name System (DNS)

    1.DNS和WINS的作用 DNS:(Domain Name Server,域名服务)用于实现域名和IP地址的相互转换. WINS:(Windows Internet Name Service) 用来 ...

  8. TabWidget/TabHost的两种使用方法

    1.概念 盛放Tab的容器就是TabHost.TabHost的实现有两种方式: 第一种继承TabActivity,从TabActivity中用getTabHost()方法获取TabHost.各个Tab ...

  9. linux的文件属性介绍、目录及路径表示方法

    一.认识linux文件 认识linux下的文件需要先学习命令:ls. 该命令用于显示指定目录下的内容,其中最常用的参数有: -l显示目录和文件的完整属性信息 -a显示所有文件和目录,包含隐藏文件和目录 ...

  10. 瞬间从IT屌丝变大神——CSS规范

    CSS规范主要包括以下内容: CSS Reset用YUI的CSS Reset. CSS采用CSSReset+common.css+app.css的形式. app.css采用分工制,一个前端工程师负责一 ...