0%

minhook源码简析

minhook源码简析

minhook的一般使用:

1
2
3
4
5
6
7
8
9
10
......
if (MH_Initialize() != MH_OK)
{
return 0;
}
......
if (MH_CreateHook(pTarget, pDetour, ppOriginal) != MH_OK) return 0;
......
if (MH_EnableHook(pTarget) != MH_OK) return 0;
......

本文简单分析了32位minhook源代码在实现hook时的一些关键核心点,主要分析的也是上述三个函数,主要核心技术点如下(一些关键性注释已经在源码中使用中说明):

  • minhook中的数据结构使用(数组与链表结合,由MEMORY_SLOT链表构成的MEMORY_BLOCK,同时MEMROY_BLOCK又是一个大的链表,能很好地起到优劣互补)
  • hde32反汇编hook处代码,建立trampoline
  • 进行hook时的多线程安全性保证

MH_Initialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
MH_STATUS WINAPI MH_Initialize(VOID)
{
MH_STATUS status = MH_OK;

EnterSpinLock();

if (g_hHeap == NULL)
{
// 创建供进程使用的全局堆对象
g_hHeap = HeapCreate(0, 0, 0);
if (g_hHeap != NULL)
{
// Initialize the internal function buffer.
InitializeBuffer();
}
else
{
status = MH_ERROR_MEMORY_ALLOC;
}
}
else
{
status = MH_ERROR_ALREADY_INITIALIZED;
}

LeaveSpinLock();

return status;
}

EnterSpinLock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static VOID EnterSpinLock(VOID)
{
SIZE_T spinCount = 0;

// Wait until the flag is FALSE.
while (InterlockedCompareExchange(&g_isLocked, TRUE, FALSE) != FALSE)
{
// No need to generate a memory barrier here, since InterlockedCompareExchange()
// generates a full memory barrier itself.

// Prevent the loop from being too busy.
if (spinCount < 32)
Sleep(0);
else
Sleep(1);

spinCount++;
}
}

InterlockedCompareExchange用于创建锁,保证当前只能一个线程能执行while之后的操作。

1
2
// Spin lock flag for EnterSpinLock()/LeaveSpinLock().
volatile LONG g_isLocked = FALSE;

在循环中放置Sleep函数,防止循环过快。

LeaveSpinLock

1
2
3
4
5
6
7
static VOID LeaveSpinLock(VOID)
{
// No need to generate a memory barrier here, since InterlockedExchange()
// generates a full memory barrier itself.

InterlockedExchange(&g_isLocked, FALSE);
}

恢复锁的值为FALSE

InitializeBuffer

1
2
3
4
VOID InitializeBuffer(VOID)
{
// Nothing to do for now.
}

UnintializeBuffer

1
2
3
4
5
6
7
8
9
10
11
12
VOID UninitializeBuffer(VOID)
{
PMEMORY_BLOCK pBlock = g_pMemoryBlocks;
g_pMemoryBlocks = NULL;

while (pBlock)
{
PMEMORY_BLOCK pNext = pBlock->pNext;
VirtualFree(pBlock, 0, MEM_RELEASE);
pBlock = pNext;
}
}

MH_CreateHook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal)
{
MH_STATUS status = MH_OK;

EnterSpinLock();

if (g_hHeap != NULL)
{
if (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour))
{
UINT pos = FindHookEntry(pTarget);
if (pos == INVALID_HOOK_POS)
{
LPVOID pBuffer = AllocateBuffer(pTarget);
if (pBuffer != NULL)
{
TRAMPOLINE ct;

ct.pTarget = pTarget;
ct.pDetour = pDetour;
ct.pTrampoline = pBuffer;
if (CreateTrampolineFunction(&ct))
{
PHOOK_ENTRY pHook = AddHookEntry();
if (pHook != NULL)
{
pHook->pTarget = ct.pTarget;
#if defined(_M_X64) || defined(__x86_64__)
pHook->pDetour = ct.pRelay;
#else
pHook->pDetour = ct.pDetour;
#endif
pHook->pTrampoline = ct.pTrampoline;
pHook->patchAbove = ct.patchAbove;
pHook->isEnabled = FALSE;
pHook->queueEnable = FALSE;
pHook->nIP = ct.nIP;
memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs));
memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs));

// Back up the target function.

if (ct.patchAbove)
{
memcpy(
pHook->backup,
(LPBYTE)pTarget - sizeof(JMP_REL),
sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
}
else
{
memcpy(pHook->backup, pTarget, sizeof(JMP_REL));
}

if (ppOriginal != NULL)
*ppOriginal = pHook->pTrampoline;
}
else
{
status = MH_ERROR_MEMORY_ALLOC;
}
}
else
{
status = MH_ERROR_UNSUPPORTED_FUNCTION;
}

if (status != MH_OK)
{
FreeBuffer(pBuffer);
}
}
else
{
status = MH_ERROR_MEMORY_ALLOC;
}
}
else
{
status = MH_ERROR_ALREADY_CREATED;
}
}
else
{
status = MH_ERROR_NOT_EXECUTABLE;
}
}
else
{
status = MH_ERROR_NOT_INITIALIZED;
}

