标 题: 【原创】消息钩子注册浅析
作 者: RootSuLe
时 间: 2011-06-18,23:10:34
链 接: http://bbs.pediy.com/showthread.php?t=135702

windows消息钩子很古老,但目前仍有很多地方需要用到,简单跟踪了一下函数执行流程,函数用法如下(MSDN):

函数功能:该函数将一个应用程序定义的挂钩处理过程安装到挂钩链中去,您可以通过安装挂钩处理过程来对系统的某些类型事件进行监控,这些事件与某个特定的线程或系统中的所有事件相关.
函数原形:

代码:
HHOOK SetWindowsHookEx(int idHook, 
      HOOKPROC lpfn,
      HINSTANCE hMod,
      DWORD dwThreadId); 

参数:
idHook:指示欲被安装的挂钩处理过程之类型,此参数可以是以下值之一:
WH_CALLWNDPROC(4): 安装一个挂钩处理过程,在系统将消息发送至目标窗口处理过程之前,对该消息进行监视,详情参见CallWndProc挂钩处理过程.
WH_CALLWNDPROCRET(12) :安装一个挂钩处理过程,它对已被目标窗口处理过程处理过了的消息进行监视,详情参见 CallWndRetProc 挂钩处理过程.
WH_CBT(5) :安装一个挂钩处理过程,接受对CBT应用程序有用的消息 ,详情参见 CBTProc 挂钩处理过程.
WH_DEBUG(9):安装一个挂钩处理过程以便对其他挂钩处理过程进行调试, 详情参见DebugProc挂钩处理过程.
WH_FOREGROUNDIDLE(11):安装一个挂钩处理过程,该挂钩处理过程当应用程序的前台线程即将进入空闲状态时被调用,它有助于在空闲时间内执行低优先级的任务.
WH_GETMESSAGE(3):安装一个挂钩处理过程对寄送至消息队列的消息进行监视,详情参见 GetMsgProc 挂钩处理过程.
WH_JOURNALPLAYBACK(1):安装一个挂钩处理过程,对此前由WH_JOURNALRECORD 挂钩处理过程纪录的消息进行寄送.详情参见 JournalPlaybackProc挂钩处理过程.
WH_JOURNALRECORD(0):安装一个挂钩处理过程,对寄送至系统消息队列的输入消息进行纪录.详情参见JournalRecordProc挂钩处理过程.
WH_KEYBOARD(2):安装一个挂钩处理过程对击键消息进行监视. 详情参见KeyboardProc挂钩处理过程.
WH_KEYBOARD_LL(13):此挂钩只能在Windows NT中被安装,用来对底层的键盘输入事件进行监视.详情参见LowLevelKeyboardProc挂钩处理过程.
WH_MOUSE(7):安装一个挂钩处理过程,对鼠标消息进行监视. 详情参见 MouseProc挂钩处理过程.
WH_MOUSE_LL(14):此挂钩只能在Windows NT中被安装,用来对底层的鼠标输入事件进行监视.详情参见LowLevelMouseProc挂钩处理过程.
WH_MSGFILTER(-1):安装一个挂钩处理过程, 以监视由对话框、消息框、菜单条、或滚动条中的输入事件引发的消息.详情参见MessageProc挂钩处理过程.
WH_SHELL(10):安装一个挂钩处理过程以接受对外壳应用程序有用的通知, 详情参见 ShellProc挂钩处理过程.
WH_SYSMSGFILTER(6):安装一个挂钩处理过程,以监视由对话框、消息框、菜单条、或滚动条中的输入事件引发的消息.这个挂钩处理过程对系统中所有应用程序的这类消息都进行监视.详情参见 SysMsgProc挂钩处理过程.

lpfn:指向相应的挂钩处理过程.若参数dwThreadId为0或者指示了一个其他进程创建的线程之标识符,则参数lpfn必须指向一个动态链接中的挂钩处理过程.否则,参数lpfn可以指向一个与当前进程相关的代码中定义的挂钩处理过程.

hMod:指示了一个动态链接的句柄,该动态连接库包含了参数lpfn 所指向的挂钩处理过程.若参数dwThreadId指示的线程由当前进程创建,并且相应的挂钩处理过程定义于当前进程相关的代码中,则参数hMod必须被设置为NULL(0).

dwThreadId:指示了一个线程标识符,挂钩处理过程与线程相关.若此参数值为0,则该挂钩处理过程与所有现存的线程相关.

返回值:若此函数执行成功,则返回值就是该挂钩处理过程的句柄;若此函数执行失败,则返回值为NULL(0).若想获得更多错误信息,请调用GetLasError函数.

备注:若参数hMod为NULL,而参数dwThreadld为0或者指示了一个其他进程创建的线程标识符,则会产生错误.

对函数CallNextHookEx进行调用以下链接下一个挂钩处理过程是可选的,但也是被推荐的否则,其他安装了此挂钩的应用程序将无法获得此挂钩通知,从而可能导致错误的行为.除非您确实希望防止其他应用程序看到此挂钩通知,您应当调用函数CallNextHookEx.
在终止一个应用程序之前,必须调用函数UnhookWindowsHookEx以释放与此挂钩相关的系统资源.
挂钩的作用域依赖与挂钩的类型.一些挂钩只能被设置成系统作用域,其他挂钩(如下所示)还可以被设置为某一特定线程的作用域:
WH_CALLWNDPROC 线程或系统
WH_CALLWNDPROCRET 线程或系统
WH_CBT 线程或系统
WH_DEBUG 线程或系统
WH_FOREGROUNDIDLE 线程或系统
WH_GETMESSAGE 线程或系统
WH_JOURNALPLAYBACK 系统
WH_JOURNALRECORD 系统
WH_KEYBOARD 线程或系统
WH_KEYBOARD_LL 线程或系统
WH_MOUSE 线程或系统
WH_MOUSE_LL 线程或系统
WH_MSGFILTER 线程或系统
WH_SHELL 线程或系统
WH_SYSMSGFILTER 系统
对于一个特定的挂钩类型,现成的挂钩先被调用,然后才是系统挂钩被调用.
系统挂钩作为共享资源,安装一次就对所用应用程序产生影响.所有的系统挂钩函数必须在库中.系统挂钩应当被限制用于一些特殊用途的应用程序或者用来作为应用程序调试的辅助工具.不再需要挂钩的库应当将相应的挂钩处理过程删除掉.

代码:
HHOOK SetWindowsHookEx(int idHook,
      HOOKPROC lpfn,
      HINSTANCE hMod,
      DWORD dwThreadId
      ); .text:77D31211 ; HHOOK __stdcall SetWindowsHookExA(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId)
.text:77D31211                 public _SetWindowsHookExA@16
.text:77D31211 _SetWindowsHookExA@16 proc near         ; DATA XREF: .text:off_77D13928.o
.text:77D31211
.text:77D31211 idHook          = dword ptr  8
.text:77D31211 lpfn            = dword ptr  0Ch
.text:77D31211 hModule         = dword ptr  10h
.text:77D31211 dwThreadId      = dword ptr  14h
.text:77D31211
.text:77D31211                 mov     edi, edi
.text:77D31213                 push    ebp
.text:77D31214                 mov     ebp, esp
.text:77D31216                 push    2               ;DWORD dwFlags
.text:77D31218                 push    [ebp+dwThreadId]
.text:77D3121B                 push    [ebp+hModule] 
.text:77D3121E                 push    [ebp+lpfn]
.text:77D31221                 push    [ebp+idHook]
.text:77D31224                 call    _SetWindowsHookExAW@20 ; SetWindowsHookExAW(x,x,x,x,x)
.text:77D31229                 pop     ebp
.text:77D3122A                 retn    10h
.text:77D3122A _SetWindowsHookExA@16 endp HHOOK __stdcall SetWindowsHookExAW(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId,DWORD dwFlags .text:77D28157 ; int __stdcall SetWindowsHookExAW(int idHook, int lpfn, HMODULE hModule, int dwThreadId, DWORD dwFlags
.text:77D28157 _SetWindowsHookExAW@20 proc near        ; CODE XREF: SetWindowsHookExW(x,x,x,x)+13.p
.text:77D28157                                         ; SetWindowsHookExA(x,x,x,x)+13.p
.text:77D28157
.text:77D28157 Filename        = word ptr -20Ch
.text:77D28157 var_4           = dword ptr -4
.text:77D28157 idHook          = dword ptr  8
.text:77D28157 lpfn            = dword ptr  0Ch
.text:77D28157 hModule         = dword ptr  10h
.text:77D28157 dwThreadId      = dword ptr  14h
.text:77D28157 Mod             = dword ptr  18h
.text:77D28157
.text:77D28157                 mov     edi, edi
.text:77D28159                 push    ebp
.text:77D2815A                 mov     ebp, esp
.text:77D2815C                 sub     esp, 20Ch       ; 为局部变量分配内存空间0x20Ch = 524字节,此为双字节数组,共261个元素,所以最大元素下标是260[MAX_PATH]
.text:77D28162                 mov     eax, ___security_cookie
.text:77D28167                 push    esi             ; 保存esi
.text:77D28168                 mov     esi, [ebp+hModule]
.text:77D2816B                 test    esi, esi
.text:77D2816D                 push    edi
.text:77D2816E                 mov     edi, [ebp+lpfn]
.text:77D28171                 mov     [ebp+var_4], eax
.text:77D28174                 jz      short loc_77D2818D ; hmod为NULL
.text:77D28176                 push    104h            ; nSize
.text:77D2817B                 lea     eax, [ebp+Filename]
.text:77D28181                 push    eax             ; lpFilename
.text:77D28182                 push    esi             ; hModule
.text:77D28183                 call    ds:__imp__GetModuleFileNameW@12 ; GetModuleFileNameW(x,x,x)
.text:77D28189                 test    eax, eax
.text:77D2818B                 jz      short loc_77D281AC ; 获取模块句柄失败
.text:77D2818D
.text:77D2818D loc_77D2818D:                           ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+1D.j
.text:77D2818D                 push    [ebp+dwFlags]    
.text:77D28190                 mov     eax, esi      ;esi也拥有模块句柄
.text:77D28192                 push    edi        ;lpfn入栈
.text:77D28193                 push    [ebp+idHook]
.text:77D28196                 neg     eax        ;取反操作,如果eax为NULL的话,CF位为0,否则CF置1
.text:77D28198                 push    [ebp+dwThreadId]
.text:77D2819B                 sbb     eax, eax      ;若CF为0,eax = 0,否则eax为-1
.text:77D2819D                 lea     ecx, [ebp+Filename]
.text:77D281A3                 and     eax, ecx      ;sbb后,若eax为0,则结果为0,否则为ecx
.text:77D281A5                 push    eax        ;判断结果
.text:77D281A6                 push    esi        ;esi拥有模块句柄
.text:77D281A7                 call    __SetWindowsHookEx@24 ; _SetWindowsHookEx(x,x,x,x,x,x)
.text:77D281AC
.text:77D281AC loc_77D281AC:                           ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+34.j
.text:77D281AC                 mov     ecx, [ebp+var_4]
.text:77D281AF                 pop     edi
.text:77D281B0                 pop     esi
.text:77D281B1                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:77D281B6                 leave
.text:77D281B7                 retn    14h
.text:77D281B7 _SetWindowsHookExAW@20 endp

伪代码:SetWindowsHookExAW

代码:
HHOOK __stdcall SetWindowsHookExAW(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId,DWORD dwFlags)
{
  WCHAR Filename[MAX_PATH];
  if (hmod != NULL)
  {
    //获取文件名
    if (!GetModuleFileNameW(hmod,Filename,MAX_PATH))
    {
      //该参数虽然存在,但经验证是伪造的,返回NULL
      return NULL;
    }
  }
  return _SetWindowsHookEx(hmod,(hmod == NULL) ? NULL:Filename,dwThreadId,idHook,lpfn,dwFlags);
}

HHOOK __stdcall _SetWindowsHookEx(HMODULE hmod,LPCWSTR Filename,DWORD dwThreadId,int idHook,HOOKPROC lpfn,DWORD dwFlags);

代码:
.text:77D281BF ; int __stdcall _SetWindowsHookEx(HMODULE hmod, LPCWSTR Filename, DWORD dwThreadId, int idHook, HOOKPROC lpfn, DWORD dwFlags)
.text:77D281BF __SetWindowsHookEx@24 proc near         ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+50.p
.text:77D281BF
.text:77D281BF usFileName      = byte ptr -10h
.text:77D281BF usModuleName    = dword ptr -8
.text:77D281BF var_4           = dword ptr -4
.text:77D281BF hmod            = dword ptr  8
.text:77D281BF Filename        = dword ptr  0Ch
.text:77D281BF dwThreadId      = dword ptr  10h
.text:77D281BF idHook          = dword ptr  14h
.text:77D281BF lpfn            = dword ptr  18h
.text:77D281BF Flags           = dword ptr  1Ch
.text:77D281BF
.text:77D281BF                 mov     edi, edi
.text:77D281C1                 push    ebp
.text:77D281C2                 mov     ebp, esp
.text:77D281C4                 sub     esp, 10h        ; 分配16字节大小堆栈空间
.text:77D281C7                 push    [ebp+Filename]  ; Filename入栈
.text:77D281CA                 and     [ebp+var_4], 0  ;
.text:77D281CE                 lea     eax, [ebp+usFileName]
.text:77D281D1                 push    eax
.text:77D281D2                 mov     [ebp+usModuleName], eax
.text:77D281D5                 call    ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
.text:77D281DB                 push    [ebp+dwFlags]
.text:77D281DE                 push    [ebp+lpfn]
.text:77D281E1                 push    [ebp+idHook]
.text:77D281E4                 push    [ebp+dwThreadId]
.text:77D281E7                 push    [ebp+usModuleName]
.text:77D281EA                 push    [ebp+hmod]
.text:77D281ED                 call    _NtUserSetWindowsHookEx@24 ; NtUserSetWindowsHookEx(x,x,x,x,x,x)
.text:77D281F2                 leave
.text:77D281F3                 retn    18h
.text:77D281F3 __SetWindowsHookEx@24 endp

伪代码:

代码:
HHOOK __stdcall _SetWindowsHookEx(HMODULE hmod,LPCWSTR Filename,DWORD dwThreadId,int idHook,HOOKPROC lpfn,DWORD dwFlags)
{
  UNICODE_STRING usFileName;
  
  //初始化UNICODE字符串结构
  RtlInitUnicodeString(&usFileName,Filename);
  return NtUserSetWindowsHookEx(hmod,&usFileName,dwThreadId,idHook,lpfn,dwFlags);
} .text:77D281FB ; __stdcall NtUserSetWindowsHookEx(x, x, x, x, x, x)
.text:77D281FB _NtUserSetWindowsHookEx@24 proc near    ; CODE XREF: _SetWindowsHookEx(x,x,x,x,x,x)+2E.p
.text:77D281FB                 mov     eax, 1225h
.text:77D28200                 mov     edx, 7FFE0300h
.text:77D28205                 call    dword ptr [edx]
.text:77D28207                 retn    18h
.text:77D28207 _NtUserSetWindowsHookEx@24 endp

////////////////////////////////内核下

代码:
HHOOK
NTAPI
NtUserSetWindowsHookEx(
      HINSTANCE Mod,
      PUNICODE_STRING ModuleName,
      DWORD ThreadId,
      int HookId,
      HOOKPROC HookProc,
      DWORD dwFlags); .text:BF85EA67 ; START OF FUNCTION CHUNK FOR _NtUserSetWindowsHookEx@24
.text:BF85EA67
.text:BF85EA67 loc_BF85EA67:                           ; CODE XREF: NtUserSetWindowsHookEx(x,x,x,x,x,x)+1C.j
.text:BF85EA67                 push    57h             ; 错误码:87 ,参数不正确
.text:BF85EA69                 call    _UserSetLastError@4 ; UserSetLastError(x)
.text:BF85EA6E                 jmp     short loc_BF85EAAE ; 准备退出
.text:BF85EA6E ; END OF FUNCTION CHUNK FOR _NtUserSetWindowsHookEx@24 .text:BF85EA75 ; int __stdcall NtUserSetWindowsHookEx(HINSTANCE Mod, PUNICODE_STRING ModuleName, DWORD ThreadId, int HookId, HOOKPROC HookProc, BOOL Ansi)
.text:BF85EA75 _NtUserSetWindowsHookEx@24 proc near    ; DATA XREF: .data:BF99E094.o
.text:BF85EA75
.text:BF85EA75 Mod             = dword ptr  8
.text:BF85EA75 ModuleName      = dword ptr  0Ch
.text:BF85EA75 ThreadId        = dword ptr  10h
.text:BF85EA75 HookId          = dword ptr  14h
.text:BF85EA75 HookProc        = dword ptr  18h
.text:BF85EA75 Ansi            = dword ptr  1Ch
.text:BF85EA75
.text:BF85EA75 ; FUNCTION CHUNK AT .text:BF85EA67 SIZE 00000009 BYTES
.text:BF85EA75
.text:BF85EA75                 mov     edi, edi
.text:BF85EA77                 push    ebp
.text:BF85EA78                 mov     ebp, esp
.text:BF85EA7A                 push    esi
.text:BF85EA7B                 call    _EnterCrit@0    ; EnterCrit()
.text:BF85EA80                 xor     esi, esi
.text:BF85EA82                 cmp     [ebp+ThreadId], esi
.text:BF85EA85                 jz      short loc_BF85EABA ; 进程ID为0,说明是系统全局消息钩子
.text:BF85EA87                 push    [ebp+ThreadId]
.text:BF85EA8A                 call    _PtiFromThreadId@4 ; 通过线程ID获取PTHREADINFO指针
.text:BF85EA8F                 cmp     eax, esi
.text:BF85EA91                 jz      short loc_BF85EA67 ; 没有获取
.text:BF85EA93
.text:BF85EA93 loc_BF85EA93:                           ; CODE XREF: NtUserSetWindowsHookEx(x,x,x,x,x,x)+47.j
.text:BF85EA93                 push    [ebp+dwFlags]
.text:BF85EA96                 push    [ebp+HookProc]
.text:BF85EA99                 push    [ebp+HookId]
.text:BF85EA9C                 push    eax
.text:BF85EA9D                 push    [ebp+ModuleName]
.text:BF85EAA0                 push    [ebp+Mod]
.text:BF85EAA3                 call    _zzzSetWindowsHookEx@24 ; zzzSetWindowsHookEx(x,x,x,x,x,x)
.text:BF85EAA8                 cmp     eax, esi        ; 判断结果为NULL
.text:BF85EAAA                 jz      short loc_BF85EAAE ; 失败则跳转
.text:BF85EAAC                 mov     esi, [eax]      ; 暂存结果
.text:BF85EAAE
.text:BF85EAAE loc_BF85EAAE:                           ; CODE XREF: NtUserSetWindowsHookEx(x,x,x,x,x,x)-7.j
.text:BF85EAAE                                         ; NtUserSetWindowsHookEx(x,x,x,x,x,x)+35.j
.text:BF85EAAE                 call    _LeaveCrit@0    ; LeaveCrit()
.text:BF85EAB3                 mov     eax, esi
.text:BF85EAB5                 pop     esi
.text:BF85EAB6                 pop     ebp
.text:BF85EAB7                 retn    18h
.text:BF85EABA ; ---------------------------------------------------------------------------
.text:BF85EABA
.text:BF85EABA loc_BF85EABA:                           ; CODE XREF: NtUserSetWindowsHookEx(x,x,x,x,x,x)+10.j
.text:BF85EABA                 xor     eax, eax
.text:BF85EABC                 jmp     short loc_BF85EA93
.text:BF85EABC _NtUserSetWindowsHookEx@24 endp
代码:
HHOOK __stdcall NtUserSetWindowsHookEx(HINSTANCE Mod, PUNICODE_STRING ModuleName, DWORD ThreadId, int HookId, HOOKPROC HookProc, DWORD dwFlags)
{
  HHOOK hRet;
  PTHREADINFO pti;
  HHOOK TempHandle;
  
  EnterCrit();
  hRet = NULL;
  if ( ThreadId )
  {
    pti = PtiFromThreadId(ThreadId);
    if ( !pti )
    {
      //设置用户模式错误码:87 ,参数不正确
      UserSetLastError(87);
    }
  }
  else
  {
    pti = NULL;
  }
  //zzzSetWindowsHookEx返回的是PHOOK类型,强制转换成HHOOK
  TempHandle = (HHOOK)zzzSetWindowsHookEx(Mod, ModuleName, pti, HookId, HookProc, dwFlags);
  if ( TempHandle )
  {
    hRet = TempHandle;
  }
  LeaveCrit();
  return hRet;
}
代码:
.text:BF85EABE ; START OF FUNCTION CHUNK FOR _zzzSetWindowsHookEx@24
.text:BF85EABE
.text:BF85EABE loc_BF85EABE:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+21.j
.text:BF85EABE                 push    593h            ; 错误码:1427,无效的挂接程序。
.text:BF85EAC3                 jmp     loc_BF85ED2A
.text:BF85EAC8 ; ---------------------------------------------------------------------------
.text:BF85EAC8
.text:BF85EAC8 loc_BF85EAC8:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+3F.j
.text:BF85EAC8                 push    595h            ; 错误码:1429,此挂接程序只可整体设置。
.text:BF85EACD                 jmp     loc_BF85ED2A
.text:BF85EAD2 ; ---------------------------------------------------------------------------
.text:BF85EAD2
.text:BF85EAD2 loc_BF85EAD2:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+6B.j
.text:BF85EAD2                 push    20h             ; 错误码:32,另一个程序正在使用此文件,进程无法访问。
.text:BF85EAD4                 jmp     loc_BF85EC31
.text:BF85EAD9 ; ---------------------------------------------------------------------------
.text:BF85EAD9
.text:BF85EAD9 loc_BF85EAD9:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+64.j
.text:BF85EAD9                 push    10h             ; 错误码:16,无法删除目录。
.text:BF85EADB                 jmp     loc_BF85EC31
.text:BF85EAE0 ; ---------------------------------------------------------------------------
.text:BF85EAE0
.text:BF85EAE0 loc_BF85EAE0:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+8C.j
.text:BF85EAE0                 mov     eax, [edi+3Ch]
.text:BF85EAE3                 mov     eax, [eax+10h]
.text:BF85EAE6                 test    byte ptr [eax+10h], 4 ; ptiCurrent->rpdesk->rpwinstaParent->dwWSF_Flags
.text:BF85EAEA                 jz      loc_BF85EC50
.text:BF85EAF0                 push    5B3h            ; 错误码:1459,该操作需要交互式窗口工作站。
.text:BF85EAF5                 jmp     loc_BF85ED2A
.text:BF85EAFA ; ---------------------------------------------------------------------------
.text:BF85EAFA
.text:BF85EAFA loc_BF85EAFA:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+C1.j
.text:BF85EAFA                 push    7Eh             ; 错误码:126,找不到指定的模块。
.text:BF85EAFC                 call    _UserSetLastError@4 ; UserSetLastError(x)
.text:BF85EB01                 push    esi
.text:BF85EB02                 call    _HMFreeObject@4 ; HMFreeObject(x)
.text:BF85EB07                 jmp     loc_BF85ED2F    ; 返回NULL
.text:BF85EB0C ; ---------------------------------------------------------------------------
.text:BF85EB0C
.text:BF85EB0C loc_BF85EB0C:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+100.j
.text:BF85EB0C                 push    dword ptr [ecx]
.text:BF85EB0E                 call    ds:__imp__KeAttachProcess@4 ; KeAttachProcess(x)
.text:BF85EB14                 mov     eax, [ebp+pti]
.text:BF85EB17                 mov     [ebp+HookId], 1
.text:BF85EB1E                 jmp     loc_BF85ECC8    ; 判断是否HOOK成功
.text:BF85EB23 ; ---------------------------------------------------------------------------
.text:BF85EB23
.text:BF85EB23 loc_BF85EB23:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+11A.j
.text:BF85EB23                 call    ds:__imp__KeDetachProcess@0 ; 解除绑定进程
.text:BF85EB29                 mov     eax, [ebp+pti]
.text:BF85EB2C                 jmp     loc_BF85ECDE    ; 记录哪个线程被HOOK了
.text:BF85EB31 ; ---------------------------------------------------------------------------
.text:BF85EB31
.text:BF85EB31 loc_BF85EB31:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+D9.j
.text:BF85EB31                 mov     eax, [edi+40h]
.text:BF85EB34                 or      dword ptr [esi+20h], 1
.text:BF85EB38                 lea     ecx, [ebx+1]
.text:BF85EB3B                 lea     eax, [eax+ebx*4+14h]
.text:BF85EB3F                 shl     edx, cl
.text:BF85EB41                 mov     [ebp+ModuleName], eax
.text:BF85EB44                 mov     eax, [edi+40h]
.text:BF85EB47                 or      [eax+0Ch], edx
.text:BF85EB4A                 and     dword ptr [esi+28h], 0
.text:BF85EB4E                 jmp     loc_BF85ECE1
.text:BF85EB53 ; ---------------------------------------------------------------------------
.text:BF85EB53
.text:BF85EB53 loc_BF85EB53:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+11.j
.text:BF85EB53                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)+16.j
.text:BF85EB53                 push    592h            ; 错误码:1426,无效的挂接程序类型。
.text:BF85EB58                 jmp     loc_BF85ED2A
.text:BF85EB5D ; ---------------------------------------------------------------------------
.text:BF85EB5D
.text:BF85EB5D loc_BF85EB5D:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+14F.j
.text:BF85EB5D                 test    ds:abHookFlags[ebx], 10h
.text:BF85EB64                 jz      loc_BF85ED13    ; 无效的钩子类型,返回
.text:BF85EB6A                 or      byte ptr [edi+4Ah], 4
.text:BF85EB6E                 push    0Eh             ; Priority
.text:BF85EB70                 push    dword ptr [edi] ; Thread
.text:BF85EB72                 call    ds:__imp__KeSetPriorityThread@8 ; KeSetPriorityThread(x,x)
.text:BF85EB78                 test    ds:abHookFlags[ebx], 4 ; HKF_JOURNAL
.text:BF85EB7F                 jz      loc_BF85ED13
.text:BF85EB85                 mov     eax, [edi+28h]
.text:BF85EB88                 mov     [ebp+var_C], eax
.text:BF85EB8B                 lea     eax, [ebp+var_C]
.text:BF85EB8E                 mov     [edi+28h], eax
.text:BF85EB91                 mov     [ebp+var_8], esi
.text:BF85EB94                 inc     dword ptr [esi+4]
.text:BF85EB97                 call    _zzzSetFMouseMoved@0 ; zzzSetFMouseMoved()
.text:BF85EB9C                 call    _ThreadUnlock1@0 ; ThreadUnlock1()
.text:BF85EBA1                 cmp     ebx, 1          ; 是否是WH_JOURNALPLAYBACK
.text:BF85EBA4                 mov     esi, eax
.text:BF85EBA6                 jnz     loc_BF85ED13
.text:BF85EBAC                 mov     eax, [edi+2Ch]
.text:BF85EBAF                 mov     _gppiInputProvider, eax
.text:BF85EBB4                 jmp     loc_BF85ED13
.text:BF85EBB4 ; END OF FUNCTION CHUNK FOR _zzzSetWindowsHookEx@24
.text:BF85EBB4 ; ---------------------------------------------------------------------------
.text:BF85EBB9                 db 5 dup(90h)
.text:BF85EBBE
.text:BF85EBBE ; =============== S U B R O U T I N E =======================================
.text:BF85EBBE
.text:BF85EBBE ; Attributes: bp-based frame
.text:BF85EBBE
.text:BF85EBBE ; HHOOK __stdcall zzzSetWindowsHookEx(HINSTANCE Mod, PUNICODE_STRING ModuleName, PTHREADINFO pti, int HookId, HOOKPROC HookProc, DWORD dwFlags)
.text:BF85EBBE _zzzSetWindowsHookEx@24 proc near       ; CODE XREF: NtUserSetWindowsHookEx(x,x,x,x,x,x)+2E.p
.text:BF85EBBE                                         ; zzzSetWindowsHookAW(x,x,x)+1A.p
.text:BF85EBBE
.text:BF85EBBE var_C           = dword ptr -0Ch
.text:BF85EBBE var_8           = dword ptr -8
.text:BF85EBBE Mod             = dword ptr  8
.text:BF85EBBE ModuleName      = dword ptr  0Ch
.text:BF85EBBE pti             = dword ptr  10h
.text:BF85EBBE HookId          = dword ptr  14h
.text:BF85EBBE HookProc        = dword ptr  18h
.text:BF85EBBE Ansi            = dword ptr  1Ch
.text:BF85EBBE arg_18          = dword ptr  20h
.text:BF85EBBE
.text:BF85EBBE ; FUNCTION CHUNK AT .text:BF85EABE SIZE 000000FB BYTES
.text:BF85EBBE
.text:BF85EBBE                 mov     edi, edi
.text:BF85EBC0                 push    ebp
.text:BF85EBC1                 mov     ebp, esp
.text:BF85EBC3                 sub     esp, 0Ch        ; 开辟新空间
.text:BF85EBC6                 push    ebx
.text:BF85EBC7                 mov     ebx, [ebp+HookId]
.text:BF85EBCA                 cmp     ebx, 0FFFFFFFFh
.text:BF85EBCD                 push    esi
.text:BF85EBCE                 push    edi
.text:BF85EBCF                 jl      short loc_BF85EB53 ; 钩子类型小于0
.text:BF85EBD1                 cmp     ebx, 0Eh        ; 钩子类型是否大于15,MSDN共定义了15种钩子类型
.text:BF85EBD4                 jg      loc_BF85EB53    ; 大于15了,也跳转
.text:BF85EBDA                 xor     edx, edx
.text:BF85EBDC                 cmp     [ebp+HookProc], edx ; HookProc是否存在
.text:BF85EBDF                 jz      loc_BF85EABE    ; 不存在,跳
.text:BF85EBE5                 mov     esi, [ebp+pti]
.text:BF85EBE8                 cmp     esi, edx        ; 验证PTHREADINFO
.text:BF85EBEA                 mov     edi, _gptiCurrent ; 使用本线程的THREADINFO
.text:BF85EBF0                 jz      loc_BF85ED1C
.text:BF85EBF6                 test    ds:abHookFlags[ebx], 2 ; 判断该消息钩子是否键盘钩子
.text:BF85EBFD                 jz      loc_BF85EAC8
.text:BF85EC03                 mov     eax, [esi+3Ch]  ; THREADINFO+3C是rpdesk
.text:BF85EC06                 cmp     eax, [edi+3Ch]  ; 判断是否属于同一个桌面
.text:BF85EC09                 jnz     loc_BF85EDCE    ; 不在同一个桌面无法设置钩子
.text:BF85EC0F                 mov     eax, [edi+2Ch]  ; 2c地方是tagPROCESSINFO *ppi
.text:BF85EC12                 mov     ecx, [esi+2Ch]
.text:BF85EC15                 cmp     eax, ecx
.text:BF85EC17                 jnz     loc_BF85ED6F    ; 应用程序必须使用DLL来钩
.text:BF85EC1D
.text:BF85EC1D loc_BF85EC1D:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+161.j
.text:BF85EC1D                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)+1DC.j ...
.text:BF85EC1D                 mov     eax, ebx
.text:BF85EC1F                 sub     eax, 0
.text:BF85EC22                 jz      loc_BF85EAD9    ; 钩子类型如果为WH_JOURNALRECORD
.text:BF85EC28                 dec     eax             ; 钩子类型数值减1,如果eax=0,则将eax置-1
.text:BF85EC29                 jz      loc_BF85EAD2
.text:BF85EC2F                 push    8
.text:BF85EC31
.text:BF85EC31 loc_BF85EC31:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)-EA.j
.text:BF85EC31                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)-E3.j
.text:BF85EC31                 pop     esi
.text:BF85EC32                 push    esi             ; DesiredAccess
.text:BF85EC33                 push    dword ptr [edi+0E8h] ; GrantedAccess
.text:BF85EC39                 call    ds:__imp__RtlAreAllAccessesGranted@8 ; RtlAreAllAccessesGranted(x,x)
.text:BF85EC3F                 test    al, al
.text:BF85EC41                 jz      loc_BF85EDB7    ; 取当前进程EPROCESS
.text:BF85EC41                                         ;
.text:BF85EC47
.text:BF85EC47 loc_BF85EC47:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+20A.j
.text:BF85EC47                 cmp     esi, 8
.text:BF85EC4A                 jnz     loc_BF85EAE0    ; DESKTOP_HOOKCONTROL = 0x8L
.text:BF85EC50
.text:BF85EC50 loc_BF85EC50:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)-D4.j
.text:BF85EC50                 push    30h             ; sizeof(HOOK)
.text:BF85EC52                 push    5               ; 枚举类型TYPE_HOOK
.text:BF85EC54                 push    dword ptr [edi+3Ch] ; pti->rpdesk
.text:BF85EC57                 push    edi             ; pti
.text:BF85EC58                 call    _HMAllocObject@16 ; 分配新的钩子结构
.text:BF85EC5D                 mov     esi, eax
.text:BF85EC5F                 test    esi, esi
.text:BF85EC61                 jz      loc_BF85ED2F    ; 分配失败
.text:BF85EC67                 or      dword ptr [esi+24h], 0FFFFFFFFh ; -1
.text:BF85EC6B                 cmp     [ebp+Mod], 0
.text:BF85EC6F                 jz      short loc_BF85EC8F ; 如果模块句柄为NULL
.text:BF85EC71                 push    [ebp+ModuleName]
.text:BF85EC74                 call    _GetHmodTableIndex@4 ; GetHmodTableIndex(x)
.text:BF85EC79                 cmp     eax, 0FFFFFFFFh ; 如果没有找到,则返回-1
.text:BF85EC7C                 mov     [esi+24h], eax
.text:BF85EC7F                 jz      loc_BF85EAFA    ; 错误码:126,找不到指定的模块。
.text:BF85EC85                 test    eax, eax
.text:BF85EC87                 jl      short loc_BF85EC8F ; 小于0则跳转
.text:BF85EC89                 push    eax             ; phkNew->ihmod
.text:BF85EC8A                 call    _AddHmodDependency@4 ; AddHmodDependency(x)
.text:BF85EC8F
.text:BF85EC8F loc_BF85EC8F:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+B1.j
.text:BF85EC8F                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)+C9.j
.text:BF85EC8F                 mov     eax, [ebp+pti]
.text:BF85EC92                 xor     edx, edx
.text:BF85EC94                 inc     edx
.text:BF85EC95                 test    eax, eax
.text:BF85EC97                 jz      loc_BF85EB31    ; 给定线程的PTHREADINFO为空
.text:BF85EC9D                 lea     ecx, [eax+ebx*4+0F8h] ; ptiThread.aphkStart[id]
.text:BF85ECA4                 mov     [ebp+ModuleName], ecx
.text:BF85ECA7                 lea     ecx, [ebx+1]
.text:BF85ECAA                 shl     edx, cl
.text:BF85ECAC                 or      [eax+98h], edx
.text:BF85ECB2                 cmp     dword ptr [eax+44h], 0
.text:BF85ECB6                 jz      short loc_BF85ECDE ; pti+0x44是pClientInfo
.text:BF85ECB8                 mov     ecx, [eax+2Ch]  ; ppi
.text:BF85ECBB                 cmp     ecx, [edi+2Ch]
.text:BF85ECBE                 jnz     loc_BF85EB0C    ; ppi不相等,就是说要HOOK的是别的进程
.text:BF85ECC4                 and     [ebp+HookId], 0
.text:BF85ECC8
.text:BF85ECC8 loc_BF85ECC8:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)-A0.j
.text:BF85ECC8                 cmp     [ebp+HookId], 0 ; 判断是否HOOK成功
.text:BF85ECCC                 mov     ecx, [eax+44h]  ; ptiThread->pClientInfo
.text:BF85ECCF                 mov     edx, [eax+98h]  ; ptiThread->fsHooks;
.text:BF85ECD5                 mov     [ecx+24h], edx  ; ecx+24h = pClientInfo->fsHooks
.text:BF85ECD8                 jnz     loc_BF85EB23    ; 已经HOOK了,则解除进程绑定
.text:BF85ECDE
.text:BF85ECDE loc_BF85ECDE:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)-92.j
.text:BF85ECDE                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)+F8.j
.text:BF85ECDE                 mov     [esi+28h], eax  ; 记录哪个线程被HOOK了
.text:BF85ECE1
.text:BF85ECE1 loc_BF85ECE1:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)-70.j
.text:BF85ECE1                 mov     eax, [ebp+dwFlags]
.text:BF85ECE4                 and     eax, 2
.text:BF85ECE7                 or      [esi+20h], eax
.text:BF85ECEA                 mov     eax, [ebp+HookProc]
.text:BF85ECED                 sub     eax, [ebp+Mod]
.text:BF85ECF0                 mov     [esi+18h], ebx
.text:BF85ECF3                 mov     [esi+1Ch], eax
.text:BF85ECF6                 mov     eax, [ebp+ModuleName]
.text:BF85ECF9                 mov     ecx, [eax]
.text:BF85ECFB                 mov     [esi+14h], ecx
.text:BF85ECFE                 mov     [eax], esi
.text:BF85ED00                 test    ds:abHookFlags[ebx], 4
.text:BF85ED07                 jnz     short loc_BF85ED33
.text:BF85ED09
.text:BF85ED09 loc_BF85ED09:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+1AD.j
.text:BF85ED09                 test    byte ptr [esi+20h], 1
.text:BF85ED0D                 jnz     loc_BF85EB5D
.text:BF85ED13
.text:BF85ED13 loc_BF85ED13:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)-5A.j
.text:BF85ED13                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)-3F.j ...
.text:BF85ED13                 mov     eax, esi
.text:BF85ED15
.text:BF85ED15 loc_BF85ED15:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+173.j
.text:BF85ED15                 pop     edi
.text:BF85ED16                 pop     esi
.text:BF85ED17                 pop     ebx
.text:BF85ED18                 leave
.text:BF85ED19                 retn    18h
.text:BF85ED1C ; ---------------------------------------------------------------------------
.text:BF85ED1C
.text:BF85ED1C loc_BF85ED1C:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+32.j
.text:BF85ED1C                 cmp     [ebp+Mod], edx
.text:BF85ED1F                 jnz     loc_BF85EC1D
.text:BF85ED25
.text:BF85ED25 loc_BF85ED25:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+1B4.j
.text:BF85ED25                 push    594h            ; 错误码:1428,没有模块句柄无法设置非本机的挂接。
.text:BF85ED2A
.text:BF85ED2A loc_BF85ED2A:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)-FB.j
.text:BF85ED2A                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)-F1.j ...
.text:BF85ED2A                 call    _UserSetLastError@4 ; UserSetLastError(x)
.text:BF85ED2F
.text:BF85ED2F loc_BF85ED2F:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)-B7.j
.text:BF85ED2F                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)+A3.j ...
.text:BF85ED2F                 xor     eax, eax        ; 返回NULL
.text:BF85ED31                 jmp     short loc_BF85ED15
.text:BF85ED33 ; ---------------------------------------------------------------------------
.text:BF85ED33
.text:BF85ED33 loc_BF85ED33:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+149.j
.text:BF85ED33                 mov     eax, [edi+28h]
.text:BF85ED36                 mov     [ebp+var_C], eax
.text:BF85ED39                 lea     eax, [ebp+var_C]
.text:BF85ED3C                 mov     [edi+28h], eax
.text:BF85ED3F                 push    1
.text:BF85ED41                 mov     [ebp+var_8], esi
.text:BF85ED44                 inc     dword ptr [esi+4]
.text:BF85ED47                 push    edi
.text:BF85ED48                 call    _zzzJournalAttach@8 ; zzzJournalAttach(x,x)
.text:BF85ED4D                 test    eax, eax
.text:BF85ED4F                 jnz     short loc_BF85ED62
.text:BF85ED51                 call    _ThreadUnlock1@0 ; ThreadUnlock1()
.text:BF85ED56                 test    eax, eax
.text:BF85ED58                 jz      short loc_BF85ED2F ; 返回NULL
.text:BF85ED5A                 push    esi
.text:BF85ED5B                 call    _zzzUnhookWindowsHookEx@4 ; zzzUnhookWindowsHookEx(x)
.text:BF85ED60                 jmp     short loc_BF85ED2F ; 返回NULL
.text:BF85ED62 ; ---------------------------------------------------------------------------
.text:BF85ED62
.text:BF85ED62 loc_BF85ED62:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+191.j
.text:BF85ED62                 call    _ThreadUnlock1@0 ; ThreadUnlock1()
.text:BF85ED67                 mov     esi, eax
.text:BF85ED69                 test    esi, esi
.text:BF85ED6B                 jnz     short loc_BF85ED09
.text:BF85ED6D                 jmp     short loc_BF85ED2F ; 返回NULL
.text:BF85ED6F ; ---------------------------------------------------------------------------
.text:BF85ED6F
.text:BF85ED6F loc_BF85ED6F:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+59.j
.text:BF85ED6F                 cmp     [ebp+Mod], edx
.text:BF85ED72                 jz      short loc_BF85ED25 ; 模块句柄为NULL
.text:BF85ED74                 mov     edx, [ecx+160h] ; PROCESSINFO+160h存储进程luidSession
.text:BF85ED7A                 cmp     edx, [eax+160h]
.text:BF85ED80                 jnz     short loc_BF85ED90 ; ptiThread->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK
.text:BF85ED82                 mov     ecx, [ecx+164h] ; PROCESSINFO+164h存储进程HighPart
.text:BF85ED88                 cmp     ecx, [eax+164h]
.text:BF85ED8E                 jz      short loc_BF85ED96
.text:BF85ED90
.text:BF85ED90 loc_BF85ED90:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+1C2.j
.text:BF85ED90                 test    byte ptr [esi+4Ah], 40h ; ptiThread->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK
.text:BF85ED94                 jz      short loc_BF85EDCE ; 错误码:5,拒绝访问
.text:BF85ED96
.text:BF85ED96 loc_BF85ED96:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+1D0.j
.text:BF85ED96                 test    byte ptr [esi+48h], 0Ch
.text:BF85ED9A                 jz      loc_BF85EC1D
.text:BF85EDA0                 test    ds:abHookFlags[ebx], 10h
.text:BF85EDA7                 jnz     loc_BF85EC1D    ; 钩子类型验证通过
.text:BF85EDAD                 push    5B2h            ; 错误码:1458,不允许使用的挂钩类型。
.text:BF85EDB2                 jmp     loc_BF85ED2A
.text:BF85EDB7 ; ---------------------------------------------------------------------------
.text:BF85EDB7
.text:BF85EDB7 loc_BF85EDB7:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+83.j
.text:BF85EDB7                 call    ds:__imp__PsGetCurrentProcess@0 ; 取当前进程EPROCESS
.text:BF85EDB7                                         ;
.text:BF85EDBD                 cmp     eax, _gpepCSRSS ; 比较是否是CSRSS.exe的进程
.text:BF85EDC3                 jnz     short loc_BF85EDCE ; 错误码:5,拒绝访问
.text:BF85EDC5                 cmp     ebx, 0FFFFFFFFh
.text:BF85EDC8                 jz      loc_BF85EC47
.text:BF85EDCE
.text:BF85EDCE loc_BF85EDCE:                           ; CODE XREF: zzzSetWindowsHookEx(x,x,x,x,x,x)+4B.j
.text:BF85EDCE                                         ; zzzSetWindowsHookEx(x,x,x,x,x,x)+1D6.j ...
.text:BF85EDCE                 push    5               ; 错误码:5,拒绝访问
.text:BF85EDD0                 jmp     loc_BF85ED2A
.text:BF85EDD0 _zzzSetWindowsHookEx@24 endp

