Windows平台实现内存hook,改写别人的函数

内存Hook

功能: 把其他人写的函数或者库中的函数在调用的时候跳转到自己写的函数中

1.实现所用到的Windows api

//写入进程内存
WriteProcessMemory(
PVZ_handle, //进程句柄
(LPVOID)address, //起始地址
date, //写入数据
sizeof(date), //写入长度
NULL);//返回值非零值代表成功。

//读进程内存
BOOL ReadProcessMemory(
HANDLE hProcess,//进程句柄
PVOID pvAddressRemote,//从读取地址 
PVOIDpvBufferLocal, //读缓冲区
DWORD dwSize, //读的大小
PDWORDpdwNumBytesRead//函数返回,实际写入缓冲区多少
);

//修改进程内存的属性  如可读可写属性
BOOL VirtualProtectEx(
HANDLE hProcess, // 要修改内存的进程句柄
LPVOID lpAddress, // 要修改内存的起始地址
DWORD dwSize, // 页区域大小
DWORD flNewProtect, // 新访问方式
PDWORD lpflOldProtect // 原访问方式 用于保存改变前的保护属性
);

//获取进程的handle
GetCurrentProcess()

2.实现的核心

思路:
2.1 修改所要hook函数的机器码,直接改成 jmp 相对地址
2.2 在汇编中jmp跳转的实际地址是 ip寄存器 + jmp后面的地址 所以是相对与当前的地址
2.3 汇编中的额jmp指令对应的机器码之一有一个是 ‘0xE9’ ,‘0xE9’后面可以跟一个4字节的地址,在32位程序中完全够用
2.4 ip寄存器指向的是下一个指令代码,在修改后 ‘E9 0x-------’占了5个字节。所以相对地址的计算方式是’目标地址 - hook的函数地址 - 5’
char write_data[5] = { 0xE9 }; //jmp 指令
unsigned long addr = (unsigned long)des_addr - (unsigned long)src_addr - 5;//计算相对地址
memcpy(write_data + 1, &addr, 4);//将计算后的相对地址值拷贝
WriteProcessMemory(GetCurrentProcess(), (LPVOID)src_addr,write_data,sizeof(write_data), NULL);//写入内存

3. 实现代码

#ifndef __MEMHOOK_H__
#define __MEMHOOK_H__

#include <Windows.h>
#include <stdint.h>

#ifdef _WIN64

#define LENGTH 12

#else

#define LENGTH 5

#endif

//返回码
#define SUCCESS						0  //成功
#define READMEM_ERROR				1  //读内存错误
#define WRITEMEM_ERROR				2  //写内存错误
#define EVISEMEM_ERROR				3  //修改内存属性错误
#define PARAMETER_ERROR				4  //参数错误

#ifdef __cplusplus
extern "C"
{
#endif

	typedef struct hook_struct
	{
		void *src_addr;  //被hook函数的地址
		void *des_addr;  //跳转的目的地址
		char buf[LENGTH];//备份缓冲区-->修改内存的原理的备份
		int retcode;     //返回码
	}hook_struct;
	//初始化hook,写入hook的目标,和跳转的函数
	int hook_init(struct hook_struct *hook, void *src_addr, void *des_addr);

	int hook_undata(struct hook_struct *hook, void *src_addr, void *des_addr);
	//开始hook
	int hook_install(struct hook_struct *hook);
	//解除hook
	int hook_uninstall(struct hook_struct *hook);
	
	int hook_uninit(struct hook_struct *hook);

#ifdef __cplusplus
}
#endif

#endif
重点写了32位 int hook_install(struct hook_struct *hook);int hook_uninstall(struct hook_struct *hook);的注释重点看懂核心部分就行了。由于x64实现和之前讲的不是一样的方法实现跳转并且暂时是不稳定的不建议使用(后面说明原因),x64我的实现思路是改为mov rax,0x----- jmp rax但是占了12个字节
#include "memhook.h"

#ifdef _WIN64

