《天书夜读:从汇编语言到windows内核编程》十五 开发windows内核HOOK

时间:2021-09-16 01:14:31

1)IoCallDriver可以过滤所有的系统请求,在wdk文档中查看一下它的原型:

1 NTSTATUS    IoCallDriver(
2 IN PDEVICE_OBJECT DeviceObject,
3 IN OUT PIRP Irp
4 );

  文档的解释是使用这个函数给关联设备对象的驱动发送一个IRP请求包,参数DeviceObject是I/0请求的目标设备,而Irp是指向一个IRP包(I/O请求包)。其它信息先忽略。然后再windbg中查看以下它的反汇编源码:

 1 kd> u IoCallDriver
2 nt!IoCallDriver:
3 804f15d0 8bff mov edi,edi
4 804f15d2 55 push ebp
5 804f15d3 8bec mov ebp,esp
6 804f15d5 8b550c mov edx,dword ptr [ebp+0Ch]
7 804f15d8 8b4d08 mov ecx,dword ptr [ebp+8]
8 804f15db ff1580675580 call dword ptr [nt!pIofCallDriver (80556780)]
9 804f15e1 5d pop ebp
10 804f15e2 c20800 ret 8

  可以看到,它把参数Irp移动到了edx,而把参数DeviceObject移动到了ecx,这是标准的fastcall调用标准,然后调用了pIofCallDriver函数,原书作者说编译以后也看不到IoCallDriver,而看到的是IofCallDriver,而这里却是pIofCallDriver,为了验证作者是否正确,我特意写代码并反汇编了一下这个函数,源代码如下:

1 DEVICE_OBJECT dev;
2 IRP Irp;
3 IoCallDriver(&dev,&Irp);

  然后反汇编结果是:

1 lea     edx, [ebp-70h]  ; Irp
2 lea ecx, [ebp-128h] ; DeviceObject
3 call ds:IofCallDriver

  可以看到,的确是被替换成了IofCallDriver,再看一下这个函数:

 1 .idata:00402000                   ; ===========================================================================
2 .idata:00402000
3 .idata:00402000 ; Segment type: Externs
4 .idata:00402000 ; _idata
5 .idata:00402000 ; NTSTATUS __fastcall IofCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp)
6 .idata:00402000 ?? ?? ?? ?? extrn IofCallDriver:dword ; CODE XREF: start+1C p
7 .idata:00402000 ; DATA XREF: start+1C r
8 .idata:00402000
9 .rdata:00402004 ; ===========================================================================
10 .rdata:00402004

  验证了是fastcall调用方式,那么,作者的说法就的确没错了(事事怀疑,我的确信不过这个作者,有阴影了)。再在windbg看下原书中IofCallDriver是个什么东西:

1 kd> u IofCallDriver
2 nt!IofCallDriver:
3 804f01a6 ff2580675580 jmp dword ptr [nt!pIofCallDriver (80556780)]
4 804f01ac cc int 3
5 804f01ad cc int 3

  这个过程就一个语句,这个JMP语句与CALL语句效果一样,只不过少了一个入栈出栈操作,可见,将IoCallDriver中的call语句中的目标函数pIofCallDriver替换为IofCallDriver,效果是一样的。

  现在要关注的是:这两个函数最终会调用pIofCallDriver函数,这个函数没看到ret语句,而且一路很长很长的add     byte ptr [eax],al指令,我对内核并不熟悉,到这里先打住算了:

 1 nt!pIofCallDriver:
2 80556780 0000 add byte ptr [eax],al
3 80556782 0000 add byte ptr [eax],al
4 nt!pIofCompleteRequest:
5 80556784 0000 add byte ptr [eax],al
6 80556786 0000 add byte ptr [eax],al
7 nt!pIoAllocateIrp:
8 80556788 0000 add byte ptr [eax],al
9 8055678a 0000 add byte ptr [eax],al
10 ……

  现在又要明确的是,既然都会转为IofCallDriver函数,而我们要Hook它,那么,我们就要修改它的跳转地址,先用“db IofCallDriver”看看这个函数的机器码:

