note20200821

Hook的分类

Address Hook

即修改某些函数的地址,他们可能存放在某个函数表中,可能是存在例程中,可能是在MSR寄存器中,也可能是某个特点函数指针。

各类表中的地址

  1. PE的IAT

仅针对某个PE的模块的IAT进行Hook,想要对加载的所有模块起作用就必须变量进程内模块,对目标API进行Hook。

  1. PE的EAT

EAT存放的是函数地址的偏移,所以想要使用针对EAT的Hook,就需要计算目标函数与修改后函数地址的偏。该方式的Hook进行后,所有试图通过输入表获取函数地址的行为都会收到影响。

  1. user32.dll的回调函数表

user32.dll中的UER32!apfnDispatch的回调函数表存放了各种用于GUI的回调函数,当修改了这里的函数地址后,就可以进行Hook。

  1. IDT

IDT即系统中断ID,IDT的基址存放在idtr寄存器中,表内项目数存放在idtl寄存器中,每个中断项的中断处理例程成为ISR。

  1. SSDT以及Shadow SSDT

SSDT表即系统服务描述符表,在API调用系统服务处理时使用。Shadow SSDT表用于处理各种GUI服务。

  1. C++类的虚函数表

即类中第一个地址,该地址为虚函数表的地址。

  1. COM接口的功能函数表

COM接口函数都放在一个函数中,与虚函数表类似。

处理例程地址

  1. DRIVER_OBJECT的MajorFunction及FastIo派遣例程地址。

windows 7 x86调试结果:

ntfs的DRIVER_OBJECT:

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
2: kd> !drvobj ntfs
Driver object (86e1c660) is for:
\FileSystem\Ntfs

Driver Extension List: (id , addr)

Device Object list:
8789c020 8723b638
2: kd> dt _DRIVER_OBJECT 86e1c660
nt!_DRIVER_OBJECT
+0x000 Type : 0n4
+0x002 Size : 0n168
+0x004 DeviceObject : 0x8789c020 _DEVICE_OBJECT
+0x008 Flags : 0x92
+0x00c DriverStart : 0x890b0000 Void
+0x010 DriverSize : 0x12f000
+0x014 DriverSection : 0x8633b2b8 Void
+0x018 DriverExtension : 0x86e1c708 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING "\FileSystem\Ntfs"
+0x024 HardwareDatabase : 0x841b3250 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
+0x028 FastIoDispatch : 0x890ef900 _FAST_IO_DISPATCH
+0x02c DriverInit : 0x891b39fa long Ntfs!GsDriverEntry+0
+0x030 DriverStartIo : (null)
+0x034 DriverUnload : (null)
+0x038 MajorFunction : [28] 0x8915a832 long Ntfs!NtfsFsdCreate+0

MajorFunction函数表:

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
2: kd> dds 0x86e1c660+0x38
86e1c698 8915a832 Ntfs!NtfsFsdCreate
86e1c69c 83efcda3 nt!IopInvalidDeviceRequest
86e1c6a0 8915b3d3 Ntfs!NtfsFsdClose
86e1c6a4 890c3a7e Ntfs!NtfsFsdRead
86e1c6a8 890c4599 Ntfs!NtfsFsdWrite
86e1c6ac 891557af Ntfs!NtfsFsdDispatchWait
86e1c6b0 890be078 Ntfs!NtfsFsdSetInformation
86e1c6b4 891557af Ntfs!NtfsFsdDispatchWait
86e1c6b8 891557af Ntfs!NtfsFsdDispatchWait
86e1c6bc 8912fc7b Ntfs!NtfsFsdFlushBuffers
86e1c6c0 8915600e Ntfs!NtfsFsdDispatch
86e1c6c4 8915600e Ntfs!NtfsFsdDispatch
86e1c6c8 8915ed28 Ntfs!NtfsFsdDirectoryControl
86e1c6cc 89146244 Ntfs!NtfsFsdFileSystemControl
86e1c6d0 8914fefd Ntfs!NtfsFsdDeviceControl
86e1c6d4 83efcda3 nt!IopInvalidDeviceRequest
86e1c6d8 89190847 Ntfs!NtfsFsdShutdown
86e1c6dc 890cd49d Ntfs!NtfsFsdLockControl
86e1c6e0 8915bc9e Ntfs!NtfsFsdCleanup
86e1c6e4 83efcda3 nt!IopInvalidDeviceRequest
86e1c6e8 8915600e Ntfs!NtfsFsdDispatch
86e1c6ec 8915600e Ntfs!NtfsFsdDispatch
86e1c6f0 83efcda3 nt!IopInvalidDeviceRequest
86e1c6f4 83efcda3 nt!IopInvalidDeviceRequest
86e1c6f8 83efcda3 nt!IopInvalidDeviceRequest
86e1c6fc 891557af Ntfs!NtfsFsdDispatchWait
86e1c700 891557af Ntfs!NtfsFsdDispatchWait
86e1c704 89120517 Ntfs!NtfsFsdPnp
86e1c708 86e1c660
86e1c70c 00000000
86e1c710 00000001
86e1c714 000a0008

