• 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;
       }
    }
  • 相关阅读:
    js保存图片至本地
    ArrayLike
    key的作用
    react Video event
    react中字符串换行
    react打包后找不到静态文件
    2020软件工程第三次作业
    003 Longest Substring Without Repeating Characters
    002addTwoNumbers
    001twoSum
  • 原文地址:https://www.cnblogs.com/ltyandy/p/11439431.html
Copyright © 2020-2023  润新知