EEYE的公告中對這個漏洞已經描述得比較詳細了。線程在退出的時候(計算機愛好者,學習計算機基礎,電腦入門,請到本站http://.,我站同時提供計算機基礎知識教程,計算機基礎知識試題供大家學習和使用),,PspExitThread會從ETHREAD.ApcState.ApcListHead[0]和ApcListHead[1]分離線程的APC隊列,這樣每個隊列都會是一個被摘除鏈表頭的循環雙向鏈表。如果有一個APC是從配額池(Quota Pool)中分配的,則占用分配進程內核對象結構一個引用,PspExitThread在處理配額池中的APC時,若分配進程已終止且該配額池的引用是進程內核對象的最後一個引用,則會在調用ExFreePool釋放該APC的過程進而調用PspProcessDelete銷毀該進程對象。漏洞成因是在銷毀進程對象過程中會調用KeStackAttachProcess和KeUnstackDetachProcess,這兩個函數都會調用KiMoveApcState來分別保存和恢復APC鏈表,問題在於在第二次調用KiMoveApcState時,重新把前面已經被摘除的鏈表頭接回到APC雙向鏈表中,導致處理後續的APC隊列時會發生ExFreePool(ETHREAD+0x30),ETHREAD是正在退出的線程的內核對象。
引用EEYE的公告,發生漏洞時函數的調用順序:
. PspExitThread
. . KeFlushQueueApc
. . (detaches APC queues from ETHREAD.ApcState.ApcListHead)
. . (APC free loop begins)
. . ExFreePool(1st_APC -- queued by exited_process)
. . . 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)
現在詳細分析一下,在PspExitThread調用的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;\
}
RemoveEntryList對以ApcListHead為鏈表頭的雙向鏈表進行摘除鏈表頭處理,而原鏈表頭指向其中第一項。
問題代碼在第二個KiMoveApcState,把備份的APC狀態結構復制回原來的ETHREAD結構時:
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];
**這2句是原因,把鏈表頭重新接回雙向鏈表。
結果在後續循環釋放鏈表中的APC結構時,就循環到了ETHREAD+0x3c(UserMode的APC),把它當成了KAPC+0xc(LIST_ENTRY)來處理,調用ExFreePool(Apc)時,這個"Apc"的地址也就是ETHREAD+0x30,實際POOL的頭結構從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 the thread is currently in a Wait state, the Wait is alertable,
// and the specified processor mode is less than or equal to the Wait
// mode, then the thread is unwaited with a status of "alerted".
//
if ((Thread->State == Waiting) && (Thread->Alertable == TRUE) &&
(AlertMode <= Thread->WaitMode)) {
KiUnwaitThread(Thread, STATUS_ALERTED, ALERT_INCREMENT);
} else {
Thread->Alerted[AlertMode] = TRUE;
}
}
我們可以在這個用戶態地址0x1000200映射偽造的數據,之後在ObfDerefrenceObject處理中會減少EPROCESS對象頭結構的引用,當引用數遞減到0時就會銷毀該結構,並會調用對象頭結構(OBJECT_HEADER)中對象類型結構(OBJECT_TYPE)中的一些函數指針,我們將在這裡獲取控制權。
MS05-055漏洞分析.