LeaveSpinLock();

return status;
}

首先对传入参数进行检查,检查hook地址是否属于可执行内存

IsExecutableAddress

1
2
3
4
5
6
7
BOOL IsExecutableAddress(LPVOID pAddress)
{
MEMORY_BASIC_INFORMATION mi;
VirtualQuery(pAddress, &mi, sizeof(mi));

return (mi.State == MEM_COMMIT && (mi.Protect & PAGE_EXECUTE_FLAGS));
}

接着判断hook地址是否已经hook

FindHookEntry

1
2
3
4
5
6
7
8
9
10
11
static UINT FindHookEntry(LPVOID pTarget)
{
UINT i;
for (i = 0; i < g_hooks.size; ++i)
{
if ((ULONG_PTR)pTarget == (ULONG_PTR)g_hooks.pItems[i].pTarget)
return i;
}

return INVALID_HOOK_POS;
}

如果被hook,返回保存的HOOK_ENTRY结构体下标;反之进行hook,返回INVALID_HOOK_POS.

AllocateBuffer(minhook中的数据结构使用)

程序在初始化时,构造了MEMORY_BLOCK和MEMORY_SLOT两个单向链表,MEMORY_SLOT单向链表在MEMORY_BLOCK结构体中。

MEMORY_SLOT结构体

1
2
3
4
5
6
7
8
9
// Memory slot.
typedef struct _MEMORY_SLOT
{
union
{
struct _MEMORY_SLOT *pNext;
UINT8 buffer[MEMORY_SLOT_SIZE];
};
} MEMORY_SLOT, *PMEMORY_SLOT;

MEMORY_BLOCK结构体

1
2
3
4
5
6
7
// Memory block info. Placed at the head of each block.
typedef struct _MEMORY_BLOCK
{
struct _MEMORY_BLOCK *pNext;
PMEMORY_SLOT pFree; // First element of the free slot list.
UINT usedCount;
} MEMORY_BLOCK, *PMEMORY_BLOCK;

返回一个未使用的MEMORY_SLOT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
LPVOID AllocateBuffer(LPVOID pOrigin)
{
PMEMORY_SLOT pSlot;
PMEMORY_BLOCK pBlock = GetMemoryBlock(pOrigin);
if (pBlock == NULL)
return NULL;

// 从MEMORY_BLOCK中移除一个MEMORY_SLOT,usedCount加1
// Remove an unused slot from the list.
pSlot = pBlock->pFree;
pBlock->pFree = pSlot->pNext;
pBlock->usedCount++;
#ifdef _DEBUG
// Fill the slot with INT3 for debugging.
memset(pSlot, 0xCC, sizeof(MEMORY_SLOT));
#endif
// 返回一个未使用过的MEMROY_SLOT
return pSlot;
}

GetMemoryBlock

获取还未使用的MEMORY_SLOT所属的MEMORY_BLOCK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
static PMEMORY_BLOCK GetMemoryBlock(LPVOID pOrigin)
{
PMEMORY_BLOCK pBlock;
#if defined(_M_X64) || defined(__x86_64__)
// 64位代码
#endif

// Look the registered blocks for a reachable one.
for (pBlock = g_pMemoryBlocks; pBlock != NULL; pBlock = pBlock->pNext)
{
#if defined(_M_X64) || defined(__x86_64__)
// 64位代码
#endif
// The block has at least one unused slot.
// 返回存在MEMORY_SLOT的MEMORY_BLOCK
if (pBlock->pFree != NULL)
return pBlock;
}

#if defined(_M_X64) || defined(__x86_64__)
// 64位代码
#else
// In x86 mode, a memory block can be placed anywhere.
pBlock = (PMEMORY_BLOCK)VirtualAlloc(
NULL, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
#endif
// 分配一块MEMORY_BLOCK,在MEMORY_BLOCK中创建单向链表
// 初始化单向链表,每次从末尾插入一个slot,在一块连续内存MEMORY_BLOCK中构建起了以MEMORY_SLOT为单位的链表
// 第一个MEMORY_SLOT指向为空
if (pBlock != NULL)
{
// Build a linked list of all the slots.
PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBlock + 1;
pBlock->pFree = NULL;
pBlock->usedCount = 0;
do
{
pSlot->pNext = pBlock->pFree;
pBlock->pFree = pSlot;
pSlot++;
} while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= MEMORY_BLOCK_SIZE - MEMORY_SLOT_SIZE);

// 全局变量g_pMemoryBlocks指向最后一个MEMORY_BLOCK,MEMORY_BLOCK构成一个单向链表
pBlock->pNext = g_pMemoryBlocks;
g_pMemoryBlocks = pBlock;
}

// 返回MEMORY_BLCOK
return pBlock;
}

CreateTrampolineFunction(反汇编hook处代码,建立trampoline)

由于代码过长,不再贴出具体的代码。其主要是调用hde32_disasm实现opcode的识别,同时构造调用原函数的trampoline,实现执行原hook处被影响的指令,同时跳转到hook后执行代码。

TRAMPOLINE结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _TRAMPOLINE
{
LPVOID pTarget; // [In] Address of the target function.
LPVOID pDetour; // [In] Address of the detour function.
LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function.

#if defined(_M_X64) || defined(__x86_64__)
LPVOID pRelay; // [Out] Address of the relay function.
#endif
BOOL patchAbove; // [Out] Should use the hot patch area?
UINT nIP; // [Out] Number of the instruction boundaries.
UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function.
UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function.
} TRAMPOLINE, *PTRAMPOLINE;

hde32_disasm

hde32反汇编库,hde32_disasm内部实现反汇编代码,能够解析汇编

AddHookEntry

HOOK_ENTRY结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _HOOK_ENTRY
{
LPVOID pTarget; // Address of the target function.
LPVOID pDetour; // Address of the detour or relay function.
LPVOID pTrampoline; // Address of the trampoline function.
UINT8 backup[8]; // Original prologue of the target function.

UINT8 patchAbove : 1; // Uses the hot patch area.
UINT8 isEnabled : 1; // Enabled.
UINT8 queueEnable : 1; // Queued for enabling/disabling when != isEnabled.

UINT nIP : 4; // Count of the instruction boundaries.
UINT8 oldIPs[8]; // Instruction boundaries of the target function.
UINT8 newIPs[8]; // Instruction boundaries of the trampoline function.
} HOOK_ENTRY, *PHOOK_ENTRY;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static PHOOK_ENTRY AddHookEntry()
{
if (g_hooks.pItems == NULL)
{
// 初始化,创建HookEntry数组
g_hooks.capacity = INITIAL_HOOK_CAPACITY;
g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc(
g_hHeap, 0, g_hooks.capacity * sizeof(HOOK_ENTRY));
if (g_hooks.pItems == NULL)
return NULL;
}
else if (g_hooks.size >= g_hooks.capacity)
{
PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc(
g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity * 2) * sizeof(HOOK_ENTRY));
if (p == NULL)
return NULL;

g_hooks.capacity *= 2;
g_hooks.pItems = p;
}

// 返回一个HOOK_ENTRY
return &g_hooks.pItems[g_hooks.size++];
}

