一般在内核SSDT HOOK的时候就是直接钩住SSDT表替换NtOpenProcess的地址来达到保护进程的目的。而在InlineHook中,侧需要更进一步的了解NtOpenProcess函数,才能更好的做inlinehook。

首先说说Windows中用户层 OpenProces,WIN32函数OpenProces执行后,调用NTDLL.DLL中NtOpenProcess函数,然后此函数INT2E自陷进入内核,开始从SSDT表中查找NtOpenProcess函数地址,继而在内核中执行NtOpenProcess函数。这是OpenProcess调用的路径。如图。

 

那么就这样说,在Windows中,OpenProcess函数是对NtOpenProcess调用的一个包装。NtOpenProcess包含在内核模块NTOSKRNL.EXE当中。

内核中NtOpenProcess函数结构如下:

NTSTATUS NtOpenProcess (
   OUT PHANDLE ProcessHandle,
   IN ACCESS_MASK DesiredAccess,
   IN POBJECT_ATTRIBUTES ObjectAttributes,
   IN PCLIENT_ID ClientId OPTIONAL);


ClientID参数是OpenProcess传递的实际PID。这个参数是可选的,但是根据我们的观察,OpenProcess在调用NtOpenProcess的时候总是使用这个参数。

在内核中执行的时候,NtOpenProcess主要实现3个功能:

1. 它通过调用PsLookupProcessByProcessId函数来验证进程是否存在。
2. 它通过调用ObOpenObjectByPointer来打开进程的句柄。
3. 如果打开进程句柄成功,就将句柄返回给调用者。

那么很自然的,PsLookupProcessByProcessId函数和ObOpenObjectByPointer函数就成为我们inlinehook的目标

先看PsLookupProcessByProcessId函数:

lkd> u 805caefa
nt!NtOpenProcess+0x1fc:
805caefa 45              inc     ebp
805caefb dc50ff          fcom    qword ptr [eax-1]
805caefe 75d4            jne     nt!NtOpenProcess+0x1d6 (805caed4)
805caf00 e8617a0000      call    nt!PsLookupProcessByProcessId (805d2966)
805caf05 ebde            jmp     nt!NtOpenProcess+0x1e7 (805caee5)
805caf07 8d45e0          lea     eax,[ebp-20h]
805caf0a 50              push    eax
805caf0b ff75cc          push    dword ptr [ebp-34h]

lkd> u nt!PsLookupProcessByProcessId
nt!PsLookupProcessByProcessId:
805d2966 8bff            mov     edi,edi
805d2968 55              push    ebp
805d2969 8bec            mov     ebp,esp
805d296b 53              push    ebx
805d296c 56              push    esi
805d296d 64a124010000    mov     eax,dword ptr fs:[00000124h]
805d2973 ff7508          push    dword ptr [ebp+8]
805d2976 8bf0            mov     esi,eax
805d2978 ff8ed4000000    dec     dword ptr [esi+0D4h]
805d297e ff35c0385680    push    dword ptr [nt!PsThreadType+0x4 (805638c0)]
805d2984 e803ad0300      call    nt!ExEnumHandleTable+0x408 (8060d68c)
805d2989 8bd8            mov     ebx,eax
805d298b 85db            test    ebx,ebx
805d298d c745080d0000c0 mov     dword ptr [ebp+8],0C000000Dh
805d2994 7432            je      nt!PsLookupProcessByProcessId+0x62 (805d29c8)

再看ObOpenObjectByPointer函数:

lkd> u 805caf15
nt!NtOpenProcess+0x217:
805caf15 8d8548ffffff    lea     eax,[ebp-0B8h]
805caf1b 50              push    eax
805caf1c ff75c8          push    dword ptr [ebp-38h]
805caf1f ff75dc          push    dword ptr [ebp-24h]
805caf22 e8bd07ffff      call    nt!ObOpenObjectByPointer (805bb6e4)
805caf27 8bf8            mov     edi,eax
805caf29 8d8548ffffff    lea     eax,[ebp-0B8h]
805caf2f 50              push    eax

lkd> u nt!ObOpenObjectByPointer
nt!ObOpenObjectByPointer:
805bb6e4 8bff            mov     edi,edi
805bb6e6 55              push    ebp
805bb6e7 8bec            mov     ebp,esp
805bb6e9 81ec94000000    sub     esp,94h
805bb6ef 53              push    ebx
805bb6f0 8b5d08          mov     ebx,dword ptr [ebp+8]
805bb6f3 56              push    esi
805bb6f4 57              push    edi