1 804f01a6  ff 25 80 67 55 80 cc cc-cc cc cc cc 8b ff 55 8b

  IntelX86系列是小端存储,目标跳转地址(pIofCallDriver)是“80556780”,可以看到,在“ff 25”之后就是“80 67 55 80”,这4个字节正是“80556780”的小端存储模样,可见“25 ff”就是JMP指令的机器码存储(可能是经过对齐调整的形式,这里不必要深究),那么我们找到IofCallDriver以后,只要偏移2个字节写入自定义的新newIofCallDriver即可实现Hook,要写Hook代码,需要先了解两个函数,一个是在代码中获取IofCallDriver的入口地址的函数MmGetSystemRoutineAddress;另一个是修改内存的函数InterlockedExchange,它们的原型和说明如下:

1 //MmGetSystemRoutineAddress函数根据函数名返回函数地址指针,失败返回NULL:
2 PVOID MmGetSystemRoutineAddress(
3 IN PUNICODE_STRING SystemRoutineName//函数名
4 );

  InterlockedExchange函数使用原子操作设置一个整型变量为一个给定值,返回被设定的值:

1 //InterlockedExchange函数使用原子操作设置一个整型变量为一个给定值,返回被设定的值:
2 LONG InterlockedExchange(
3 IN OUT PLONG Target,//目标变量的地址
4 IN LONG Value//变量的值
5 );

  还需要注意的一点是,由十四(9)的CRO知道,要改写页,必须先将CR0的WP位置1,使只读页可写,Hook完以后再写回来。下面看Hook代码(注意,原书代码有各种错误,被作者坑死了):

  1 #include <ntddk.h>
