EEYE的公告中對(duì)這個(gè)漏洞已經(jīng)描述得比較詳細(xì)了。線程在退出的時(shí)候(計(jì)算機(jī)愛(ài)好者,學(xué)習(xí)計(jì)算機(jī)基礎(chǔ),電腦入門(mén),請(qǐng)到本站,我站同時(shí)提供計(jì)算機(jī)基礎(chǔ)知識(shí)教程,計(jì)算機(jī)基礎(chǔ)知識(shí)試題供大家學(xué)習(xí)和使用),,PspExitThread會(huì)從ETHREAD.ApcState.ApcListHead[0]和ApcListHead[1]分離線程的APC隊(duì)列,這樣每個(gè)隊(duì)列都會(huì)是一個(gè)被摘除鏈表頭的循環(huán)雙向鏈表。如果有一個(gè)APC是從配額池(Quota Pool)中分配的,則占用分配進(jìn)程內(nèi)核對(duì)象結(jié)構(gòu)一個(gè)引用,PspExitThread在處理配額池中的APC時(shí),若分配進(jìn)程已終止且該配額池的引用是進(jìn)程內(nèi)核對(duì)象的最后一個(gè)引用,則會(huì)在調(diào)用ExFreePool釋放該APC的過(guò)程進(jìn)而調(diào)用PspProcessDelete銷(xiāo)毀該進(jìn)程對(duì)象。漏洞成因是在銷(xiāo)毀進(jìn)程對(duì)象過(guò)程中會(huì)調(diào)用KeStackAttachProcess和KeUnstackDetachProcess,這兩個(gè)函數(shù)都會(huì)調(diào)用KiMoveApcState來(lái)分別保存和恢復(fù)APC鏈表,問(wèn)題在于在第二次調(diào)用KiMoveApcState時(shí),重新把前面已經(jīng)被摘除的鏈表頭接回到APC雙向鏈表中,導(dǎo)致處理后續(xù)的APC隊(duì)列時(shí)會(huì)發(fā)生ExFreePool(ETHREAD+0x30),ETHREAD是正在退出的線程的內(nèi)核對(duì)象。
引用EEYE的公告,發(fā)生漏洞時(shí)函數(shù)的調(diào)用順序:
. 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)
現(xiàn)在詳細(xì)分析一下,在PspExitThread調(diào)用的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對(duì)以ApcListHead為鏈表頭的雙向鏈表進(jìn)行摘除鏈表頭處理,而原鏈表頭指向其中第一項(xiàng)。
問(wèn)題代碼在第二個(gè)KiMoveApcState,把備份的APC狀態(tài)結(jié)構(gòu)復(fù)制回原來(lái)的ETHREAD結(jié)構(gòu)時(shí):
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句是原因,把鏈表頭重新接回雙向鏈表。
結(jié)果在后續(xù)循環(huán)釋放鏈表中的APC結(jié)構(gòu)時(shí),就循環(huán)到了ETHREAD+0x3c(UserMode的APC),把它當(dāng)成了KAPC+0xc(LIST_ENTRY)來(lái)處理,調(diào)用ExFreePool(Apc)時(shí),這個(gè)”Apc””的地址也就是ETHREAD+0x30,實(shí)際POOL的頭結(jié)構(gòu)從ETHREAD+0x28 KernelStack開(kāi)始。
當(dāng)獲得KernelStack可以為合法的頭結(jié)構(gòu)時(shí),將會(huì)把頭結(jié)構(gòu)中的ProcessBilled當(dāng)成一個(gè)EPROCESS結(jié)構(gòu)進(jìn)行釋放,這時(shí)這個(gè)””EPROCESS””結(jié)構(gòu)為一用戶(hù)態(tài)地址,一般是為0x200(因?yàn)镾tate=2),因?yàn)檫@個(gè)地址在地址空間的第一個(gè)頁(yè),是不允許訪問(wèn)的,所以我們還要使ETHREAD結(jié)構(gòu)中在State之后的一個(gè)成員不為0,也就是Alerted數(shù)組中某一項(xiàng)不為0。Alerted數(shù)組分別對(duì)應(yīng)于記錄該線程在用戶(hù)態(tài)和內(nèi)核態(tài)下是否以被提醒過(guò),分別為一個(gè)字節(jié)大小。在用戶(hù)態(tài)下使對(duì)應(yīng)內(nèi)核態(tài)的Alerted位為1不太可能,卻能使對(duì)應(yīng)用戶(hù)態(tài)的Alerted位為1。在將目標(biāo)線程暫停后,可以在該線程被插入APC之前先調(diào)用ZwAlertThread來(lái)使其調(diào)用KeAlertThread,可以使Alerted[UserMode]為1,原因參考下面代碼,記得此時(shí)這個(gè)線程不能是Alertable狀態(tài)的。這時(shí)對(duì)應(yīng)于偽造的EPROCESS地址為0x1000200:
這段代碼是用于在KeAlertThread里置位Alerted:
if (Alerted == FALSE) {
//
// If the thread is currently in a Wait state