侧边栏壁纸
博主头像
胜星的博客博主等级

行动起来,活在当下

  • 累计撰写 23 篇文章
  • 累计创建 38 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

HOOK_KiFastCall

胜星
2022-05-19 / 0 评论 / 0 点赞 / 311 阅读 / 18178 字

HOOK_KiFastCall

系统调用

用户层的API调用最终都需要想进入到0环,其功能才能够实现,下图描述的就是整个windows操作系统的体系结构

image-20220518224921738

Kernel32.dll , user32.dll 等所有用户层DLL它们在调用API时,最终都会调用到 ntdll.dll 中. ntdll.dll 是系统内核代码的存根部分,这个存根部分的代码非常的简单

image-20220518224848702

函数内部的逻辑都是一致的:

  1. 将一个数值保存到 eax
  2. 调用一个相同的函数: edx ,实际这个函数是 KiFastSystemCall

此函数内部也只做了两件事情, 将栈顶 esp 保存到了 edx 中. 然后执行 sysenter 指令, 然后就进入到0环执行代码, 如果使用OD调试,那么调试就到此为止了, 内核代码的执行过 程在OD中就跟不进去了。SYSENTER 这条指令执行后发生了什么? 为什么它当这条简单的指令执行完毕之后, API 的功能就被实现了(创建文件,创建进程等等功能

SYSENTER指令

无论windows系统上面有多少个用户层API , 最终都需要进入到内核层, 进入内核层的方法 只有一个: SYSENTER

image-20220518231204539

**那么在进入0环之后, 内核该如何区分用户层到底要执行什么功能呢? 是创建进程呢,还是 创建文件呢? **

进入到0环之后, 如果要执行的功能是创建文件, 那么文件路径在哪里? 也就是函数的参数 在哪里?

对于第一个问题, Windows使用调用号来解决. 在用户层调用 SYSENTER 指令切换到0环之 前, 每个函数都会将一个数值保存到 eax 寄存器中, 这个数值就是一个调用号:

image-20220518231435942

对于第二个问题, 由于每个线程都有一套线程上下文,都有一个独立的栈. 进入到内核后, 内核也会使用自己的内核栈. 因此, 在进入内核之前. 使用 edx 寄存器保存了 esp 的值

image-20220518231748088

还有一个最重要的问题, SYSENTER 执行之后就进入到内核层, 那到底进入到内核层的哪 个地址???

首先, SYSENTER 执行的时候, 会读取三个特殊寄存器,从这三个特殊寄存器中取出内核栈 的栈顶( esp ) , 内核代码段段选择子( cs ) ,以及代码的首地址( eip ), 保存这三个值得寄 存器是MSR(模组特殊寄存器)寄存器组,这组寄存器没有名字, 只有编号, 由于没有名字, 无法通过正常的汇编指令来存取值, Intel 提供了两条指令来读写这些寄存器:

  • rdmsr 读取MSR寄存器 其中高32位存放在EDX 低32位存放在EAX(64位和32位是一 样,只是64位时rdx和rcx的高32位会被清零),使用ECX传递寄存器编号
  • wrmsr 写入MSR寄存器 和读取一样写入时是用EDX表示高32位,EAX表示低32位. 使用ECX传递寄存器编号

而上述三个值分别保存在MSR寄存器中的下面三个编号的寄存器中:

image-20220518232153682

也就是说, Windows在启动,进行初始化的时候会将内核栈栈顶,内核CS段选择子,以及 KiFastCallEntry 函数的地址一一存放到MSR寄存器组的这几个编号的寄存器中。

SYSENTER 被执行, CPU就直接使用这些寄存器的值来初始化真正的 CS , EIP , ESP 寄存器。

因此, SYSENTER 执行之后, 就跑到内核的 KiFastCallEntry 函数中执行代码了

KiFastCallEntry分析

本函数的调用, 是因为用户层执行了 SYSENTER 函数, 在执行这个指令时, 用户层将要执行 的功能保存到了 eax 寄存器中, 将函数的参数保存到了用户层的栈里, 栈顶地址被保存到 了 edx 寄存器中 , 进入到内核层后, ESP寄存器的值已经被替换成内核栈的栈顶, 和用户 层的栈已经不再是同一个了.

如今, 这个函数要做的事情是这样的:

  • 根据 eax 保存的调用号来调用相对应的函数, 来完成功能.
  • 由于权限有别, 在内核层中不能直接使用用户栈的内容 , 函数需要将保存在用户栈的 参数拷贝到内核栈

要完成上述功能的时候, 函数需要几个信息:

  • 哪个调用号对应哪个函数(例如在用户层中,调用CreateFileW时,使用了0x42的调用号,进入到内核层后,也需 要根据0x42这个调用号找到内核版的创建文件的API.)
  • 用户层的API参数个数是不一样的, 在把用户栈中的参数拷贝到内核栈的时候,拷贝多 少个字节?

为解决这两个问题, Windows设计了两张表, 一张被称之为函数表, 一张被称之为参数个数表

这样一来, 通过调用号作为序号, 就能找到函数的地址,同样, 通过调用号作为序号, 也能找到函数的参数个数, 使用参数个数乘以4(字节)就可以得到参数的总字节数.

Windows内核把这张函数地址表称为 : 系统服务描述表(System Service Descriptor Table ) 简称SSDT.

image-20220518233846391

但是, Windows内核实际设计了两张表, 一张专门用于保存和用户界面相关的服务(例如像 创建窗口这种服务就保存在这张表里面),这张表被称为 ShadowSSDT , 而另一张则只保存 非用户界面相关的系统服务,例如创建文件,创建进程等.

typedef struct _KSYSTEM_SERVICE_TABLE{
 PULONG ServiceTableBase; //函数地址表的首地址
 PULONG ServiceCounterTableBase;// 函数表中每个函数被调用的次数
 ULONG NumberOfService;// 服务函数的个数, NumberOfS
 UCHAR* ParamTableBase; // 参数个数表首地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

而内核又使用一个结构体把两种表保存了起来:

typedef struct _KSERVICE_TABLE_DESCRIPTOR{
 KSYSTEM_SERVICE_TABLE ntoskrnl;// ntoskrnl.exe的服务函数,即SSDT
 KSYSTEM_SERVICE_TABLE win32k; // win32k.sys的服务函数(GDI32.dll/User32.d
 KSYSTEM_SERVICE_TABLE notUsed1; // 不使用
 KSYSTEM_SERVICE_TABLE notUsed2; // 不使用
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

虽然在内核中服务函数的地址被放在了两个表中查询, 但是用户层的调用号只有一套, 为 了区分调用号到底是哪张表的调用号, Windows把一个4字节的调用号设计成了下面的结构:

image-20220518234225072

  • 服务表号
    • 表号等于0 时, 表示使用SSDT表
    • 表号等于1 时, 表示使用ShadowSSDT表
  • 索引号 表中的下标

KPCR

内核处理器控制区(Kernel Processor Control Region)

0环的ETHREAD结构体是记录线程的相关信息,EPROCESS结构体是记录进程相关的 信息,同样我们每个CPU也有一个结构体来记录每个CPU的状态这个结构体就是 KPCR 结构体

这个结构体保存了内核的大量信息, 例如,GDT,IDT表的地址,当前线程对象(ETHREAD).

而Windows把这个结构体的基地址保存在一个段中,通过 0x30 段选择子就可以找到此段 描述符, 段描述中的基地址就是其首地址:

image-20220518235522146

通过windbg指令查看这个地址上的数据:

image-20220518235604728

在这个结构体中, 还有一个更大的结构体: _KPRCB , 这个结构体也保存了很多的信息,其中 一个是当前线程内核对象的首地址. 而在线程内核对象( KTHREAD )结构体中, 就有一个字 段保存这SSDT表的地址

image-20220518235920145

KPCR 结构体如下下面该结构体中几个主要的成员

<pre>kd> dt _KPCR
ntdll!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD // 内核层S
+0x004 Used_StackBase : Ptr32 Void
+0x008 Spare2 : Ptr32 Void
+0x00c TssCopy : Ptr32 Void
+0x010 ContextSwitches : Uint4B
+0x014 SetMemberCopy : Uint4B
+0x018 Used_Self : Ptr32 Void
+0x01c SelfPcr : Ptr32 _KPCR
+0x020 Prcb : Ptr32 _KPRCB // PRCB首地址
+0x024 Irql : UChar
+0x028 IRR : Uint4B
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B
+0x034 KdVersionBlock : Ptr32 Void
+0x038 IDT : Ptr32 _KIDTENTRY // IDT表首地址
+0x03c GDT : Ptr32 _KGDTENTRY // GDT表首地址
+0x040 TSS : Ptr32 _KTSS // TSS任务段首地址
+0x044 MajorVersion : Uint2B
+0x046 MinorVersion : Uint2B
+0x048 SetMember : Uint4B
+0x04c StallScaleFactor : Uint4B
+0x050 SpareUnused : UChar
+0x051 Number : UChar
+0x052 Spare0 : UChar
+0x053 SecondLevelCacheAssociativity : UChar
+0x054 VdmAlert : Uint4B
+0x058 KernelReserved : [14] Uint4B
+0x090 SecondLevelCacheSize : Uint4B
+0x094 HalReserved : [16] Uint4B
+0x0d4 InterruptMode : Uint4B
+0x0d8 Spare1 : UChar
+0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB // 此结构体字段内部还保存了大量的和CPU相关的结构

KiFastCallEntery 分析

8404a750 mov ecx,23h ; 数据段段选择子
8404a755 push 30h ; 内核FS段选择子,保存的是_KPCR结构
8404a757 pop fs ;切换FS段寄存器.
8404a759 mov ds,cx
8404a75b mov es,cx
8404a75d mov ecx,dword ptr fs:[40h] ; PcTss
8404a764 mov esp,dword ptr [ecx+4] ;PcTss.TssEsp0 , 内核的ESP寄存器
8404a767 push 23h 
8404a769 push edx
8404a76a pushfd ; 标记寄存器入栈
8404a76b push 2 ; 将标志寄存器设置为2(清空了DF标志位)
8404a76d add edx,8 	; edx保存的是内核栈顶地址, 加8之后得到的是第一个参数的地址
 					; edx = 用户层第一个参数的首地址(用户栈地址)
 					;
 					; 用户层栈顶布局:
 					; edx + 0x0 [返回地址1]
 					; edx + 0x4 [返回地址2]
 					; edx + 0x8 [ 形参1 ]
 					; edx + 0xc [ 形参2 ]
 					; edx += 8之后, edx保存的就是第一个形参的首地址.
 					; 
8404a770 popfd ; 
8404a771 or byte ptr [esp+1],2 ; 将中断允许标志位置1
8404a776 push 1Bh ; 将用户的CS段选择子入栈
8404a778 push dword ptr ds:[0FFDF0304h]; 将返回地址入栈
8404a77e push 0
8404a780 push ebp
8404a781 push ebx
8404a782 push esi
8404a783 push edi
8404a784 mov ebx,dword ptr fs:[1Ch]; PRCB Address
8404a78b push 3Bh; 用户层的FS
8404a78d mov esi,dword ptr [ebx+124h]; get current thread address
8404a793 push dword ptr [ebx]
8404a795 mov dword ptr [ebx],0FFFFFFFFh
8404a79b mov ebp,dword ptr [esi+28h]
8404a79e push 1
8404a7a0 sub esp,48h
8404a7a3 sub ebp,29Ch
8404a7a9 mov byte ptr [esi+13Ah],1
8404a7b0 cmp ebp,esp
8404a7b2 jne nt!KiFastCallEntry2+0x49 (8404a74b)
8404a7b4 and dword ptr [ebp+2Ch],0
8404a7b8 test byte ptr [esi+3],0DFh
8404a7bc mov dword ptr [esi+128h],ebp
8404a7c2 jne nt!Dr_FastCallDrSave (8404a600)
8404a7c8 mov ebx,dword ptr [ebp+60h]
8404a7cb mov edi,dword ptr [ebp+68h]
8404a7ce mov dword ptr [ebp+0Ch],edx
8404a7d1 mov dword ptr [ebp+8],0BADB0D00h
8404a7d8 mov dword ptr [ebp],ebx
8404a7db mov dword ptr [ebp+4],edi
8404a7de sti ; 屏蔽中断
8404a7df mov edi,eax ; eax调用号,格式:19-1(服务表)-12(索引号)
8404a7e1 shr edi,8 ; eax>>8 实际是为了获取服务表的值. 
8404a7e4 and edi,10h ; 和0x10按位与之后,如果服务表的数值为0.则edi为0,否则edi
8404a7e7 mov ecx,edi ; ecx 保存的是服务表号
8404a7e9 add edi,dword ptr [esi+0BCh];esi保存的是线程内核对象的
 									 ;首地址(KTHREAD),0BC偏移是字段:Servic
 									 ;edi是服务表偏移, 在ServiceTable中,第
 									 ;一个SSDT字段正好是0x10个字节.
 									 ;因此,edi = 服务表结构体的首地址.
 									 ;服务表结构体为:_KSYSTEM_SERVICE_TAB
8404a7ef mov ebx,eax ; eax是调用号
8404a7f1 and eax,0FFFh	; 取调用号的低12位, 得到的就是服务表的序号了.
 						; eax = 服务表序号
8404a7f6 cmp eax,dword ptr [edi+8];edi是服务表首地址,+8就是NumberOfService
8404a7f9 jae nt!KiBBTUnexpectedRange (8404a532); 如果序号大于等于个数了,就跳
8404a7ff cmp ecx,10h ; ecx保存的是服务表号,0是SSDT,0x10是ShadowSSDT
8404a802 jne nt!KiFastCallEntry+0xce (8404a81e)	; 如果不是ShadowSSDT就跳转,
 												; ShadowSSDT的处理
8404a804 mov ecx,dword ptr [esi+88h] ; esi保存的是线程内核对象的首地址(KTHR
 									 ; +0x088 是 Teb首地址
 									 ; ecx = Teb首地址
8404a80a xor esi,esi
8404a80c or esi,dword ptr [ecx+0F70h]; ecx+0F70 ==> Teb.GdiBatchCount
8404a812 je nt!KiFastCallEntry+0xce (8404a81e)
8404a814 push edx
8404a815 push eax
8404a816 call dword ptr [nt!KeGdiFlushUserBatch (8417582c)]; 调用函数
8404a81c pop eax
8404a81d pop edx
8404a81e inc dword ptr fs:[6B0h] ; fs保存的是KPCR,+6B0是_GDI_TEB_BATCH结构
8404a825 mov esi,edx ; edx是在用户层传入进来的用户层栈顶.现在 esi=用户层栈顶
8404a827 xor ecx,ecx
8404a829 mov edx,dword ptr [edi+0Ch] ; edi=服务表结构体的首地址, +0C是Para
 									; 也就是函数的参数个数表
 									; edx = 函数的参数个数表首地址
8404a82c mov edi,dword ptr [edi] 	; edi=服务表结构体的首地址, +0是Servi
 									; 也就是服务函数地址表
 									; edi = 服务函数地址表首地址
8404a82e mov cl,byte ptr [eax+edx] 	; eax 是调用号, edx是函数参数个数表
 									; 取出函数参数个数表中调用号对应的函数的
 									; cl = 函数参数个数
8404a831 mov edx,dword ptr [edi+eax*4];edi=服务表结构体的首地址,eax=是调用号
 									  ; 取出调用号对应的函数的地址
 									  ; edx = 服务函数地址
8404a834 sub esp,ecx ; 开辟栈空间, 开辟的大小就是ecx
 					 ; ecx= 函数参数个数
8404a836 shr ecx,2 	 ; ecx /= 2;
8404a839 mov edi,esp ; 把当前栈顶赋值给edi
8404a83b cmp esi,dword ptr [nt!MmUserProbeAddress (84175704)] ; 将esi执行
 															  ; esi=用户层栈顶
8404a841 jae nt!KiSystemCallExit2+0xa5 (8404aa75)
8404a847 rep movs dword ptr es:[edi],dword ptr [esi]; 将esi指向的数据拷贝到edi
 													; 实际就是将用户栈的数据拷贝到内核栈, 拷
 													; 数就是: 参数个数/2 * 4;
8404a849 test byte ptr [ebp+6Ch],1 ; 
8404a84d je nt!KiFastCallEntry+0x115 (8404a865)
8404a84f mov ecx,dword ptr fs:[124h] ; fs[124h]==>当前线程对象,
 									 ; ecx = 当前线程对象地址
8404a856 mov edi,dword ptr [esp] ; edi=第一个参数的值
8404a859 mov dword ptr [ecx+13Ch],ebx ; ecx+13c=>KTHREAD.SystemCallNumbe
8404a85f mov dword ptr [ecx+12Ch],edi ; ecx+12c=>KTHREAD.FirstArgument , 
8404a865 mov ebx,edx ; ebx= edx = 服务函数地址
8404a867 test byte ptr [nt!PerfGlobalGroupMask+0x8 (841430c8)],40h
8404a86e setne byte ptr [ebp+12h]
8404a872 jne nt!KiServiceExit2+0x17b (8404ac04)
8404a878 call ebx ; 调用服务函数 终于调用了!!!!!!!!!!!!
8404a87a test byte ptr [ebp+6Ch],1
8404a87e je nt!KiFastCallEntry+0x164 (8404a8b4)
8404a880 mov esi,eax
8404a882 call dword ptr [nt!_imp__KeGetCurrentIrql (84016168)]
8404a888 or al,al
8404a88a jne nt!KiServiceExit2+0x142 (8404abcb)
8404a890 mov eax,esi
8404a892 mov ecx,dword ptr fs:[124h]
8404a899 test byte ptr [ecx+134h],0FFh
8404a8a0 jne nt!KiServiceExit2+0x160 (8404abe9)
8404a8a6 mov edx,dword ptr [ecx+84h]
8404a8ac or edx,edx
8404a8ae jne nt!KiServiceExit2+0x160 (8404abe9)
8404a8b4 mov esp,ebp
8404a8b6 cmp byte ptr [ebp+12h],0
8404a8ba jne nt!KiServiceExit2+0x187 (8404ac10)
8404a8c0 mov ecx,dword ptr fs:[124h]
8404a8c7 mov edx,dword ptr [ebp+3Ch]
8404a8ca mov dword ptr [ecx+128h],edx
nt!KiServiceExit:
8404a8d0 cli
8404a8d1 test byte ptr [ebp+72h],2
8404a8d5 jne nt!KiServiceExit+0xd (8404a8dd)
8404a8d7 test byte ptr [ebp+6Ch],1
8404a8db je nt!KiServiceExit+0x74 (8404a944)
8404a8dd mov ebx,dword ptr fs:[124h]
8404a8e4 test byte ptr [ebx+2],2
8404a8e8 je nt!KiServiceExit+0x22 (8404a8f2)
8404a8ea push eax
8404a8eb push ebx
8404a8ec call nt!KiCopyCounters (840eb811)
8404a8f1 pop eax
8404a8f2 mov byte ptr [ebx+3Ah],0
8404a8f6 cmp byte ptr [ebx+56h],0
8404a8fa je nt!KiServiceExit+0x74 (8404a944)
8404a8fc mov ebx,ebp
8404a8fe mov dword ptr [ebx+44h],eax
8404a901 mov dword ptr [ebx+50h],3Bh
8404a908 mov dword ptr [ebx+38h],23h
8404a90f mov dword ptr [ebx+34h],23h
8404a916 mov dword ptr [ebx+30h],0
8404a91d mov ecx,1
8404a922 call dword ptr [nt!_imp_KfRaiseIrql (8401615c)]
8404a928 push eax
8404a929 sti
8404a92a push ebx
8404a92b push 0
8404a92d push 1
8404a92f call nt!KiDeliverApc (840833fe)
8404a934 pop ecx
8404a935 call dword ptr [nt!_imp_KfLowerIrql (84016158)]
8404a93b mov eax,dword ptr [ebx+44h]
8404a93e cli
8404a93f jmp nt!KiServiceExit+0xd (8404a8dd)
8404a941 lea ecx,[ecx]
8404a944 mov edx,dword ptr [esp+4Ch]
8404a948 mov dword ptr fs:[0],edx
8404a94f mov ecx,dword ptr [esp+48h]
8404a953 mov esi,dword ptr fs:[124h]
8404a95a mov byte ptr [esi+13Ah],cl
8404a960 test dword ptr [esp+2Ch],0FFFF23FFh
8404a968 jne nt!KiSystemCallExit2+0x1c (8404a9ec)
8404a96e test dword ptr [esp+70h],20000h
8404a976 jne nt!Kei386EoiHelper+0x134 (8404b3bc)
8404a97c test word ptr [esp+6Ch],0FFF8h
8404a983 je nt!KiSystemCallExit2+0x72 (8404aa42)
8404a989 cmp word ptr [esp+6Ch],1Bh
8404a98f bt word ptr [esp+6Ch],0
8404a996 cmc
8404a997 ja nt!KiSystemCallExit2+0x60 (8404aa30)
8404a99d cmp word ptr [ebp+6Ch],8
8404a9a2 je nt!KiServiceExit+0xd9 (8404a9a9)
8404a9a4 lea esp,[ebp+50h]
8404a9a7 pop fs
8404a9a9 lea esp,[ebp+54h]
8404a9ac pop edi
8404a9ad pop esi
8404a9ae pop ebx
8404a9af pop ebp
8404a9b0 cmp word ptr [esp+8],80h
8404a9b7 ja nt!Kei386EoiHelper+0x150 (8404b3d8)
8404a9bd add esp,4
8404a9c0 test dword ptr [esp+4],1
nt!KiSystemCallExitBranch:
8404a9c8 jne nt!KiSystemCallExit2 (8404a9d0)
8404a9ca pop edx
8404a9cb pop ecx
8404a9cc popfd
8404a9cd jmp edx
nt!KiSystemCallExit:
8404a9cf iretd
nt!KiSystemCallExit2:
8404a9d0 test dword ptr [esp+8],100h
8404a9d8 jne nt!KiSystemCallExit (8404a9cf)
8404a9da pop edx
8404a9db add esp,4
8404a9de and dword ptr [esp],0FFFFFDFFh
8404a9e5 popfd
8404a9e6 pop ecx
8404a9e7 sti
8404a9e8 sysexit ; 退出系统调用,回到用户层

HOOK

HOOK的方式有几种:

  1. 将MSR寄存器组中的 SYSENTER_EIP_MSR 寄存器(编号 0x176 )改成自己的函数地 址,这样一来, CPU每次执行 SYSENTER 时调用的就是保存在这个寄存器中的函数.
  2. 使用内联HOOK, 将 KiFastCallEntry 头改成 jmp XXXX , 跳转到自己的函数. 自 己的函数执行完之后再跳转回去.

SYSENTER HOOK : 修改MSR寄存器组进行HOOK

#include <Ntifs.h>
#include <ntddk.h>
CHAR* PsGetProcessImageFileName(PEPROCESS*);
VOID UnLoad(DRIVER_OBJECT* object);
void installSysenterHook();
void uninstallSysenterHook();
void MyKiFastCallEntry();
ULONG_PTR g_oldKiFastCallEntery;
ULONG g_uPid = 2940; // 需要保护的进程ID, 这个PID可以通过内核通讯来修改.

NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
{
	DbgBreakPoint();
	driver->DriverUnload = UnLoad;
	// 安装HOOK
	installSysenterHook();
	return STATUS_SUCCESS;
}

VOID UnLoad(DRIVER_OBJECT* object)
{
 // 卸载HOOK
	uninstallSysenterHook();
}

// 安装HOOK
void _declspec(naked) installSysenterHook()
{
 	_asm
 	{
 		push edx;
 		push eax;
 		push ecx;
 		;// 备份原始函数
 		mov ecx, 0x176 ;//SYSENTER_EIP_MSR寄存器的编号.保存着KiFastCallEntry的
 		rdmsr; // // 指令使用ecx寄存器的值作为MSR寄存器组的编号,将这个编号的寄存器
 		mov[g_oldKiFastCallEntery], eax;// 将地址保存到全局变量中.
 		;// 将新的函数设置进去.
 		mov eax, MyKiFastCallEntry;
 		xor edx, edx;
 		wrmsr; // 指令使用ecx寄存器的值作为MSR寄存器组的编号,将edx:eax写入到这个编
 		pop ecx;
 		pop eax;
 		pop edx;
 		ret;
 	}
 }

// 卸载HOOK
void uninstallSysenterHook()
{
 	_asm
 	{
 		push edx;
 		push eax;
 		push ecx;
 		;// 将新的函数设置进去.
 		mov eax, [g_oldKiFastCallEntery];
 		xor edx, edx;
 		mov ecx, 0x176;
 		wrmsr; // 指令使用ecx寄存器的值作为MSR寄存器组的编号,将edx:eax写入到这个编
 		pop ecx;
 		pop eax;
 		pop edx;
 	}
}

void _declspec(naked) MyKiFastCallEntry()
{
	/**
	* 本函数是从用户层直接切换进来的.
	* 在本函数中,有以下信息可以使用:
	* 1. eax保存的是调用号
	* 2. edx保存着用户层的栈顶,且用户层的栈顶布局为:
	* edx+0 [ 返回地址1 ]
	* edx+4 [ 返回地址2 ]
	* edx+8 [ 形 参1 ]
	* edx+C [ 形 参2 ]
	* 3. 要HOOK的API是 OpenProcess,其调用号和参数信息为:
	* 调用号 - 0xBE
	* 函数参数 - 
	* NtOpenProcess(
	* [edx+08h] PHANDLE ProcessHandle,// 输出参数,进程句柄
	* [edx+0Ch] ACCESS_MASK DesiredAccess,// 打开的权限
	* [edx+10h] POBJECT_ATTRIBUTES ObjectAttributes,// 对象属性,无用
	* [edx+14h] PCLIENT_ID ClientId // 进程ID和线程ID的结构
	* 最后一个参数的结构体原型为:
	* typedef struct _CLIENT_ID
	 * {
	* PVOID UniqueProcess;// 进程ID
	* PVOID UniqueThread; // 线程ID(在这个函数中用不到)
	* } CLIENT_ID, *PCLIENT_ID;
	*
	* HOOK 步骤:
	* 1. 检查调用号是不是0xBE(ZwOpenProcess)
	* 2. 检查进程ID是不是要保护的进程的ID
	* 3. 如果是,则将进程ID改为0,再调用原来的函数,这样一来,即使功能被执行,
	* 也无法打开进程, 或者将访问权限设置为0,同样也能让进程无法被打开.
	* 4. 如果不是,则调用原来的KiFastCallEntry函数
	*/
	_asm
	{
		;// 1. 检查调用号
		cmp eax, 0xBE ;
		jne _DONE ; // 调用号不是0xBE,执行第4步
		;// 2. 检查进程ID是不是要保护的进程的ID
		push eax; // 备份寄存器
		;// 2. 获取参数(进程ID)
		mov eax, [edx + 0x14];// eax保存的是PCLIENT_ID
		mov eax, [eax] ;// eax保存的是PCLIENT_ID->UniqueProcess
		;// 3. 判断是不是要保护的进程ID
		cmp eax, [g_uPid];
		pop eax ;// 恢复寄存器
		jne _DONE ;// 不是要保护的进程就跳转
		;// 3.1 是的话就该调用参数,让后续函数调用失败.
		mov[edx + 0xC], 0; // 将访问权限设置为0
		_DONE:
		; // 4. 调用原来的KiFastCallEntry
		jmp g_oldKiFastCallEntery;
	}
}

0

评论区