2 DRIVER_INITIALIZE DriverEntry;
3 DRIVER_UNLOAD DriverUnload;
4
5 //定义类型
6 typedef unsigned char BYTE;
7 typedef long LONG;
8 typedef unsigned long ULONG;
9 typedef ULONG* PULONG;
10
11 //首先定义一个函数指针类型
12 typedef NTSTATUS (FASTCALL *pMy_IofCallDriver)(IN PDEVICE_OBJECT,IN OUT PIRP);
13 NTSTATUS FASTCALL MyIofCallDriver(IN PDEVICE_OBJECT pDev,IN OUT PIRP pIrp);
14
15 //定义三个全局变量,必须定义为全局变量
16 pMy_IofCallDriver newIofCallDriver = NULL;//新的函数指针
17 pMy_IofCallDriver *pOldIofCallDriver = NULL;//旧的跳转地址指针
18 UNICODE_STRING funcName;//目标API(IofCallDriver)函数名
19
20 //用函数newIofCallDriver去替换现有的IofCallDriver
21 //同时返回旧的IofCallDriver所跳转的实际地址,失败则返回空
22 pMy_IofCallDriver *MyHookIofCallDriverXp(IN BOOLEAN HookOrUnhook)
23 {
24
25 //目标API(IofCallDriver)入口地址
26 //这里必须定义为BYTE类型,不然定义为LONG类型则之后的+2操作会算出错误的地址
27 BYTE *addr = NULL;
28 //得到IofCallDriver的入口地址
29
30 addr = MmGetSystemRoutineAddress(&funcName);
31 if ( addr == NULL )
32 {
33 return NULL;
34 }
35
36 //ULONG addr = (ULONG)IofCallDriver;
37
38
39 if ( HookOrUnhook )
40 {
41 //获取入口地址后,加个字节的地址就是旧地址
42 //IofCallDriver的执行体的地址
43
44 pOldIofCallDriver = (pMy_IofCallDriver *)(*((PULONG)(addr + 2)));//保存旧跳转地址
45 //CR0第位为写保护位,如果为则不可写,尝试写将导致蓝屏
46 //下面关中断并对CR0进行清零
47 __asm{
48 cli
49 push eax
50 mov eax,cr0
51 and eax,not 10000h
52 mov cr0,eax
53 pop eax
54 }
55 //写入新的跳转地址,newIofCallDriver这里必须要全局变量,不能使用局部变量
56 //或者定义全局变量,而传递指针进来,否则不断蓝屏,原因未知
57 InterlockedExchange((PLONG)(addr + 2),(ULONG)(&newIofCallDriver));
58 //下面开中断并对CR0进行置
59 __asm{
60 push eax
61 mov eax,cr0
62 or eax,10000h
63 mov cr0,eax
64 pop eax
65 sti
66 }
67
68
69 return pOldIofCallDriver;
70 }
71 else
72 {
73 //复原
74 if (pOldIofCallDriver == NULL)
75 {
76 return NULL;
77 }
78
79 //下面关中断并对CR0进行清零
80 __asm{
81 cli
82 push eax
83 mov eax,cr0
84 and eax,not 10000h
85 mov cr0,eax
86 pop eax
87 }
88 InterlockedExchange((PLONG)(addr + 2),(ULONG)(pOldIofCallDriver));//写入就的跳转地址
89 //下面开中断并对CR0进行置
90 __asm{
91 push eax
92 mov eax,cr0
93 or eax,10000h
94 mov cr0,eax
95 pop eax
96 sti
97 }
98 return pOldIofCallDriver;
99 }
100 }
101
102 //定义自己的IofCallDriver实现函数
103 NTSTATUS FASTCALL MyIofCallDriver(IN PDEVICE_OBJECT pDev,IN OUT PIRP pIrp)
104 {
105 //这里打印一句话
106 KdPrint(("Enter MyIofCallDriver...\n"));
107 //继续执行旧函数体
108 return (*pOldIofCallDriver)(pDev,pIrp);
109 }
110
111 //提供一个Unload函数值是为了让这个程序能动态卸载,方便调试
112 VOID DriverUnload(PDRIVER_OBJECT driver)
113 {
114 //打印一句
115 KdPrint(("Our driver is unloading...\n"));
116 //停止HOOk
117 MyHookIofCallDriverXp(FALSE);
118 }
119
120
121 //DriverEntry,入口函数。相当于main
122 NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
123 {
124 //初始话要Hook的函数名
125 RtlInitUnicodeString(&funcName,L"IofCallDriver");
126 newIofCallDriver = MyIofCallDriver;
127 MyHookIofCallDriverXp(TRUE);
128 driver->DriverUnload = DriverUnload;
129 return STATUS_SUCCESS;
130 }

  这段代码是根据原书的代码修正的,可以看到,虽然可以运行了,但是函数各种不和谐。各种解释不通。

    a)第一:InterlockedExchange这个API很怪,真是稀奇古怪

    b)第二:调试时各种蓝屏,pOldIofCallDriver必须定义为pMy_IofCallDriver *,而这是一个二级指针啦,而赋值时却pOldIofCallDriver = (pMy_IofCallDriver *)(*((PULONG)(addr + 2))),我的妈,这解释不通了,如果不这么定义,那么在执行return (*pOldIofCallDriver)(pDev,pIrp)时一定蓝屏

    c)第三:我试过用pOldIofCallDriver定义为pMy_IofCallDriver类型,把return (*pOldIofCallDriver)(pDev,pIrp)语句用各种汇编方式来重新,结果没一个成功,全部蓝屏

  下面的代码是另一种写法,多用汇编写的,不牵涉到指针问题,蛋疼的指针:

  1 #include <ntddk.h>
2 DRIVER_INITIALIZE DriverEntry;
3 DRIVER_UNLOAD DriverUnload;
4
5 //定义类型
6 typedef unsigned long ULONG;
7
8 //首先定义一个函数指针类型
9 typedef NTSTATUS (FASTCALL *pMy_IofCallDriver)(IN PDEVICE_OBJECT,IN OUT PIRP);
10 NTSTATUS FASTCALL MyIofCallDriver(IN PDEVICE_OBJECT pDev,IN OUT PIRP pIrp);
11
12 //定义三个全局变量,必须定义为全局变量
13 pMy_IofCallDriver newIofCallDriver = NULL;//新的函数指针
14 pMy_IofCallDriver pOldIofCallDriver = NULL;//旧的跳转地址指针
15 UNICODE_STRING funcName;//目标API(IofCallDriver)函数名
16
17
18 //用函数newIofCallDriver去替换现有的IofCallDriver
19 //同时返回旧的IofCallDriver所跳转的实际地址,失败则返回空
20 pMy_IofCallDriver MyHookIofCallDriverXp(IN BOOLEAN HookOrUnhook)
21 {
22
23 //目标API(IofCallDriver)入口地址
24 ULONG addr = (ULONG)MmGetSystemRoutineAddress(&funcName);
25 if ( (PVOID)addr == NULL )
26 {
27 return NULL;
28 }
29
30 if ( HookOrUnhook )
31 {
32 //保存旧地址(入口地址后个字节的地址就是旧地址)
33 __asm
34 {
35 mov eax,addr
36 mov esi,[eax+2]
37 mov eax,[esi]
38 mov pOldIofCallDriver,eax
39 }
40
41 //CR0第位为写保护位,如果为则不可写,尝试写将导致蓝屏
42 //下面先关中断并对CR0进行清零
43 //然后开中断并对CR0进行置
44 __asm
45 {
46 cli
47 mov eax,cr0
48 push eax
49 and eax,0xfffeffff
50 mov cr0,eax
51 mov eax,addr
52 mov esi,[eax+2]
53 mov dword ptr [esi],offset MyIofCallDriver
54 pop eax
55 mov cr0,eax
56 sti
57 }
58 return pOldIofCallDriver;
59 }
60 else
61 {
62 //复原
63 if (pOldIofCallDriver == NULL)
64 {
65 return NULL;
66 }
67
68 __asm
69 {
70 cli
71 mov eax,cr0
72 push eax
73 or eax,0x00010000
74 mov cr0,eax
75 mov eax,addr
76 mov esi,[eax+2]
77 mov eax,pOldIofCallDriver
78 mov dword ptr [esi],eax
79 pop eax
80 mov cr0,eax
81 sti
82 }
83
84 return pOldIofCallDriver;
85 }
86 }
87
88 //定义自己的IofCallDriver实现函数
89 NTSTATUS FASTCALL MyIofCallDriver(IN PDEVICE_OBJECT pDev,IN OUT PIRP pIrp)
90 {
91 //这里打印一句话
92 KdPrint(("Enter MyIofCallDriver...\n"));
93 //继续执行旧函数体
94 __asm
95 {
96 mov ecx,pDev
97 mov edx,pIrp
98 Call pOldIofCallDriver
99 }
100 }
101
102 //提供一个Unload函数值是为了让这个程序能动态卸载,方便调试
103 VOID DriverUnload(PDRIVER_OBJECT driver)
104 {
105 //打印一句
106 KdPrint(("Our driver is unloading...\n"));
107 //停止HOOk
108 MyHookIofCallDriverXp(FALSE);
109 }
110
111
112 //DriverEntry,入口函数。相当于main
113 NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
114 {
115 //初始话要Hook的函数名
116 RtlInitUnicodeString(&funcName,L"IofCallDriver");
117 newIofCallDriver = MyIofCallDriver;
118 MyHookIofCallDriverXp(TRUE);
119 driver->DriverUnload = DriverUnload;
120 return STATUS_SUCCESS;
121 }

  输出为:

《天书夜读:从汇编语言到windows内核编程》十五 开发windows内核HOOK 

内核HOOK函数IoCallDriver.bmp

 

2)在(1)中提过,IofCallDriver和IoCallDriver最终都将使用一个跳转指令跳转到pIofCallDriver,按照作者的意思,这应该只是一个跳转表,现在追踪IofCallDriver在JMP语句上单步步入进来,可以看到实际运行的是IopfCallDriver,下面对IopfCallDriver内核代码执行分析,分析前,先回顾(1)并明确一下参数:pDevObj存入了ecx,而pIrp存入了edx;此外,途中可能遇到的各种结构体偏移到底指那些域可在windbg中使用dt指令查得,如[edx + 23h],由于edx为IRP结构体指针,故“dt nt!_IRP”输出:

 1 0: kd> dt nt!_IRP
