萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 網路安全 >  >> 病毒防護

MS05-055 漏洞深度分析:APC 鏈表操作導致的內核提權

漏洞背景

MS05-055 是一個經典的 Windows 內核漏洞,EEYE 的公告已對其有詳細描述。本文將從源碼層面深入剖析其成因與利用過程。

核心漏洞機制

當線程退出時,PspExitThread 會從 ETHREAD.ApcState.ApcListHead[0] 和 ApcListHead[1] 分離線程的 APC 隊列,使每個隊列成為一個被摘除鏈表頭的循環雙向鏈表。若某 APC 從配額池分配,則佔用分配進程內核對象一個引用。在處理配額池中的 APC 時,若分配進程已終止且該引用是進程對象的最後一個引用,PspExitThread 在調用 ExFreePool 釋放 APC 過程中會進一步調用 PspProcessDelete 銷毀進程對象。

漏洞關鍵在於銷毀進程對象時會調用 KeStackAttachProcess 和 KeUnstackDetachProcess,這兩個函數均會調用 KiMoveApcState 分別保存和恢復 APC 鏈表。問題出在第二次調用 KiMoveApcState 時,將先前已被摘除的鏈表頭重新接回 APC 雙向鏈表,導致後續處理 APC 隊列時發生 ExFreePool(ETHREAD+0x30),其中 ETHREAD 為正在退出的線程內核對象。

函數調用鏈

PspExitThread
  KeFlushQueueApc
    (分離 APC 隊列)
    (APC 釋放循環開始)
    ExFreePool(1st_APC -- 由已退出進程排隊)
      ExFreePoolWithTag(1st_APC)
        ObfDereferenceObject(exited_process)
          ObpRemoveObjectRoutine
            PspProcessDelete
              KeStackAttachProcess(exited_process)
                KiAttachProcess
                  KiMoveApcState(ETHREAD.ApcState --> duplicate)
                  KiSwapProcess
              PspExitProcess(0)
              KeUnstackDetachProcess
                KiMoveApcState(duplicate --> ETHREAD.ApcState)
                KiSwapProcess
  ExFreePool(2nd_APC)

關鍵代碼分析

在 KeFlushQueueApc 中,摘除鏈表頭的代碼:

RemoveEntryList(&Thread->ApcState.ApcListHead[ApcMode]);
NextEntry = FirstEntry;

#define RemoveEntryList(Entry) {
  PLIST_ENTRY _EX_Blink;
  PLIST_ENTRY _EX_Flink;
  _EX_Flink = (Entry)->Flink;
  _EX_Blink = (Entry)->Blink;
  _EX_Blink->Flink = _EX_Flink;
  _EX_Flink->Blink = _EX_Blink;
}

這將以 ApcListHead 為頭的雙向鏈表進行摘除操作,原鏈表頭指向第一項。

問題代碼在第二個 KiMoveApcState,恢復 APC 狀態時:

First = Source->ApcListHead[KernelMode].Flink;
Last = Source->ApcListHead[KernelMode].Blink;
Destination->ApcListHead[KernelMode].Flink = First;
Destination->ApcListHead[KernelMode].Blink = Last;
First->Blink = &Destination->ApcListHead[KernelMode];
Last->Flink = &Destination->ApcListHead[KernelMode];

這兩句將鏈表頭重新接回雙向鏈表,導致後續循環釋放 APC 結構時,循環到了 ETHREAD+0x3c(UserMode 的 APC),將其誤認為 KAPC+0xc (LIST_ENTRY) 處理,調用 ExFreePool(Apc) 時,該 "Apc" 地址實為 ETHREAD+0x30,實際池頭結構從 ETHREAD+0x28 KernelStack 開始。

利用條件構造

當 KernelStack 可作為合法頭結構時,會將頭結構中的 ProcessBilled 視為 EPROCESS 進行釋放,此時該 "EPROCESS" 為用戶態地址,通常為 0x200(因 State=2)。因該地址處於地址空間第一頁,不可訪問,故需使 ETHREAD 中 State 之後的成員不為 0,即 Alerted 數組中某項不為 0。Alerted 分別記錄線程在用戶態和內核態是否被提醒,各佔一字節。在用戶態下置內核態 Alerted 為 1 較難,但可置用戶態 Alerted 為 1。掛起目標線程後,在其被插入 APC 前調用 ZwAlertThread 觸發 KeAlertThread,可使 Alerted[UserMode]=1(前提:線程非 Alertable 狀態)。此時偽造的 EPROCESS 地址為 0x1000200。

KeAlertThread 中置位 Alerted 的邏輯:

if (Alerted == FALSE) {
  if ((Thread->State == Waiting) && (Thread->Alertable == TRUE) &&
      (AlertMode <= Thread->WaitMode)) {
    KiUnwaitThread(Thread, STATUS_ALERTED, ALERT_INCREMENT);
  } else {
    Thread->Alerted[AlertMode] = TRUE;
  }
}

在該用戶態地址 0x1000200 映射偽造數據,後續 ObfDereferenceObject 處理中會減少 EPROCESS 對象頭引用,引用歸零時銷毀結構,並調用對象頭(OBJECT_HEADER)中對象類型(OBJECT_TYPE) 的函數指針,從而獲取控制權。

copyright © 萬盛學電腦網 all rights reserved