<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    windows消息機制詳解

    一顆小胡椒2022-06-10 16:15:55

    前言

    windows是一個消息驅動的系統,windows的消息提供了應用程序之間、應用程序與windows 系統之間進行通信的手段。要想深入理解windows,消息機制的知識是必不可少的。

    基礎

    進程接收來自于鼠標、鍵盤等其他消息都是通過消息隊列進行傳輸的

    常規模式下,有一個專用的進程來接收這些消息,然后再插入某個進程的消息隊列,但是這樣的話會涉及到頻繁的進程間的通信,效率很差

    windows為了解決這一問題,因為高2G的內核空間每個進程都是共用的,所以微軟想到把消息的接收放到了0環,使用GUI線程

    <1> 當線程剛創建的時候,都是普通線程,指向的是SSDT表

    Thread.ServiceTable-> KeServiceDescriptorTable

    <2> 當線程第一次調用Win32k.sys時,會調用一個函數:PsConvertToGuiThread,我們知道在3環進0環的過程中會取得一個調用號,當調用號在100以下的時候,在ntosknl.exe里面,當調用號大于100則是圖形處理函數,調用Win32k.sys

    如果是一個GUI線程,win32Thread指向的就是THREADINFO結構,如果是普通線程,這里就是一個空指針

    主要做幾件事:

    a. 擴充內核棧,必須換成64KB的大內核棧,因為普通內核棧只有12KB大小。

    b.創建一個包含消息隊列的結構體,并掛到KTHREAD上。對應的就是MessageQueue屬性

    c.Thread.ServiceTable-> KeServiceDescriptorTableShadow,把Thread.ServiceTable指向SSDTShadow表,這個表既包含了SSDT表里面的函數,又包含了win32k.sys里面的圖形函數

    d.把需要的內存數據映射到本進程空間

    總結:

    <1> 消息隊列存儲在0環,通過KTHREAD.Win32Thread可以找到

    <2> 并不是所有線程都要消息隊列,只有GUI線程才有消息隊列

    <3> 一個GUI線程對應1個消息隊列

    窗口與線程

    我們知道創建windows窗口使用的是CreateWindow,而這個函數底層調用的是CreateWindowExACreateWindowExW,我們逆向分析一下CreateWindowExW

    首先調用CreateWindowEx

    然后調用VerNtUserCreateWindowEx

    再調用NtUserCreateWindowEx

    通過NtUserCreateWindowEx進入0環

    windows窗口都在0環有一個結構體,就是WINDOW_OBJECTpti即窗口對象指向的線程。一個線程可以對應多個窗口,但是在同一個程序里面多個窗口只能對應一個線程

    總結

    1、窗口是在0環創建的

    2、窗口句柄是全局的

    3、一個線程可以用多個窗口,但每個窗口只能屬于一個線程

    一個GUI線程只有一個消息隊列,一個線程可以有很多個窗口,一個線程中所有的窗口共享同一個消息隊列

    消息的接收

    首先在3環創建窗口和窗口類的對象,對應0環的_WINDOW_OBJECT結構

    消息隊列的結構

    <1> SentMessagesListHead //接到SendMessage發來的消息
    <2> PostedMessagesListHead //接到PostMessage發來的消息
    <3> HardwareMessagesListHead //接到鼠標、鍵盤的消息
    

    如果要取所有隊列的消息,則第二個參數設置為NULL,后兩個參數全部設置為0

    GetMessage的主要功能:循環判斷是否有該窗口的消息,如果有,將消息存儲到MSG指定的結構,并將消息從列表中刪除。

    GetMessage(  LPMSG lpMsg,  //返回從隊列中摘下來的消息
      HWND hWnd,  //過濾條件一:發個這個窗口的消息
      UNIT wMsgFilterMin, //過濾條件
      UNIT wMsgFilterMax //過濾條件
    );
    

    使用GetMessage()獲取信息,另外一個程序利用SendMessage發送給窗口,這里GetMessage會接收到消息并直接處理

    NtUserGetMessage

    User32!GetMessage 調用 w32k!NtUserGetMessage

    do
    {
     //先判斷SentMessagesListHead是否有消息 如果有處理掉
     do
     {
      ....
      KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,
                                   Arguments,
                                   ArgumentLength,
                                   &ResultPointer,
                                   &ResultLength);
      ....
     }while(SentMessagesListHead != NULL)
     //以此判斷其他的6個隊列,里面如果有消息 返回  沒有繼續
    }while(其他隊列!=NULL)
    

    SendMessage/PostMessage

    SendMessage為同步,PostMessage為異步,GetMessage只處理第一個鏈表即SentMessagesListHead里面的消息

    當一個程序利用SendMessage向另外一個程序發送消息時,另外一個程序會用GetMessage接收,這個過程GetMessage會在0環的SentMessagesListHead鏈表里面搜索是否存在SendMessage,如果存在SendMessageGetMessage就會在兩個程序的共享內存里面向發送消息的程序發送一個結果,在這個過程中,發送消息的程序是一直處于等待狀態的,只有接收到返回的消息才會結束,這稱為同步

    如果利用PostMessage發送消息,處于第二個鏈表里面,GetMessage不會處理,而程序發完消息之后也會立即結束,不會有等待的過程,這成為異步,如果要處理,使用DispatchMessage()處理

    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    

    消息的分發

    這里如果只有GetMessage的話,關閉窗口是關閉不了的

    DispatchMessage

    User32!DispatchMessage 調用 w32k!NtUserDispatchMessage

    <1> 根據窗口句柄找到窗口對象

    <2> 根據窗口對象得到窗口過程函數,由0環發起調用

    如果使用DispatchMessage分發消息,根據窗口句柄調用相關的窗口過程,即可關閉

    因為很多個消息共用一個消息隊列,所以通過GetMessage取出消息之后,需要用DispatchMessage進行消息的分發

    DispatchMessage通過GetMessage取出的句柄,進入0環找到Window_Object對象,再找到對應的窗口過程調用

    TranslateMessage是用來處理鍵盤輸出的函數,定義一個函數

     case WM_CHAR:
      {
       sprintf(szBuffer, "Down : %c", wParam);
       MessageBox(hwnd, szBuffer, "", 0);
       return 0;
      }
    

    這里如果不使用TranslateMessage,則沒有WM_CHAR這個消息,需要自己定義WM_KEYDOWN

     case WM_KEYDOWN:
      {
       sprintf(szBuffer, "Down : %d", wParam);
       MessageBox(hwnd, szBuffer, "", 0);
       return 0;
      }
    

    消息有很多,但是不是每個消息都需要我們自己去處理,所以與我們無關的消息就使用windows提供的DefWindowProc讓微軟替我們處理即可

    內核回調機制

    窗口過程函數除了GetMessageDispatchMessage 能夠調用,一些在0環的函數也能夠直接進行調用。例如CreateWindow不向消息隊列里面發送消息,而是直接調用3環提供的函數

    這些消息類型可以被直接調用

    這里對WM_CREATE進行修改,當創建成功的時候彈窗

    這里并沒有執行到GetMessageTranslateMessage就彈窗,說明被CreateWindow調用0環函數,0環函數通過回調機制(KeUserModeCallBack),再調用窗口過程函數

    所以調用窗口過程只能是以下三種情況

    <1> GetMessage()在處理SentMessagesListHead中消息時
    <2> DispatchMessage()在處理其他隊列中的消息時
    <3> 內核代碼
    

    1、從0環調用3環函數的幾種方式:

    APC、異常、內核回調

    2、凡是有窗口的程序就有可能0環直接調用3環的程序。回調機制中0環調用3環的的代碼是函數:KeUserModeCallback

    3、回到3環的落腳點:

    APC:ntdll!KiUserApcDispatcher

    異常:ntdll!KiUserExceptionDispatcher

    KeUserModeCallback

    KeUserModeCallback在0環對應NtUserDispatchMessage,調用IntDispatchMessage。通過UserGetWindowObject獲得一個Window_Object類型,通過對象得到當前窗口的對應的窗口函數,然后調用co_IntCallWindowProc

    調用KeUserModeCallback,第一個值為索引,第二個值為窗口回調過程中所有有用的信息。第一個索引值, KeUserModeCallback函數的第一個參數就是索引,其實它是一個宏,有很多個對應的值

    內核回調在3環的落腳點,有很多個地方,我們拿著索引去3環里面找回調函數地址表,如果索引為0,則取表里面的第一個函數,如果索引為1,則取表里面的第二個函數

    PEB+0x2C 回調函數地址表,由user32.dll提供

    這里打開一個exe,通過fs:[0]找到TEB

    TEB的0x30偏移為PEB

    PEB的0x2C偏移即為回調地址函數表

    這里通過KeUserModeCallback的第一個值,即索引找到函數之后,這個函數再去調用窗口過程函數,窗口過程函數已經通過Arguments放在了堆棧里面

    消息隊列消息機制
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    windows消息機制詳解
    2022-06-10 16:15:55
    要想深入理解windows,消息機制的知識是必不可少的。
    在Windows大部分應用都是基于消息機制,他們都擁有一個消息過程函數,根據不同消息完成不同功能,windows通過鉤子機制來截獲和監視系統中的這些消息。一般鉤子分局部鉤子與全局鉤子,局部鉤子一般用于某個線程,而全局鉤子一般通過dll文件實現相應的鉤子函數。
    全局鉤子注入在Windows大部分應用都是基于消息機制,他們都擁有一個消息過程函數,根據不同消息完成不同功能,windows通過鉤子機制來截獲和監視系統中的這些消息。一般鉤子分局部鉤子與全局鉤子,局部鉤子一般用于某個線程,而全局鉤子一般通過dll文件實現相應的鉤子函數。
    代碼框架 想法是盡量用一個通用的注入框架,有異常接收,令牌權限開啟,獲取進程PID的功能
    簡介這次實驗是在WIN7 X86系統上進程,使用的編譯器是VS2017。所謂的DLL注入,其實就是在其他的進程中把我們編寫的DLL加載進去。所以DLL注入的核心就是把要注入的DLL的路徑寫到目標進程中,然后在目標進程中調用LoadLibrary函數,并且指定參數為保存了DLL路徑的地址。要實現DLL注入,首先就要創建一個用來注入的DLL。
    添加消息的任務我們稱為producer,而取出并使用消息的任務,我們稱之為consumer。kafka應運而生,它是專門設計用來做消息中間件的系統。這兩點也是kafka要解決的核心問題。為此,kafka提出了partition的概念。由于消息不會被刪除,因此可以等消費者明確告知kafka這條消息消費成功以后,再去更新游標。對于同一個topic,不同的消費組有各自的游標。
    分布式流平臺Kafka
    2022-08-02 10:13:27
    無論消息是否被消費,Kafka集群都會持久的保存所有發布的消息,直到過期。Kafka中采用分區的設計主要有兩個目的:第一,當日志大小超過了單臺服務器的限制,允許日志進行擴展。在Kafka中實現消費的方式是將日志中的分區劃分到每一個消費者實例上,以便在任何時間,每個實例都是分區唯一的消費者。
    在介紹Nginx的負載均衡實現之前,先簡單的說下負載均衡的分類,主要分為硬件負載均衡和軟件負載均衡 ,硬件負載均衡是使用專門的軟件和硬件相結合的設備,設備商會提供完整成熟的解決方案,比如F5,在數據的穩定性以及安全性來說非常可靠,但是相比軟件而言造價會更加昂貴;軟件的負載均衡以Nginx這類軟件為主,實現的一種消息隊列分發機制
    Dll注入
    2021-11-08 14:57:41
    最近太忙啦XDM,又在做一些列的分析復現工作量有點大,更新要慢一點了。一致,也不會覆蓋其他的進程信息。
    如果是在消費端丟失數據,那么多次消費結果完全一模一樣的幾率很低。這時已經fetch的數據還沒有處理完成但已經被commit掉,因此沒有機會再次被處理,數據丟失。網絡負載很高或者磁盤很忙寫入失敗的情況下,沒有自動重試重發消息。
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类