int hook_init(struct hook_struct *hook, void *src_addr, void *des_addr)
{
	if (hook == NULL || src_addr == NULL || des_addr == NULL) return -PARAMETER_ERROR;

	hook->src_addr = src_addr;
	hook->des_addr = des_addr;
	hook->retcode = 0;
	memset(hook->buf, 0, sizeof(hook->buf));

	return SUCCESS;
}

int hook_undata(struct hook_struct *hook, void *src_addr, void *des_addr)
{
	return hook_init(hook, src_addr, des_addr);
}

int hook_install(struct hook_struct *hook)
{
	if (hook == NULL) return -PARAMETER_ERROR;

	DWORD OldProtect = 0;
	if (VirtualProtectEx(GetCurrentProcess(), (LPVOID)hook->src_addr, LENGTH, PAGE_EXECUTE_READWRITE, &OldProtect) == FALSE)
	{
		hook->retcode = -EVISEMEM_ERROR;
		return -EVISEMEM_ERROR;
	}

	SIZE_T length = 0;
	if (ReadProcessMemory(GetCurrentProcess(), (PVOID)hook->src_addr, hook->buf, LENGTH, &length) == FALSE)
	{
		hook->retcode = -READMEM_ERROR;
		return -READMEM_ERROR;
	}

	unsigned char write_data[LENGTH] = { 0x48,0xB8 };
	memcpy(write_data + 2, &hook->des_addr, 8);
	write_data[LENGTH - 2] = 0xFF;
	write_data[LENGTH - 1] = 0xE0;
	if (WriteProcessMemory(GetCurrentProcess(), (LPVOID)hook->src_addr, write_data, sizeof(write_data), NULL) == FALSE)
	{
		hook->retcode = -WRITEMEM_ERROR;
		return -WRITEMEM_ERROR;
	}

	DWORD Protect = 0;
	if (VirtualProtectEx(GetCurrentProcess(), (LPVOID)hook->src_addr, LENGTH, OldProtect, &Protect) == FALSE)
	{
		hook->retcode = -EVISEMEM_ERROR;
		return -EVISEMEM_ERROR;
	}

	return hook->retcode;
}

int hook_uninstall(struct hook_struct *hook)
{
	if (hook == NULL) return -PARAMETER_ERROR;

	DWORD OldProtect = 0;
	if (VirtualProtectEx(GetCurrentProcess(), (LPVOID)hook->src_addr, LENGTH, PAGE_EXECUTE_READWRITE, &OldProtect) == FALSE)
	{
		hook->retcode = -EVISEMEM_ERROR;
		return -EVISEMEM_ERROR;
	}

	if (WriteProcessMemory(GetCurrentProcess(), (LPVOID)hook->src_addr, hook->buf, LENGTH, NULL) == FALSE)
	{
		hook->retcode = -WRITEMEM_ERROR;
		return -WRITEMEM_ERROR;
	}

	DWORD Protect = 0;
	if (VirtualProtectEx(GetCurrentProcess(), (LPVOID)hook->src_addr, LENGTH, OldProtect, &Protect) == FALSE)
	{
		hook->retcode = -EVISEMEM_ERROR;
		return -EVISEMEM_ERROR;
	}

	return SUCCESS;
}

int hook_uninit(struct hook_struct *hook)
{
	if (hook == NULL) return -PARAMETER_ERROR;

	hook->src_addr = NULL;
	hook->des_addr = NULL;
	hook->retcode = 0;
	memset(hook->buf, 0, sizeof(hook->buf));

	return SUCCESS;
}

#else

int hook_init(struct hook_struct *hook, void *src_addr, void *des_addr)
{
	if (hook == NULL || src_addr == NULL || des_addr == NULL) return -PARAMETER_ERROR;

	hook->src_addr = src_addr;
	hook->des_addr = des_addr;
	hook->retcode = 0;
	memset(hook->buf, 0, sizeof(hook->buf));

	return SUCCESS;
}

