windows消息处理过程及消息钩--发文于2013-11-3

应用层发消息: 发送消息过程 SendMessage(user32.dll)->SendMessageWorker,先检查有没有hook消息钩子,有的话调用CsSendMessage,进入消息钩子过滤函数。
没有的话,看是不是系统消息,是的话在Message表中找到对应msg id的索引值,通过索引值在在gapfnScSendMessage数组中找到对应的消息处理函数
如果是NtUserMessageCall的话,则进驱动处理消息。

进驱动层发消息: 进入NtUserMessageCall后,如果是系统消息,同上,在gapfnMessageCall数组中找到对应的处理函数。比如msg id为0x112,最小化窗口消息,
调用过程为NtUserfnNCDESTORY->xxxWrapSendMessage->xxxSendMessageTimeout->xxxInterSendMsgEx,在这个函数中,先通过AllocSMS分配一个消息结构体,
在将消息加入到目标线程的pSMSReceiveList链表中,代表目标线程有要接受的消息,然后调用SetWakeBit唤醒目标线程,激发目标线程的EventQueueServer,然后等待
自己的EventQueueServer被激发。然后调用xxxSleepThread是自己处于等待状态。在等待前,调用xxxReceiveMessage检查是否有hook消息钩子,如果有的话,回调CallClientProc
->xxxHkCallHook 分发处理消息钩子函数。 自己会setevent,while死循环阻塞在KeWaitForSingleObject等待。在此函数中,先检查是哪种互斥对象,如果对象被激活,则调用处理函数。否则KiSwapThread通知cpu切换为别的线程运行。调用KiComputeWaitInterval继续等待。

安装钩子过程:进入驱动调用NtUserSetWindowsHookEx->zzzSetWindowsHookEx修改目标线程的ptiThread->aphkStart[nFilterType + 1]钩子数组。

键盘按键:ioapic寄存器接收,读出index和哪个cpu处理,local apic发给键盘中断处理例程,再传给键盘端口驱动,端口类驱动。==win32k的RawInputThread读顶层类设备状态,读出按键扫描码,经过底层键盘钩子处理,匹配是win,alt+tab键等对应处理,再匹配普通字符,发给顶层窗口的线程,进入wm_char处理



消息优先级:QS_SENDMESSAGE > QS_POSTMESSAGE > QS_QUIT > QS_INPUT > QS_PAINT > QS_TIMER

msg:   112
user消息用的是win32k自己的一套通信方式,最多就用了个event来同步
跨线程的WM_COPYDATA没有使用共享内存,反而复制了两次数据
发送者SendMessage->xxxSendMessageTimeout->xxxInterSendMsgEx(UserAllocPoolWithQuota分配内核内存,将用户数据复制到内核空间)->SetWakeBit唤醒接受者->SetWakeBit等待应答
接受者xxxReceiveMessage->XXXSENDMESSAGETOCLIENT(宏)->ScSendMessageSMS(也是宏)->SfnCOPYDATA(sender side)->CaptureCallbackData(把数据从内核空间复制到用户空间)->KeUserModeCallback(转到用户模式)->SfnCOPYDATA(receiver side)->窗口过程->回到内核模式,应答发送者


回调函数什么时候被调用, 每个回调函数的情况都不一样, 就如你老板说的, 是系统定义好的, 对方(系统/库)对这个回调函数的文档说明一般只讲个大概不会讲细节, 你想弄清楚"触发机制", 那就去看对方的源码, 具体在哪些情况下哪些代码会call你的callback函数. 如果对方文档不详源码不给, 那就要靠自己的经验去猜去反汇编.
1) comp
comp可算是最简单的callback, 你调用qsort时把comp(你/crt库之间的callback)作为参数, 告诉对方(crt库)比较大小时call你这个comp. 接下去qsort立即触发comp, 反复调用comp直至qsort返回.

[] Vc7\crt\src\qsort.c
void __cdecl qsort (
    void *base,
    size_t num,
    size_t width,
    int (__cdecl *comp)(const void *, const void *)
    )
{
        for (;;) {
            if (mid > loguy) {
                do  {
                    loguy += width;
                } while (loguy < mid && comp(loguy, mid) <= 0);
            }
            if (mid <= loguy) {
                do  {
                    loguy += width;
                } while (loguy <= hi && comp(loguy, mid) <= 0);
            }
        }
}

2) ThreadProc
你调用AfxBeginThread时把ThreadProc(你/mfc库之间的callback)作为参数, 告诉对方(mfc库)线程开始执行时call你这个ThreadProc.

AfxBeginThread把ThreadProc作为CWinThread内部参数保存起来, 调用成员函数CWinThread::CreateThread. CWinThread::CreateThread调用_beginthreadex时把_AfxThreadEntry(mfc库/crt库之间的callback)作为参数.

_beginthreadex把_AfxThreadEntry放在_ptiddata ptd, 调用Windows API CreateThread时把_threadstartex(crt库/系统之间的callback)作为参数.

CreateThread按原参数(加上hProcess)调用CreateRemoteThread. CreateRemoteThread把lpStartAddress(AfxBeginThread是_threadstartex, 如果你直接用Windows API CreateThread, 则是你的ThreadProc)放在CONTEXT ThreadContext. 并把ThreadContext.Eip设置为BaseThreadStartThunk(子系统/子系统之间的callback).

AfxBeginThread (pfnThreadProc = ThreadProc, pParam = Param)
 CWinThread::CWinThread (m_pfnThreadProc = pfnThreadProc = ThreadProc, m_pThreadParams = pParam = Param)
 CWinThread::CreateThread (startup.pThread = this)
  _beginthreadex (ptd->_initaddr = initialcode = _AfxThreadEntry, ptd->_initarg = argument = startup, CREATE_SUSPENDED)
   CreateThread (lpStartAddress = _threadstartex, lpParameter = ptd) (user mode/kernel mode切换)
    CreateRemoteThread (ThreadContext.Eip = BaseThreadStartThunk, ThreadContext.Eax = lpStartAddress, ThreadContext.Ebx = lpParameter)
     BaseCreateStack
     BaseInitializeContext
     NtCreateThread (ThreadContext = &ThreadContext, CreateSuspended = TRUE)
      PspCreateThread (ThreadContext = ThreadContext, StartRoutine = NULL, StartContext = NULL, Thread->StartAddress = ThreadContext->Eip)
       ObCreateObject 创建线程对象
       MmCreateKernelStack
       KeInitializeThread (SystemRoutine = PspUserThreadStartup, StartRoutine = NULL, StartContext = ThreadContext->Eip, ContextFrame = ThreadContext)
        KeInitializeApc (SuspendApc, KernelRoutine = KiSuspendNop, NormalRoutine = KiSuspendThread, NormalContext = NULL, ApcMode = KernelMode)
        KeInitializeSemaphore (SuspendSemaphore, Semaphore->Header.SignalState = 0, Semaphore->Limit = 2)
        KiInitializeContextThread (Thread->PreviousMode = TrFrame->PreviousPreviousMode = UserMode, SwitchFrame->RetAddr = KiThreadStartup)
         KeContextToKframes
       KeSuspendThread (if CreateSuspended)
        KiInsertQueueApc (SuspendApc)
       KeReadyThread
        KiReadyThread 把线程对象转到就绪状态
     CsrClientCallServer
     NtResumeThread (if !CREATE_SUSPENDED)
      KeResumeThread (Thread->SuspendSemaphore.Header.SignalState += 1)
       KiWaitTest (if Thread->SuspendSemaphore.Header.WaitListHead not empty)
        KiWaitSatisfyAny (Thread->SuspendSemaphore.Header.SignalState -= 1)
        KiUnwaitThread
         KiReadyThread (Thread->State = Ready)
  ResumeThread
  WaitForSingleObject (startup.hEvent)
  SuspendThread (if CREATE_SUSPENDED)
  SetEvent (startup.hEvent2)

|_
| SwitchFrame
|_
| PSystemRoutine (PspUserThreadStartup)
|_
| PStartRoutine (NULL)
|_
| PStartContext (BaseThreadStartThunk)
|_
| PUserContextFlag (1)
|_
| TrFrame (copy from ThreadContext)
|_
| NpxFrame
|_
  Thread->InitialStack

AfxBeginThread到达KiReadyThread之后就逐级返回. 线程切换轮到新线程, 新线程先处理SuspendApc, 等待SuspendSemaphore.
如果在处理SuspendApc之前, CreateRemoteThread已调用NtResumeThread, 那么SuspendSemaphore立即满足, 无需等待, 立即执行KiThreadStartup.
反之, 则转到等待状态, 直到CreateRemoteThread调用NtResumeThread之后, 又转到就绪状态. 线程切换又轮到新线程, 开始执行KiThreadStartup.

AfxBeginThread创建的线程总是CREATE_SUSPENDED, 在_beginthreadex返回后再ResumeThread, 在_AfxThreadEntry callback后再SuspendThread(如果你给AfxBeginThread参数有CREATE_SUSPENDED).

KiSuspendNop
KiSuspendThread
 KeWaitForSingleObject (SuspendSemaphore, WaitMode = KernelMode, Alertable = FALSE, Timeout = NULL)
  KiWaitSatisfyOther (if Semaphore->Header.SignalState > 0, Semaphore->Header.SignalState -= 1)
  KiSwapThread (else, Thread->State = Waiting)
   SwapContext

KiThreadStartup
 PspUserThreadStartup
  KeInitializeApc (StartApc, KernelRoutine = PspNullSpecialApc, NormalRoutine = PspSystemDll.LoaderInitRoutine, NormalContext = NULL, ApcMode = UserMode)
  KeInsertQueueApc (StartApc, SystemArgument1 = PspSystemDll.DllBase)
   KiInsertQueueApc (StartApc)
  DbgkCreateThread
 KiServiceExit2 (TFrame = &TrFrame, Thread->Tcb.Alerted = 0)
  KiDeliverApc (StartApc, PreviousMode = UserMode, ExceptionFrame = NULL, TrapFrame = TFrame, Thread->TrapFrame = TFrame)
   PspNullSpecialApc
    ExFreePool
   KiInitializeUserApc (TrapFrame = TFrame, TFrame->Eip = (ULONG)KeUserApcDispatcher, TFrame->HardwareEsp = UserStack)
    KeContextFromKframes
  iretd (esp = &TFrame->Eip)

|_UserStack
| NormalRoutine (PspSystemDll.LoaderInitRoutine)
|_
| NormalContext (NULL)
|_
| SystemArgument1 (PspSystemDll.DllBase)
|_
| SystemArgument2 (NULL)
|_
| ContextFrame (copy from ThreadContext)
|_
  ThreadContext.Esp

KeUserApcDispatcher (UserStartApc) 
KiUserApcDispatcher
 PspSystemDll.LoaderInitRoutine
 LdrInitializeThunk (NormalContext = &ContextFrame)
  LdrpInitialize (Context = NormalContext)
   LdrpInitializeThread
    LdrpAllocateTls
    LdrpCallInitRoutine (DLL_THREAD_ATTACH)
    LdrpCallTlsInitializers (DLL_THREAD_ATTACH)
 NtContinue (ContextRecord = &ContextFrame, TestAlert = TRUE) (user mode/kernel mode切换)
  KiContinue (TrFrame->Eip = ContextRecord->Eip)
   KiContinuePreviousModeUser
    KeContextToKframes
  KeTestAlertThread
  KiServiceExit2

BaseThreadStartThunk (eax = ThreadContext.Eax = lpStartAddress = _threadstartex, ebx = ThreadContext.Ebx = lpParameter = ptd)
 BaseThreadStart (lpStartAddress = _threadstartex, lpParameter = ptd)
  CsrNewThread
   NtRegisterThreadTerminatePort
  _threadstartex (ptd = ptd)
   _callthreadstartex (ptd->_initaddr = _AfxThreadEntry, ptd->_initarg = startup)
    _AfxThreadEntry (pStartup = startup, pThread = pStartup->pThread, pThread->m_pfnThreadProc = ThreadProc, pThread->m_pThreadParams = Param)
     AfxInitThread
      SetWindowsHookEx (WH_MSGFILTER)
     SetEvent (pStartup->hEvent)
     WaitForSingleObject (pStartup->hEvent2)
     ThreadProc (lpParameter = Param) 你/mfc库之间的callback绕了一大圈终于被调用
     AfxEndThread
    _endthreadex
  ExitThread

3) WndProc
WndProc的情况就复杂多了, 前几天我只是想弄清楚窗口移动时几个消息的先后顺序和作用, 花了好几天时间才管见一斑. 要是把WndProc被调用(几百个消息各种情况下)的"触发机制"全弄清楚了, 也就是对Windows的窗口子系统了如指掌了.

xxxMoveWindow/xxxSetWindowPlacement
 xxxSetWindowPos

xxxSetWindowPos/xxxMinMaximize
 InternalBeginDeferWindowPos
 _DeferWindowPos
 xxxEndDeferWindowPosEx
  xxxCalcValidRects
   WM_WINDOWPOSCHANGING (if !SWP_NOSENDCHANGING)
    xxxDefWindowProc
     xxxAdjustSize (if !SWP_NOSIZE)
      xxxInitSendValidateMinMaxInfo
       WM_GETMINMAXINFO
   WM_NCCALCSIZE (if !SWP_NOSIZE || SWP_FRAMECHANGED)
    xxxDefWindowProc
     xxxCalcClientRect
      GetCaptionHeight
      GetWindowBorders
      xxxMenuBarCompute
  xxxDoSyncPaint
  xxxSendChangedMsgs
   WM_WINDOWPOSCHANGED (if !SWP_NOCHANGE)
    xxxDefWindowProc
     xxxHandleWindowPosChanged
      WM_MOVE (if !SWP_NOCLIENTMOVE)
      xxxSendSizeMessage (if !SWP_NOCLIENTSIZE)
       WM_SIZE

WM_NCLBUTTONDOWN
 xxxDefWindowProc
  xxxDWP_NCMouse
   WM_SYSCOMMAND (SC_SIZE + ht - HTSIZEFIRST + WMSZ_SIZEFIRST)

WM_NCLBUTTONUP
 xxxDefWindowProc
  xxxDWP_NCMouse
   xxxHandleNCMouseGuys
    WM_SYSCOMMAND

WM_SYSCOMMAND (SC_SIZE + ht - HTSIZEFIRST + WMSZ_SIZEFIRST)
 xxxDefWindowProc
  xxxSysCommand

xxxSysCommand (SC_RESTORE/SC_MINIMIZE/SC_MAXIMIZE)
 xxxShowWindow
  xxxMinMaximize
   CkptRestore
   InternalBeginDeferWindowPos
   _DeferWindowPos
   xxxEndDeferWindowPosEx (SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_STATECHANGE)

xxxSysCommand (SC_SIZE/SC_MOVE)
 xxxMoveSize
  xxxInitSendValidateMinMaxInfo
   WM_GETMINMAXINFO
  xxxMS_FlushWigglies
  bSetDevDragWidth
  xxxDrawDragRect
  WM_ENTERSIZEMOVE
  xxxCapture
  xxxMS_TrackMove

xxxMS_TrackMove (WM_MOUSEMOVE)
 xxxTM_MoveDragRect
  WM_SIZING
  xxxDrawDragRect

xxxMS_TrackMove (WM_LBUTTONUP)
 xxxTM_MoveDragRect
 xxxDrawDragRect
 xxxReleaseCapture
 xxxSetWindowPos
 WM_EXITSIZEMOVE

3.1) SendMessage
SendMessage的消息SDK文档称之为nonqueued messages, 没放到消息队列(mlPost/mlInput)里面, GetMessage不会得到SendMessage的消息.
mlPost是PostMessage的消息队列, 比如WM_HOTKEY, WM_TIMER, WM_LOGONNOTIFY. WM_QUIT一般没存放在队列(Desktop/console窗口例外), 是直接放到xxxReadPostMessage的参数.
mlInput是键盘鼠标和系统事件的消息队列. psmsReceiveList是异线程SendMessage的SMS(SendMessage Structure)队列. GetMessage只取回mlPost/mlInput的消息, SMS是另外处理的.

xxxInternalGetMessage
 while (TRUE) {
 while (QS_SENDMESSAGE) xxxReceiveMessage (pti->psmsReceiveList)
 if (QS_POSTMESSAGE) xxxReadPostMessage (pti->mlPost) break
 if (QS_INPUT|QS_EVENT) xxxScanSysQueue (pti->pq->mlInput) break
 xxxSleepThread
 }

#define QS_ALLINPUT (QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE)

typedef struct tagTHREADINFO { ...
    PQ              pq;                 // keyboard and mouse input queue
    PSMS            psmsReceiveList;    // SMSs to be processed
    MLIST           mlPost;             // posted message list.
} THREADINFO;

* Message Queue structure.
typedef struct tagQ { ...
    MLIST       mlInput;            // raw mouse and key message list.
} Q, *PQ;



SendMessage如果同线程, 是直接call WndProc. 如果异线程, 一般是调用NtUserMessageCall(特殊的消息是调用fnCOPYGLOBALDATA/fnEMGETSEL这些系统函数). NtUserMessageCall又根据不同的消息分别调用45个NtUserfn*系统函数. 比如WM_SETTEXT是调用NtUserfnINSTRINGNULL, 进而调用xxxInterSendMsgEx.

xxxInterSendMsgEx构建一个sms(SendMessage Structure), 把消息复制到sms, 如果消息有指针参数, 比如WM_SETTEXT的lParam是字符指针, 就分配系统内存把指针所指的内容复制过来, 把sms加到系统gpsmsList队列首, 再把sms加到ptiReceiver->psmsReceiveList队列尾. 接着设置目标线程的QS_SENDMESSAGE标志, 激发目标线程的EventQueueServer, 然后等待自己的EventQueueServer.  

目标线程一般多在消息循环的GetMessage中等待自己的EventQueueServer(如果正在处理消息, 处理完仍是回到GetMessage), EventQueueServer激发, 导致KeWaitForSingleObject返回, xxxSleepThread返回, 进入xxxReceiveMessage, 把sms从ptiReceiver->psmsReceiveList队列(从头到尾逐个处理)移除, 调用xxxSendMessageToClient进入usermode call WndProc再返回kernel mode, 接着设置源线程的QS_SMSREPLY标志, 激发源线程的EventQueueServer, 然后继续自己的消息循环.



源线程的EventQueueServer激发, 导致KeWaitForSingleObject返回, xxxSleepThread返回到xxxInterSendMsgEx, 把sms从系统gpsmsList队列移除, 释放系统内存和sms, 逐级返回到SendMessage.

SendMessage (同线程)
 SendMessageWorker
  UserCallWinProcCheckWow
   InternalCallWinProc
    WndProc

SendMessage (异线程)
 SendMessageWorker
  NtUserMessageCall (user mode/kernel mode切换)
   EnterCrit
   NtUserfnINSTRINGNULL (WM_SETTEXT)
    RtlInitLargeUnicodeString
    xxxWrapSendMessage (xParam = 0)
     xxxSendMessageTimeout (fuFlags = SMTO_NORMAL, uTimeout = 0, lpdwResult = NULL)
      xxxInterSendMsgEx (pism = 0, lRet = psms->lRet)
       AllocSMS (psms->spwnd = pwnd, psms->ptiCallBackSender = NULL, psms->flags = 0)
       HeavyAllocPool (dwFlags = DAP_USEQUOTA)
       HMAssignmentLock (psms->spwnd)
       SetWakeBit (pti = ptiReceiver, wWakeBit = QS_SENDMESSAGE)
        KeSetEvent (pti->pEventQueueServer, PriorityIncrement = 2, WaitImmediatelyFollowed = FALSE)
       KeSetKernelStackSwapEnable (Enable = FALSE)
       xxxSleepThread (fsWakeMask = QS_SMSREPLY, Timeout = 0, fInputIdle = FALSE)
        ClearQueueServerEvent
         KeClearEvent(ptiCurrent->pEventQueueServer)
        LeaveCrit 
        KeWaitForSingleObject (ptiCurrent->pEventQueueServer)
        EnterCrit
       KeSetKernelStackSwapEnable (Enable = OldState)
       SetWakeBit (pti = ptiSender, wWakeBit = QS_SMSREPLY)
       UnlinkSendListSms (ppsmsUnlink = NULL)
        HMAssignmentUnlock (psms->spwnd)
        UserFreePool
        FreeSMS
   LeaveCrit

xxxInternalGetMessage
 xxxSleepThread (fsWakeMask = QS_ALLINPUT | QS_EVENT | QS_ALLPOSTMESSAGE, Timeout = 0, fInputIdle = TRUE)
  ClearQueueServerEvent
   KeClearEvent(ptiCurrent->pEventQueueServer) 
  LeaveCrit
  KeWaitForSingleObject (ptiCurrent->pEventQueueServer, WaitReason = WrUserRequest, WaitMode = UserMode, Alertable = FALSE, Timeout = NULL)
  EnterCrit
 xxxReceiveMessage
  xxxSendMessageToClient
   SfnINSTRINGNULL
    AllocCallbackMessage
    CaptureCallbackData
    LeaveCrit
    KeUserModeCallback (ApiNumber = 0x1B)
     KiGetUserModeStackAddress
     KiCallUserMode ([esp].TsEip = KiUserCallbackDispatcher)
     KiServiceExit
     KiSystemCallExitBranch
     KiSystemCallExit2
     sysexit (kernel mode进入user mode)
     KiUserCallbackDispatcher
      __fnINSTRING
       FixupCallbackPointers 
       DispatchClientMessage
        UserCallWinProcCheckWow
         InternalCallWinProc
          WndProc
       XyCallbackReturn
       int 2b (user mode返回kernel mode)
     KiCallbackReturn
   EnterCrit
  SetWakeBit(pti = ptiSender, wWakeBit = QS_SMSREPLY)
   KeSetEvent (pti->pEventQueueServer)

你可能感兴趣的:(安全,内核,逆向,消息处理)