知道NtOpenProcess执行的过程后,为防止打开某特定进程的句柄,可以直接inlinehook ObOpenObjectByPointer函数,Hook 这个函数有一大好处,那就是进程线程都一起保护了

ObOpenObjectByPointer(
                             IN PVOID Object,
                             IN ULONG HandleAttributes,
                             IN PACCESS_STATE PassedAccessState OPTIONAL,
                             IN ACCESS_MASK DesiredAccess,
                             IN POBJECT_TYPE ObjectType OPTIONAL,
                             IN KPROCESSOR_MODE AccessMode,
                             OUT PHANDLE Handle
                             )

NTSTATUS
T_ObOpenObjectByPointer(
                             IN PVOID Object,
                             IN ULONG HandleAttributes,
                             IN PACCESS_STATE PassedAccessState OPTIONAL,
                             IN ACCESS_MASK DesiredAccess,
                             IN POBJECT_TYPE ObjectType OPTIONAL,
                             IN KPROCESSOR_MODE AccessMode,
                             OUT PHANDLE Handle
                             )
{
    PEPROCESS EPROCESS;
    if((Object!=NULL) && (MmIsAddressValid(Object)))// 地址有效性验证
    {
        if(OBJECT_TO_OBJECT_HEADER(Object) ->Type== *PsProcessType)// 若为进程对象
        {
            if((PsGetCurrentProcess() !=ProtectedProcess))// 若操作者不是受保护的进程自己
            {
                if(Object==ProtectedProcess)// 若被操作进程是受保护进程
                {
                    returnSTATUS_ACCESS_DENIED;// 拒绝访问
                }

             }
         }
        else
             if(OBJECT_TO_OBJECT_HEADER(Object) ->Type== *PsThreadType)// 若为线程对象
            {
                EPROCESS=IoThreadToProcess(Object);// 获取线程对应进程的 EPROCESS
                if(EPROCESS==ProtectedProcess)// 若是受保护进程
                {
                    if((PsGetCurrentProcess() !=ProtectedProcess))// 若操作者不是受保护进程自己
                    {
                        returnSTATUS_ACCESS_DENIED;// 拒绝访问
                    }
                 }
             }

     }
// 正常调用,执行原 API
    returnMy_ObOpenObjectByPointer(
        Object,
        HandleAttributes,
        PassedAccessState,
        DesiredAccess,
        ObjectType,
        AccessMode,
        Handle
        );
}


My_ObOpenObjectByPointer(
        Object,
        HandleAttributes,
        PassedAccessState,
        DesiredAccess,
        ObjectType,
        AccessMode,
        Handle
        );

函数中,执行完ObOpenObjectByPointer函数前几字节后JMP到ObOpenObjectByPointer函数内部。

 

 

句柄:句柄的最高Bit表明了句柄是属于内核的还是属于用户态的 最高位为1,为内核态句柄,反之为用户态句柄。

不过有两个例外: 用来表示当前进程句柄的-1 和用来表示当前线程句柄的-2

 

 

有关NtOpenProcess的问题:

 

系统中有系统句柄表,每个进程还有自己的句柄表,但是如果在驱动里创建一个句柄,那么这个句柄到底是属于进程的还是系统的? 我过去一直以为这个是由 ETHREAD::PreviousMode决定的,其实不是。这个是由传递给NtOpenProcess的参数ObjectAttributes决定的,这个参数中如果带有  OBJ_KERNEL_HANDLE 参数,并且

ETHREAD:: PreviousMode = KernelMode,那么这个句柄就会被创建在系统全局句柄表中。

 

如果 ETHREAD:: PreviousMode = UserMode,那么即使 ObjectAttributes 中含有 OBJ_KERNEL_HANDLE  参数,也会被 ObSanitizeHandleAttributes() 函数干掉。因此如果要创建系统全局表中的句柄,就必须令 PreviousMode  = KernelMode

 

另外在从驱动程序中调用NtOpenProcess时,因为传递的参数地址都是内核态地址,此时如果PreviousMode = UserMode,那么在进入 try块后可能会产生异常(未亲自验证),导致无法正常完成NtOpenProcess调用。因此要在内核中创建属于进程上下文句柄表的句柄,要设置PreviousMode  = KernelMode,并在 ObjectAttributes 中去除 OBJ_KERNEL_HANDLE 参数即可。

 

 

说起来NtOpenProcess并不复杂,只是个流程函数而已,大部分活都是Ob函数族和Ps函数族在做。最后是在ObpCreateHandle()完成了句柄的创建,但是究竟选择进程句柄表还是全局表,则是由 EThread::PreviousMode   ObjectAttributes 一起决定的。