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