问题: 在使用Hook的过程中,经常会遇到SetWindowsHookEx返回NULL的情况,GetLastError或者在监视窗口$err,hr后可以看到错误代码及解释,例如87号参数错误,但是参数错误又分好多种情况,到底我们在哪一步出错了很难知道,比如之前我通过CreateProcess创建了一个子进程,然后使用返回的线程Id传入SetWindowsHookEx,一直返回的错误代码都是87,调试的时候看到线程Id已经传入成功,并且使用Process Explorer验证过都没有问题,最后不断地查啊查,终于知道原来CreateProcess并没有等待新进程初始化完毕就返回了,所以还需要调用WaitForInputIdle来等待线程初始化结束。这样对主线程Hook成功,但是接下来又遇到了问题,我想对进程的所有线程进行Hook,这时候出现了主线程可以Hook成功,其它线程有的可以成功,有的不成功,返回的错误代码还是87,这个问题到现在都没有解决。SetWindowsHookEx内部一定需要对传入的线程Id进行某种检查,但是到底是怎么样的检查却不知道。所以,现在的问题转化成了SetWindowsHookEx内部运作的机理到底是什么?
下面开始发现之旅~
=======================================================================
1. MSDN
SetWindowsHookEx原型
HHOOK WINAPI SetWindowsHookEx( __in int idHook, __in HOOKPROC lpfn, __in HINSTANCE hMod, __in DWORD dwThreadId );
MSDN中的解释只足够基本使用,而不能知道原理是什么,所以,我就想知道SetWindowsHookEx的代码是怎么实现的。
========================================================================
2. ReactOS
这是一个开源的项目,相当于重新实现Windows操作系统http://www.reactos.org/zh/index.html
/* * ReactOS kernel * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * * PROJECT: ReactOS user32.dll * FILE: dll/win32/user32/windows/hook.c * PURPOSE: Hooks * PROGRAMMER: Casper S. Hornstrup ([email protected]) * UPDATE HISTORY: * 09-05-2001 CSH Created */ /* * @implemented */ HHOOK WINAPI SetWindowsHookExA( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId) { return IntSetWindowsHook(idHook, lpfn, hMod, dwThreadId, TRUE); } /* * @implemented */ HHOOK WINAPI SetWindowsHookExW( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId) { return IntSetWindowsHook(idHook, lpfn, hMod, dwThreadId, FALSE); }
------------------------------------------------------------------------------------------------------------------------------------
在同一文件中,可以看到
static HHOOK FASTCALL IntSetWindowsHook( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId, BOOL bAnsi) { WCHAR ModuleName[MAX_PATH]; UNICODE_STRING USModuleName; if (NULL != hMod) { if (0 == GetModuleFileNameW(hMod, ModuleName, MAX_PATH)) { return NULL; } RtlInitUnicodeString(&USModuleName, ModuleName); } else { RtlInitUnicodeString(&USModuleName, NULL); } return NtUserSetWindowsHookEx(hMod, &USModuleName, dwThreadId, idHook, lpfn, bAnsi); }
上述代码我们可以看到,如果GetModuleFileNameW函数返回0,这时函数回返回NULL,但是这里没有调用SetLastErrror,所以我们不知道这里的错误代码是什么。
------------------------------------------------------------------------------------------------------------------------------------
GetModuleFileNameW是什么?
DWORD WINAPI GetModuleFileName( __in_opt HMODULE hModule, __out LPTSTR lpFilename, __in DWORD nSize );
函数的功能是:由指定的模块hModule得到包含这个模块的文件的路径,而且这个模块必须是被当前进程所载入的。通过工具IceSword我们可以看到一个进程都载入了哪些模块。通过MSDN中SetWindowsHookEx的参数解释,可以知道这个hModule是一个包含Hook过程的DLL的句柄,如果Hook过程在当前进程中,那么这个hModule要为NULL。
------------------------------------------------------------------------------------------------------------------------------------
RelInitUnicodeString函数是什么?
VOID WINAPI RtlInitUnicodeString( __inout PUNICODE_STRING DestinationString, __in_opt PCWSTR SourceString );
函数的功能是:根据ModuleName,即包含模块文件的路径得到一个UNICODE_STRING类型的变量USModuleName,就是初始化一个UNICODE_STRING类型的变量。
然后,IntSetWindowsHook又调用了NtUserSetWindowsHookEx,然后,继续~
------------------------------------------------------------------------------------------------------------------------------------
/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS kernel * PURPOSE: Window hooks * FILE: subsystems/win32/win32k/ntuser/hook.c * PROGRAMER: Casper S. Hornstrup ([email protected]) * James Tabor ([email protected]) * Rafal Harabien ([email protected]) * NOTE: Most of this code was adapted from Wine, * Copyright (C) 2002 Alexandre Julliard */ HHOOK APIENTRY NtUserSetWindowsHookEx( HINSTANCE Mod, PUNICODE_STRING UnsafeModuleName, DWORD ThreadId, int HookId, HOOKPROC HookProc, BOOL Ansi) { PWINSTATION_OBJECT WinStaObj; PHOOK Hook; UNICODE_STRING ModuleName; NTSTATUS Status; HHOOK Handle; PETHREAD Thread = NULL; PTHREADINFO pti, ptiHook = NULL; DECLARE_RETURN(HHOOK); TRACE("Enter NtUserSetWindowsHookEx\n"); UserEnterExclusive(); pti = PsGetCurrentThreadWin32Thread(); if (HookId < WH_MINHOOK || WH_MAXHOOK < HookId ) { EngSetLastError(ERROR_INVALID_HOOK_FILTER); RETURN( NULL); } if (!HookProc) { EngSetLastError(ERROR_INVALID_FILTER_PROC); RETURN( NULL); } if (ThreadId) /* thread-local hook */ { if ( HookId == WH_JOURNALRECORD || HookId == WH_JOURNALPLAYBACK || HookId == WH_KEYBOARD_LL || HookId == WH_MOUSE_LL || HookId == WH_SYSMSGFILTER) { ERR("Local hook installing Global HookId: %d\n",HookId); /* these can only be global */ EngSetLastError(ERROR_GLOBAL_ONLY_HOOK); RETURN( NULL); } if (!NT_SUCCESS(PsLookupThreadByThreadId((HANDLE)(DWORD_PTR) ThreadId, &Thread))) { ERR("Invalid thread id 0x%x\n", ThreadId); EngSetLastError(ERROR_INVALID_PARAMETER); RETURN( NULL); } ptiHook = Thread->Tcb.Win32Thread; ObDereferenceObject(Thread); if ( ptiHook->rpdesk != pti->rpdesk) // gptiCurrent->rpdesk) { ERR("Local hook wrong desktop HookId: %d\n",HookId); EngSetLastError(ERROR_ACCESS_DENIED); RETURN( NULL); } if (Thread->ThreadsProcess != PsGetCurrentProcess()) { if ( !Mod && (HookId == WH_GETMESSAGE || HookId == WH_CALLWNDPROC || HookId == WH_CBT || HookId == WH_HARDWARE || HookId == WH_DEBUG || HookId == WH_SHELL || HookId == WH_FOREGROUNDIDLE || HookId == WH_CALLWNDPROCRET) ) { ERR("Local hook needs hMod HookId: %d\n",HookId); EngSetLastError(ERROR_HOOK_NEEDS_HMOD); RETURN( NULL); } if ( (ptiHook->TIF_flags & (TIF_CSRSSTHREAD|TIF_SYSTEMTHREAD)) && (HookId == WH_GETMESSAGE || HookId == WH_CALLWNDPROC || HookId == WH_CBT || HookId == WH_HARDWARE || HookId == WH_DEBUG || HookId == WH_SHELL || HookId == WH_FOREGROUNDIDLE || HookId == WH_CALLWNDPROCRET) ) { EngSetLastError(ERROR_HOOK_TYPE_NOT_ALLOWED); RETURN( NULL); } } } else /* System-global hook */ { ptiHook = pti; // gptiCurrent; if ( !Mod && (HookId == WH_GETMESSAGE || HookId == WH_CALLWNDPROC || HookId == WH_CBT || HookId == WH_SYSMSGFILTER || HookId == WH_HARDWARE || HookId == WH_DEBUG || HookId == WH_SHELL || HookId == WH_FOREGROUNDIDLE || HookId == WH_CALLWNDPROCRET) ) { ERR("Global hook needs hMod HookId: %d\n",HookId); EngSetLastError(ERROR_HOOK_NEEDS_HMOD); RETURN( NULL); } } Status = IntValidateWindowStationHandle( PsGetCurrentProcess()->Win32WindowStation, KernelMode, 0, &WinStaObj); if (!NT_SUCCESS(Status)) { SetLastNtError(Status); RETURN( NULL); } ObDereferenceObject(WinStaObj); Hook = UserCreateObject(gHandleTable, NULL, &Handle, otHook, sizeof(HOOK)); if (!Hook) { RETURN( NULL); } Hook->ihmod = (INT)Mod; // Module Index from atom table, Do this for now. Hook->Thread = Thread; /* Set Thread, Null is Global. */ Hook->HookId = HookId; Hook->rpdesk = ptiHook->rpdesk; Hook->phkNext = NULL; /* Dont use as a chain! Use link lists for chaining. */ Hook->Proc = HookProc; Hook->Ansi = Ansi; TRACE("Set Hook Desk 0x%x DeskInfo 0x%x Handle Desk 0x%x\n",pti->rpdesk, pti->pDeskInfo,Hook->head.rpdesk); if (ThreadId) /* Thread-local hook */ { InsertHeadList(&ptiHook->aphkStart[HOOKID_TO_INDEX(HookId)], &Hook->Chain); ptiHook->sphkCurrent = NULL; Hook->ptiHooked = ptiHook; ptiHook->fsHooks |= HOOKID_TO_FLAG(HookId); if (ptiHook->pClientInfo) { if ( ptiHook->ppi == pti->ppi) /* gptiCurrent->ppi) */ { _SEH2_TRY { ptiHook->pClientInfo->fsHooks = ptiHook->fsHooks; ptiHook->pClientInfo->phkCurrent = NULL; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { ERR("Problem writing to Local ClientInfo!\n"); } _SEH2_END; } else { KeAttachProcess(&ptiHook->ppi->peProcess->Pcb); _SEH2_TRY { ptiHook->pClientInfo->fsHooks = ptiHook->fsHooks; ptiHook->pClientInfo->phkCurrent = NULL; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { ERR("Problem writing to Remote ClientInfo!\n"); } _SEH2_END; KeDetachProcess(); } } } else { InsertHeadList(&ptiHook->rpdesk->pDeskInfo->aphkStart[HOOKID_TO_INDEX(HookId)], &Hook->Chain); Hook->ptiHooked = NULL; //gptiCurrent->pDeskInfo->fsHooks |= HOOKID_TO_FLAG(HookId); ptiHook->rpdesk->pDeskInfo->fsHooks |= HOOKID_TO_FLAG(HookId); ptiHook->sphkCurrent = NULL; ptiHook->pClientInfo->phkCurrent = NULL; } RtlInitUnicodeString(&Hook->ModuleName, NULL); if (Mod) { Status = MmCopyFromCaller(&ModuleName, UnsafeModuleName, sizeof(UNICODE_STRING)); if (!NT_SUCCESS(Status)) { IntRemoveHook(Hook); SetLastNtError(Status); RETURN( NULL); } Hook->ModuleName.Buffer = ExAllocatePoolWithTag( PagedPool, ModuleName.MaximumLength, TAG_HOOK); if (NULL == Hook->ModuleName.Buffer) { IntRemoveHook(Hook); EngSetLastError(ERROR_NOT_ENOUGH_MEMORY); RETURN( NULL); } Hook->ModuleName.MaximumLength = ModuleName.MaximumLength; Status = MmCopyFromCaller( Hook->ModuleName.Buffer, ModuleName.Buffer, ModuleName.MaximumLength); if (!NT_SUCCESS(Status)) { ExFreePoolWithTag(Hook->ModuleName.Buffer, TAG_HOOK); Hook->ModuleName.Buffer = NULL; IntRemoveHook(Hook); SetLastNtError(Status); RETURN( NULL); } Hook->ModuleName.Length = ModuleName.Length; /* Make proc relative to the module base */ Hook->offPfn = (ULONG_PTR)((char *)HookProc - (char *)Mod); } else Hook->offPfn = 0; TRACE("Installing: HookId %d Global %s\n", HookId, !ThreadId ? "TRUE" : "FALSE"); RETURN( Handle); CLEANUP: TRACE("Leave NtUserSetWindowsHookEx, ret=%i\n",_ret_); UserLeave(); END_CLEANUP; }
------------------------------------------------------------------------------------------------------------------------------------
这段代码比较长,一点点分析~
PsGetCurrentThreadWin32Thread函数用于得到当前线程所在的进程
PsGetCurrentThreadWin32Thread我没有在MSDN中找到,但是ReactOS源代码中有定义:
/* * PROJECT: ReactOS Kernel * LICENSE: GPL - See COPYING in the top level directory * FILE: ntoskrnl/ps/thread.c * PURPOSE: Process Manager: Thread Management * PROGRAMMERS: Alex Ionescu ([email protected]) * Thomas Weidenmueller ([email protected]) */ PVOID NTAPI PsGetCurrentThreadWin32Thread(VOID) { return PsGetCurrentThread()->Tcb.Win32Thread; }
------------------------------------------------------------------------------------------------------------------------------------
随后用if语句检查了参数HookId的范围以及HookProc是否为空,这里都调用了SetLastError设置了错误代码。
------------------------------------------------------------------------------------------------------------------------------------
随后当线程Id不为0的时候,即当这个钩子不是全局钩子的时候,
先检查了钩子的类型,因为有几种钩子类型只能是全局钩子,这个MSDN中有说明。
------------------------------------------------------------------------------------------------------------------------------------
随后调用了一个函数PsLookupThreadByThreadId检查线程Id,而这里很可能就是我的代码出错的地方,查MSDN~
可惜的是MSDN只说明了这个函数根据线程id得到一个ETHREAD结构类型的变量,此结构我在MSDN中并未查到,但是点这里可以看到它的定义
哎,本来都想跳过这儿了,还是不放心,ReactOS中找到PsLookupThreadByThreadId的源代码
/* * PROJECT: ReactOS Kernel * LICENSE: GPL - See COPYING in the top level directory * FILE: ntoskrnl/ps/thread.c * PURPOSE: Process Manager: Thread Management * PROGRAMMERS: Alex Ionescu ([email protected]) * Thomas Weidenmueller ([email protected]) */ NTSTATUS NTAPI PsLookupThreadByThreadId(IN HANDLE ThreadId, OUT PETHREAD *Thread) { PHANDLE_TABLE_ENTRY CidEntry; PETHREAD FoundThread; NTSTATUS Status = STATUS_INVALID_PARAMETER; PAGED_CODE(); PSTRACE(PS_THREAD_DEBUG, "ThreadId: %p\n", ThreadId); KeEnterCriticalRegion(); /* Get the CID Handle Entry */ CidEntry = ExMapHandleToPointer(PspCidTable, ThreadId); if (CidEntry) { /* Get the Process */ FoundThread = CidEntry->Object; /* Make sure it's really a process */ if (FoundThread->Tcb.Header.Type == ThreadObject) { /* Safe Reference and return it */ if (ObReferenceObjectSafe(FoundThread)) { *Thread = FoundThread; Status = STATUS_SUCCESS; } } /* Unlock the Entry */ ExUnlockHandleTableEntry(PspCidTable, CidEntry); } /* Return to caller */ KeLeaveCriticalRegion(); return Status; }
这段代码首先调用ExMapHandleToPointer得到CID句柄,我们需要保证ThreadId的存在。
PspCidTable是一个进程和线程句柄表,系统通过它来跟踪进程与线程。
这里有看雪的一篇翻译:http://bbs.pediy.com/showthread.php?t=49033
以及这里:http://www.cnblogs.com/Thriving-Country/archive/2011/09/18/2180143.html
或许会对PspCidTable有一些了解。
------------------------------------------------------------------------------
后面的代码比较了通过PsGetCurrentThreadWin32Thread和PsLookupThreadByThreadId得到的线程信息中rpdesk成员变量的值是否相等
即当前线程与传入的线程Id所表示的线程的rpdesk属性是否相等,若不相等,SetLastError并返回NULL,那么这个rpdesk属于是什么意思呢?
根据pti,ptiHook的类型PTHREADINFO,我们需要查找这个结构,同样MSDN再次不给力了,于是……
ReactOS:http://www.reactos.org/wiki/Techwiki:Win32k/THREADINFO
ReactOS给出了出处,这里有一个有注释的:http://rsdn.ru/forum/winapi/2816416.1.aspx
从中我们可以看到rpdesk的类型是PDESKTOP,我猜测这是指向当前桌面的指针,那么这个相等判断是在检测线程是不是在同一桌面内,一头雾水~~~
从SetLastError参数ERROR_ACCESS_DENIED可以看出这里是一个与桌面有关的权限问题。
------------------------------------------------------------------------------------------------------------------------------------
后面的代码,当传入的线程所属进程不是当前进程,并且传入的模块句柄是空,会SetLastError,表示这种条件下模块句柄不应该为空,应该是一个DLL的句柄,
即钩子过程应该放在一个DLL中。
------------------------------------------------------------------------------------------------------------------------------------
随后检查了线程的TIF类型,从TIF_CSRSSTHREAD|TIF_SYSTEMTHREAD,以及SetLastError的参数ERROR_HOOK_TYPE_NOT_ALLOWED可以看出,
系统线程是不允许Hook的。
------------------------------------------------------------------------------------------------------------------------------------
当钩子是全局钩子,即传入的线程Id是0时
此时又要检查Mod的值是否为空,全局钩子的钩子过程必须放在DLL中
------------------------------------------------------------------------------------------------------------------------------------
再后来,调用了IntValidateWindowStationHandle,这个函数是什么功能呢?从参数来看是检查了当前进程的Window Station,
Window Station是一种安全对象,进程第一次调用USER32或者GDI32中的函数时会自动与Window Station及桌面相连。
当用户登录到系统中时,winlogon进程会创建一个交互式window station:WinSta0以及三个桌面,因此winlogon进程与WinSta0关联。
交互式window station包含剪贴板,键盘,鼠标,显示器和三个桌面。
关于window station以及它与进程之间的关系,可以查看MSDN
http://msdn.microsoft.com/en-us/library/windows/desktop/ms687096(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684859(v=vs.85).aspx
以及这两篇博文:
http://blog.csdn.net/2608/article/details/1916773
http://hi.baidu.com/175943462/blog/item/71bda735bc841242241f145a.html
以及潘爱民老师的《WINDOWS内核原理与实现》的9.2.3节窗口管理。
IntValidateWindowStationHandle源代码在ReactOS中是这样的
/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS Win32k subsystem * PURPOSE: Window stations * FILE: subsystems/win32/win32k/ntuser/winsta.c * PROGRAMER: Casper S. Hornstrup ([email protected]) * TODO: The process window station is created on * the first USER32/GDI32 call not related * to window station/desktop handling */ /* * IntValidateWindowStationHandle * * Validates the window station handle. * * Remarks * If the function succeeds, the handle remains referenced. If the * fucntion fails, last error is set. */ NTSTATUS FASTCALL IntValidateWindowStationHandle( HWINSTA WindowStation, KPROCESSOR_MODE AccessMode, ACCESS_MASK DesiredAccess, PWINSTATION_OBJECT *Object) { NTSTATUS Status; if (WindowStation == NULL) { WARN("Invalid window station handle\n"); EngSetLastError(ERROR_INVALID_HANDLE); return STATUS_INVALID_HANDLE; } Status = ObReferenceObjectByHandle( WindowStation, DesiredAccess, ExWindowStationObjectType, AccessMode, (PVOID*)Object, NULL); if (!NT_SUCCESS(Status)) SetLastNtError(Status); return Status; }
首先检查了当前进程的WindowStation是否为空,然后调用ObReferenceObjectByHandle,这些工作貌似都是在作一种权限的验证~~有懂的看到了可以讲一下
-----------------------------------------------------------------------------------------------------------------------------------后面的代码先是
Hook = UserCreateObject(gHandleTable, NULL, &Handle, otHook, sizeof(HOOK));
创建了一个钩子,然后又根据是全局还是线程局子将新创建的钩子加入到钩子链表中。这里还有一个问题。潘爱民老师的《WINDOWS内核原理与实现》9.2.3节窗口管理中讲到
钩子时说windows子系统为每个GUI线程维护一个钩子链表,那么如果不是GUI线程就不能HOOK吗?windows如何区分一个线程是不是GUI线程?
接下来设置Hook的成员变量
Hook的类型是HOOK,ReactOS中定义在ntuser.h中:
typedef struct tagHOOK { THRDESKHEAD head; struct tagHOOK *phkNext; /* This is for user space. */ int HookId; /* Hook table index */ ULONG_PTR offPfn; ULONG flags; /* Some internal flags */ INT ihmod; PTHREADINFO ptiHooked; struct _DESKTOP *rpdesk; /* ReactOS */ LIST_ENTRY Chain; /* Hook chain entry */ struct _ETHREAD* Thread; /* Thread owning the hook */ HOOKPROC Proc; /* Hook function */ BOOLEAN Ansi; /* Is it an Ansi hook? */ UNICODE_STRING ModuleName; /* Module name for global hooks */ } HOOK, *PHOOK;
-----------------------------------------------------------------------------------------------------------------------------------
我们来总结一下:
HHOOK WINAPI SetWindowsHookEx( __in int idHook, __in HOOKPROC lpfn, __in HINSTANCE hMod, __in DWORD dwThreadId );
Hook Scope WH_CALLWNDPROC Thread or global WH_CALLWNDPROCRET Thread or global WH_CBT Thread or global WH_DEBUG Thread or global WH_FOREGROUNDIDLE Thread or global WH_GETMESSAGE Thread or global WH_JOURNALPLAYBACK Global only WH_JOURNALRECORD Global only WH_KEYBOARD Thread or global WH_KEYBOARD_LL Global only WH_MOUSE Thread or global WH_MOUSE_LL Global only WH_MSGFILTER Thread or global WH_SHELL Thread or global WH_SYSMSGFILTER Global only
另外dwThreadId所指的线程还不能是系统线程,前面的代码中可以看到此线程不能有TIF_CSRSSTHREAD|TIF_SYSTEMTHREAD属性。
========================================================================
3. Google之~
在驱动开发网上,有这样一个帖子:谁能剖析一下setwindowshookex内部执行情况:http://bbs3.driverdevelop.com/read.php?tid-85664-page-e.html
回复里的同学给出了一份代码说是Win2K的,但是我不知道它说的Win2K是什么……惭愧……
继续Google,发现了这位同学的博客:http://blog.csdn.net/uvbs/article/details/2778911
然后发现了这是一份源代码目录下的一个文件,然后Google得知原来2004年windows 2000和windows nt 4.0发生了源代码泄露事件,然后就去下载~~~貌似现在只有eMule上面有了,看雪论坛上有人给出了链接,下载,观看,与ReactOS的NtUserSetWindowsHookEx对应的有一个zzzSetWindowsHookEx函数
可以比较一下与ReactOS的实现有什么不同,这里的注释写的比较全。
/***************************************************************************\ * zzzSetWindowsHookEx * * SetWindowsHookEx() is the updated version of SetWindowsHook(). It allows * applications to set hooks on specific threads or throughout the entire * system. The function returns a hook handle to the application if * successful and NULL if a failure occured. * * History: * 28-Jan-1991 DavidPe Created. * 15-May-1991 ScottLu Changed to work client/server. * 30-Jan-1992 IanJa Added bAnsi parameter \***************************************************************************/ PHOOK zzzSetWindowsHookEx( HANDLE hmod, PUNICODE_STRING pstrLib, PTHREADINFO ptiThread, int nFilterType, PROC pfnFilterProc, DWORD dwFlags) { ACCESS_MASK amDesired; PHOOK phkNew; TL tlphkNew; PHOOK *pphkStart; PTHREADINFO ptiCurrent; /* * Check to see if filter type is valid. */ if ((nFilterType < WH_MIN) || (nFilterType > WH_MAX)) { RIPERR0(ERROR_INVALID_HOOK_FILTER, RIP_VERBOSE, ""); return NULL; } /* * Check to see if filter proc is valid. */ if (pfnFilterProc == NULL) { RIPERR0(ERROR_INVALID_FILTER_PROC, RIP_VERBOSE, ""); return NULL; } ptiCurrent = PtiCurrent(); if (ptiThread == NULL) { /* * Is the app trying to set a global hook without a library? * If so return an error. */ if (hmod == NULL) { RIPERR0(ERROR_HOOK_NEEDS_HMOD, RIP_VERBOSE, ""); return NULL; } } else { /* * Is the app trying to set a local hook that is global-only? * If so return an error. */ if (!(abHookFlags[nFilterType + 1] & HKF_TASK)) { RIPERR0(ERROR_GLOBAL_ONLY_HOOK, RIP_VERBOSE, ""); return NULL; } /* * Can't hook outside our own desktop. */ if (ptiThread->rpdesk != ptiCurrent->rpdesk) { RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "Access denied to desktop in zzzSetWindowsHookEx - can't hook other desktops"); return NULL; } if (ptiCurrent->ppi != ptiThread->ppi) { /* * Is the app trying to set hook in another process without a library? * If so return an error. */ if (hmod == NULL) { RIPERR0(ERROR_HOOK_NEEDS_HMOD, RIP_VERBOSE, ""); return NULL; } /* * Is the app hooking another user without access? * If so return an error. Note that this check is done * for global hooks every time the hook is called. */ if ((!RtlEqualLuid(&ptiThread->ppi->luidSession, &ptiCurrent->ppi->luidSession)) && !(ptiThread->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) { RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "Access denied to other user in zzzSetWindowsHookEx"); return NULL; } if ((ptiThread->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)) && !(abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) { /* * Can't hook console or GUI system thread if inter-thread * calling isn't implemented for this hook type. */ RIPERR1(ERROR_HOOK_TYPE_NOT_ALLOWED, RIP_WARNING, "nFilterType (%ld) not allowed in zzzSetWindowsHookEx", nFilterType); return NULL; } } } /* * Check if this thread has access to hook its desktop. */ switch( nFilterType ) { case WH_JOURNALRECORD: amDesired = DESKTOP_JOURNALRECORD; break; case WH_JOURNALPLAYBACK: amDesired = DESKTOP_JOURNALPLAYBACK; break; default: amDesired = DESKTOP_HOOKCONTROL; break; } if (!RtlAreAllAccessesGranted(ptiCurrent->amdesk, amDesired)) { RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "Access denied to desktop in zzzSetWindowsHookEx"); return NULL; } if (amDesired != DESKTOP_HOOKCONTROL && (ptiCurrent->rpdesk->rpwinstaParent->dwWSF_Flags & WSF_NOIO)) { RIPERR0(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION, RIP_WARNING, "Journal hooks invalid on a desktop belonging to a non-interactive WindowStation."); return NULL; } #if 0 /* * Is this a journal hook? */ if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) { /* * Is a journal hook of this type already installed? * If so it's an error. * If this code is enabled, use PhkFirstGlobalValid instead * of checking phkStart directly */ if (ptiCurrent->pDeskInfo->asphkStart[nFilterType + 1] != NULL) { RIPERR0(ERROR_JOURNAL_HOOK_SET, RIP_VERBOSE, ""); return NULL; } } #endif /* * Allocate the new HOOK structure. */ phkNew = (PHOOK)HMAllocObject(ptiCurrent, ptiCurrent->rpdesk, TYPE_HOOK, sizeof(HOOK)); if (phkNew == NULL) { return NULL; } /* * If a DLL is required for this hook, register the library with * the library management routines so we can assure it's loaded * into all the processes necessary. */ phkNew->ihmod = -1; if (hmod != NULL) { #if defined(WX86) phkNew->flags |= (dwFlags & HF_WX86KNOWNDLL); #endif phkNew->ihmod = GetHmodTableIndex(pstrLib); if (phkNew->ihmod == -1) { RIPERR0(ERROR_MOD_NOT_FOUND, RIP_VERBOSE, ""); HMFreeObject((PVOID)phkNew); return NULL; } /* * Add a dependency on this module - meaning, increment a count * that simply counts the number of hooks set into this module. */ if (phkNew->ihmod >= 0) { AddHmodDependency(phkNew->ihmod); } } /* * Depending on whether we're setting a global or local hook, * get the start of the appropriate linked-list of HOOKs. Also * set the HF_GLOBAL flag if it's a global hook. */ if (ptiThread != NULL) { pphkStart = &ptiThread->aphkStart[nFilterType + 1]; /* * Set the WHF_* in the THREADINFO so we know it's hooked. */ ptiThread->fsHooks |= WHF_FROM_WH(nFilterType); /* * Set the flags in the thread's TEB */ if (ptiThread->pClientInfo) { BOOL fAttached; /* * If the thread being hooked is in another process, attach * to that process so that we can access its ClientInfo. */ if (ptiThread->ppi != ptiCurrent->ppi) { KeAttachProcess(&ptiThread->ppi->Process->Pcb); fAttached = TRUE; } else fAttached = FALSE; ptiThread->pClientInfo->fsHooks = ptiThread->fsHooks; if (fAttached) KeDetachProcess(); } /* * Remember which thread we're hooking. */ phkNew->ptiHooked = ptiThread; } else { pphkStart = &ptiCurrent->pDeskInfo->aphkStart[nFilterType + 1]; phkNew->flags |= HF_GLOBAL; /* * Set the WHF_* in the SERVERINFO so we know it's hooked. */ ptiCurrent->pDeskInfo->fsHooks |= WHF_FROM_WH(nFilterType); phkNew->ptiHooked = NULL; } /* * Does the hook function expect ANSI or Unicode text? */ phkNew->flags |= (dwFlags & HF_ANSI); /* * Initialize the HOOK structure. Unreferenced parameters are assumed * to be initialized to zero by LocalAlloc(). */ phkNew->iHook = nFilterType; /* * Libraries are loaded at different linear addresses in different * process contexts. For this reason, we need to convert the filter * proc address into an offset while setting the hook, and then convert * it back to a real per-process function pointer when calling a * hook. Do this by subtracting the 'hmod' (which is a pointer to the * linear and contiguous .exe header) from the function index. */ phkNew->offPfn = ((ULONG_PTR)pfnFilterProc) - ((ULONG_PTR)hmod); #ifdef HOOKBATCH phkNew->cEventMessages = 0; phkNew->iCurrentEvent = 0; phkNew->CacheTimeOut = 0; phkNew->aEventCache = NULL; #endif //HOOKBATCH /* * Link this hook into the front of the hook-list. */ phkNew->phkNext = *pphkStart; *pphkStart = phkNew; /* * If this is a journal hook, setup synchronized input processing * AFTER we set the hook - so this synchronization can be cancelled * with control-esc. */ if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) { /* * Attach everyone to us so journal-hook processing * will be synchronized. * No need to DeferWinEventNotify() here, since we lock phkNew. */ ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew); if (!zzzJournalAttach(ptiCurrent, TRUE)) { RIPMSG1(RIP_WARNING, "zzzJournalAttach failed, so abort hook %#p", phkNew); if (ThreadUnlock(&tlphkNew) != NULL) { zzzUnhookWindowsHookEx(phkNew); } return NULL; } if ((phkNew = ThreadUnlock(&tlphkNew)) == NULL) { return NULL; } } UserAssert(phkNew != NULL); /* * Later 5.0 GerardoB: The old code just to check this but * I think it's some left over stuff from server side days. .* Let's assert on it for a while * Also, I added the assertions in the else's below because I reorganized * the code and want to make sure we don't change behavior */ UserAssert(ptiCurrent->pEThread && THREAD_TO_PROCESS(ptiCurrent->pEThread)); /* * Can't allow a process that has set a global hook that works * on server-side winprocs to run at background priority! Bump * up it's dynamic priority and mark it so it doesn't get reset. */ if ((phkNew->flags & HF_GLOBAL) && (abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) { ptiCurrent->TIF_flags |= TIF_GLOBALHOOKER; KeSetPriorityThread(&ptiCurrent->pEThread->Tcb, LOW_REALTIME_PRIORITY-2); if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) { ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew); /* * If we're changing the journal hooks, jiggle the mouse. * This way the first event will always be a mouse move, which * will ensure that the cursor is set properly. */ zzzSetFMouseMoved(); phkNew = ThreadUnlock(&tlphkNew); /* * If setting a journal playback hook, this process is the input * provider. This gives it the right to call SetForegroundWindow */ if (nFilterType == WH_JOURNALPLAYBACK) { gppiInputProvider = ptiCurrent->ppi; } } else { UserAssert(nFilterType != WH_JOURNALPLAYBACK); } } else { UserAssert(!(abHookFlags[nFilterType + 1] & HKF_JOURNAL)); UserAssert(nFilterType != WH_JOURNALPLAYBACK); } /* * Return pointer to our internal hook structure so we know * which hook to call next in CallNextHookEx(). */ DbgValidateHooks(phkNew, phkNew->iHook); return phkNew; }
这段代码就不分析了~~
(完)