问题: 在使用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中的解释在这里:
MSDN中的解释只足够基本使用,而不能知道原理是什么,所以,我就想知道SetWindowsHookEx的代码是怎么实现的。
========================================================================
2. ReactOS
这是一个开源的项目,相当于重新实现Windows操作系统
/*
* 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 (chorns@users.sourceforge.net)
* 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);
}
从上面的代码可以看出,SetWindowsHookEx是调用了IntSetWindowsHook,然后我们继续~
------------------------------------------------------------------------------------------------------------------------------------
在同一文件中,可以看到
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 (chorns@users.sourceforge.net)
* James Tabor (james.tabor@rectos.org)
* Rafal Harabien (rafalh@reactos.org)
* 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 (alex.ionescu@reactos.org)
* Thomas Weidenmueller (w3seek@reactos.org)
*/
PVOID
NTAPI
PsGetCurrentThreadWin32Thread(VOID)
{
return PsGetCurrentThread()->Tcb.Win32Thread;
}
而PsGetCurrentThread在MSDN中是有定义的,用于得到当前线程。
------------------------------------------------------------------------------------------------------------------------------------
随后用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 (alex.ionescu@reactos.org)
* Thomas Weidenmueller (w3seek@reactos.org)
*/
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:
ReactOS给出了出处,这里有一个有注释的:
从中我们可以看到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:///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 (chorns@users.sourceforge.net)
* 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
);
可能出错的地方:
1. hMod
hMod是一个模块句柄,如果这表示DLL句柄,那么你必须保证它是有效的,要禁的起GetModuleFileNameW函数的考验
2. idHook
idHook必须在一定范围内,每一种钩子类型都定义成整数类型,在一定范围内,你必须保证idHook不超出这个范围
3. idHook & dwThreadId
注意有些钩子只能是全局的,其它钩子既可以是全局的,又可以是线程的,这里必须要说明的是线程钩子中,线程既可以是本线程创建的,也可以是其它线程创建的,但是,如果是其它线程创建的,或者这是一个全局钩子,那么钩子过程必须放在DLL中,即此时hMod不可以为空,而应该是DLL的句柄,这一点之前学习的过程中看到许多人有混淆,认为dwThreadId如果不是本线程生成的,那么必须是0,这一点是不正确的。
这里是MSDN提供的一个钩子类型的表
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
4.dwThreadId
dwThreadId必须要禁的起PsLookupThreadByThreadId的检验,即这个dwThreadId不能随便指定一个,在这里我是有疑问的,假如这个dwThreadId我是用CreateToolhelp32Snapshot得到的,但是线程不断经历生老病死,我怎么能保证那时得到的dwThreadId后来还活着呢?如果不能保证,那么如何做才能Hook到一个进程的所有线程呢?
另外dwThreadId所指的线程还不能是系统线程,前面的代码中可以看到此线程不能有TIF_CSRSSTHREAD|TIF_SYSTEMTHREAD属性。
========================================================================
3. Google之~
在驱动开发网上,有这样一个帖子:谁能剖析一下setwindowshookex内部执行情况:
回复里的同学给出了一份代码说是Win2K的,但是我不知道它说的Win2K是什么……惭愧……
继续Google,发现了这位同学的博客:
/***************************************************************************\
* 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;
}
这段代码就不分析了~~
(完)