SetWindowsHookEx 的资料整理 与 内部机理 的深入分析

问题: 在使用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 function

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 (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: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 (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内部执行情况: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;
}


这段代码就不分析了~~

(完)


转载请注明出处:http://blog.csdn.net/on_1y/article/details/7572522






posted @ 2012-05-17 17:23  Iambda  阅读(677)  评论(0编辑  收藏  举报