最后保存hook的所有条件到HOOK_ENTRY当中,同时备份原代码,为hook最准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 if (pHook != NULL)
{
pHook->pTarget = ct.pTarget;
#if defined(_M_X64) || defined(__x86_64__)
pHook->pDetour = ct.pRelay;
#else
pHook->pDetour = ct.pDetour;
#endif
pHook->pTrampoline = ct.pTrampoline;
pHook->patchAbove = ct.patchAbove;
pHook->isEnabled = FALSE;
pHook->queueEnable = FALSE;
pHook->nIP = ct.nIP;
memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs));
memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs));

// Back up the target function.

if (ct.patchAbove)
{
memcpy(
pHook->backup,
(LPBYTE)pTarget - sizeof(JMP_REL),
sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
}
else
{
memcpy(pHook->backup, pTarget, sizeof(JMP_REL));
}

if (ppOriginal != NULL)
// 设置trampoline
*ppOriginal = pHook->pTrampoline;
}

MH_EnableHook

调用EnableHook

1
2
3
4
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget)
{
return EnableHook(pTarget, TRUE);
}

EnableHook

EnableHook的内容就比较简单了,主要利用HOOK_ENTRY修改pTarget,其中需要重点关注的就是用来保证hook时多线程安全的Freeze函数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static MH_STATUS EnableHook(LPVOID pTarget, BOOL enable)
{
MH_STATUS status = MH_OK;

EnterSpinLock();

if (g_hHeap != NULL)
{
if (pTarget == MH_ALL_HOOKS)
{
status = EnableAllHooksLL(enable);
}
else
{
FROZEN_THREADS threads;
UINT pos = FindHookEntry(pTarget);
if (pos != INVALID_HOOK_POS)
{
if (g_hooks.pItems[pos].isEnabled != enable)
{
// 保证线程安全
Freeze(&threads, pos, ACTION_ENABLE);

status = EnableHookLL(pos, enable);

Unfreeze(&threads);
}
else
{
status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED;
}
}
else
{
status = MH_ERROR_NOT_CREATED;
}
}
}
else
{
status = MH_ERROR_NOT_INITIALIZED;
}

LeaveSpinLock();

return status;
}

Freeze(进行hook时的多线程安全性保证)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static VOID Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action)
{
pThreads->pItems = NULL;
pThreads->capacity = 0;
pThreads->size = 0;
// 枚举当前进程中除当前执行线程以外的所有线程
EnumerateThreads(pThreads);

if (pThreads->pItems != NULL)
{
UINT i;
for (i = 0; i < pThreads->size; ++i)
{
HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]);
if (hThread != NULL)
{
// 将线程挂起
SuspendThread(hThread);
// 处理已经执行到hook位置的线程
ProcessThreadIPs(hThread, pos, action);
CloseHandle(hThread);
}
}
}
}

####EnumerateThreads

这个函数比较简单,就是找出在当前进程中,除了当前进程的当前执行线程以外的所有线程,保存在pThreads当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static VOID EnumerateThreads(PFROZEN_THREADS pThreads)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
THREADENTRY32 te;
te.dwSize = sizeof(THREADENTRY32);
if (Thread32First(hSnapshot, &te))
{
do
{
if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD))
&& te.th32OwnerProcessID == GetCurrentProcessId()
&& te.th32ThreadID != GetCurrentThreadId())
{
if (pThreads->pItems == NULL)
{
pThreads->capacity = INITIAL_THREAD_CAPACITY;
pThreads->pItems
= (LPDWORD)HeapAlloc(g_hHeap, 0, pThreads->capacity * sizeof(DWORD));
if (pThreads->pItems == NULL)
break;
}
else if (pThreads->size >= pThreads->capacity)
{
LPDWORD p = (LPDWORD)HeapReAlloc(
g_hHeap, 0, pThreads->pItems, (pThreads->capacity * 2) * sizeof(DWORD));
if (p == NULL)
break;

pThreads->capacity *= 2;
pThreads->pItems = p;
}
pThreads->pItems[pThreads->size++] = te.th32ThreadID;
}

te.dwSize = sizeof(THREADENTRY32);
} while (Thread32Next(hSnapshot, &te));
}
CloseHandle(hSnapshot);
}
}

后续就是挂起线程,以及处理已经执行到hook位置的线程,以保证hook时的多线程安全性。

以上就是Minhook库的32位源代码重要部分的简析。