CVE-2021-40449 NtGdiResetDC UAF


背景

  CVE-2021-40449是一個存在於Win32k內核驅動中的UAF漏洞。該漏洞在2021年八月下旬九月上旬被Kaspersky發現用於野外攻擊活動中。通過Hook win32k驅動執行 NtGdiResetDC 過程中發生的用戶模式回調,完成對目標對象的釋放和占用,最終實現指定內核函數的調用,以進行內核內存的讀寫操作,修改利用對象的Token權限,實現EOP。

分析

  此次分析是在Windows 10 1809中進行。
  首先在用戶模式調用 CreateDC 時,會執行至win32k內核調用 win32kfull!NtGdiResetDC ,再執行至 win32kbase!hdcOpenDCW ,調用堆棧如下:

......  win32kbase!PDEVOBJ::PDEVOBJ
......  win32kbase!hdcOpenDCW+0x240
......  win32kfull!GreResetDCInternal+0x11a
......  win32kfull!NtGdiResetDC+0xd6
......  nt!KiSystemServiceCopyEnd+0x25
......  win32u!NtGdiResetDC+0x14
......  gdi32full!ResetDCWInternal+0x16b
......  GDI32!ResetDCW+0x31
......  CVE_2021_40449!main

  執行的用戶回調主要發生在 win32kbase!PDEVOBJ::PDEVOBJ 中,該函數應是一個 PDEV 對象的初始化函數,和 win32kfull!NtGdiResetDC 傳入參數中的 HDC 有關聯。初始化函數中有兩個用戶回調: PDEVOBJ::EnablePDEV 、 PDEVOBJ::CompletePDEV 。這兩個用戶回調主要是對 HDC 中的 PDEV 對象進行操作, PDEV 對象通過 PDEV::Allocate 分配內存。


  執行完初始化函數,回到 hdcOpenDCW ,繼續執行至 GreCreateDisplayDC ,該函數初始化一個 PDC 對象,並將上面初始化的 PDEV 對象的內存地址放到 PDC 偏移 +0x30 處。

  然后返回 PDC 0 偏移處的 DC 句柄值 HDC ,該值也作為 win32kbase!hdcOpenDCW 的返回值,返回值 win32kfull!GreResetDCInternal 。
   hdcOpenDCW 返回的 HDC 傳入 DCOBJ::DCOBJ ,返回 hdcOpenDCW 初始化的 PDC 對象的內存地址。

  接着讀取 PDEV 對象 0xAB8 偏移處的函數指針並執行,注意此處的 PDEV 並不是在上一步的 hdcOpenDCW 中初始化的,而是在用戶態調用 ResetDC 前,調用 CreateDC 生成的。為進行區分,本文中將其稱為 HDC_user 。
   GreResetDCInternal 的函數參數 HDC_user ,同樣通過 DCOBJ::DCOBJ 返回 PDC_user 對象,該對象偏移 0x30 處為 PDEV_user 對象的內存地址。
  取 PDEV_user 偏移 0xAB8 處函數指針,執行 UMPDDrvResetPDEV ,傳入參數分別為 PDEV_user 和 PDEV_kernel 偏移 0x708 處的指針,指向各自的 DEVMODE 結構,這里同樣會發生一次用戶態函數回調,不過該回調不進行考慮,因為此漏洞利用范圍內,被利用的主要是該指針。

  完成 UMPDDrvResetPDEV 回調后,執行 win32kbase!HmgSwapLockedHandleContents ,該函數會將 PDC_user 和 PDC_kernel 首部的 HDC 值和 PDC 的 引用計數值 進行了互換,從而完成 devmode 修改的功能。

  后面則是將兩個 PDC 對象的引用計數值分別減 1 ,並調用 win32kbase!bDeleteDCInternal 將 HDC_kernel 索引到的 PDC 對象偏移 0x30 處指針指向的 PDEV 對象引用計數值減 1 ,值變為 0 。而又因為之前的 HmgSwap 操作,這里的 PDC 和 PDEV 實際都是用戶傳入的 HDC 原本指向的對象。
  根據MSDN所說,“當該計數器降至零,該對象就會被釋放”“一旦句柄計數減為零,對象的名稱就會從對象管理器的命名空間中刪除”。意味着該對象可以被占用,而 hdcOpenDCW 中又存在用戶回調,在用戶回調中再對相同的 HDC 執行一次 ResetDC ,那么該 HDC 對應 PDEV 對象引用值將減為 0 ,占用該 PDEV 對象后結束回調,回到內核。
  至於漏洞的觸發點,在原本的 UMPDDrvResetPDEV 調用處,該調用發生在 hdcOpenDCW 之后,調用函數的地址從 PDEV_user 中獲取,通過占用,可以獲取到修改器調用目標為一個內核讀寫函數。

利用

  該UAF漏洞的利用主要為以下幾個步驟:

  1. 使用 NtQuerySystemInformation 獲取利用進程 Token.Privileges 在內核中的位置;
  2. 泄露出一個可以用於內核寫的內核函數,這里比較通用是 nt!RtlSetAllBits ;
  3. 構造一個 Fake_RTL_BITMAP ,作為 nt!RtlSetAllBits 函數參數,大多使用 ThreadName 的方式進行構造,不過同樣也可以手動申請一片用戶態內存進行構造;
  4. HOOK用戶回調 DrvEnablePDEV (Hook DrvCompletePDEV 雖然可以成功占用,但執行不到漏洞觸發點),在Hook函數中對相同 HDC 再執行一次 ResetDC ,返回后使用構造的 Fake Palette 去占用被釋放的 PDEV 對象,然后結束當前回調;
  5. 漏洞觸發,當前進程權限位全部被啟用,完成提權。

  在Hook函數中完成占用后的內存布局前后對比如下所示:

  PDEV對象占用成功后,完成回調,返回 GreResetDCInternal ,可以看到成功地調用到 nt!RtlSetAllBits 。

   nt!RtlSetAllBits 中僅將 rcx 作為參數,而漏洞觸發處的第一個參數 rcx 同樣可以通過占用指定。
   nt!RtlSetAllBits 中取 rcx 地址 0x08 偏移處的 QWORD 作為寫入的目標地址,而 rcx 偏移 0 處的 DWORD 值整除 0x40 后作為計數值,每次向目標地址寫入 rax 寄存器的值, rax 固定為 0xffffffffffffffff 。


  POC代碼

總結

  這次我分析這個漏洞時嘗試盡量不看網上公開的POC,僅根據Kaspersky的文章尋找漏洞位置,結果花了很多時間,遇到挺多問題的。比如尋找漏洞點時,不會出現 BSOD ,並且 !pool 不能馬上看到對象內存狀態變成 free ,還是去瞄了一些公開的POC,確認自己方向沒問題。
  emmm最后好歹自己完成了POC,雖然耗時長且代碼拉胯,相比那些優秀的POC通用性低,但是收獲也很多,起碼漏洞前后附近的代碼各個角落都翻了一遍,而且一些坑下次可以避免。

參考

[1] MysterySnail attacks with Windows zero-day
[2] CVE-2021-40449 Exploitation


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM