一般在内核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 一起决定的。