稳定多线程中的inline hook

稳定多线程中的inline hook

  • 实验平台:WIN10
  • 实验工具:VS2017,OD

我所说的是windows下的inline hook,总所周知inline hook是windows平台下很灵活好用的一种hook方式, 但inline hook是非线程稳定的,也就是说在多线程的程序中,频繁的进行inline hook有可能会导致程序崩溃,不稳定。

之前在为了解决这个问题,只找到了Hook七个字节的方法,而这两天空余时间研究了下inline hook中线程同步的方法,下面我就分享下,如有不对之处,欢迎大佬指出批评。

在inline hook中实现线程同步,下面我介绍自己所知的3种方法:

  • 原子操作 inline hook
  • 信号量 inline hook
  • 修改7字节 inline hook

简单介绍下inline hook

  1. 找到想要HOOK的函数,获取函数原型并实现自己的对应函数。
  2. 获取目标函数的地址并保存,用于卸载 。HOOk(GetProcAddress)
  3. 通过 新函数的位置 - 目标函数的位置 - 5求得跳转偏移。
  4. 使用偏移构建跳转指令并写入到目标函数的执行流程中。
  5. 写入代码时需要修改并还原保护属性。

首先进行一下普通inline hook观察效果。注入程序我就不介绍了,之后附上源码。
我这里hook的是MessageBoxW对话框函数,每当被hook的程序调用了MessageBoxW函数,弹出的对话框里面的字符串就会被我修改,下图是hook前和hook后弹框的对比图。
在这里插入图片描述
下面附上inline hook的源代码,核心点是安装钩子的函数,之后的原子操作,信号量,修改7字节都是在此代码的基础上稍加修改:

#include "stdafx.h"

// 原函数的地址数据
BYTE g_OldData[5];
// 替换后的数据
BYTE g_NewData[5] = { 0xE9 };

// 声明自己的函数
int
WINAPI
MyMsg(_In_opt_ HWND hWnd,
	_In_opt_ LPCWSTR lpText,
	_In_opt_ LPCWSTR lpCaption,
	_In_ UINT uType);

// 安装钩子
void InHook()
{
	// 保存原函数地址
	memcpy(g_OldData, MessageBoxW, 5);

	// 计算出要跳转的偏移(自己的函数地址 - 原函数地址 - 5)	   
	DWORD Offset = (DWORD)MyMsg - (DWORD)MessageBoxW - 5;
	*(DWORD*)(g_NewData + 1) = Offset;

	// 修改内存属性
	DWORD Protect;
	VirtualProtect(MessageBoxW, 5, PAGE_EXECUTE_READWRITE, &Protect);

	// 替换函数地址		
	memcpy(MessageBoxW, g_NewData, 5);

	// 还原内存属性
	VirtualProtect(MessageBoxW, 5, Protect, &Protect);
}

// 卸载钩子
void UnHook()
{
	DWORD Protect;
	VirtualProtect(MessageBoxW, 5, PAGE_EXECUTE_READWRITE, &Protect);
	memcpy(MessageBoxW, g_OldData, 5);
	VirtualProtect(MessageBoxW, 5, Protect, &Protect);
}

// 自己的Hook函数
int
WINAPI
MyMsg(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCWSTR lpText,
	_In_opt_ LPCWSTR lpCaption,
	_In_ UINT uType)
{
	// 替换字符串
	lpText = L"九阳道人内联Hook成功!";
	lpCaption = L"九阳道人";

	// 先卸载钩子
	UnHook();
	// 在调用真正的MessageBoxW函数
	int nRet = MessageBoxW(hWnd, lpText, lpCaption, uType);
	// 最后在把钩子装上
	InHook();
	return nRet;
}

原子操作 inline hook

简单形容一下就是:一个线程中的原子操作在更改一个变量时,其它所有线程相关的操作都处于暂停状态,这就是所谓的互斥。
在inline hook中写入5个字节的地方我们使用原子操作,这就可以杜绝其它线程的访问,从而使程序稳定的运行。
在这里使用到一个宏,他会以原子方式把新值赋给旧值。:

LONGLONG InterlockedExchange64(
	LONGLONG*  OldDataAddr,   //旧数据的地址
	LONGLONG   NewData	   	   //新数据的值
)
// 返回值是旧数据的值