int hook_undata(struct hook_struct *hook, void *src_addr, void *des_addr)
{
	return hook_init(hook, src_addr, des_addr);
}
//hook 启动函数
int hook_install(struct hook_struct *hook)
{
	if (hook == NULL) return -PARAMETER_ERROR;

	DWORD OldProtect = 0;
	if (VirtualProtectEx(GetCurrentProcess(), (LPVOID)hook->src_addr, LENGTH, PAGE_EXECUTE_READWRITE, &OldProtect) == FALSE)//修改内存属性,把其改为可读写
	{
		hook->retcode = -EVISEMEM_ERROR;
		return -EVISEMEM_ERROR;
	}

	SIZE_T length = 0;
	if (ReadProcessMemory(GetCurrentProcess(), (PVOID)hook->src_addr, hook->buf, LENGTH, &length) == FALSE)//读备份 5个字节
	{
		hook->retcode = -READMEM_ERROR;
		return -READMEM_ERROR;
	}

	char write_data[LENGTH] = { 0xE9 };
	unsigned long addr = (unsigned long)hook->des_addr - (unsigned long)hook->src_addr - 5;
	memcpy(write_data + 1, &addr, 4);
	if (WriteProcessMemory(GetCurrentProcess(), (LPVOID)hook->src_addr, write_data, sizeof(write_data), NULL) == FALSE)//写入实现跳转
	{
		hook->retcode = -WRITEMEM_ERROR;
		return -WRITEMEM_ERROR;
	}

	DWORD Protect = 0;
	if (VirtualProtectEx(GetCurrentProcess(), (LPVOID)hook->src_addr, LENGTH, OldProtect, &Protect) == FALSE)//改回原来的属性
	{
		hook->retcode = -EVISEMEM_ERROR;
		return -EVISEMEM_ERROR;
	}

	return hook->retcode;
}
//解除hook
int hook_uninstall(struct hook_struct *hook)
{
	if (hook == NULL) return -PARAMETER_ERROR;

	DWORD OldProtect = 0;
	if (VirtualProtectEx(GetCurrentProcess(), (LPVOID)hook->src_addr, LENGTH, PAGE_EXECUTE_READWRITE, &OldProtect) == FALSE)
	{
		hook->retcode = -EVISEMEM_ERROR;
		return -EVISEMEM_ERROR;
	}

	if (WriteProcessMemory(GetCurrentProcess(), (LPVOID)hook->src_addr, hook->buf, LENGTH, NULL) == FALSE)//改回原来的5个字节
	{
		hook->retcode = -WRITEMEM_ERROR;
		return -WRITEMEM_ERROR;
	}

	DWORD Protect = 0;
	if (VirtualProtectEx(GetCurrentProcess(), (LPVOID)hook->src_addr, LENGTH, OldProtect, &Protect) == FALSE)
	{
		hook->retcode = -EVISEMEM_ERROR;
		return -EVISEMEM_ERROR;
	}

	return SUCCESS;
}

int hook_uninit(struct hook_struct *hook)
{
	if (hook == NULL) return -PARAMETER_ERROR;

	hook->src_addr = NULL;
	hook->des_addr = NULL;
	hook->retcode = 0;
	memset(hook->buf, 0, sizeof(hook->buf));

	return SUCCESS;
}

#endif

4.测试代码

我使用的环境是vs2017。大家可以把上面两段代码复制一下

在这里插入图片描述

#include <iostream>
#include <string.h>

#include "memhook.h"

struct hook_struct hook;

char* __cdecl Mystrncpy(
	char * _Str1,
	char const* _Str2,
	size_t      _MaxCount
) {
	std::cout << "Mystrncpy" << std::endl;

	std::cout << _Str1 << "\n" << _Str2 << "\n" << std::endl;

	return NULL;
}

void print_A()
{
	printf("A-------------------\n");
}

void print_B()
{
	printf("B-------------------\n");
}

