|
|
| 首页 | 技术文章 | 软件下载 | 博客 | 论坛 | 精品教程 | 黑客动画 | 视频资源 | 在线服务 | 黑客游戏 | | ||||
|
|
||||||||
|
||||||||
|
|||||
| MS05-055漏洞分析[转贴] | |||||
作者:SoBeIt 文章来源:安全焦点 点击数: 更新时间:2006-1-11 ![]() |
|||||
|
MS05-055漏洞分析 SoBeIt 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, 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)中的一些函数指针,我们将在这里获取控制权。 利用难度一是在一般用户权限的用户态下如何获取内核堆栈的地址,二是如何使APC结构是从配额池(quato pool)中分配。后者通过调用ZwQueueApcThread即可,这时APC都是从配额池中分配: Apc = ExAllocatePoolWithQuotaTag( (NonPagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE), sizeof(*Apc), 'pasP' ); if ( !Apc ) { st = STATUS_NO_MEMORY; } else { KeInitializeApc( Apc, &Thread->Tcb, OriginalApcEnvironment, PspQueueApcSpecialApc, NULL, (PKNORMAL_ROUTINE)ApcRoutine, UserMode, ApcArgument1 ); 至于怎么在用户态下获取内核态堆栈地址,我们知道GetThreadContext和SetThreadContext在内核态下都是通过插入APC到目标线程中来实现。但是在内核中的实现函数NtGetContextThread和NtSetContextThread在处理上有些问题,Apc和用于装载返回的环境信息的CONTEXT结构都是从堆栈中分配且未初始化为0,所以用于返回的CONTEXT中默认的数据都是内核堆栈中的数据。若未设置CONTEXT.ContextFlags中的某个标志,则不会覆盖该标志所对应的一些寄存器值,导致可以获取原来内核堆栈中数据。一般来说,内核堆栈中的数据是基本没有顺序的,但可以让内核先进行一系列比较长的代用,比如在用户态执行CreateThread,这时内核堆栈的数据基本都是有序的,而且肯定会包含内核栈帧。我们也就可以准确获取内核堆栈地址也就是ETHREAD.KernelStack的高24位,也就是可以控制伪造池头结构中的PoolType和PoolIndex项。要求是PoolType & 32(SESSION_POOL_MASK)和& 64(POOL_VERIFIER_MASK)都不为1,且& 8(POOL_QUOTA_MASK)为1,还有PoolType & 3不为0来决定是PagedPool或NonPagedPool。且PoolIndex必须置位0x80,来标志该pool是已分配的,不然会蓝屏。 要触发漏洞必须在条件适合的目标线程(也就是ETHREAD.KernelStack满足上面提到的要求)插入至少2个APC,一个辅助进程的APC,一个无关APC。这就需要一个辅助进程的配合,一般我们自己启动这个进程,该进程向已暂停的目标线程调用ZwAlertThread置位Alerted,并调用ZwQueueApcThread插入APC,然后自己终止。目标线程在辅助进程终止后恢复运行,调用ZwQueueApcThread向自己插入APC,然后终止,漏洞就会触发。 在触发漏洞前,可以看到APC链表头已被摘除: 03c ApcListHead[1] +03c struct _LIST_ENTRY *Flink = 814B0354 +040 struct _LIST_ENTRY *Blink = 814B5C14 kd> dd 814B0354 l 2 814b0354 814b5c14 814b5c14 kd> dd 814B5C14 l 2 814b5c14 814b0354 814b0354 触发后,可以看到链表头已经被接上: +148 ApcListHead[1] +148 struct _ |
|||||
| 文章录入:IceRiver 责任编辑:IceRiver | |||||
| 【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口】 | |||||
| 最新热点 | 最新推荐 | 相关文章 | ||
| MSN视频暗藏杀机 微软补丁忙 MSSQL数据库SA权限入侵的感悟 MSN Space大赛官方网站入侵经 瑞星公司09月05日发布 每日计 Thomson SpeedTouch 2030 SI “MSN性感相册”病毒变种多达 机构数据: QQ市场占有率为M 微软携安全厂商建统一战线 M QQ和MSN成传播病毒重要渠道 通过MSN传播的IRCBot msnmsg |
网友评论:(只显示最新5条。评论内容只代表网友观点,与本站立场无关!) |
| 关于我们 - 版权声明 - 帮助(?) - 广告服务 - 联系我们 - 友情链接 - 用户注册 - | Powered by ICE RIVER - STUDIO |
| » CnXHacker.CoM | © CopyRight 2002-2006, CnXHacker.CoM™, Inc. All Rights Reserved. |