FastIo函数表:

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
2: kd> dds 0x86e1c660+0x28
86e1c688 890ef900 Ntfs!NtfsFastIoDispatch
86e1c68c 891b39fa Ntfs!GsDriverEntry
86e1c690 00000000
86e1c694 00000000
86e1c698 8915a832 Ntfs!NtfsFsdCreate
86e1c69c 83efcda3 nt!IopInvalidDeviceRequest
86e1c6a0 8915b3d3 Ntfs!NtfsFsdClose
86e1c6a4 890c3a7e Ntfs!NtfsFsdRead
86e1c6a8 890c4599 Ntfs!NtfsFsdWrite
86e1c6ac 891557af Ntfs!NtfsFsdDispatchWait
86e1c6b0 890be078 Ntfs!NtfsFsdSetInformation
86e1c6b4 891557af Ntfs!NtfsFsdDispatchWait
86e1c6b8 891557af Ntfs!NtfsFsdDispatchWait
86e1c6bc 8912fc7b Ntfs!NtfsFsdFlushBuffers
86e1c6c0 8915600e Ntfs!NtfsFsdDispatch
86e1c6c4 8915600e Ntfs!NtfsFsdDispatch
86e1c6c8 8915ed28 Ntfs!NtfsFsdDirectoryControl
86e1c6cc 89146244 Ntfs!NtfsFsdFileSystemControl
86e1c6d0 8914fefd Ntfs!NtfsFsdDeviceControl
86e1c6d4 83efcda3 nt!IopInvalidDeviceRequest
86e1c6d8 89190847 Ntfs!NtfsFsdShutdown
86e1c6dc 890cd49d Ntfs!NtfsFsdLockControl
86e1c6e0 8915bc9e Ntfs!NtfsFsdCleanup
86e1c6e4 83efcda3 nt!IopInvalidDeviceRequest
86e1c6e8 8915600e Ntfs!NtfsFsdDispatch
86e1c6ec 8915600e Ntfs!NtfsFsdDispatch
86e1c6f0 83efcda3 nt!IopInvalidDeviceRequest
86e1c6f4 83efcda3 nt!IopInvalidDeviceRequest
86e1c6f8 83efcda3 nt!IopInvalidDeviceRequest
86e1c6fc 891557af Ntfs!NtfsFsdDispatchWait
86e1c700 891557af Ntfs!NtfsFsdDispatchWait
86e1c704 89120517 Ntfs!NtfsFsdPnp
  1. StartIo等特殊例程的地址。

StartIoatapi对象中:

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
2: kd> !drvobj atapi
Driver object (874c6840) is for:
\Driver\atapi

Driver Extension List: (id , addr)
(88db2006 86d89418)
Device Object list:
874bb030 8749e028 87495028 8748c028
87483028 8747a028 87471028 87468028
87461028 87455028 8744c028 87443028
8743a028 87431028 87428028 8741f028
87416028 8740d028 87404028 873fb028
873f2028 873e9028 873e0028 873d7028
873ce028 873c5028 873bc028 873b3028
873aa028 873a3028 8739c028 863da028
863de028
2: kd> dt _DRIVER_OBJECT 874c6840
nt!_DRIVER_OBJECT
+0x000 Type : 0n4
+0x002 Size : 0n168
+0x004 DeviceObject : 0x874bb030 _DEVICE_OBJECT
+0x008 Flags : 0x12
+0x00c DriverStart : 0x88e13000 Void
+0x010 DriverSize : 0x9000
+0x014 DriverSection : 0x8633b6b0 Void
+0x018 DriverExtension : 0x874c68e8 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING "\Driver\atapi"
+0x024 HardwareDatabase : 0x841b3250 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
+0x028 FastIoDispatch : (null)
+0x02c DriverInit : 0x88e1903e long atapi!GsDriverEntry+0
+0x030 DriverStartIo : (null)
+0x034 DriverUnload : 0x88daade6 void ataport!IdePortUnload+0
+0x038 MajorFunction : [28] 0x88daf8cc long ataport!PortWdmAlwaysStatusSuccessIrp+0
  1. OBJECT_TYPE中_OBJECT_TYOE_INITIALIZER包含的各种处理过程。