2 +0x000 Type : Int2B
3 +0x002 Size : Uint2B
4 +0x004 MdlAddress : Ptr32 _MDL
5 +0x008 Flags : Uint4B
6 +0x00c AssociatedIrp : __unnamed
7 +0x010 ThreadListEntry : _LIST_ENTRY
8 +0x018 IoStatus : _IO_STATUS_BLOCK
9 +0x020 RequestorMode : Char
10 +0x021 PendingReturned : UChar
11 +0x022 StackCount : Char
12 +0x023 CurrentLocation : Char
13 +0x024 Cancel : UChar
14 +0x025 CancelIrql : UChar
15 +0x026 ApcEnvironment : Char
16 +0x027 AllocationFlags : UChar
17 +0x028 UserIosb : Ptr32 _IO_STATUS_BLOCK
18 +0x02c UserEvent : Ptr32 _KEVENT
19 +0x030 Overlay : __unnamed
20 +0x038 CancelRoutine : Ptr32 void
21 +0x03c UserBuffer : Ptr32 Void
22 +0x040 Tail : __unnamed

  从而得到[edx + 23h]实际上是pIrp->CurrentLocation。这些过程在之后的分析中省略,不过,对于[edx+60h]等之类看似越界的,其实是在Tail域中,初次接触越界的,分析一下:Tail被标识为__unnamed,无名域,查看下在编译器中输入IRP然后按F12查看一下这个域(我发现WDK文档说明这个域的时候有误,缺少东西),可以看到Tail实际上是一个联合体,内部是结构体与联合体的嵌套:

 1 union {
2 struct {
3 union {
4 KDEVICE_QUEUE_ENTRY DeviceQueueEntry;//长度为0x0d
5 struct {
6 PVOID DriverContext[4];
7 } ; //长度为0x10
8 } ; //长度为0x10
9 PETHREAD Thread;//长度为0x04
10 PCHAR AuxiliaryBuffer;//长度为0x04
11 struct {
12 LIST_ENTRY ListEntry; //长度为0x08
13 union {
14 struct _IO_STACK_LOCATION *CurrentStackLocation;//长度为0x04
15 ULONG PacketType;//长度为0x04
16 };//长度为0x04
17 };//长度为0x0c
18 PFILE_OBJECT OriginalFileObject;//长度为0x04
19 } Overlay;//长度为0x28
20 KAPC Apc; //长度为0x2f
21 PVOID CompletionKey;//长度为0x04
22 } Tail;

  长度必须自行计算,这里没考虑对齐,也不知道要不要考虑,先不管。因为60H – 40H = 20H,PVOID CompletionKey为4个字节,肯定不是它;KAPC Apc长度为0x2f,Overlay长度为0x28均有可能,而KAPC结构体为:

 1 0: kd> dt nt!_KAPC
2 +0x000 Type : Int2B
3 +0x002 Size : Int2B
4 +0x004 Spare0 : Uint4B
5 +0x008 Thread : Ptr32 _KTHREAD
6 +0x00c ApcListEntry : _LIST_ENTRY
7 +0x014 KernelRoutine : Ptr32 void
8 +0x018 RundownRoutine : Ptr32 void
9 +0x01c NormalRoutine : Ptr32 void
10 +0x020 NormalContext : Ptr32 Void
11 +0x024 SystemArgument1 : Ptr32 Void
12 +0x028 SystemArgument2 : Ptr32 Void
13 +0x02c ApcStateIndex : Char
14 +0x02d ApcMode : Char
15 +0x02e Inserted : UChar

  那么有两三种可能,要么是pIrp->Tail.Apc.NormalContext(这是一个23位VOID指针);要么是pIrp->Tail. Overlay. CurrentStackLocation(这是一个_IO_STACK_LOCATION类型指针);要么就是pIrp->Tail. Overlay. PacketType(这是一个ULONG)类型。之后必须由源代码上下文来确定到底是哪个了。下面是代码分析:

 1 nt!IopfCallDriver:
2 ;pIrp-> CurrentLocation --
3 804f016e fe4a23 dec byte ptr [edx+23h] ds:0023:883389e3=02
4 ;if( pIrp.->CurrentLocation > 0) jmp 804f0186 else call nt!KeBugCheckEx
5 804f0171 8a4223 mov al,byte ptr [edx+23h]
6 804f0174 84c0 test al,al;相当于and操作,仅影响标志位
7 804f0176 7f0e jg nt!IopfCallDriver+0x18 (804f0186);有符号大于则跳转
8 804f0178 6a00 push 0
9 804f017a 6a00 push 0
10 804f017c 6a00 push 0
11 804f017e 52 push edx
12 804f017f 6a35 push 35h
13 804f0181 e8a2ad0000 call nt!KeBugCheckEx (804faf28);蓝屏报错
14 ;pIrp-> CurrentLocation > 0成立
15 ;由于上面出现了CurrentLocation,所以这里猜测是pIrp->Tail. Overlay. CurrentStackLocation
16 ;这个sub eax,24h有点奇怪,如果上面猜测成了,那么CurrentStackLocation是一个_IO_STACK_LOCATION类型指针,减去24h只能是往上偏移(注意,这里由名字知道,应该是一个stack,stack是向上增长的—高内存到低内存,所以应该是向上偏移,即回到上一个Location---代码开始处对Location进行了减1操作,现在恢复到减1之前的Location)一个_IO_STACK_LOCATION,在windbg查看该数据结构“nt!_IO_STACK_LOCATION”发现它的长度正好是24h,所以猜测进一步验证
17 ;struct _IO_STACK_LOCATION * CurStackLocation = --pIrp->Tail. Overlay. CurrentStackLocation;
18 804f0186 8b4260 mov eax,dword ptr [edx+60h]
19 804f0189 83e824 sub eax,24h
20 804f018c 56 push esi
21 804f018d 894260 mov dword ptr [edx+60h],eax
22 ;CurStackLocation-> DeviceObject = pDevObj
23 804f0190 894814 mov dword ptr [eax+14h],ecx
24 ;eax = CurStackLocation->MajorFunction
25 804f0193 0fb600 movzx eax,byte ptr [eax]
26 ;esi = pDevObj-> DriverObject
27 804f0196 8b7108 mov esi,dword ptr [ecx+8]
28 ;call pDevObj->DriverObject-> MajorFunction[CurStackLocation->MajorFunction]( pDevObj,pIrp)
29 804f0199 52 push edx
30 804f019a 51 push ecx
31 804f019b ff548638 call dword ptr [esi+eax*4+38h]
32 804f019f 5e pop esi
33 804f01a0 c3 ret

  还原为C语言应该是:

 1 NTSTATUS  FASTCALL  IopfCallDriver(PDEVICE_OBJECT  pDevObj,PIRP  pIrp)
2 {
3 //pIrp的CurrentLocation减1
4 pIrp-> CurrentLocation --;
5 //如果减1后当前位置不大于0则蓝屏报错
6 if( pIrp.->CurrentLocation <= 0)
7 KeBugCheckEx(0x35,pIrp,0,0,0);
8 //将CurrentStackLocation指向设备栈中后一个设备
9 struct _IO_STACK_LOCATION* CurStackLocation = --pIrp->Tail.Overlay.CurrentStackLocation;
10 //设置设备对象
11 CurStackLocation-> DeviceObject = pDevObj;
12 //调用分发函数
13 return pDevObj->DriverObject-> MajorFunction[CurStackLocation->MajorFunction]( pDevObj,pIrp);
14 }

  第一:原书再次出错,作者把“jg”指令翻译成“>= 0”,但这里还有个疑问,从“sub  eax,24h”可以看到,它执行的操作是上一个Location,那么当Location > 0才继续操作的话,实际操作的最低Location就应该是2,如果Location从0(或者1)开始编号,那么就是堆栈中至少有3(或者2)个_IO_STACK_LOCATION。这个是单纯从代码中分析得出的结论,如果与事实相悖,那么我的分析肯定就出错了,而现在暂时没法确定对错,对内核不太熟悉。

  第二:作者调试的是VISTA,我的是XP SP3,可见没有了所谓的全局变量,所以原书说的HOOK方法不再可行。

 

3) Inline HOOk原理:

  a)把IofCallDriver开头指令拷贝下来,移动到中继函数里

  b)在IofCallDriver开头写入跳转指令跳转到中继函数

  c)中继函数中执行自己的IofCallDriver钩子函数调用

  d)中继函数中执行原来IofCallDriver函数拷贝过来的几条指令

  e)跳转回原来的IofCallDriver中被写入的跳转指令之后继续执行原函数

  下面是代码,原作者太懒了啊,没源码也没光盘,还各种理由,学编程这东西只要测试并看懂源码和原理,自己写一遍有毛毛帮助。需要注意,这段代码使用到了xde反汇编引擎,我下载的版本xde.c文件中少写了个头文件,需添加“#include <memory.h>”。关于inline  hook的关键部分已经注释了,这里不多说了,输出效果和上面相同:

  1 #include "xde.h"
2 #include <ntddk.h>
3
4 typedef unsigned long ULONG;
5
6 //函数声明
7 MyIofCallDriver(void);
8 DRIVER_INITIALIZE DriverEntry;
9 DRIVER_UNLOAD DriverUnload;
10
11 //全局变量
12 ULONG gDestCodeAddr = 0;//旧代码保存区域的地址
13 ULONG gBackCodeAddr = 0;//跳回继续执行地址
14 UNICODE_STRING apiFuncName;//要HOOK的API名字
15
16
17 //对xp的IofCallDriver函数进行inline hook
18 //成功返回TRUE,失败返回FALSE
19 BOOLEAN InlineHookIofCallDriverXp(IN PUNICODE_STRING pFuncName,//要HOOK的API名称
20 IN ULONG uMyFuncAddr,//自定义的中继函数地址
21 IN BOOLEAN bHookOrUnhook//是否HOOK
22 )
23 {
24 //局部变量
25 ULONG uHookApiAddr = 0;//被HOOK的API起始地址
26 ULONG start_address = 0;//解析指令时一条指令的起始地址
27 size_t length = 0;//解析指令返回的实际长度
28 size_t total_length = 0;//源API中要被转移的指令的长度
29 size_t myjmp_length = 0;//汇编JMP指令返回的实际长度
30 struct xde_instr code_instr = {0};//指令结构体
31 struct xde_instr myjmp = {0};//JMP指令结构体
32 unsigned char code[12];//使用较大空间容纳JMP指令
33 //KIRQL irql;//用于提高中断级
34
35 //目标API入口地址
36 uHookApiAddr = (ULONG)MmGetSystemRoutineAddress(pFuncName);
37 if ( (PVOID)uHookApiAddr == NULL )
38 {
39 return FALSE;
40 }
41 //写入绝对跳转指令到结构体
42 myjmp.opcode = 0xea;//写操作码(jmp机器指令)
43 myjmp.addrsize = 4;//地址长度为个字节
44 myjmp.datasize = 2;//段选择子作为立即数,长度为字节
45 myjmp.addr_d[0] = uMyFuncAddr;//填写要跳转的地址(自定义函数)
46 myjmp.data_w[0] = 8;//段选择子。内核代码段总是为8
47 myjmp_length = xde_asm(code,&myjmp);//汇编一条指令
48
49 //解析要HOOK的API开头须移动多少字节的指令
50 if ( bHookOrUnhook == TRUE )//HOOK
51 {
52 start_address = uHookApiAddr;//一条指令的起始地址
53 }
54 else//UnHook
55 {
56 start_address = gBackCodeAddr;//一条指令的起始地址
57 }
58 //要是起始地址为空,则失败
59 if ( start_address == 0 )
60 {
61 return FALSE;
62 }
63 while ( total_length < myjmp_length )//当长度不足以用来填充JMP指令时
64 {
65 //解析一条指令
66 length = xde_disasm((unsigned char *)start_address,&code_instr);
67 if( length == 0 )//解析失败
68 {
69 return FALSE;
70 }
71 //计算已经反汇编的指令总长度
72 total_length += length;
73 //解析指令起始地址移动
74 start_address += length;
75 }
76 gBackCodeAddr = start_address;//保存跳回的地址
77
78 //提高中断级,禁止线程切换,发生问题
79 //要求当前函数的IRQL <= DISPATCH_LEVEL,我这里报错
80 //irql = KeRaiseIrqlToDpcLevel();
81
82 //CR0的WP位进行清零
83 __asm{
84 cli //中断级无法改变,改为关闭中断写法
85 mov eax,cr0
86 push eax
87 and eax,0xfffeffff
88 mov cr0,eax
89 }
90
91 if ( bHookOrUnhook == TRUE )//HOOK
92 {
93 //在IofCallDriver开头写入跳转指令之前,需要先转移原来的代码
94 if ( gDestCodeAddr == 0 )//目标地址未获取
95 {
96 MyIofCallDriver();//进行获取
97 }
98 __asm{
99 mov esi,uHookApiAddr //IofCallDriver的原来起始地址(源地址)
100 mov edi,gDestCodeAddr //要拷贝到的地址(目的地址)
101 mov ecx,total_length //总长度
102 cld //清方向标志位,也就是使DF的值为0
103 rep movsb //拷贝(字符串传送指令,按字节传送数据)
104 }
105
106 //写入绝对跳转指令JMP
107 __asm{
108 lea esi,code      //存放JMP指令的缓冲区(源地址)
109 mov edi,uHookApiAddr //要拷贝到的地址(目的地址)
110 mov ecx,myjmp_length //总长度
111 cld //清方向标志位,也就是使DF的值为0
112 rep movsb //拷贝(字符串传送指令,按字节传送数据)
113 }
114 }
115 else//UnHOOK
116 {
117 //判断旧代码保存区域地址是否为空
118 if ( gDestCodeAddr == 0 )
119 {
120 //恢复CR0
121 __asm{
122 pop eax
123 mov cr0,eax
124 sti
125 }
126 return FALSE;
127 }
128 //将旧代码保存区域的旧代码复制回目标API起始地址
129 __asm{
130 mov esi,gDestCodeAddr //旧代码保存区起始地址(源地址)
131 mov edi,uHookApiAddr //API起始地址(目的地址)
132 mov ecx,total_length //总长度
133 cld //清方向标志位,也就是使DF的值为
134 rep movsb //拷贝(字符串传送指令,按字节传送数据)
135 }
136 }
137
138
139 //恢复CR0
140 __asm{
141 pop eax
142 mov cr0,eax
143 sti
144 }
145
146 //降低中断级
147 //KeLowerIrql(irql);
148
149 return TRUE;
150 }
151
152 static __declspec(naked) MyIofCallDriver(void)
153 {
154
155 //判断标号是否被读出
156 if( gDestCodeAddr == 0 )
157 {
158 //将标号old_codes标号地址放入gDestCodeAddr
159 __asm{
160 push old_codes
161 pop gDestCodeAddr
162 ret
163 }
164 }
165
166 //首先将参数入栈
167 __asm{
168 pushad
169 }
170
171 //下面写入自己要做的事情,这里仅仅打印一句话
172 KdPrint(("Enter Inline Hook...\n"));
173
174 //下面为存放旧代码区域
175 __asm{
176 //先恢复参数
177 popad
178 old_codes:
179 //写入足够多的nop,以够容纳旧代码
180 nop
181 nop
182 nop
183 nop
184 nop
185 nop
186 nop
187 nop
188 nop
189 nop
190 nop
191 nop
192 nop
193 nop
194 nop
195 nop
196 nop
197 nop
198 nop
199 }
200 //跳回原来的地方继续执行
201 __asm{
202 jmp gBackCodeAddr
203 }
204 }
205
206
207 //提供一个Unload函数值是为了让这个程序能动态卸载,方便调试
208 VOID DriverUnload(PDRIVER_OBJECT driver)
209 {
210 //打印一句
211 KdPrint(("Our driver is unloading...\n"));
212 //停止HOOk
213 if(InlineHookIofCallDriverXp(&apiFuncName,(ULONG)MyIofCallDriver,FALSE) == FALSE)
214 {
215 KdPrint(("UnHook failed!\n"));
216 }
217 }
218
219
220 //DriverEntry,入口函数。相当于main
221 NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
222 {
223 RtlInitUnicodeString(&apiFuncName,L"IofCallDriver");
224 if (InlineHookIofCallDriverXp(&apiFuncName,(ULONG)MyIofCallDriver,TRUE) == FALSE)
225 {
226 KdPrint(("Inline Hook failed!\n"));
227 }
228 driver->DriverUnload = DriverUnload;
229 return STATUS_SUCCESS;
230 }