查看原文
其他

稳定多线程中的inline hook

九阳道人 看雪学院 2019-09-17

实验平台:WIN10

实验工具:VS2017,OD


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

 

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

 

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


1、 原子操作 inline hook

2、 信号量 inline hook

3、 修改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;
}


 




- End -




看雪ID:九阳道人 

https://bbs.pediy.com/user-847228.htm  



本文由看雪论坛 九阳道人  原创

转载请注明来自看雪社区




⚠️ 注意


2019 看雪安全开发者峰会门票正在热售中!

长按识别下方二维码即可享受 2.5折 优惠!





往期热门回顾

1、针对微控制器的时钟错误注入攻击

2、注意!Linux 曝出 TCP 拒绝服务漏洞

3、针孔摄像头无处不在,优衣库试衣间也没能逃过......

4、黑科技堪比易容术!只需40分钟,你就可以变成另一个人~








公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com





↙点击下方“阅读原文”,查看更多干货

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存