那么在inline hook中写入数据的时候我们改为原子操作就能达到优化的效果,主要就是安装钩子函数,更改如下图:
在这里插入图片描述
在这里插入图片描述

信号量 inline hook

信号量是一个能跨进程使用的好东西!
简而言之,假设创建了一个信号量,设置它的信号数为1,使用WaitForSingleObject()时该信号数减1,也就说这个信号数此时为0了,此时其他线程中再有代码使用WaitForSingleObject()时那个线程就处于阻塞状态,必须等到信号数非0了,他才能继续运行下去。使用ReleaseSemaphore()函数可把信号数加1。

利用信号量的这个特性貌似也起到优化inline hook的效果,前提条件是在其他任意程序中创建一个信号量,因为它是跨进程的:

	/*使用信号量Hook时才用到*/
	// 创建一个信号数位1的信号量
	CreateSemaphore(NULL, 1, 1, L"九阳道人");

然后HOOK代码如下:
在这里插入图片描述
在这里插入图片描述
信号量的作用不止于此,在内联hook中,之前我在15PB的老师"WM"发掘出用信号量传递进程PID,当真是好用之极,让我大开眼界。

修改7字节 inline hook

修改7字节 inline hook这种技术我在《逆向工程核心原理》一书中看到作者称之为"热补丁",那我也就称它就hook 热补丁吧。为了了解他先做一些准备工作。
首先使用OD观察普通API函数(大多数API都一样)的汇编代码,第一行指令是mov edi,edi 这条2个字节的命令显然没有任何作用完全可以忽略。
在这里插入图片描述
再观察普通的inline hook后MessageBoxW函数地址的汇编代码(注意这里要选运行程序注入后,使用OD附加调试)。它直接跳转到我们自己的那个函数中去。jmp(0xE9)后面是4个字节,所以破坏了原函数的可调用性,如果之占据mov edi,edi 这条2个字节的就是实现跳转的话那么从此以后就不必再频繁修改该函数地址上的数据了。注意观察大多数函数地址上面都有5个或以上的无用字节
在这里插入图片描述
头两个字节改为0xEB,0xF9,实现了跳转到此地址之上的5个字节的位置,在这5个字节中在构造跳转到函数的代码,那么就完美解决了hook时多线程不稳定的问题。修改7字节时如下:
在这里插入图片描述
修改7字节的热补丁技术,主要就是安装钩子和实现自己的函数值得注意:

void InHook()
{
	// 保存旧地址的前两个字节
	memcpy(g_OldData, MessageBoxW, 2);
	//memcpy(g_OldData, (const void*)((DWORD)MessageBoxW - 5), 7);

	// 跳转到5个字节之前的位置的字节码
	BYTE pByte[2] = { 0xEB,0xF9 };

	// 优化程序
	if (*(BYTE*)MessageBoxW == 0xEB)
		return;

	// 计算偏移
	DWORD Offset = (DWORD)MyMsg - (DWORD)MessageBoxW;
	*(DWORD*)(g_NewData + 1) = Offset;

	// 修改内存属性
	DWORD Protect;
	VirtualProtect((LPVOID)((DWORD)MessageBoxW - 5), 7, PAGE_EXECUTE_READWRITE, &Protect);

	// 修改函数地址数据
	memcpy((LPVOID)((DWORD)MessageBoxW - 5), g_NewData, 5);
	memcpy(MessageBoxW, pByte, 2);

	// 还原内存属性
	VirtualProtect((LPVOID)((DWORD)MessageBoxW - 5), 7, Protect, &Protect);
}


// 自己的函数
int
WINAPI
MyMsg(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCWSTR lpText,
	_In_opt_ LPCWSTR lpCaption,
	_In_ UINT uType)
{
	lpText = L"九阳道人修改7字节内联HOOK注入成功";
	lpCaption = L"九阳道人";

	// 该函数地址下移2个字节照样能调用
	FARPROC pFunc = (FARPROC)((DWORD)MessageBoxW + 2);
	int han = ((FnMsg)pFunc)(hWnd, lpText, lpCaption, uType);

	return han;
}

在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值