08系统调用

系统调用

用户层的API调用最终都需要进入到0环,其功能才能够实现:

1567152760571

上图描述的就是整个windows操作系统的体系结构. Kernel32.dll, user32.dll 等所有用户层DLL它们在调用API时,最终都会调用到ntdll.dll中. ntdll.dll是系统内核代码的存根部分,这个存根部分的代码非常的简单: 1567152813551

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

  1. 将一个数值保存到eax

  2. 调用一个相同的函数:edx ,实际这个函数是KiFastSystemCall:

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

SYSENTER指令

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

1567152864729

那么在进入0环之后, 内核该如何区分用户层到底要执行什么功能呢? 是创建进程呢,还是创建文件呢? 进入到0环之后, 如果要执行的功能是创建文件, 那么文件路径在哪里? 也就是函数的参数在哪里?

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

1567152898149

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

还有一个最重要的问题, 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寄存器中的下面三个编号的寄存器中:

MSR编号

作用描述

0x174
SYSENTER_CS_MSR
保存内核CS段选择子

0x175
SYSENTER_ESP_MSR
保存内核栈栈顶

0x176
SYSENTER_EIP_MSR
保存进入到内核之后要执行的函数地址,函数名实际是:KiFastCallEntry

也就是说, Windows在启动,进行初始化的时候会将内核栈栈顶,内核CS段选择子,以及KiFastCallEntry函数的地址一一存放到MSR寄存器组的这几个编号的寄存器中. 当SYSENTER被执行, CPU就直接使用这些寄存器的值来初始化真正的CS,EIP,ESP寄存器. 因此, SYSENTER执行之后, 就跑到内核的KiFastCallEntry函数中执行代码了.

KiFastCallEntry分析

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

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

  • 根据eax保存的调用号来调用相对应的函数, 来完成功能.

  • 由于权限有别, 在内核层中不能直接使用用户栈的内容 , 函数需要将保存在用户栈的参数拷贝到内核栈

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

  1. 哪个调用号对应哪个函数

    例如在用户层中,调用CreateFileW时,使用了0x42的调用号,进入到内核层后,也需要根据0x42这个调用号找到内核版的创建文件的API.

  2. 用户层的API参数个数是不一样的, 在把用户栈中的参数拷贝到内核栈的时候,拷贝多少个字节?

为解决这两个问题, Windows设计了两张表, 一张被称之为函数表, 一张被称之为参数个数表. 这样一来, 通过调用号作为序号, 就能找到函数的地址, 同样, 通过调用号作为序号, 也能找到函数的参数个数, 使用参数个数乘以4(字节)就可以得到参数的总字节数. Windows内核把这张函数地址表称为 : 系统服务描述表(System Service Descriptor Table ) 简称SSDT.

1567153021870

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

typedef  struct  _KSYSTEM_SERVICE_TABLE
{
   PULONG ServiceTableBase;   //函数地址表的首地址
   PULONG ServiceCounterTableBase;// 函数表中每个函数被调用的次数
   ULONG   NumberOfService;// 服务函数的个数,                       NumberOfService * 4 就是整个地址表的大小
   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.dll 的内核支持),即ShadowSSDT
   KSYSTEM_SERVICE_TABLE   notUsed1; // 不使用
   KSYSTEM_SERVICE_TABLE   notUsed2; // 不使用
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

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

1567153089548

  • 服务表号

表号等于0 时, 表示使用SSDT表. 表号等于1 时, 表示使用ShadowSSDT表.

  • 索引号

表中的下标.

KPCR

内核处理器控制区(Kernel Processor Control Region) 0环的ETHREAD结构体是记录线程的相关信息,EPROCESS结构体是记录进程相关的信息,同样我们每个CPU也有一个结构体来记录每个CPU的状态这个结构体就是KPCR结构体 这个结构体保存了内核的大量信息, 例如,GDT,IDT表的地址,当前线程对象(ETHREAD). 而Windows把这个结构体的基地址保存在一个段中,通过0x30段选择子就可以找到此段描述符, 段描述中的基地址就是其首地址: 1567153129931

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

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

1567153168781

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

<pre>kd> dt _KPCR
ntdll!_KPCR
+0x000 NtTib           : _NT_TIB
+0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD // 内核层SHE链头节点的地址.
+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还是0x10.
8404a7e7 mov     ecx,edi ; ecx 保存的是服务表号
8404a7e9 add     edi,dword ptr [esi+0BCh];esi保存的是线程内核对象的
                                         ;首地址(KTHREAD),0BC偏移是字段:ServiceTable
                                         ; edi是服务表偏移, 在ServiceTable中,第0个是SSDT
                                         ; 一个SSDT字段正好是0x10个字节.
                                         ; 因此,edi = 服务表结构体的首地址.
                                         ; 服务表结构体为:_KSYSTEM_SERVICE_TABLE


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保存的是线程内核对象的首地址(KTHREAD)
                                         ; +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是ParamTableBase,
                                         ; 也就是函数的参数个数表
                                         ; edx = 函数的参数个数表首地址
8404a82c mov     edi,dword ptr [edi]     ; edi=服务表结构体的首地址, +0是ServiceTableBase
                                         ; 也就是服务函数地址表
                                         ; 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,字节数为ecx
                                         ; 实际就是将用户栈的数据拷贝到内核栈, 拷贝的字节
                                         ; 数就是: 参数个数/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.SystemCallNumber, 保存调用号
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寄存器组的编号,将这个编号的寄存器中的值读取到edx:eax
       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;
   }
}
原文地址:https://www.cnblogs.com/ltyandy/p/11439431.html