//zzzSetWindowsHookEx允许在制定的线程中或者系统全局范围内设置钩子,该函数返回一个HOOK对象的句柄如果成功,如果有错误发生,则返回NULL

代码:
PHOOK zzzSetWindowsHookEx(
      HANDLE hmod,      //模块句柄
      PUNICODE_STRING pstrLib,  //库名字
      PTHREADINFO ptiThread,    //线程信息
      int id,        //钩子ID(类型)
      PROC pfnFilterProc,    //钩子回调地址
      DWORD dwFlags)      //版本标志
{
    ACCESS_MASK amDesired;
    PHOOK       phkNew;
    TL          tlphkNew;
    PHOOK       *pphkStart;
    PTHREADINFO ptiCurrent;     //检查钩子类型是否允许,MSDN共定义16种钩子
    if ((nFilterType < WH_MIN) || (nFilterType > WH_MAX)) 
    {
  //设置错误码,1426,无效的挂接类型
  UserSetLastError(1426);
        return NULL;
    }     //检验提供的钩子函数回调
    if (pfnFilterProc == NULL) 
    {
  //设置错误码,1427,无效的挂接过程
  UserSetLastError(1427);
        return NULL;
    }   //获取当前调用者线程的THREADINFO结构指针
    ptiCurrent = PtiCurrent();     if (ptiThread == NULL) 
    {
  //如果应用程序没有使用DLL就用全局HOOK
         if (hmod == NULL) 
   {
    //设置错误码,1428,没有模块句柄无法设置非本机的挂接
    UserSetLastError(1428);
               return NULL;
         }
    } 
   else 
  {
  //如果应用程序想设置本地HOOK,但使用的只能全局的钩子类型
        if (!(abHookFlags[nFilterType + 1] & HKF_TASK)) 
  {
    //设置错误码,1429,此程序只可整体设置
    UserSetLastError(1428);
              return NULL;
        }     //不能HOOK不同在同一桌面的线程
        if (ptiThread->rpdesk != ptiCurrent->rpdesk) 
  {
    //设置错误码,5,拒绝访问
    UserSetLastError(5);
              return NULL;
        }         if (ptiCurrent->ppi != ptiThread->ppi) 
  {       //如果程序想HOOK别的进程,但没有使用DLL
            if (hmod == NULL) 
      {
    //设置错误码,1428,没有模块句柄无法设置非本机的挂接
    UserSetLastError(1428);
                return NULL;
            }       //如果要HOOK别的用户没有权限
            if ((!RtlEqualLuid(&ptiThread->ppi->luidSession,
                               &ptiCurrent->ppi->luidSession)) &&
                        !(ptiThread->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) 
      {
    //设置错误码,5,拒绝访问
    UserSetLastError(5);
    return NULL;
            }             if ((ptiThread->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)) &&
                    !(abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) 
      {
    //不能HOOK控制台程序以及系统线程
    //设置错误码,1458,不允许使用的挂钩类型
    UserSetLastError(1458);
                return NULL;
            }    
        }
    }   //权限检查都通过了
    switch( id )
    {
      //WH_JOURNALRECORD = HKF_SYSTEM | HKF_JOURNAL | HKF_INTERSENDABLE
  case WH_JOURNALRECORD:
        amDesired = DESKTOP_JOURNALRECORD;
        break;     case WH_JOURNALPLAYBACK:
        amDesired = DESKTOP_JOURNALPLAYBACK;
        break;     default:
        amDesired = DESKTOP_HOOKCONTROL;
        break;
    }   
  /*   //权限是否足够
  BOOLEAN
  NTAPI
  RtlAreAllAccessesGranted(ACCESS_MASK GrantedAccess,
  ACCESS_MASK DesiredAccess)
  {
    PAGED_CODE_RTL();
    return ((GrantedAccess & DesiredAccess) == DesiredAccess);
  }   */
    if (!RtlAreAllAccessesGranted(ptiCurrent->amdesk, amDesired)) 
    {
  //设置错误码,5,拒绝访问
  UserSetLastError(5);
        return NULL;
    }     if (amDesired != DESKTOP_HOOKCONTROL &&
        (ptiCurrent->rpdesk->rpwinstaParent->dwWSF_Flags & WSF_NOIO)) 
    {
  //错误码:1459,该操作需要交互式窗口工作站。
  UserSetLastError(5);
  return NULL;
    }   //为新的钩子对象分配空间
    phkNew = (PHOOK)HMAllocObject(ptiCurrent, ptiCurrent->rpdesk,TYPE_HOOK, sizeof(HOOK));     //若分配失败,则直接返回NULL
    if (phkNew == NULL) 
    {
        return NULL;
    }
    //如果一个DLL是本钩子所必须的,那么像系统注册该DLL,注册后,才能保证被加载到别的进程中
    phkNew->ihmod = -1;
    if (hmod != NULL) 
    { #if defined(WX86)         phkNew->flags |= (dwFlags & HF_WX86KNOWNDLL); #endif         phkNew->ihmod = GetHmodTableIndex(pstrLib);         if (phkNew->ihmod == -1) 
  {
    //错误码:126,系统找不到指定的模块
    UserSetLastError(126);     //释放掉刚分配的HOOK对象
              HMFreeObject((PVOID)phkNew);
             return NULL;
        }   //为模块曾加一个属性,说明这个模块被HOOK了多少次
        if (phkNew->ihmod >= 0) 
  {
            AddHmodDependency(phkNew->ihmod);
        }
    }     //找到钩子链表,如果是全局钩子,则设置HF_GLOBAL标志
    if (ptiThread != NULL) 
    {
        //获取本线程传入钩子类型开始地址
  pphkStart = &ptiThread->aphkStart[id];   //设置WHF_*标志在THREADINFO中,就知道被HOOK过了
        ptiThread->fsHooks |= WHF_FROM_WH(id);         if (ptiThread->pClientInfo) 
  {
            BOOL fAttached;       //判断是否是要钩其他进程
            if (ptiThread->ppi != ptiCurrent->ppi) 
      {
                //附加到别的进程,这样,就可以访问别的进程的内存
    KeAttachProcess(&ptiThread->ppi->Process->Pcb);
                fAttached = TRUE;
            } 
      else
      {
    fAttached = FALSE;
      }                       //是指该线程被HOOK,当被HOOK进程接受到windows消息后,会先判断是否存在HOOK
            ptiThread->pClientInfo->fsHooks = ptiThread->fsHooks;             if (fAttached)
      {
    KeDetachProcess();
      }                
        }   //记录哪个线程被HOOK了
        phkNew->ptiHooked = ptiThread;     } 
    //线程信息为NULL,说明要HOOK当前系统所有线程
    else 
    {
        
  //从当前线程中找到桌面对象,只要钩住桌面,就钩住了所有线程
  pphkStart = &ptiCurrent->pDeskInfo->aphkStart[id];   //设置全局HOOK标志
        phkNew->flags |= HF_GLOBAL;         ptiCurrent->pDeskInfo->fsHooks |= WHF_FROM_WH(id);         phkNew->ptiHooked = NULL;
     }     //钩子类型版本,ANSI或者是UNICODE
    phkNew->flags |= (dwFlags & HF_ANSI);
    phkNew->iHook = id;     //模块在不同的进程中回被加载到不同的地址空间,出于这个原因,当设置钩子时,需要计算出偏移,跟PE文件VA类似
    phkNew->offPfn = ((ULONG_PTR)pfnFilterProc) - ((ULONG_PTR)hmod);     //把这个钩子添加的钩子链表的后面
    phkNew->phkNext = *pphkStart;
    //重置钩子链表
    *pphkStart = phkNew;   //如果是日志钩子,需要对同步进行处理
    if (abHookFlags[id] & HKF_JOURNAL) 
    {
        ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew);
        if (!zzzJournalAttach(ptiCurrent, TRUE)) 
  {
            if (ThreadUnlock(&tlphkNew) != NULL) 
      {
                zzzUnhookWindowsHookEx(phkNew);
            }
            return NULL;
        }
        if ((phkNew = ThreadUnlock(&tlphkNew)) == NULL) 
  {
            return NULL;
        }
    }   //不允许一个进程,设置了全局挂钩,工程运行在后台的权限,提升优先级和标记所以不能重置
    if ((phkNew->flags & HF_GLOBAL) &&
            (abHookFlags[id] & HKF_INTERSENDABLE)) 
  {         ptiCurrent->TIF_flags |= TIF_GLOBALHOOKER;
        KeSetPriorityThread(&ptiCurrent->pEThread->Tcb, LOW_REALTIME_PRIORITY-2);         if (abHookFlags[id] & HKF_JOURNAL) 
  {
            ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew);   //如果要改变HOOK,移动鼠标,这样第一个时间总是鼠标移动时间,可以确保光标被正确设置
            zzzSetFMouseMoved();
            phkNew = ThreadUnlock(&tlphkNew);   //若要设置一个回放钩子,这个进程提供输入,这使它有SetForegroundWindow的机会
            if (id == WH_JOURNALPLAYBACK) 
      {
                gppiInputProvider = ptiCurrent->ppi;
            }
        }
  else 
  {
            UserAssert(id != WH_JOURNALPLAYBACK);
        }
    } 
    else 
    {
        UserAssert(!(abHookFlags[id] & HKF_JOURNAL));
        UserAssert(id != WH_JOURNALPLAYBACK);
    }     //返回钩子指针,以便CallNextHookEx调用
    DbgValidateHooks(phkNew, phkNew->iHook);
    return phkNew;
}

至此,假定已经注册了一个消息钩子,简单查一下钩子如何被调用,由于windows是基于消息事件驱动的,在windows向应用程序发送消息之前会先检查是否有钩子,如果有消息钩子存在,则会调用钩子函数,如果钩子函数在一个动态链接库中,且该链接库尚未映射到应用程序进程空间,则系统用LoadLibrary加载该DLL到址空间中,然后调用钩子函数

代码:
LRESULT SendMessage(HWND hWnd,
        UINT Msg,
        WPARAM wParam,
        LPARAM lParam
        );

SendMessage主要调用:
LRESULT SendMessageWorker(PWND pwnd, UINT message, WPARAM wParam, LPARAM lParam, BOOL fAnsi);
/*参看win2k代码
SendMessageWorker取得CLIENTINFO信息,并检查fsHooks域,如果存在HOOK,return CsSendMessage(hwnd, message, wParam, lParam, 0L,
FNID_SENDMESSAGE, fAnsi);

代码:
#define CsSendMessage(hwnd, msg, wParam, lParam, xParam, pfn, bAnsi) \
        (((msg) >= WM_USER) ? \
            NtUserMessageCall(hwnd, msg, wParam, lParam, xParam, pfn, bAnsi) : \
            gapfnScSendMessage[MessageTable[msg].iFunction](hwnd, msg, wParam, lParam, xParam, pfn, bAnsi))

MessageTable是一个全局的表,存在系统消息的数组,其中存在消息钩子入口地址
*/
R3下枚举系统中的消息钩子,网上已有例子.

//可能用到的结构体:

代码:
CONST BYTE abHookFlags[CWINHOOKS] = {
    HKF_SYSTEM | HKF_TASK | HKF_NZRET              , // WH_MSGFILTER (-1)
    HKF_SYSTEM | HKF_JOURNAL          | HKF_INTERSENDABLE   , // WH_JOURNALRECORD 0
    HKF_SYSTEM | HKF_JOURNAL          | HKF_INTERSENDABLE   , // WH_JOURNALPLAYBACK 1
    HKF_SYSTEM | HKF_TASK | HKF_NZRET | HKF_INTERSENDABLE   , // WH_KEYBOARD 2
    HKF_SYSTEM | HKF_TASK                                   , // WH_GETMESSAGE 3
    HKF_SYSTEM | HKF_TASK                                   , // WH_CALLWNDPROC 4
    HKF_SYSTEM | HKF_TASK                                   , // WH_CBT 5
    HKF_SYSTEM                                              , // WH_SYSMSGFILTER 6
    HKF_SYSTEM | HKF_TASK             | HKF_INTERSENDABLE   , // WH_MOUSE 7
    HKF_SYSTEM | HKF_TASK                                   , // WH_HARDWARE 8
    HKF_SYSTEM | HKF_TASK                                   , // WH_DEBUG 9
    HKF_SYSTEM | HKF_TASK                                   , // WH_SHELL 10
    HKF_SYSTEM | HKF_TASK                                   , // WH_FOREGROUNDIDLE 11
    HKF_SYSTEM | HKF_TASK                                   , // WH_CALLWNDPROCRET 12
    HKF_SYSTEM | HKF_LOWLEVEL         | HKF_INTERSENDABLE   , // WH_KEYBOARD_LL 13
    HKF_SYSTEM | HKF_LOWLEVEL         | HKF_INTERSENDABLE     // WH_MOUSE_LL 14
    
#ifdef REDIRECTION
    ,HKF_SYSTEM | HKF_LOWLEVEL         | HKF_INTERSENDABLE     // WH_HITTEST 15
#endif
}; typedef struct tagHOOK {
    THRDESKHEAD     head;
    struct tagHOOK  *phkNext;
    int             iHook;              // 钩子类型
    DWORD           offPfn;
    UINT            flags;              // 钩子标志
    int             ihmod;
    PTHREADINFO     ptiHooked;          // 被HOOK的线程
    PDESKTOP        rpdesk;             // 全局钩子
#ifdef HOOKBATCH
    DWORD           cEventMessages;   
    DWORD           iCurrentEvent; 
    DWORD           CacheTimeOut; 
    PEVENTMSG       aEventCache; 
#endif
} HOOK, *PHOOK; typedef struct _W32PROCESS
{
  PEPROCESS     peProcess;
  DWORD         RefCount;
  ULONG         W32PF_flags;
  PKEVENT       InputIdleEvent;
  DWORD         StartCursorHideTime;
  struct _W32PROCESS * NextStart;
  PVOID         pDCAttrList;
  PVOID         pBrushAttrList;
  DWORD         W32Pid;
  LONG          GDIHandleCount;
  LONG          UserHandleCount;
  PEX_PUSH_LOCK GDIPushLock;  /* Locking Process during access to structure. */
  RTL_AVL_TABLE GDIEngUserMemAllocTable;  /* Process AVL Table. */
  LIST_ENTRY    GDIDcAttrFreeList;
  LIST_ENTRY    GDIBrushAttrFreeList;
} W32PROCESS, *PW32PROCESS; typedef struct tagTHREADINFO {
    W32THREAD;
    LIST_ENTRY      PtiLink;            // 当前桌面中其他线程的链表
    PTL             ptl;                // Listhead for thread lock list
    PTL             ptlOb;              // Listhead for kernel object thread lock list
    PTL             ptlPool;            // Listhead for temp pool usage
    int             cEnterCount;
    struct tagPROCESSINFO *ppi;         // 线程所属进程的PROCESSINFO结构指针
    struct tagQ    *pq;                 // 键盘鼠标输入队列
    PKL             spklActive;         // active keyboard layout for this thread
    MLIST           mlPost;             // 已投递消息列表
    USHORT          fsChangeBitsRemoved;// Bits removed during PeekMessage
    USHORT          cDeskClient;        // Ref count for CSRSS desktop
    PCLIENTTHREADINFO pcti;             // Info that must be visible from client
    CLIENTTHREADINFO cti;              // Use this when no desktop is available
    HANDLE          hEventQueueClient;
    PKEVENT         pEventQueueServer;
    PKEVENT        *apEvent;            // Wait array for xxxPollAndWaitForSingleObject
    PDESKTOP        rpdesk;
    HDESK           hdesk;              // 桌面句柄
    ACCESS_MASK     amdesk;             // 允许桌面权限
    PDESKTOPINFO    pDeskInfo;          // Desktop info visible to client
    PCLIENTINFO     pClientInfo;        // Client info stored in TEB
    DWORD           TIF_flags;          // TIF_ flags go here.
    PUNICODE_STRING pstrAppName;        // 程序名
    struct tagSMS *psmsSent;           // Most recent SMS this thread has sent
    struct tagSMS *psmsCurrent;        // Received SMS this thread is currently processing
    struct tagSMS *psmsReceiveList;    // SMSs to be processed
    LONG            timeLast;           // Time, position, and ID of last message
    POINT           ptLast;
    DWORD           idLast;
    int             cQuit;
    int             exitCode;
    int             cPaintsReady;
    UINT            cTimersReady;
    PMENUSTATE      pMenuState;
    union {
        PTDB            ptdb;           // Win16Task Schedule data for WOW thread
        PWINDOWSTATION pwinsta;        // Window station for SYSTEM thread
        PDESKTOP        pdeskClient;    // Desktop for CSRSS thread
    };     PSVR_INSTANCE_INFO psiiList;        // thread DDEML instance list
    DWORD           dwExpWinVer;
    DWORD           dwCompatFlags;      // The Win 3.1 Compat flags
    UINT            cWindows;           // Number of windows owned by this thread
    UINT            cVisWindows;        // Number of visible windows on this thread
    struct tagQ    *pqAttach;           // calculation variabled used in
                                        // AttachThreadInput()
    int             iCursorLevel;       // keep track of each thread's level
    DWORD           fsReserveKeys;      // Keys that must be sent to the active
                                        // active console window.
    struct tagTHREADINFO *ptiSibling;   // pointer to sibling thread info
    PMOVESIZEDATA   pmsd;
    DWORD           fsHooks;                // WHF_ Flags for which hooks are installed
    PHOOK           asphkStart[CWINHOOKS]; // 本线程已注册的钩子
    PHOOK           sphkCurrent;            // Hook this thread is currently processing
    PSBTRACK        pSBTrack;
#ifdef FE_IME
    PWND            spwndDefaultIme;    // Default IME Window for this thread
    PIMC            spDefaultImc;       // Default input context for this thread
    HKL             hklPrev;            // Previous active keyboard layout
#endif
} THREADINFO; typedef struct _LUID {
    DWORD LowPart;
    LONG HighPart;
} LUID, *PLUID; typedef struct _PROCESSINFO
{
  W32PROCESS;
  PTHREADINFO ptiList;
  PTHREADINFO ptiMainThread;
  struct _DESKTOP* rpdeskStartup;
  PCLS pclsPrivateList;
  PCLS pclsPublicList;
  INT cThreads;
  DWORD dwhmodLibLoadedMask;
  HANDLE ahmodLibLoaded[CLIBS];
  struct _WINSTATION_OBJECT *prpwinsta;
  HWINSTA hwinsta;
  ACCESS_MASK amwinsta;
  DWORD dwHotkey;
  HMONITOR hMonitor;
  LUID luidSession;
  USERSTARTUPINFO usi;
  DWORD dwLayout;
  DWORD dwRegisteredClasses;
  LIST_ENTRY MenuListHead;
  FAST_MUTEX PrivateFontListLock;
  LIST_ENTRY PrivateFontListHead;
  FAST_MUTEX DriverObjListLock;
  LIST_ENTRY DriverObjListHead;
  struct _KBL* KeyboardLayout; // THREADINFO only
  W32HEAP_USER_MAPPING HeapMappings;
} PROCESSINFO; typedef struct _CLIENTINFO
{
    ULONG_PTR CI_flags;
    ULONG_PTR cSpins;
    DWORD dwExpWinVer;
    DWORD dwCompatFlags;
    DWORD dwCompatFlags2;
    DWORD dwTIFlags; // ThreadInfo TIF_Xxx flags for User space.
    PDESKTOPINFO pDeskInfo;
    ULONG_PTR ulClientDelta;
    PHOOK phkCurrent;
    ULONG fsHooks;
    CALLBACKWND CallbackWnd;
    DWORD dwHookCurrent;
    INT cInDDEMLCallback;
    PCLIENTTHREADINFO pClientThreadInfo;
    ULONG_PTR dwHookData;
    DWORD dwKeyCache;
    BYTE afKeyState[8];
    DWORD dwAsyncKeyCache;
    BYTE afAsyncKeyState[8];
    BYTE afAsyncKeyStateRecentDow[8];
    HKL hKL;
    USHORT CodePage;
    UCHAR achDbcsCF[2];
    MSG msgDbcsCB;
    LPDWORD lpdwRegisteredClasses;
    ULONG Win32ClientInfo3[27];
    PPROCESSINFO ppi;
} CLIENTINFO, *PCLIENTINFO;

线程为窗口维护一个THREADINFO结构,每个线程有一个gptiCurrent的全局变量.可从当前线程中,枚举出系统所有消息钩子,若要枚举其他单个线程的消息钩子,关键是要获得该线程的THREADINFO结构指针,通过未公开函数win32k.sys中PtiFromThreadId(HANDLE ThreadId)得到,该函数内部使用NTSTATUS LockThreadByClientId(HANDLE ThreadId,PETHREAD pEThread)得到线程ETHREAD,然后使用PTHREADINFO PtiFromThread(pEThread);得到THREADINFO指针,这样即可通过THREADINFO.asphkStart[CWINHOOKS]遍历该线程中的消息钩子.

代码:
#define PtiFromThread(pEThread) ((PTHREADINFO)((pEThread)->Tcb.Win32Thread))

CWINHOOKS = WH_MAX - WH_MIN + 1
#define WH_MIN              (-1)
#if(WINVER >= 0x0400)
#if (_WIN32_WINNT >= 0x0400)
#define WH_MAX             14
#else
#define WH_MAX             12
#endif // (_WIN32_WINNT >= 0x0400)
#else
#define WH_MAX             11
#endif ForExample:
VOID DumpThreadMsgHooks(HANDLE ThreadId)
{
  PHOOK Currenthk;
  PTHREADINFO pti,Temppti;
  int HookCount = 0;   pti = PtiFromThreadId(ThreadId);
  Temppti = pti;
  while(Temppti->PtiLink.FLink != pti)
  {
    for(;HookCount < CWINHOOKS;HookCount++)
    {
      Currenthk = pti->asphkStart[HookCount];
      if(Currenthk)
      {
        KdPrint(("Hook id is %d\n",Currenthk->iHook));
      }
    }
    Temppti = Temppti->PtiLink.FLink;
  }
  
}

只能分析这么点了,不足或错误之处,还请各位指正*转载请注明来自看雪论坛@PEdiy.com

jpg改rar

windows消息钩子注册底层机制浅析的更多相关文章

  1. windows消息钩子

    1.消息钩子的概念: Windows应用程序是基于消息驱动的,不论什么线程仅仅要注冊窗体类都会有一个消息队列用于接收用户输入的消息和系统消息.为了拦截消息,Windows提出了钩子的概念.钩子(Hoo ...

  2. Dll注入:Windows消息钩子注入

    SetWindowsHook() 是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的.当消息到达后,在目标窗口处理函数之 ...

  3. windows消息机制(转)

    1. 引言Windows 在操作系统平台占有绝对统治地位,基于Windows 的编程和开发越来越广泛.Dos 是过程驱动的,而Windows 是事件驱动的[6],这种差别的存在使得很多Dos 程序员不 ...

  4. 我对windows消息机制的理解(参考深入浅出MFC,欢迎批评指正!!)

    以消息为基础,以事件驱动之 程序的进行依靠外部消息来驱动,即:程序不断等待任何可能的输入,然后做判断,然后再做适当的处理. 消息输入:操作系统捕获,以消息形式进入程序.(操作系统通过其USERS模块中 ...

  5. <Win32_1>深入浅出windows消息机制[转自crocodile_]

    上学期学习了Java ,感觉Java写一个窗口真心简单,很易上手,也就难怪很多开发人员选择Java作为自己的开发编程语言.但是由于自身对windows的热爱,让我觉得c.c++语言才是我亲睐的编程语言 ...

  6. 深入Delphi -- Windows 消息机制

    http://www.txsz.net/xs/delphi/3/Windows%20%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6.htm Windows 消息机制 by m ...

  7. 收藏:Windows消息机制

    百度百科介绍的windows消息机制也不错:http://baike.baidu.com/view/672379.htm Windows的应用程序一般包含窗口(Window),它主要为用户提供一种可视 ...

  8. 理解Windows消息循环机制

    理解消息循环和整个消息传送机制对Windows编程十分重要.如果对消息处理的整个过程不了解,在windows编程中会遇到很多令人困惑的地方. 什么是消息(Message)每个消息是一个整型数值,如果查 ...

  9. windows消息的循环机制

    首先来了解几个基本概念: 消息:在了解什么是消息先来了解什么是事件.事件可分为几种,由输入设备触发的,比如鼠标键盘等等.由窗体控件触发的,比如button控件,file菜单等.还有就是来自Window ...

随机推荐

  1. bzoj1616

    水水啊,直接搜就行,不过bfs好像会mle(一定是我太菜了QAQ) #include<iostream> #include<algorithm> #include<cst ...

  2. js,java,浮点数运算错误及应对方法

    js,java浮点数运算错误及应对方法 一,浮点数为什么会有运算错误 IEEE 754 标准规定了计算机程序设计环境中的二进制和十进制的浮点数自述的交换.算术格式以及方法. 现有存储介质都是2进制.2 ...

  3. <<< 判断提交方式是get还是post

    if("GET".equals(request.getMethod())){ System.out.println("提交方式是GET"); }else if( ...

  4. 我们为什么要学习Java

    有人说Java已死,学Java还有前途吗? 这怎么可能呢? “Java已死”的论断从12年开始就反复有人提,但是直到今天,Java仍然活着,2015年还荣登TIOBE指数的编程语言奖,成为年度冠军.如 ...

  5. How to create vlan on Linux (with Cisco Catalyst Switch)

    In this article I want to share to you on how to create and configure vlan on Linux through Cisco Ca ...

  6. Java学习笔记10

    31.编写当年龄age大于13且小于18时结果为true的布尔表达式age > 13 && age < 18 32.编写当体重weight大于50或身高大于160时结果为t ...

  7. thinkphp 导出exl功能

    /** * 导出数据为excel表格 *@param $data 一个二维数组,结构如同从数据库查出来的数组 *@param $title excel的第一行标题,一个数组,如果为空则没有标题 *@p ...

  8. 在.net中使用GAC

    转自:http://blog.log4d.com/2011/01/gac/ GAC GAC是什么?是用来干嘛的?GAC的全称叫做全局程序集缓存,通俗的理解就是存放各种.net平台下面需要使用的dll的 ...

  9. NOIP2009 Hankson的趣味题

    题目描述 Description Hanks 博士是BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫Hankson.现在,刚刚放学回家的Hankson 正在思考一个有趣的问题.今天在 ...

  10. PHP流式上传和表单上传(美图秀秀)

    最近需要开发一个头像上传的功能,找了很多都需要授权的,后来找到了美图秀秀,功能非常好用. <?php /** * Note:for octet-stream upload * 这个是流式上传PH ...