首页
社区
课程
招聘
[原创]稳定多线程中的inline hook
2019-6-19 07:56 13390

[原创]稳定多线程中的inline hook

2019-6-19 07:56
13390

稳定多线程中的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;
}

 

最后把附上源码,里面还有远程线程注入代码。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞4
打赏
分享
最新回复 (22)
雪    币: 3496
活跃值: (749)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kxzpy 2019-6-19 09:42
2
0
不错,讲的很好,这个能远程注入dll吗?比如有的易语言写的dll注入工具,是这个原理吗
雪    币: 3496
活跃值: (749)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kxzpy 2019-6-19 09:50
3
0

最后把附上源码,里面还有远程线程注入代码

附件里 远程注入的代码是 那个啊?
雪    币: 5490
活跃值: (1740)
能力值: ( LV2,RANK:150 )
在线值:
发帖
回帖
粉丝
九阳道人 3 2019-6-19 10:45
4
0
第一个工程就是注入的代码
雪    币: 914
活跃值: (2178)
能力值: ( LV5,RANK:68 )
在线值:
发帖
回帖
粉丝
万剑归宗 1 2019-6-19 14:15
5
0
你是卖豆浆机的那个?
雪    币: 60
活跃值: (881)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
wonderzdh 1 2019-6-19 18:11
6
0
暂停其他线程,你想怎么hook怎么hook。
雪    币: 9010
活跃值: (1916)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
PPTV 2019-6-19 22:33
7
0
真正稳定是第三种,用信号量和互斥量不够完美,影响性能。
雪    币: 908
活跃值: (169)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
花弄影h 2019-6-20 08:46
8
0
你这个  只能自己多线程安全  可以看看  调试器原理  是怎么下断点 怎么执行断点......
雪    币: 3496
活跃值: (749)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kxzpy 2019-6-20 11:11
9
0
第一个就是帖子里的源代码吧,就是编译出来的hook.dll,那个注入其他进程的exe文件好像没有,我咋用你这个dll呢?
上传的附件:
雪    币: 5490
活跃值: (1740)
能力值: ( LV2,RANK:150 )
在线值:
发帖
回帖
粉丝
九阳道人 3 2019-6-20 12:37
10
0
楼上那哥们,原代码都发了,全部在里面,稍微翻一下就能找到注入的原代码,还有中文注释,作为一个学计算机的,你不要这么懒好么!
雪    币: 11208
活跃值: (4784)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
hhkqqs 1 2019-6-21 08:47
11
0
楼主,传统的inline hook有线程隐患主要是unhook()没执行完吧,如果是修改了函数参数后立即无条件跳转到FunctionAddress+5呢,还会存在这种问题么?
雪    币: 1638
活跃值: (2753)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qj111111 2019-6-21 18:46
12
0
直接用CMPXCHG8B 这个汇编,一次替换8字节,怎么会有线程安全问题,用得着这么繁琐么
雪    币: 5490
活跃值: (1740)
能力值: ( LV2,RANK:150 )
在线值:
发帖
回帖
粉丝
九阳道人 3 2019-6-21 19:17
13
0
11楼,跳到 目标地址+5 之后会发生错误,因为内存没有开辟新的栈帧
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zylyy 2019-6-22 11:19
14
0
一直用的微软得inline hook
雪    币: 11208
活跃值: (4784)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
hhkqqs 1 2019-6-22 14:52
15
0
九阳道人 11楼,跳到 目标地址+5 之后会发生错误,因为内存没有开辟新的栈帧
我的理解是按你上面那个hook message的例子,自己构造的函数本身就写有push ebp mov esp, ebp 如果只是对参数进行判断修改没有产生任何新的栈帧,立刻跳转回去问题也不大啊,只不过构造的函数不ret而已
雪    币: 243
活跃值: (80)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qiegao 2019-6-26 15:29
16
0
九阳道人 楼上那哥们,原代码都发了,全部在里面,稍微翻一下就能找到注入的原代码,还有中文注释,作为一个学计算机的,你不要这么懒好么!
顶学长!
雪    币: 216
活跃值: (250)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tsoo 2019-6-29 13:21
17
0

三个方法,前两个具有本质性的错误.

1. interlocked 只能"锁"你自己. 对于inline hook修改指令导致的不稳定性没有任何帮助.
2. 同上. 信号量同样只能锁你自己.

如八楼所说, 这两个方法, 只是让你自己多个线程同时对同一个点进行HOOK的时候变安全. 对于修改指令导致的不稳定性没有任何帮助.

雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huhonghui 2020-2-19 04:58
18
0
帖子写的非常棒,前2个方法并不行,第三个是正解!
雪    币: 6567
活跃值: (3194)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
htpidk 2020-2-20 08:05
19
0
前两个方法对于程序里的线程来说没有作用,其他线程不会因为你的原子和信号量在那一直等待,这两种方法只适合自己写的程序,第三种方法是对的,还有我认为普通的inlinehook对于多线程不安全是因为大部分时候hook头几个字节的时候破坏了接下来的几条指令,如果此时刚好有其他线程的eip在这几条被破坏的指令里,就会在线程切换的时候发生崩溃
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_jkptksxw 2020-3-5 15:16
20
0
用了第三个是正解!谢谢,对我有帮助~~
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
You jump,I see 2020-4-4 20:24
21
0
收藏一下先
雪    币: 4069
活跃值: (1954)
能力值: ( LV4,RANK:42 )
在线值:
发帖
回帖
粉丝
蜜蜂啊 2020-10-16 11:04
22
0
min hook 实现的更加巧妙 https://github.com/TsudaKageyu/minhook
雪    币: 11208
活跃值: (4784)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
hhkqqs 1 2020-10-18 11:58
23
0
蜜蜂啊 min hook 实现的更加巧妙 https://github.com/TsudaKageyu/minhook
Trampoline我也很喜欢用,凡是热补丁解决不了的特殊函数头它都能搞定,通用库这种玩意说白了就是个收集各类函数头的体力活
游客
登录 | 注册 方可回帖
返回