int main(int argc,char *argv[])
{
	hook_init(&hook, strncpy, Mystrncpy); // 把strncpy hook 跳转到自己定义的Mystrncpy
	hook_install(&hook);//开始hook
	
	char dbuf[] = "hello qweqweeqeqweqwe";
	const char *sbuf = "hello world\n";
	strncpy(dbuf, sbuf,strlen(sbuf)+1);//调用被hook的函数

	std::cout << "hook 中" << std::endl;
	std::cout << dbuf << "\n" << sbuf << "\n" << std::endl;

	hook_uninstall(&hook);//解除hook

	std::cout << "解除 hook " << std::endl;
	strncpy(dbuf, sbuf, strlen(sbuf)+1);//再次调用
	std::cout << dbuf << "\n" << sbuf << "\n" << std::endl;

    hook_undata(&hook,print_A,print_B);

	hook_install(&hook);//hook后调用两次print_A();

	print_A();

	hook_uninstall(&hook);

	print_A();
    
	hook_uninit(&hook);

	return 0;
}
输出结果

在这里插入图片描述

5. 利用vs中的反汇编查看我们改写的机器码
5.1 原来的机器码 strncpy

在这里插入图片描述

修改后的

在这里插入图片描述

上面那段是用自己写的去hook库中的函数,下面我们来看一下两个自己写的函数

在这里插入图片描述

可以明显的看到我们hook成功了,并且没有改变的时候没有影响其他的内存。
特征:在vs中自己写的函数会先跳转到一个地方,那里有一堆跳转的指令,可以看到上图
5.2这个特征对64位程序hook的影响
首先还是看自己写的函数去hook库中的函数
原来的strncpy

在这里插入图片描述

修改后的

在这里插入图片描述

看起来好像没问题,那我们继续看 两个自己写的函数
原来的 print_A

在这里插入图片描述

修改后的

在这里插入图片描述

现在可以看到问题了,我的64位修改思路是mov rax,0x----- jmp rax 一共12个字节,但是因为这个特征,如果在hook的时候恰好调用了_acrt_iob_func那么就会出现问题。有这个特征意味着写64位hook的时候要注意的问题。
  • 24
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个封装了VT hook函数的示例代码,可以实现对VT指令的拦截和替换: ```c++ #include <Windows.h> // 定义 VT hook 函数 BOOL VTHook(DWORD vtAddr, LPVOID hookFunc, LPBYTE orgCode) { // 检查 VT 指令地址是否有效 if (vtAddr == 0) return FALSE; // 备份原始的 VT 指令 memcpy(orgCode, (void*)vtAddr, 6); // 修改 VT 指令,跳转到钩子函数 BYTE hookCode[6] = { 0xE9, 0x00, 0x00, 0x00, 0x00, 0xCC }; DWORD hookAddr = (DWORD)hookFunc - vtAddr - 5; memcpy((void*)vtAddr, hookCode, 6); *(DWORD*)(vtAddr + 1) = hookAddr; return TRUE; } // 定义钩子函数 void hookFunc() { printf("VT instruction is hooked!\n"); // 恢复原始的 VT 指令 BYTE orgCode[6]; DWORD orgAddr = __readmsr(0xC0000080); memcpy((void*)orgAddr, orgCode, 6); // 跳转到原始的 VT 指令 __asm { jmp [orgAddr] } } int main() { // 获取 VT 指令地址 DWORD vtAddr = __readmsr(0xC0000080); // 安装 VT hook BYTE orgCode[6]; if (VTHook(vtAddr, hookFunc, orgCode)) { // 执行 VT 指令 __asm { mov eax, 0x1234 mov ebx, 0x5678 mov ecx, 0x9ABC vmcall } // 卸载 VT hook memcpy((void*)vtAddr, orgCode, 6); } return 0; } ``` 以上代码封装了一个VTHook函数,可以方便地安装和卸载VT hook。它还定义了一个钩子函数hookFunc,用于替换VT指令。在main函数中,它首先获取VT指令地址,然后调用VTHook函数来安装VT hook。安装完成后,它执行一个测试用的VT指令,触发钩子函数的调用。最后,它卸载VT hook,恢复原始的VT指令。需要注意的是,以上代码仅供演示VT hook的基本原理,实际应用需要根据具体场景进行设计和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值