特殊基础器中的地址

Windows使用MSR寄存器组中的IA32_SYSNTER_EIP的值作为内核调用的入口,当在ntdll中调用汇编指令sysenter进入内核时,CPU会首先执行到这里。可以修改MSR寄存器中保存的快速调用入口函数地址。

1
2
3
4
5
6
7
8
9
10
11
12
0: kd> rdmsr 0x176
msr[176] = 00000000`83e790c0
0: kd> u 83e790c0
nt!KiFastCallEntry:
83e790c0 b923000000 mov ecx,23h
83e790c5 6a30 push 30h
83e790c7 0fa1 pop fs
83e790c9 8ed9 mov ds,cx
83e790cb 8ec1 mov es,cx
83e790cd 648b0d40000000 mov ecx,dword ptr fs:[40h]
83e790d4 8b6104 mov esp,dword ptr [ecx+4]
83e790d7 6a23 push 23h

特定的函数指针

特定的函数指针存放在一个已知的地址中,如nt!IoCompleteRequest

1
2
3
4
5
6
7
8
9
10
0: kd> u nt!IoCompleteRequest
nt!IoCompleteRequest:
83f14401 8bff mov edi,edi
83f14403 55 push ebp
83f14404 8bec mov ebp,esp
83f14406 8a550c mov dl,byte ptr [ebp+0Ch]
83f14409 8b4d08 mov ecx,dword ptr [ebp+8]
83f1440c ff15605afa83 call dword ptr [nt!pIofCompleteRequest (83fa5a60)];函数存在指针中
83f14412 5d pop ebp
83f14413 c20800 ret 8
1
2
3
4
5
6
7
8
9
0: kd> dd nt!pIofCompleteRequest
83fa5a60 83eb3809 83ec0eed 83ecf3a5 00000003
83fa5a70 86545000 86546000 00000120 ffffffff
83fa5a80 00000001 00000000 00000002 00000000
83fa5a90 00000004 00000000 00000008 00000000
83fa5aa0 00000000 00000000 00000000 00000000
83fa5ab0 00000000 00000000 00000000 00000000
83fa5ac0 00000000 00000000 00000000 00000000
83fa5ad0 00000000 00000000 00000000 00000000
1
2
3
4
5
6
7
8
9
10
0: kd> uu 83eb3809
nt!IopfCompleteRequest:
83eb3809 8bff mov edi,edi
83eb380b 55 push ebp
83eb380c 8bec mov ebp,esp
83eb380e 83e4f8 and esp,0FFFFFFF8h
83eb3811 83ec24 sub esp,24h
83eb3814 53 push ebx
83eb3815 56 push esi
83eb3816 8bf1 mov esi,ecx

这些函数都与IRP处理有关。

Inline Hook

直接修改指令的Hook。

一般有5种模式。

jmp xxxxxxxx(5字节)

直接跳转xxxxxxxx

1
E9 xx xx xx xx

push xxxxxxxx/ret(6字节)

通过压栈返回实现跳转到xxxxxxxx

1
2
68 xx xx xx xx
c3

mov eax,xxxxxxx/jmp eax(7字节)

1
2
B8 xx xx xx 00
FF E0

call Hook(更换指令或输入表)

类似Address Hook只不过是直接修改CPU中的指令call指向的函数地址。

如:

1
e8 09 00 00 00

Hook为:

1
e8 10 00 00 00

HotPatch Hook

一个短跳加一个长跳(7个字节)。

如:

1
2
3
4
5
6
7
90
90
90
90
90
90
8b ff;入口地址

改为:

1
2
e9 66 08 66 89
eb f9;入口地址,跳转到上一条长跳

可以通用hook不管有没有SEH的api入口。

基于异常处理的Hook

其实就是修改SEH或者VEH链中存放SEH或者VEH处理函数的地址。

一些不是Hook的Hook

PE文件被感染,导致OEP被修改

系统回调机制和分层模型

  1. 各种回调机制;

如消息钩子以及内核中各种回调。

  1. 分层服务和过滤驱动模型;

如LSP服务提供者以及驱动模型(Class/Port/MiniPort)。

Hook位置的挑选

从用户程序到端口驱动,越往下Hook得到的控制权就越晚,也越难被绕过。应用层上,对API Hook就已经够用了;内核中,一般Hook SSDT和SSDT Shaodow以及用于切换ring0 ring3的KiFastCallEntry比较方便。

Hook的典型过程

Inline Hook以及Address Hook都需要定义一个Detour函数替代被Hook的函数,其原型,调用约定以及返回值都需要与原函数一样。以MessageBoxA为例,自定义Detour函数如下:

1
2
3
4
5
6
int WINAPI My_MessageBoxA(
HWMD hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);

这里使用WINAPI来显示定义Detour函数与被Hook函数调用约定一样,为__stdcall,否则在VC++默认编译时,对函数默认调用约定为__cdecl

如果在Detour函数中调用原函数来实现功能,就需要某种方式调用原函数,这里Address Hook与Inline Hook方式是不一样的。

Address Hook实施过程

处理定义Detour函数,还需要定义一个与被Hook函数原型一致的函数指针,指向原始函数:

1
2
3
4
5
6
7
typedef int (WINAPI* POI_MessageBoxA)(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
POI_MessageBoxA oldMessageBox = NULL;

然后查表替换原地址,关闭写保护,以及写入Detour函数地址。

IAT 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
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
BOOL InstallModuleIATHook(
HMODULE hModToHook,
char* szModuleName,
char* szFuncName,
PVOID DetourFunc,
PULONG* pThunkPointer,
ULONG* pOriginalFuncAddr
)
{
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor;
PIMAGE_THUNK_DATA pThunkData;
ULONG ulSize;
HMODULE hModule = 0;
ULONG TargetFunAddr;
PULONG lpAddr;
char* szModName;
BOOL result = FALSE;
BOOL bRetn = FALSE;

//获取目标函数地址
hModule = LoadLibrary(szModuleName);
TargetFunAddr = (ULONG)GetProcAddress(hModule, szFuncName);

//获取待Hook模块输入表的起始地址
pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModToHook,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulSize);

while (pImportDescriptor->FirstThunk)//FirstThunk里存储的就是IAT的RVA
{
szModName = (char*)((PBYTE)hModToHook+pImportDescriptor->Name);//找到需要Hook的模块
cout << "当前模块名称:" << szModName << endl;
if (stricmp(szModName, szModuleName)!= 0) {
cout << "不是需求匹配的模块!" << endl;
pImportDescriptor++;
continue;
}
pThunkData = (PIMAGE_THUNK_DATA)((BYTE*)hModToHook + pImportDescriptor->FirstThunk);
while (pThunkData->u1.Function)
{
lpAddr = (ULONG*)pThunkData;
if ((*lpAddr) == TargetFunAddr)
{
cout << "找到目标地址!" << endl;

//修改输入表内存页为可写
DWORD dwOldProtect;
MEMORY_BASIC_INFORMATION mbi;//存放虚拟页
VirtualQuery(lpAddr, &mbi, sizeof(mbi));//找到目标虚拟页
bRetn = VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);//将虚拟页修改为可wrx
if (bRetn) {
if (pThunkData != NULL) {//修改成功
*pThunkPointer = lpAddr;
}
if (pOriginalFuncAddr != NULL) {
*pOriginalFuncAddr = *lpAddr;
}
*lpAddr = (ULONG)DetourFunc;//修改地址
result = TRUE;
//恢复内存地址
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwOldProtect, 0);
cout << "目标函数:" << szFuncName << "地址被Hook为" << DetourFunc <<"。"<< endl;

}
break;
}
pThunkData++;
}
pImportDescriptor++;
}
FreeLibrary(hModule);

oldMessageBox = (POI_MessageBoxA)pOriginalFuncAddr;

return result;
}

实验结果:

虚函数Hook

几个注意点:

  1. 确定目标函数在目标雷中的位置以及函数原型;
  2. 定义DetourClass和TrampolineClass;
  3. 修改虚函数表实现Hook;

SSDT Hook

  1. 定义Detour函数以及一个函数指针;
  2. 通过硬编码方式检索系统版本,找出SSDT索引;
  3. 去掉虚拟页写保护,这里可以通过清楚CPU控制寄存器cr0;
  4. 安装Hook;

Inline Hook实施过程

几个概念:

  1. TargetFun:用被Hook的目标函数;
  2. DetourFun:用于替代TargrtFun的自定义函数;
  3. TrampolineFun:调用函数的入口。在该函数中要执行的TargetFun中被替换的前几条指令;

具体过程

这里针对MessageBoxA进行实验。

  1. 确定Hook方式以及在TrampolineFun中使用的指令;

可以观察到MessageBoxA函数开头如下:

1
2
3
763E13D0    8BFF            mov edi,edi
763E13D2 55 push ebp
763E13D3 8BEC mov ebp,esp

正好是5个字节,使用长跳的机器码进行Inline Hook,即将这几个字节修改为如下形式:

1
E9 xx xx xx xx

当Detour函数执行完毕,需要使用TrampolineFun调用这三条指令来正常调用原函数:

1
2
3
mov edi,edi
push ebp
mov ebp,esp
  1. 设置TrampolineFun函数;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PBYTE AddrMessageBoxA = 
(PBYTE)GetProcAddress(GetModuleHandle("user32.dll"), "MessageBoxA");

//TrampolineFun,保存原函数正常执行
__declspec(naked)
int WINAPI OriginalMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
_asm {
mov edi,edi
push ebp
mov ebp,esp
jmp g_JmpBackAddr
}
}
  1. 将jmp指令写入,并设置TrampolineFun即可;

这里为了方便显示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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include"windows.h"
#include<iostream>
using std::cout;
using std::endl;
using std::cin;

void show_msg();
void show_several_bytes();
void show_byte(ULONG addr, int length = 1);

int main() {
while (1) {
cout << "TEST YOUR HOOK" << endl;
cout << "1.show messagebox;" << endl;
cout << "2.show several bytes of the api;" << endl;
cout << "input the index:" << endl;
char input;
cin >> input;
if (input == '1') {
show_msg();
}
else if (input == '2') {
show_several_bytes();
}
else {
cout << "WRONG INPUT!\n";
}
cout << endl;
}
}

void show_msg() {
MessageBox(NULL, "pop-ups", "pop-ups", MB_OK);
}

void show_several_bytes() {
HMODULE user32_mod = GetModuleHandle("user32.dll");
ULONG msgbox_addr = (ULONG)GetProcAddress(user32_mod, "MessageBoxA");
show_byte(msgbox_addr, 5);
}

void show_byte(ULONG addr, int length) {
printf("0x%p 开始的 %d 个字节:", addr, length);
for (int i = 0; i < length; i++) {
BYTE t;
__asm {
mov eax, addr
mov al, byte ptr[eax]
mov t, al
}
printf("%x ", t);
addr = addr + 1;
}
cout << endl;
}

测试结果(这里由于没有对源代码的一部分进行修改导致了地址的错误,这里就演示一下字节的修改):

Hook前:

Hook后:

tips

  1. 使用ReadProcessMemory以及WriteProcessMemory函数可以方便的读写内存;
  2. 方便的显示指定地址字节的内容的函数设计:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void show_byte(ULONG addr, int length) {
printf("0x%p 开始的 %d 个字节:", addr, length);
for (int i = 0; i < length; i++) {
BYTE t;
__asm {
mov eax, addr
mov al, byte ptr[eax]
mov t, al
}
printf("%x ", t);
addr = addr + 1;
}
cout << endl;
}

Hook的检测

Address Hook检测

寻找原始的Address,与当前Address进行对比。

  1. IAT Hook:加载待检测的PE模块,然后对IAT进行对比。
  2. SSDT Hook,Shadow SSDT Hook:将内核文件加载,手动计算实际地址,然后进行对比。
  3. 一些无法获取确切地址的Hook,检测Address是否再这个驱动模块的地址范围。

Inline Hook检测

步骤如下:

  1. 加载待检测的PE映像,根据实际地址进行重定位。
  2. 对代码所在的节进行检查和比对。