原poc作者James Forshaw使用C#实现,我一直未复现成功,不过通过原poc的代码我大致明白了漏洞的成因和触发方法,原poc环境是win10 1803 X64系统.在cve2018-8550更新补丁出来不久,微软就取消了64位ole32.dll和coml2.dll调试符号提供,不过32的仍然可以正常提供.为了方便调试我用vc在32位系统上成功复现了poc.
poc采用的反射从低权限进程(poc进程)向高权限进程(bits服务)Unmarshal方式实现触发bits复制了一个poc进程中的一个句柄至高权限进程从而用来创建进程实现提权,Unmarshal采用的ClassID为CLSID_DfMarshal={ 0x0000030B, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, } ,Bits服务器调用了ole32(在win10中被代码迁移到coml2)的DfUnMarshalInterface方法来Unmarshal的数据流中SDfMarshalPacket被poc精心构造的数据包,由于poc使用的是StgCreateDocfile创建IStorage对象,所以最终DfUnMarshalInterface调用了CExposedDocFile::Unmarshal来Unmarshal,下面是逆向的代码:
DfUnMarshalInterface默认是采用MSHCTX=MSHCTX_INPROC也就是同进程内Unmrahsl方式读取的指针都是的同进程对象,不过它也支持UnmarshalSharedMemory方式,也就是从结构的hMem字段从audiodg.exe的共享内存初始化相应的共享对象
windows音频服务允许建立一个Initialize在audiodg.exe进程共享内存中AUDCLNT_SHAREMODE=AUDCLNT_SHAREMODE_SHARED的共享内存,并开辟这样一块共享内存给poc进程和bits进程使用.通过ntdll未公开的函数实现找到位于audiodg.exe进程共享内存的句柄,写入交换数据后,将共享内存的句柄值写入SDfMarshalPacket->hMem,关于ntdll未公开的函数具体实现poc中有代码请读者自行研究.
在CSharedMemoryBlock::InitUnMarshal中CSharedMemoryBlockObj->_pbBase被赋值成了SDfMarshalPacket->hMem共享内存映射后的地址,之后在CSmAllocator::Init中 CPerContext->_pbBase = CSharedMemoryBlockObj->_pbBase + 8,之后在UnmarshalSharedMemory接着就调用CPerContext::SetThreadAllocatorState接着调用CSmAllocator::SetState将NtCurrentTeb()->ReservedForOle赋值成CPerContext->_pbBase.最终得出的结论是NtCurrentTeb()->ReservedForOle-8就是共享内存内存映射的相对地址.在poc中使用如下方式操作共享内存
在poc中映射的共享内存基址directoryMappedAddressLocal就是bits进程中NtCurrentTeb()->ReservedForOle-8即bits进程映射的共享内存基址.以下是poc操作共享内存的方法
CBasedMapSectionPtr pdf = (CBasedMapSectionPtr)(pck->CBasedPubDocFileObj + (ULONG)directoryMappedAddressLocal + 8);
pdf->_SelftobjectPtr = 0x46444250;
在windbg验证结论,共享内存CPerContext的地址为0617a350,可见它继承与CSmAllocator,它的CPerContext->CSharedMemoryBlockObj(+0x4)也就是CSharedMemoryBlock地址为060def40,CSharedMemoryBlock->_pbBase地址为03e40000,同时可以看到ReservedForOle指针指向的地址为03e40008,验证了结论.
设置好共享内存后会进入UnmarshalContext,其中先从SDfMarshalPacket->CBasedGlobalContextObj中通过CContextList::_Find找第一个CGlobalContext,其实CGlobalContext继承于CContextList结构,会根据CContextList->pctxNext找下一个CContextList直到CContextList->ctxid为要找的进程id为止.之后会验证下CPerContext句柄是否有效,其实只要构造一个名为"\Sessions\0\BaseNamedObjects\OleDfRoot%X%08lX"的内核Event通过NtCreateEvent,Bits服务的Sessions为0,详见poc代码.
之后进入 CFileStream::Unmarshal,同样通过CContextList::_Find和方式通过设置和bits服务相同的pid从SDfMarshalPacket->CBasedGlobalFileStreamBaseObj找第一个CFileStream也就是poc中往共享内存0x7279 - 0x10写入的CFileStream,它的CFileStream->_hFile被赋值为0导致GetFileType返回-1导致验证失败(不影响程序运行),这个时候它的ctxid已经可以检测到为0,这时会进入poc中新建线程的第一个if语句断点.由于验证失败进入else,其中fstmOut->_CGlobalFileStreamPtr被赋值成SDfMarshalPacket->CBasedGlobalFileStreamBaseObj,fstmGlobal赋值成SDfMarshalPacket->CGlobalFileStreamPtr,之后它的fstmGlobal->_CGlobalFileStreamPtr->_pctxHead=0x7279被 CContextList::Add设置成fstmGlobal的相对地址,可以去判断读取SDfMarshalPacket->CBasedGlobalFileStreamBaseObj->_pctxHead已经不是0x7279,其实是fstmGlobal的相对地址.笔者发现一个小tips,fstmGlobal->_CGlobalFileStreamPtr也是可以在SDfMarshalPacket中读取到的,这样实际上求fstmGlobal->_CGlobalFileStreamPtr-SDfMarshalPacket->CBasedGlobalFileStreamBaseObj值就可以计算出bits进程的映射共享内存的映射基址也就是(NtCurrentTeb()->ReservedForOle->pvThreadBase-8)的值,从而实现读写bits进程的任意指定真实地址(非通过偏移内存计算)内存数据在共享内存区域,有兴趣的读者可以自行尝试后续研究.
之后进入CFileStream::InitWorker,that->_CGlobalFileStreamPtr->_pctxHead=0x7279,(CFileStream*)(0x7279 - 0x10)是第一个找到的fstmFoundNew,由于的我在poc中预先设置了
fstmFoundNew->baseContext.pctxNext = 0x7279这样pctxNext又链接到了它自己,这样可以构建 while ( fstmFoundNew )无限循环,产生一个时间差,让poc中的新建线程有时间读取最终复制的句柄.
最终bits服务调用DuplicateHandle函数复制了poc中pWorkFileStream->_hFile设置的句柄,使用这个句柄后作为创建新进程的父句柄,最后成功弹出了一个System权限NotePad,如图:
引用
我的poc地址
原poc
P0地址
作者来自ZheJiang Guoli Security Technology
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-8-9 09:02
被王cb编辑
,原因: