EEYE的公告中对这个漏洞已经描述得比较详细了。线程在退出的时候(计算机爱好者,学习计算机基础,电脑入门,请到本站,我站同时提供计算机基础知识教程,计算机基础知识试题供大家学习和使用),,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
下一条:电脑的基本知识