SSDT Hook
SSDT表
系统服务描述符表
这里以Windows XP SP3为例
kd> dd KeServiceDescriptorTable
80553fa0 80502b8c 00000000 0000011c 80503000
80553fb0 00000000 00000000 00000000 00000000
80553fc0 00000000 00000000 00000000 00000000
80553fd0 00000000 00000000 00000000 00000000
80553fe0 00002710 bf80c0b6 00000000 00000000
80553ff0 f8b7ba80 f82ffb60 820808f8 806e2f40
80554000 00000000 00000000 00000000 00000000
80554010 28728ec0 01db120e 00000000 00000000
第一个参数指向的是内核函数存储的首地址
kd> dd 80502b8c
ReadVirtual: 80502b8c not properly sign extended
80502b8c 8059a948 805e7db6 805eb5fc 805e7de8
80502b9c 805eb636 805e7e1e 805eb67a 805eb6be
80502bac 8060cdfe 8060db50 805e31b4 805e2e0c
80502bbc 805cbde6 805cbd96 8060d424 805ac5ae
80502bcc 8060ca3c 8059edbe 805a6a00 805cd8c4
80502bdc 80500828 8060db42 8056ccd6 8053600e
80502bec 806060d4 805b2c3a 805ebb36 8061ae56
80502bfc 805f0028 8059b036 8061b0aa 8059a8e8
第三个参数11c(284)代表的是SSDT表有多少个内核函数
第四个参数指向的是一个地址,这个地址表示的内核函数相对应的参数个数
kd> dd 80503000
ReadVirtual: 80503000 not properly sign extended
80503000 2c2c2018 44402c40 1818080c 0c040408
80503010 08081810 0808040c 080c0404 2004040c
80503020 140c1008 0c102c0c 10201c0c 20141038
80503030 141c2424 34102010 080c0814 04040404
80503040 0428080c 1808181c 1808180c 040c080c
80503050 100c0010 10080828 0c08041c 00081004
80503060 0c080408 10040828 0c0c0404 28240428
80503070 0c0c0c30 0c0c0c18 0c10300c 0c0c0c10
比如0x2c = 44,44/4 = 11个参数
分析
我们来看一个函数OpenProcess是具体怎么调用的
tips:最好是什么编译环境,就在什么环境分析,xp上编的在xp上分析,win10上编的在win10上分析
可以看见,OpenProcess也是调用了kernel32.dll
kernel32.dll里面看见了syscall的调用,调用号是0x7A
这时候找到SSDT表
kd> dd KeServiceDescriptorTable
80553fa0 80502b8c 00000000 0000011c 80503000
80553fb0 00000000 00000000 00000000 00000000
80553fc0 00000000 00000000 00000000 00000000
80553fd0 00000000 00000000 00000000 00000000
80553fe0 00002710 bf80c0b6 00000000 00000000
80553ff0 f8b67a80 f82ffb60 81be2928 806e2f40
80554000 00000000 00000000 00000000 00000000
80554010 08909a40 01db1211 00000000 00000000
kd> dd 80502b8c+7a*4
ReadVirtual: 80502d74 not properly sign extended
80502d74 805c2296 805e49fc 805e4660 805a0722
80502d84 8060c254 805ba77a 805c2522 805e4a1a
80502d94 805e47d0 8060e1b0 8063cc78 805c0346
80502da4 805eedce 805eaa16 805eac02 805aea08
80502db4 806062dc 8056d0ce 8060db50 8060db50
80502dc4 8053d02e 80607e68 80608ac8 80570074
80502dd4 805b4de0 805703ca 806063a4 8056d222
80502de4 8060d2dc 80570c46 805ccee0 8059b6fc
在这里0x805c2296就是我们要找的函数调用表的首地址,进去看个伪代码
kd> u 805c2296 L50
805c2296 68c4000000 push 0C4h
805c229b 68a8aa4d80 push offset nt!ObWatchHandles+0x25c (804daaa8)
805c22a0 e86b6cf7ff call nt!_SEH_prolog (80538f10)
805c22a5 33f6 xor esi,esi
805c22a7 8975d4 mov dword ptr [ebp-2Ch],esi
805c22aa 33c0 xor eax,eax
805c22ac 8d7dd8 lea edi,[ebp-28h]
805c22af ab stos dword ptr es:[edi]
805c22b0 64a124010000 mov eax,dword ptr fs:[00000124h]
805c22b6 8a8040010000 mov al,byte ptr [eax+140h]
805c22bc 8845cc mov byte ptr [ebp-34h],al
805c22bf 84c0 test al,al
805c22c1 0f848f000000 je nt!NtOpenProcess+0xc0 (805c2356)
805c22c7 8975fc mov dword ptr [ebp-4],esi
805c22ca a1d4995580 mov eax,dword ptr [nt!MmUserProbeAddress (805599d4)]
805c22cf 8b4d08 mov ecx,dword ptr [ebp+8]
805c22d2 3bc8 cmp ecx,eax
805c22d4 7202 jb nt!NtOpenProcess+0x42 (805c22d8)
805c22d6 8930 mov dword ptr [eax],esi
805c22d8 8b01 mov eax,dword ptr [ecx]
805c22da 8901 mov dword ptr [ecx],eax
805c22dc 8b5d10 mov ebx,dword ptr [ebp+10h]
805c22df f6c303 test bl,3
805c22e2 7405 je nt!NtOpenProcess+0x53 (805c22e9)
805c22e4 e8d5970400 call nt!ExRaiseDatatypeMisalignment (8060babe)
805c22e9 a1d4995580 mov eax,dword ptr [nt!MmUserProbeAddress (805599d4)]
805c22ee 3bd8 cmp ebx,eax
805c22f0 7207 jb nt!NtOpenProcess+0x63 (805c22f9)
805c22f2 8930 mov dword ptr [eax],esi
805c22f4 a1d4995580 mov eax,dword ptr [nt!MmUserProbeAddress (805599d4)]
805c22f9 397308 cmp dword ptr [ebx+8],esi
805c22fc 0f9545e6 setne byte ptr [ebp-1Ah]
805c2300 8b4b0c mov ecx,dword ptr [ebx+0Ch]
805c2303 894dc8 mov dword ptr [ebp-38h],ecx
805c2306 8b4d14 mov ecx,dword ptr [ebp+14h]
KeServiceDescriptorTable和KeServiceDescriptorTableShadow
在 NT 4.0 以上的 Windows 操作系统中,默认就存在两个系统服务描述表,这两个调度表对应了两类不 同的系统服务,这两个调度表为: KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow ,其中 KeServiceDescriptorTable 主要是处理来自 ring3 层 kernel32.dll 中的系统调用,而 KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,并且 KeServiceDescriptorTable 在 ntoskrnl.exe 是导出的,而 KeServiceDescriptorTableShadow 则是没有被 Windows 操作系统所导出
SSDT表的结构
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服务函数
KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服务函数(GDI32.dll/User32.dll 的
内核支持)
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
其中每一项又是一个结构体KSYSTEM_SERVICE_TABLE,通过结构体表示为如下,也是我们用指令dd KeServiceDescriptorTable看到的内容
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调
用的次数
ULONG NumberOfService; // 服务函数的个数, NumberOfService * 4 就是整个地址表的
大小
ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
系统调用
由于三环和零环有数据需要交互,所以在Windows中,在User层和Kernel层分别定义了一个_KUSER_SHARED_DATA结构区域,用于User层和Kernel层共享某些数据,它们使用固定的地址值映射,
在32位系统中
User 层地址为:0x7ffe0000
Kernel 层地址为:0xffdf0000
在内核中,首先在0x300偏移处看见了例如SystemCall的调用地址
kd> dt _KUSER_SHARED_DATA 0xffdf0000
nt!_KUSER_SHARED_DATA
+0x000 TickCountLow : 0x974
+0x004 TickCountMultiplier : 0xfa00000
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0x14c
+0x02e ImageNumberHigh : 0x14c
+0x030 NtSystemRoot : [260] 0x43
+0x238 MaxStackTraceDepth : 0
+0x23c CryptoExponent : 0
............................................
+0x300 SystemCall : 0x7c92e4f0
+0x304 SystemCallReturn : 0x7c92e4f4
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0
+0x330 Cookie : 0x820b9fac
在用户层中,我们也可以展开来看
kd> dt _KUSER_SHARED_DATA 0x7ffe0000
nt!_KUSER_SHARED_DATA
+0x000 TickCountLow : 0x974
+0x004 TickCountMultiplier : 0xfa00000
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0x14c
+0x02e ImageNumberHigh : 0x14c
+0x030 NtSystemRoot : [260] 0x43
+0x238 MaxStackTraceDepth : 0
+0x23c CryptoExponent : 0
+0x240 TimeZoneId : 0
+0x244 Reserved2 : [8] 0
+0x264 NtProductType : 1 ( NtProductWinNt )
+0x268 ProductTypeIsValid : 0x1 ''
+0x26c NtMajorVersion : 5
+0x270 NtMinorVersion : 1
+0x274 ProcessorFeatures : [64] ""
+0x2b4 Reserved1 : 0x7ffeffff
+0x2b8 Reserved3 : 0x80000000
+0x2bc TimeSlip : 0
+0x2c0 AlternativeArchitecture : 0 ( StandardDesign )
+0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0
+0x2d0 SuiteMask : 0x110
+0x2d4 KdDebuggerEnabled : 0x3 ''
+0x2d5 NXSupportPolicy : 0x2 ''
+0x2d8 ActiveConsoleId : 0
+0x2dc DismountCount : 0
+0x2e0 ComPlusPackage : 0xffffffff
+0x2e4 LastSystemRITEventTickCount : 0
+0x2e8 NumberOfPhysicalPages : 0x1ff6c
+0x2ec SafeBootMode : 0 ''
+0x2f0 TraceLogging : 0
+0x2f8 TestRetInstruction : 0xc3
+0x300 SystemCall : 0x7c92e4f0
+0x304 SystemCallReturn : 0x7c92e4f4
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0
+0x330 Cookie : 0x820b9fac
可以看见几个相似偏移的地址是相同的,证明这些映射的物理页也是相同的
也可以用别的指令看看,结论是相同的
kd> dd 0x7ffe0000
7ffe0000 0001e3a9 0fa00000 8122bb6a 00000004
7ffe0010 00000004 1af24bac 01db1269 01db1269
7ffe0020 f1dcc000 ffffffbc ffffffbc 014c014c
7ffe0030 003a0043 0057005c 004e0049 004f0044
7ffe0040 00530057 00000000 00000000 00000000
7ffe0050 00000000 00000000 00000000 00000000
7ffe0060 00000000 00000000 00000000 00000000
7ffe0070 00000000 00000000 00000000 00000000
kd> dd 0xffdf0000
ReadVirtual: ffdf0000 not properly sign extended
ffdf0000 0001e3a9 0fa00000 8122bb6a 00000004
ffdf0010 00000004 1af24bac 01db1269 01db1269
ffdf0020 f1dcc000 ffffffbc ffffffbc 014c014c
ffdf0030 003a0043 0057005c 004e0049 004f0044
ffdf0040 00530057 00000000 00000000 00000000
ffdf0050 00000000 00000000 00000000 00000000
ffdf0060 00000000 00000000 00000000 00000000
ffdf0070 00000000 00000000 00000000 00000000
虽然指向的是同一个物理页,但是在User层是只读的,在Kernel层是可写的
Cr3寄存器
CR3是一个控制寄存器,该寄存器内保存有页目录表物理地址,其实CR3内部存放的就是页目录表的内
存基地址,运用CR3切换可实现对特定进程内存地址的强制读写操作
Cr3寄存器不同于其他寄存器,在所有的寄存器中,只有Cr3寄存器存储的地址是物理地址,其他寄存器 存储的都是线性地址
Cr3寄存器所存储的物理地址指向了一个页目录表(Page-Directory Table,PDT),在Windows中,一个页的大小通常位4KB,即一个页(页目录表)可以存储1024个页目录表项(PDE)
同样,第二级为页表(PTT),每个页表的大小为4kb,即一个页表可以存储1024个页表项(PTE)
PDE&PTE
通过上面这张图我们可以看见,在PDE或者PTE中,只有0位置为1,那么整个表才有效,1位,2位分别是读写位和特权位,分别表示属性可读可写和是否是特权用户
查看PDE和PTE
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
Failed to get VadRoot
PROCESS 821b9830 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00aff000 ObjectTable: e1000cf8 HandleCount: 243.
Image: System
Failed to get VadRoot
PROCESS 82063360 SessionId: none Cid: 0170 Peb: 7ffd6000 ParentCid: 0004
DirBase: 08fc0020 ObjectTable: e13e1510 HandleCount: 19.
Image: smss.exe
这里我们看见了DirBase是09580020,开个用户层看看,这里PTE属性最后是5,也就是二进制的0101,R/W位为0,则属性为可读
kd> !vtop 08fc0020 0x7ffe0000
X86VtoP: Virt 000000007ffe0000, pagedir 0000000008fc0020
X86VtoP: PAE PDPE 0000000008fc0028 - 0000000008d2f001
X86VtoP: PAE PDE 0000000008d2fff8 - 0000000008cfa067
X86VtoP: PAE PTE 0000000008cfaf00 - 8000000000041025
X86VtoP: PAE Mapped phys 0000000000041000
Virtual address 7ffe0000 translates to physical address 41000.
然后再开一个内核层的看看,PTE的最后的属性是3,即为0011,R/W位为1,可读可写
kd> !vtop 08fc0020 0xffdf0000
X86VtoP: Virt 00000000ffdf0000, pagedir 0000000008fc0020
X86VtoP: PAE PDPE 0000000008fc0038 - 0000000008d2d001
X86VtoP: PAE PDE 0000000008d2dff0 - 0000000000b10163
X86VtoP: PAE PTE 0000000000b10f80 - 0000000000041163
X86VtoP: PAE Mapped phys 0000000000041000
Virtual address ffdf0000 translates to physical address 41000.
调用原理
在我们了解了KUSER_SHARED_DATA结构体后,就可以知道call的实际上是 Systemcall 的地址,通过反 汇编查看,通过 sysenter 指令(快速调用)进入0环。操作系统会在系统启动的时候在 KUSER_SHARED_DATA 结构体的+300的位置,写入一个函数,这个函数就是 KiFastSystemCall 或者 KiIntSystemCall,我们可以看见SystemCall
kd> dt _KUSER_SHARED_DATA 0xffdf0000
nt!_KUSER_SHARED_DATA
+0x000 TickCountLow : 0x10859
+0x004 TickCountMultiplier : 0xfa00000
..............................................
+0x300 SystemCall : 0x7c92e4f0
+0x304 SystemCallReturn : 0x7c92e4f4
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0
+0x330 Cookie : 0xbd8441e5
我们可以看见这个SystemCall的地址是0x7c92e4f0
Windows中三环进零环需要我们有换栈,换寄存器这些操作
在进入0环之前,需要我们更改CS,SS,ESP,EIP四个寄存器
CS的权限由3变为0 意味着需要新的CS
SS与CS的权限永远一致 需要新的SS
权限发生切换的时候,堆栈也一定会切换,需要新的ESP
进0环后代码的位置,需要EIP
如果通过中断门(int 2e)进0环,需要的CS、EIP在IDT表中,需要查内存(SS与 ESP由TSS提供)
而CPU如果支持sysenter指令时,操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,sysenter 指令执行时,**CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,**所以叫快速调用
API通过中断门进0环: 固定中断号为0x2E CS/EIP由门描述符提供 ESP/SS由TSS提供 进入0环后执行的内核函数:NT!KiSystemService
API通过sysenter指令进0环: CS/ESP/EIP由MSR寄存器提供(SS是算出来的) 进入0环后执行的内核函数:NT!KiFastCallEntry
SSDT hook
上面我们提到过
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服务函数
KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服务函数(GDI32.dll/User32.dll 的
内核支持)
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
对于其中的每一项,又是一个结构体,代码如下
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个
服务被调用的次数
ULONG NumberOfService; // 服务函数的个数, NumberOfService * 4 就是整个地址表的
大小
ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
Cr4寄存器
在我们的函数准备好之后,需要我们将该函数的指针覆盖原来的NtOpenProcess指针
我们知道物理页的内存的R/W位的属性是由PDE和PTE相与来的,那么我们就可以,通过改变SSDT对应的PDE和PTE的属性,将其设置成为可读可写的
这里首先我们用Cr4寄存器判断是2-9-9-12分页还是10-10-12分页
if(RCR4 & 0x00000020)
{//说明是2-9-9-12分页
KdPrint(("2-9-9-12分页 %p\n",RCR4));
KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9)
&0x007FFFF8))));
*(DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)) |= 0x02;
KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9)
&0x007FFFF8))));
}
else
{//说明是10-10-12分页
KdPrint(("10-10-12分页\n"));
KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) &
0x003FFFFC))));
*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)) |= 0x02;
KdPrint(("PTE2 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10)
&0x003FFFFC))));
}
Cr0寄存器
我们可以使用PsGetCurrentThread函数来获取当前KTHREAD的首地址,但是SSDT表所在的内存页的属性是只读,没有写入的权限,所以需要吧地址设置为可写入,这里使用Cr0来关闭寄存器的只读属性
在这个寄存器中,重点关注三个标志位
PE 是否启用保护模式,置1则启用。
PG 是否使用分页模式,置1则开启分页模式, 此标志置1时,PE 标志也必须置1,否则CPU报异常。
WP WP为1时,不能修改只读的内存页,WP为0时,可以修改只读的内存页。
在进行Hook时,只要把CR0寄存器中的WP位设置为0,就能对内存进行写入操作
//关闭页只读保护
__asm
{
push eax;
mov eax, cr0;
and eax, ~0x10000; // 与0x10000相与后取反
mov cr0, eax;
pop eax;
ret;
}
//开启页只读保护
__asm
{
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
ret;
}
demo(x86未验证)
#include<ntddk.h>
#include<ntstatus.h>
//记录函数的地址
ULONG uOldNtOpenProcess;
//内核之SSDT-HOOK
//系统服务表
typedef struct _KSYSTEM_SERVICE_TABLE {
PULONG ServiceTableBase; //函数地址表中的首地址
PULONG ServiceCounterTableBase; //函数表中每个函数被调用的次数
ULONG NumberOfService; //服务函数的个数
ULONG ParamTableBsse; //参数个数表的首地址
} KSYSTEM_SERVICE_TABLE,* PSSYSTEM_SERVICE_TABLE;
//服务描述符
typedef struct _KSYSTEM_TABLE_DESCRIPTOR {
KSYSTEM_SERVICE_TABLE ntoskrnl;//ntoskrnl.exe的服务函数,SSDT
KSYSTEM_SERVICE_TABLE win32k;//win32k.sys的服务函数,ShadowSSDT
KSYSTEM_SERVICE_TABLE notUsed1; //
KSYSTEM_SERVICE_TABLE notUsed2;
}KSERCIVCE_TABLE_DESCRIPTOR, * PKSERCIVCE_TABLE_DESCRIPTOR;
//定义HOOK函数的类型
typedef NTSTATUS(NTAPI* FuZwOpenProcess)(
_Out_ PHANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PCLIENT_ID ClientId
);
//自写的函数声明
NTSTATUS NTAPI MyZwOpenProcess(
_Out_ PHANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PCLIENT_ID ClientId
);
//这里KeSerciveDescriptorTable 为ntoskrnl.exe所导出的全局变量
extern PKSERCIVCE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
//记录系统的该函数
FuZwOpenProcess g_OldZwOpenProcess;
//服务描述符表指针
KSERCIVCE_TABLE_DESCRIPTOR* g_pSercivceTable = NULL;
//要保护的进程ID
ULONG g_Pid = 1624;
//安装钩子
NTSTATUS HookNtOpenProcess();
//卸载钩子
NTSTATUS UnHookOpenProcess();
//关闭页写入保护
void ShutPageProtect();
//开启页写入保护
void OpenPageProtect();
//卸载驱动
void DricerUnload(DRIVER_OBJECT* obj);
//驱动入口主函数
NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path) {
KdPrint(("驱动启动成功!\n"));
//安装钩子
HookNtOpenProcess();
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
//卸载驱动
void DriverUnload(DRIVER_OBJECT* obj) {
//卸载钩子
UnHookOpenProcess();
KdPrint(("驱动卸载成功!\n"));
}
NTSTATUS HookNtOpenProcess() {
NTSTATUS Status;
Status = STATUS_SUCCESS;
//1.关闭页只读保护
ShutPageProtect();
//2.写入原来的函数到SSDT表内
uOldNtOpenProcess = KeServiceDescriptorTable -> ntoskrnl.ServiceTableBase[0x7a];
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] =(ULONG)MyZwOpenProcess;
//3.开启页只读保护
OpenPageProtect();
return Status;
}
//卸载钩子
NTSTATUS UnHookNtOpenProcess() {
NTSTATUS status;
status = STATUS_SUCCESS;
//1.关闭页保护
ShutPageProtect();
//2.写入原来的函数进入SSDT表内
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] = uOldNtOpenProcess;
//3.开启页只读保护
OpenPageProtect();
return status;
}
//关闭页只读保护
void _declspec(naked) ShutPageProtect()
{
__asm
{
push eax;
mov eax, cr0;
and eax, ~0x10000;
mov cr0, eax;
pop eax;
ret;
}
}
//开启页只读保护
void _declspec(naked) OpenPageProtect()
{
__asm
{
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
ret;
}
}
//Hook 函数
NTSTATUS NTAPI MyZwOpenProcess(
_Out_ PHANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PCLIENT_ID ClientId
)
{
//当此进程为要保护的进程时
if (ClientId->UniqueProcess == (HANDLE)g_Pid) {
//设置为拒绝访问
DesiredAccess = 0;
}
return NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}